oh-my-muse/docs/memorys/2026-06-07-P1R7eAccountSourceOwnerPropagation真实链路.md

303 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 036 tests passfailures/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 073 tests passfailures/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` testsfailures=0、errors=0、skipped=0Flyway `_test` XML 单独保留 4/4failures=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 passfailures/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://<host>/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 已双 PASSBanach 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。