补齐 Content focused tests、P1R mixed gates、Flyway _test、dependency gate 与 evidence memory,证明 P1R-7f 只推进到 needs_verification,不代表 completed。
16 KiB
P1R7f Content Source Owner Propagation 真实链路
日期:2026-06-08
结论
P1R-7f Content source owner propagation 主体实现、V21 SQL、Content focused tests、P1R mixed executable gates、V21 Flyway _test、dependency tree gate、full reactor build、隔离 coverage scanner 与 protected diff gate 已形成 needs_verification evidence。
当前仍不能写 completed。本轮证据只代表 Content saveBlock 首次成功 -> muse_content_block_source_attribution(source_status=active) -> Content publish outbox -> Content worker -> EventsPublishApi -> muse_unified_event -> /app-api/muse/events 对 owner 可见的链路推进到证据层,不代表 Events / P1R-7 / Content completed。
本轮未修改 OpenAPI、coverage scanner 或真实 coverage 报告。
已验证事实
工作区与保护文件
- 正确 worktree:
/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0。 - 分支:
dev/1.0.0。 - 起点 HEAD:
68cee5d test(p1r): 收口 Account 事件传播真实链路门禁。 - 本轮执行过
git pull --ff-only origin dev/1.0.0,结果为Already up to date.。 - 受保护文件最终 staged / unstaged diff 均为空:
docs/api-contracts/account/openapi.yamldocs/api-contracts/market/openapi.yamldocs/api-contracts/ai/openapi.yamldocs/api-contracts/knowledge/openapi.yamldocs/api-contracts/events/openapi.yamlmuse-cloud/scripts/p1r-audit-api-coverage.pydocs/superpowers/reports/p1r-api-coverage.jsondocs/superpowers/reports/p1r-api-coverage.md
本轮实现范围
- Content server 新增直接依赖
muse-module-events-api。 - 新增 Content 本域 publish outbox:
ContentEventPublishOutboxDOContentEventPublishOutboxMapperContentEventPublishOutboxServiceContentEventPublishOutboxServiceImplContentEventPayloadsContentEventPublishWorkerMuseContentEventsPropertiesMuseContentEventsConfiguration
ContentAppServiceImpl.saveBlock在首次成功写入muse_content_block_source_attribution(source_status=active)后,同事务创建 Content Events publish outbox。- command replay 与 revision conflict 不创建 outbox。
- 新增 V21 migration:
muse-cloud/sql/muse/V21__extend_content_events_publish_outbox.sql。
合同边界
- 第一切片只覆盖
ContentAppServiceImpl.saveBlock首次成功路径。 - 不覆盖
create_block、delete_block、splitBlock、mergeBlocks、mergeBlockSuggestion。 - 不覆盖 import、parse、export、planning、meta projection、admin risk action。
- Events source tuple:
sourceOwner=contentsourceType=content_block_source_attributionsourceId=blockIdsourceRevision=blockRevisioneventType=notification
- notification type 固定为
source_status_change。 - 幂等 key:
commandId=content_evt:<sha256(tenantId|blockId|blockRevision|source_status_change) 前 32 位 hex>outboxId=content_out:<sha256(tenantId|blockId|blockRevision|source_status_change) 前 32 位 hex>
- outbox 唯一约束覆盖
(tenant_id, block_id, block_revision, notification_type)。 - payload exact allowlist:
- 顶层只允许
type/message/resourceRef/timestamp。 resourceRef只允许resourceType/resourceId。message固定为内容来源状态已更新。resourceRef.resourceId保持 Number/Long 语义,值为 block id。EventsPublishReqDTO.resourceId仍按 Events DTO 合同使用 String。
- 顶层只允许
- 禁止发布正文、sourceSnapshot 原文、sourceObjectId、authorizationSnapshotId、lineagePayload、licenseRestrictionSnapshot、auditReason、requestHash、resultSnapshot、错误堆栈。
- worker 默认配置关闭:
muse.content.events.publish-worker.enabled=false。 - worker 只发布
source_status=active,非 active / invalid owner / invalid payload 均 fail-closed。 - 终态回写使用
publish_status='running' AND attempt_count=#{claimedAttemptCount}防止过期 claim 覆盖新终态;终态更新 0 行会记录 stale claim 诊断日志。
验证证据
Full reactor build
执行:
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o clean install -DskipTests -Dspring-boot.repackage.skip=true
结果:exit 0,62/62 reactor modules success,包含 BUILD SUCCESS。
Content focused tests
执行:
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o test \
-pl muse-module-content/muse-module-content-server -am \
-Dtest=ContentAppServiceTest,ContentEventPublishOutboxServiceTest,ContentEventPublishWorkerTest \
-Dsurefire.failIfNoSpecifiedTests=false
结果:exit 0,29 tests pass,failures/errors/skipped 均为 0。
Surefire XML:
| 测试类 | tests | failures | errors | skipped |
|---|---|---|---|---|
ContentAppServiceTest |
13 | 0 | 0 | 0 |
ContentEventPublishOutboxServiceTest |
6 | 0 | 0 | 0 |
ContentEventPublishWorkerTest |
10 | 0 | 0 | 0 |
覆盖点:
saveBlock首次成功路径在 source attribution insert 后创建 outbox。- command replay 不创建 outbox。
- revision conflict 不更新 block、不写 source attribution、不创建 outbox。
- outbox payload exact allowlist 与敏感字段负断言。
- configured
maxAttempt与claimTimeoutSeconds生效。 - timestamp 优先使用 source attribution
createTime,缺失时回退 outboxcreateTime。 - worker disabled 不 claim。
- accepted / duplicate accepted -> published。
- rejected / blocked -> dead_letter。
- CommonResult error / exception -> retryable 或 retry exhausted dead_letter。
- invalid payload / invalid owner / 非 active source status -> dead_letter 且不调用 EventsPublishApi。
- terminal update 具备 claim attempt ownership guard。
- stale terminal update 记录诊断日志。
P1R mixed executable gates
执行:
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o test \
-pl muse-server -am \
-Dtest=P1rContentEventsPublishMigrationSqlTest,P1rContentEventsPublishDependencyTest,P1rContentEventsPublishEndToEndTest,P1rContentRealApiGateTest,P1rAccountEventsPublishMigrationSqlTest,P1rAccountEventsPublishDependencyTest,P1rAccountEventsPublishEndToEndTest,P1rAccountRealApiGateTest,P1rAccountRouteOwnershipTest,P1rMarketEventsPublishMigrationSqlTest,P1rMarketEventsPublishDependencyTest,P1rMarketEventsPublishEndToEndTest,P1rKnowledgeEventsPublishMigrationSqlTest,P1rKnowledgeEventsPublishDependencyTest,P1rKnowledgeEventsPublishEndToEndTest,P1rAiEventsPublishMigrationSqlTest,P1rAiEventsPublishDependencyTest,P1rAiEventsPublishEndToEndTest,P1rApiCoverageReportTest,P1rEventsRealApiGateTest,P1rEventsRouteOwnershipTest,P1rAiRealApiGateTest,P1rAiRouteOwnershipTest \
-Dsurefire.failIfNoSpecifiedTests=false
结果:exit 0,88 tests pass,failures/errors/skipped 均为 0。
Surefire XML:
| 测试类 | tests | failures | errors | skipped |
|---|---|---|---|---|
P1rContentEventsPublishMigrationSqlTest |
3 | 0 | 0 | 0 |
P1rContentEventsPublishDependencyTest |
2 | 0 | 0 | 0 |
P1rContentEventsPublishEndToEndTest |
5 | 0 | 0 | 0 |
P1rContentRealApiGateTest |
5 | 0 | 0 | 0 |
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 |
覆盖点:
- Content server 直接依赖
muse-module-events-api,不依赖muse-module-events-server。 - Events server 不反向依赖 AI / Knowledge / Market / Member / Content server。
- V21 SQL 静态断言覆盖字段、唯一约束、check 约束、索引、trigger、BaseDO/TenantBaseDO 审计列、敏感列和外键禁用。
- focused E2E 使用真实
EventsPublishServiceImpl,只 mock / in-memoryUnifiedEventMapper,避免 bare fake EventsPublishApi 假绿。 - Content notification 只对 owner 可见,非 owner 不可见。
- duplicate same block revision 复用同一 eventId/sequenceNo,different revision 产生不同 notification。
- rejected -> dead_letter 且 SSE visible query 为空。
- non-active source status -> dead_letter 且不调用 Events。
- stale claim terminal update attempt mismatch 不覆盖当前 running outbox。
- 同时回归 Account/Market/Knowledge/AI/Events 既有 P1R gates 与 coverage report gate。
Surefire XML 计数核对
clean build 后已重新运行 focused tests 与 P1R mixed executable gates,以保留最终 surefire XML。
结果:Content focused + P1R mixed 目标 XML 合计 117 tests,failures=0、errors=0、skipped=0。
V21 Flyway _test
准备:
source ~/.config/muse-repo/infra.env
createdb -h "$MUSE_POSTGRES_HOST" -p "$MUSE_POSTGRES_PORT" -U "$MUSE_POSTGRES_USERNAME" muse_p1r7f_content_events_publish_test
执行:
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=P1rContentEventsPublishFlywayMigrationIT \
-Dflyway.postgresql.transactional.lock=false \
-Dp1r.flyway.locations=filesystem:sql/muse \
-Dp1r.flyway.url="jdbc:postgresql://$MUSE_POSTGRES_HOST:$MUSE_POSTGRES_PORT/muse_p1r7f_content_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,P1rContentEventsPublishFlywayMigrationIT 4 tests pass,failures/errors/skipped 均为 0。
关键输出:
flyway_success=true
flyway_url=jdbc:postgresql://<host>/muse_p1r7f_content_events_publish_test
migrations_executed=21
successful_migration_count=21
target_schema_version=21
flyway_latest=21:extend content events publish outbox
v21_table=muse_content_event_publish_outbox
v21_indexes=idx_muse_content_event_publish_outbox_claim,idx_muse_content_event_publish_outbox_owner_status,idx_muse_content_event_publish_outbox_block_revision
v21_triggers=trg_muse_content_event_publish_outbox_update_time
Flyway IT 覆盖:
- 只允许
_test数据库。 - 拒绝 JDBC URL 携带 credential query。
- V1-V21 clean migrate。
- V21 表、索引、约束、trigger 存在。
event_type='chunk'、notification_type!='source_status_change'、source_status='deleted'、非法publish_status、非法 owner/block/resource/attempt 会被数据库拒绝。
调试留痕:
- 首次 Flyway
_test失败于测试 helper 对 PostgreSQL 单值 check constraint 的判定过窄,真实约束为CHECK (((event_type)::text = 'notification'::text)),生产 SQL 未改,修正的是测试判定函数。 - 第二次运行出现长等待,线程栈显示新建 JDBC 连接或 Flyway advisory lock 阶段等待;最终通过复用已建立 connection 做非法 insert 验收并在每次失败后 rollback,避免连接层不稳定干扰 schema 断言。
Dependency tree gate
执行:
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o -pl muse-module-content/muse-module-content-server -DskipTests dependency:tree > /tmp/p1r7f-content-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/p1r7f-events-server-dependency-tree.txt
rg "muse-module-events-api" /tmp/p1r7f-content-server-dependency-tree.txt
! rg "muse-module-events-server" /tmp/p1r7f-content-server-dependency-tree.txt
! rg "muse-module-(ai|knowledge|market|member|content)-server" /tmp/p1r7f-events-server-dependency-tree.txt
结果:
- Content server tree 命中
muse-module-events-api。 - Content server tree 未命中
muse-module-events-server。 - Events server tree 未命中 AI / Knowledge / Market / Member / Content server。
Coverage scanner 隔离副本
首次尝试在临时 venv 安装 PyYAML 失败,原因为 pip TLS 证书校验失败,不计入 scanner 失败。随后确认系统 Python 已有 PyYAML 6.0.3,复用同一 /tmp 隔离副本执行 scanner。
执行:
tmpdir=$(mktemp -d /tmp/p1r7f-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,仅在 /tmp/p1r7f-coverage-scan.N1DHvZ 生成报告。
summary:
completedOperations=100
needsVerificationOperations=133
incompleteOperations=0
genericPersistenceOperations=0
ssePlaceholderOperations=0
Protected diff 与 whitespace
执行:
git diff --check
git diff --name-only -- 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 --name-only -- 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 --checkexit 0。- protected unstaged diff 输出为空。
- protected staged diff 输出为空。
推断
- 当前实现与 P1R-7f 执行版第一切片一致:只把 Content
saveBlock产生的 active source attribution terminal fact 推送到 Events notification 链路,不试图把 Content 全部写命令一次性纳入 Events。 - V21 DB check、worker fail-closed、SQL gate、Flyway
_test与 E2E 共同降低了把 Content save 误发布为chunk或把 deleted source status 发布给 SSE 的风险。
假设
- 本轮 focused E2E 使用 in-memory / mocked mapper 验证
EventsPublishServiceImpl语义,仍不是完整生产运行时。 - 后续如果要推进 Content / Events / P1R-7 completed,仍需要单独 approval gate,不能复用本文件作为 completed 依据。
后续动作
Fresh implementation review
fresh implementation 双 review 已通过:
- Hubble correctness/spec review:PASS,无 P0/P1/P2/P3 blocker。
- Dalton quality/data-integrity/testing review:PASS,无 P0/P1/P2/P3 blocker。
Hubble 复核了正确 worktree、protected diff、scope、saveBlock 接入点、outbox/worker source tuple、V21 防 chunk/deleted、真实 EventsPublishServiceImpl E2E、依赖边界与 git diff --check。
Dalton 复核了 V21 migration 的 NOT NULL / unique / check / index / trigger、无 FK/敏感列、事务 outbox、幂等复用、payload exact allowlist、worker 状态机、focused/P1R/Flyway 测试质量与 Surefire XML 防空跑,读取目标 XML 得到 121 tests 且 failures/errors/skipped 均为 0。
后续动作
- 可按逻辑分组准备提交。
- 提交、push 或推进 completed 仍需用户明确批准。
- 本轮双 PASS 仍不代表 Events / P1R-7 / Content completed。