oh-my-muse/docs/memorys/2026-06-08-P1R7fContentSourceOwnerPropagation真实链路.md
zizi f84c18fbfb test(p1r): 收口 Content 事件传播真实链路门禁
补齐 Content focused tests、P1R mixed gates、Flyway _test、dependency gate 与 evidence memory,证明 P1R-7f 只推进到 needs_verification,不代表 completed。
2026-06-08 23:17:54 +08:00

16 KiB
Raw Blame History

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
  • 起点 HEAD68cee5d test(p1r): 收口 Account 事件传播真实链路门禁
  • 本轮执行过 git pull --ff-only origin dev/1.0.0,结果为 Already up to date.
  • 受保护文件最终 staged / unstaged diff 均为空:
    • 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

本轮实现范围

  • Content server 新增直接依赖 muse-module-events-api
  • 新增 Content 本域 publish outbox
    • ContentEventPublishOutboxDO
    • ContentEventPublishOutboxMapper
    • ContentEventPublishOutboxService
    • ContentEventPublishOutboxServiceImpl
    • ContentEventPayloads
    • ContentEventPublishWorker
    • MuseContentEventsProperties
    • MuseContentEventsConfiguration
  • ContentAppServiceImpl.saveBlock 在首次成功写入 muse_content_block_source_attribution(source_status=active) 后,同事务创建 Content Events publish outbox。
  • command replay 与 revision conflict 不创建 outbox。
  • 新增 V21 migrationmuse-cloud/sql/muse/V21__extend_content_events_publish_outbox.sql

合同边界

  • 第一切片只覆盖 ContentAppServiceImpl.saveBlock 首次成功路径。
  • 不覆盖 create_blockdelete_blocksplitBlockmergeBlocksmergeBlockSuggestion
  • 不覆盖 import、parse、export、planning、meta projection、admin risk action。
  • Events source tuple
    • sourceOwner=content
    • sourceType=content_block_source_attribution
    • sourceId=blockId
    • sourceRevision=blockRevision
    • eventType=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 062/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 029 tests passfailures/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 maxAttemptclaimTimeoutSeconds 生效。
  • timestamp 优先使用 source attribution createTime,缺失时回退 outbox createTime
  • 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 088 tests passfailures/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-memory UnifiedEventMapper,避免 bare fake EventsPublishApi 假绿。
  • Content notification 只对 owner 可见,非 owner 不可见。
  • duplicate same block revision 复用同一 eventId/sequenceNodifferent 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 testsfailures=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 0P1rContentEventsPublishFlywayMigrationIT 4 tests passfailures/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 --check exit 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 reviewPASS无 P0/P1/P2/P3 blocker。
  • Dalton quality/data-integrity/testing reviewPASS无 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。