303 lines
14 KiB
Markdown
303 lines
14 KiB
Markdown
# 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://<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 已双 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。
|