# P1R7e Account Source Owner Propagation 真实链路 日期:2026-06-07 ## 结论 P1R-7e Account/Member source owner propagation 主体实现、V20 SQL、Member focused tests、P1R Account Events publish gate tests、dependency tree gate 与 protected diff gate 已形成 `needs_verification` evidence。 当前仍不能写 `completed`。本轮证据只代表 Account quota adjustment terminal fact -> Account publish outbox -> Account worker -> EventsPublishApi -> `muse_unified_event` -> `/app-api/muse/events` 对 `accountUserId` 可见的链路推进到证据层,不代表 Events / P1R-7 / Account completed。 本轮未修改 OpenAPI、coverage scanner 或真实 coverage 报告。 ## 已验证事实 ### 工作区与保护文件 - 正确 worktree:`/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0`。 - 分支:`dev/1.0.0`。 - 最新 migration 前置检查:当前 V19 后新增 V20,未发现既有 V20+。 - 受保护文件最终 staged / unstaged diff 均为 exit 0: - `docs/api-contracts/account/openapi.yaml` - `docs/api-contracts/market/openapi.yaml` - `docs/api-contracts/ai/openapi.yaml` - `docs/api-contracts/knowledge/openapi.yaml` - `docs/api-contracts/events/openapi.yaml` - `muse-cloud/scripts/p1r-audit-api-coverage.py` - `docs/superpowers/reports/p1r-api-coverage.json` - `docs/superpowers/reports/p1r-api-coverage.md` ### 本轮实现范围 - Member server 新增直接依赖 `muse-module-events-api`。 - 新增 Account 本域 publish outbox: - `AccountEventPublishOutboxDO` - `AccountEventPublishOutboxMapper` - `AccountEventPublishOutboxService` - `AccountEventPublishOutboxServiceImpl` - `AccountEventPayloads` - `AccountEventPublishWorker` - `MuseAccountEventsProperties` - `MuseAccountEventsConfiguration` - `AccountQuotaServiceImpl.adminCreateQuotaAdjustment` 在每条 `muse_member_entitlement_audit_log(change_type=quota_adjustment)` insert 后,同事务创建 Account Events publish outbox。 - command replay 不补历史 outbox。 - 新增 V20 migration:`muse-cloud/sql/muse/V20__extend_account_events_publish_outbox.sql`。 ## 合同边界 - 每条 entitlement audit log 对应一条 outbox,不做 command 聚合,不做历史 backfill。 - Events source tuple: - `sourceOwner=account` - `sourceType=account_quota_adjustment` - `sourceId=auditLogId` - `sourceRevision=__none__` - `eventType=notification` - notification type 固定为 `quota_alert`。 - `sourceCommandId` 优先使用原始 `reqVO.getCommandId()`;历史 fallback 才从 `idempotencyKey.lastIndexOf(':')` 解析。 - payload exact allowlist: - 顶层只允许 `type/message/resourceRef/timestamp`。 - `resourceRef` 只允许 `resourceType/resourceId`。 - `message` 固定为 `额度已调整`。 - `resourceRef.resourceId` 保持 Number/Long 语义,值为 entitlement audit log id。 - `EventsPublishReqDTO.resourceId` 仍按 Events DTO 合同使用 String。 - 禁止发布 requestHash、reason 原文、operatorUserId、before/after snapshot、correlationId、external detail、error stack、credential。 - timestamp 优先使用 audit log `createTime`,缺失时回退 outbox `createTime`。 - worker 默认配置关闭:`muse.account.events.publish-worker.enabled=false`。 - 终态回写使用 `publish_status='running' AND attempt_count=#{claimedAttemptCount}` 防止过期 claim 覆盖新终态;终态更新 0 行会记录 stale claim 诊断日志。 ## 验证证据 ### Member focused tests 执行: ```bash JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" mvn -pl muse-module-member/muse-module-member-server -Dtest=AccountEventPublishOutboxServiceTest,AccountEventPublishWorkerTest,AccountQuotaServiceTest -Dsurefire.failIfNoSpecifiedTests=false test ``` 结果:exit 0,36 tests pass,failures/errors/skipped 均为 0。 Surefire XML: | 测试类 | tests | failures | errors | skipped | |---|---:|---:|---:|---:| | `AccountQuotaServiceTest` | 17 | 0 | 0 | 0 | | `AccountEventPublishOutboxServiceTest` | 8 | 0 | 0 | 0 | | `AccountEventPublishWorkerTest` | 11 | 0 | 0 | 0 | 覆盖点: - quota adjustment 首次成功路径为每条 entitlement audit log 创建 outbox。 - command replay 不创建 outbox。 - 多 resourceType 产生多条不同 outbox。 - invalid owner / missing audit log id / 非 quota_adjustment 不创建 outbox。 - payload exact allowlist 与敏感字段负断言。 - `resourceRef.resourceId` 是数值型 audit log id。 - configured `maxAttempt` 生效。 - timestamp 使用 audit log `createTime`,缺失时回退 outbox `createTime`。 - worker disabled 不 claim。 - accepted / duplicate accepted -> published。 - rejected / blocked -> dead_letter。 - CommonResult error / exception -> retryable 或 retry exhausted dead_letter。 - invalid owner / payload invalid -> dead_letter 且不调用 EventsPublishApi。 - terminal update 具备 claim attempt ownership guard。 - stale terminal update 记录诊断日志。 ### P1R mixed executable gates 执行: ```bash JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o test \ -pl muse-server -am \ -Dtest=P1rAccountEventsPublishMigrationSqlTest,P1rAccountEventsPublishDependencyTest,P1rAccountEventsPublishEndToEndTest,P1rAccountRealApiGateTest,P1rAccountRouteOwnershipTest,P1rMarketEventsPublishMigrationSqlTest,P1rMarketEventsPublishDependencyTest,P1rMarketEventsPublishEndToEndTest,P1rKnowledgeEventsPublishMigrationSqlTest,P1rKnowledgeEventsPublishDependencyTest,P1rKnowledgeEventsPublishEndToEndTest,P1rAiEventsPublishMigrationSqlTest,P1rAiEventsPublishDependencyTest,P1rAiEventsPublishEndToEndTest,P1rApiCoverageReportTest,P1rEventsRealApiGateTest,P1rEventsRouteOwnershipTest,P1rAiRealApiGateTest,P1rAiRouteOwnershipTest \ -Dsurefire.failIfNoSpecifiedTests=false ``` 结果:exit 0,73 tests pass,failures/errors/skipped 均为 0。 Surefire XML: | 测试类 | tests | failures | errors | skipped | |---|---:|---:|---:|---:| | `P1rAccountEventsPublishMigrationSqlTest` | 3 | 0 | 0 | 0 | | `P1rAccountEventsPublishDependencyTest` | 2 | 0 | 0 | 0 | | `P1rAccountEventsPublishEndToEndTest` | 4 | 0 | 0 | 0 | | `P1rAccountRealApiGateTest` | 4 | 0 | 0 | 0 | | `P1rAccountRouteOwnershipTest` | 4 | 0 | 0 | 0 | | `P1rMarketEventsPublishMigrationSqlTest` | 3 | 0 | 0 | 0 | | `P1rMarketEventsPublishDependencyTest` | 2 | 0 | 0 | 0 | | `P1rMarketEventsPublishEndToEndTest` | 4 | 0 | 0 | 0 | | `P1rKnowledgeEventsPublishMigrationSqlTest` | 3 | 0 | 0 | 0 | | `P1rKnowledgeEventsPublishDependencyTest` | 2 | 0 | 0 | 0 | | `P1rKnowledgeEventsPublishEndToEndTest` | 5 | 0 | 0 | 0 | | `P1rAiEventsPublishMigrationSqlTest` | 4 | 0 | 0 | 0 | | `P1rAiEventsPublishDependencyTest` | 2 | 0 | 0 | 0 | | `P1rAiEventsPublishEndToEndTest` | 4 | 0 | 0 | 0 | | `P1rApiCoverageReportTest` | 5 | 0 | 0 | 0 | | `P1rEventsRealApiGateTest` | 5 | 0 | 0 | 0 | | `P1rEventsRouteOwnershipTest` | 4 | 0 | 0 | 0 | | `P1rAiRealApiGateTest` | 6 | 0 | 0 | 0 | | `P1rAiRouteOwnershipTest` | 7 | 0 | 0 | 0 | 覆盖点: - Member server 直接依赖 `muse-module-events-api`,不依赖 `muse-module-events-server`。 - Events server 不反向依赖 AI / Knowledge / Market / Member / Content server。 - V20 SQL 静态断言覆盖字段、唯一约束、check 约束、索引、trigger、BaseDO/TenantBaseDO 审计列、敏感列和外键禁用。 - focused E2E 证明 Account worker 调用真实 `EventsPublishServiceImpl` 后,`quota_alert` 只对 `accountUserId` 可见,admin operator 不可见,非 owner 不可见。 - 多条 quota alert 对同一 owner 可见。 - rejected / dead_letter 后 SSE visible query 为空。 - stale claim terminal update attempt mismatch 不覆盖当前 running outbox。 - 同时回归 Account 真实 API gate、Account route ownership gate、AI/Knowledge/Market/Events 既有 P1R gates 与 coverage report gate。 ### Full reactor build 执行: ```bash JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o clean install -DskipTests -Dspring-boot.repackage.skip=true ``` 结果:exit 0,日志保留在 `/tmp/p1r7e-full-reactor-20260608022955.log`;机器统计 reactor summary 为 `62` 个 `SUCCESS`,包含 `BUILD SUCCESS`,Finished at `2026-06-08T02:30:46+08:00`。 ### Surefire XML 计数核对 clean build 后已重新运行 focused tests、P1R mixed executable gates 与 Flyway `_test`,以保留最终 surefire XML。 结果:Account focused + P1R mixed 目标 XML 合计 `109` tests,failures=0、errors=0、skipped=0;Flyway `_test` XML 单独保留 4/4,failures=0、errors=0、skipped=0。 ### V20 Flyway `_test` 准备: ```bash source ~/.config/muse-repo/infra.env psql "host=$MUSE_POSTGRES_HOST port=$MUSE_POSTGRES_PORT user=$MUSE_POSTGRES_USERNAME dbname=postgres" -c 'CREATE DATABASE muse_p1r7e_account_events_publish_test' ``` 已验证 `muse_p1r7e_account_events_publish_test` 存在。 执行: ```bash source ~/.config/muse-repo/infra.env export P1R_FLYWAY_PASSWORD="$MUSE_POSTGRES_PASSWORD" JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o test -pl muse-server -am \ -Dtest=P1rAccountEventsPublishFlywayMigrationIT \ -Dflyway.postgresql.transactional.lock=false \ -Dp1r.flyway.locations=filesystem:sql/muse \ -Dp1r.flyway.url="jdbc:postgresql://$MUSE_POSTGRES_HOST:$MUSE_POSTGRES_PORT/muse_p1r7e_account_events_publish_test" \ -Dp1r.flyway.user="$MUSE_POSTGRES_USERNAME" \ -Djava.net.useSystemProxies=false \ -DsocksProxyHost= \ -DsocksProxyPort= \ -Dhttp.proxyHost= \ -Dhttp.proxyPort= \ -Dhttps.proxyHost= \ -Dhttps.proxyPort= \ -Dsurefire.failIfNoSpecifiedTests=false ``` 结果:exit 0,`P1rAccountEventsPublishFlywayMigrationIT` 4 tests pass,failures/errors/skipped 均为 0。 补证据:clean build 后已重新运行该 Flyway IT,`muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountEventsPublishFlywayMigrationIT.xml` 已保留,机器校验为 tests=4、failures=0、errors=0、skipped=0。 关键输出: ```text flyway_success=true flyway_url=jdbc:postgresql:///muse_p1r7e_account_events_publish_test migrations_executed=20 successful_migration_count=20 target_schema_version=20 flyway_latest=20:extend account events publish outbox v20_table=muse_account_event_publish_outbox v20_indexes=idx_muse_account_event_publish_outbox_claim,idx_muse_account_event_publish_outbox_owner_status,idx_muse_account_event_publish_outbox_audit_log v20_triggers=trg_muse_account_event_publish_outbox_update_time ``` Flyway IT 覆盖: - 只允许 `_test` 数据库。 - 拒绝 JDBC URL 携带 credential query。 - V1-V20 clean migrate。 - V20 表、索引、约束、trigger 存在。 - `event_type`、`notification_type`、`publish_status`、`account_user_id`、`audit_log_id/resource_ref_id` 非法插入会被数据库拒绝。 ### Dependency tree gate 执行: ```bash JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o -pl muse-module-member/muse-module-member-server -DskipTests dependency:tree > /tmp/p1r7e-member-server-dependency-tree.txt JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o -pl muse-module-events/muse-module-events-server -DskipTests dependency:tree > /tmp/p1r7e-events-server-dependency-tree.txt ``` 结果: - Member server tree 命中 `muse-module-events-api`。 - Member server tree 未命中 `muse-module-events-server`。 - Events server tree 对 AI / Knowledge / Market / Member / Content server 无命中输出。 - 命令输出 `dependency_gate=pass`。 ### Isolated coverage scanner 执行范围:只在 `/tmp/p1r7e-coverage-scan.Ozo9Xm` 隔离副本运行 scanner,真实 worktree 的 `docs/superpowers/reports/*` 未被修改。 执行: ```bash tmpdir=$(mktemp -d /tmp/p1r7e-coverage-scan.XXXXXX) rsync -a --delete --exclude .git --exclude 'muse-cloud/**/target' ./ "$tmpdir"/ cd "$tmpdir" python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check ``` 结果:exit 0。 summary: ```json { "totalOperations": 233, "completedOperations": 100, "needsVerificationOperations": 133, "incompleteOperations": 0, "genericPersistenceOperations": 0, "ssePlaceholderOperations": 0 } ``` 边界核对: - Events `streamEvents=dedicated / needs_verification`。 - Account 33 operations 均为 `dedicated / needs_verification`。 - 未推进 completed。 ### Diff gates 执行: ```bash git diff --check git diff --quiet -- docs/api-contracts/account/openapi.yaml docs/api-contracts/market/openapi.yaml docs/api-contracts/ai/openapi.yaml docs/api-contracts/knowledge/openapi.yaml docs/api-contracts/events/openapi.yaml muse-cloud/scripts/p1r-audit-api-coverage.py docs/superpowers/reports/p1r-api-coverage.json docs/superpowers/reports/p1r-api-coverage.md git diff --cached --quiet -- docs/api-contracts/account/openapi.yaml docs/api-contracts/market/openapi.yaml docs/api-contracts/ai/openapi.yaml docs/api-contracts/knowledge/openapi.yaml docs/api-contracts/events/openapi.yaml muse-cloud/scripts/p1r-audit-api-coverage.py docs/superpowers/reports/p1r-api-coverage.json docs/superpowers/reports/p1r-api-coverage.md ``` 结果: - `git diff --check` exit 0。 - protected unstaged diff exit 0。 - protected staged diff exit 0。 - protected 文件 diff name-only 为空。 ## 风险与后续 - 早前 implementer 用 `127.0.0.1:5432/muse_p1r7e_events_publish_test` 运行 Flyway IT 失败,原因是本地 PostgreSQL 服务不可达;该旧失败已被 controller 使用 `~/.config/muse-repo/infra.env` 和 `muse_p1r7e_account_events_publish_test` 真实 `_test` 库的 4/4 PASS 覆盖。 - 本轮没有修改 OpenAPI、coverage scanner 或真实 coverage report,也没有推进 completed。 - focused E2E 是内存 mapper + 真实 `EventsPublishServiceImpl`,不是 HTTP/SSE + 真实 DB 全链路;controller 后续可补真实 controller/SSE 验证。 - 最终 fresh implementation review 已双 PASS:Banach correctness/spec PASS、Raman quality/data-integrity/testing PASS,均无 P0/P1/P2/P3 blocker。 - 双 PASS 只代表 P1R-7e Account quota adjustment source owner propagation evidence 可以进入提交整理,不代表 Events/P1R-7/Account completed。 - 当前未 push;提交整理仍不得推进 completed。