oh-my-muse/docs/agent-specs/2026-06-07-P1R7eAccountSourceOwnerPropagation执行版.md

36 KiB
Raw Blame History

P1R7e Account Source Owner Propagation 执行版

结论

P1R-7e 只执行 Account/Member source owner propagation 的第四 owner 切片:把 Account quota adjustment terminal fact 转成 Account 本域 Events publish outbox再由 Account worker 调用 EventsPublishApi 写入 muse_unified_event,最终通过 /app-api/muse/events 对被调整账户用户 SSE 可见。

本执行版不允许把 Events / P1R-7 / Account 推进到 completed,不允许修改 OpenAPI、coverage scanner 或 coverage report不允许把 security event、quota request、integration call 或 account audit 表复用为 Events publish outbox。

flowchart LR
    Command["adminCreateQuotaAdjustment<br/>commandId + expected snapshot"] --> Quota["muse_member_quota<br/>limit / used / revision"]
    Command --> Audit["muse_account_audit<br/>admin operation audit"]
    Command --> Log["muse_member_entitlement_audit_log<br/>one row per resourceType"]
    Log --> Outbox["muse_account_event_publish_outbox<br/>one row per entitlement audit log"]
    Outbox --> Worker["Account publish worker<br/>claim/retry/dead_letter"]
    Worker --> Api["EventsPublishApi<br/>events-api only"]
    Api --> Unified["muse_unified_event<br/>notification/quota_alert"]
    Unified --> Stream["/app-api/muse/events<br/>account owner visible SSE"]

Review Gate

P1R-7e 审阅版已通过 fresh review

  • Gibbs spec compliance reviewPASS无 P0/P1/P2。
  • Ampere quality / feasibility reviewPASS无 P0/P1/P2。

旧 Goodall feasibility review 对早期 security event 候选 FAIL。该反馈已验证有效并已在审阅版中通过改选 quota adjustment -> quota_alert 关闭;旧 FAIL 不计入当前最终审阅版 PASS gate。

P1R-7e 执行版第三轮 fresh review 已通过:

  • Pauli execution spec compliance reviewPASS无 P0/P1/P2已验证 scope 未扩大、protected staged/unstaged diff gate、V20 migration 编号前提、P1R mixed gate、Surefire XML 计数、隔离 coverage scanner 和 completed 边界。
  • Hooke execution quality / feasibility reviewPASS无 P0/P1/P2已验证 AccountQuotaServiceImpl.adminCreateQuotaAdjustment 同事务 audit log / outbox 接入点、Events quota_alert 合同、payload/resourceRef/sourceRevision/timestamp 合同、最终验证假绿口。

旧执行版 review 中Pascal PASS / Schrodinger FAIL、Feynman PASS / Kant FAIL 均因后续执行版修订失效,不计入当前最终 PASS gate其中有效反馈已经关闭到本执行版。本轮双 PASS 只允许进入 fresh implementer 实现,不代表 P1R-7e 实现完成,也不代表 Events / P1R-7 / Account completed。

非阻塞风险已在本执行版冻结:

  • 每条 muse_member_entitlement_audit_log 发布一条 quota_alert,不做 command 聚合。
  • resourceRef.resourceId 固定为数值型 entitlement audit log id。
  • sourceRevision 固定使用 Events 现有 __none__,不新增 audit log revision。

边界

本轮目标

  • 在 Member(Account) owner 内新增独立 Events publish outbox。
  • 只接入 AccountQuotaServiceImpl.adminCreateQuotaAdjustment 首次成功路径。
  • 只发布 muse_member_entitlement_audit_log(change_type=quota_adjustment) 形成的用户可见 quota alert。
  • 每条 entitlement audit log 对应一条 outbox 和一条 Events notification。
  • 通过 Account worker 调用 EventsPublishApi
  • 用 focused tests、P1R mixed gates、Flyway _test、dependency gate、dependency tree 和隔离 coverage scanner 证明链路进入 needs_verification 证据层。

本轮非目标

  • 不发布 muse_member_security_event
  • 不发布 muse_account_security_event_ack
  • 不发布 muse_account_quota_request
  • 不发布 muse_account_integration_call
  • 不发布 muse_member_usage_record
  • 不发布 New-API binding、call attribution job、export task、download credential 或 file service 事件。
  • 不发布 admin reason 原文、requestHash、before/after snapshot、operatorUserId、correlationId、external provider detail、error stack 或 credential。
  • 不抽取 AI / Knowledge / Market / Account 共享 outbox 框架。
  • 不为历史 entitlement audit log 做 backfill。
  • 不修改 docs/api-contracts/*/openapi.yaml
  • 不修改 muse-cloud/scripts/p1r-audit-api-coverage.py
  • 不修改 docs/superpowers/reports/p1r-api-coverage.json.md

受保护文件

实现代理不得修改以下文件:

  • 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

已验证事实

  • 正确 worktree 是 /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
  • 当前分支是 dev/1.0.0,远端跟踪是 origin/dev/1.0.0
  • git pull --ff-only origin dev/1.0.0 返回 Already up to date.
  • 当前 HEAD 为 e8ed7d7 test(p1r): 收口 Market 事件传播真实链路门禁
  • P1R-7b 已提交并 push3db5fbe230152c
  • P1R-7c 已提交并 pushcef686be55618f
  • P1R-7d 已提交并 pushb36b153e8ed7d7
  • P1R-7e 审阅版路径为 docs/agent-specs/2026-06-07-P1R7eAccountSourceOwnerPropagation审阅版.md
  • P1R-7e 审阅版 fresh review 已双 PASSGibbs spec PASSAmpere quality PASS。
  • 当前 coverage 边界仍为 completedOperations=100needsVerificationOperations=133incompleteOperations=0genericPersistenceOperations=0ssePlaceholderOperations=0
  • Events streamEvents 当前是 dedicated / needs_verification
  • Account 33 operations 当前是 dedicated / needs_verification
  • 当前受保护文件 diff 为空。
  • 当前最新 migration 是 muse-cloud/sql/muse/V19__extend_market_events_publish_outbox.sqlP1R-7e 下一 migration 编号应为 V20。若实现前 live 文件列表已出现更高版本,必须停止并修订本执行版的 migration 编号。
  • EventsPublishReqDTO 必填 commandIdtenantIdownerUserIdsourceOwnersourceTypesourceIdeventTypepayloadSummaryemittedAt
  • EventsPublishServiceImpl 当前先按 commandId 回放,再按 source tuple 回放。
  • EventsPublishServiceImpl 当前已声明 notification event type。
  • EventsPublishServiceImpl 当前已声明 quota_alert notification type。
  • Events OpenAPI SSENotificationEvent.data.resourceRef.resourceId 为 int64。
  • AccountQuotaServiceImpl.adminCreateQuotaAdjustment 成功后写 muse_member_entitlement_audit_log(changeType=quota_adjustment),每个 adjustment 一条 log。
  • MemberEntitlementAuditLogDO 包含 idaccountUserIdchangeTypesourceOwnersourceIdidempotencyKeybeforeValueSnapshotdeltaValueSnapshotafterValueSnapshotreasonCodereasonMessageoperatorUserIdauditEventId
  • MemberEntitlementAuditLogMapper.selectPageByAccountUserId 已把 quota ledger 固定为 quota_adjustment,并按 OpenAPI source owner allowlist 过滤。
  • muse-module-member-server/pom.xml 当前没有 muse-module-events-api 依赖;实现只允许新增 events-api direct dependency不得新增 events-server dependency。
  • MemberSecurityEventDO 虽有 App 读侧和 ack但当前高敏导出生产写入路径被 fail-closed 阻断,不是 P1R-7e 第一切片 source fact。

推断

  • P1R-7e 可复用 P1R-7b/P1R-7c/P1R-7d 的 outbox 状态机、worker claim、stale claim guard、dependency tree gate 和 Flyway _test 模式。
  • Account 第一切片按 entitlement audit log 逐条发布,比按 command 聚合更稳source fact 粒度、idempotencyKey 粒度和 resourceType 粒度一致。
  • Account outbox 与 entitlement audit log 必须保持两张表 / 两套状态机,否则 Account ledger 查询事实与 Events publish 补偿状态会互相污染。

假设

  • P1R-7e 允许 Member server 新增 muse-module-events-api direct dependency。
  • P1R-7e 允许新增 muse_account_event_publish_outbox
  • P1R-7e 第一批通知的 SSE owner 是 MemberEntitlementAuditLogDO.accountUserId,不是 admin operator。
  • P1R-7e 第一批使用 Events 现有 notification/quota_alert,不新增 OpenAPI event type 或 notification type。

设计合同

事件合同

第一批只允许以下 Account source fact 进入 publish outbox

Account source fact changeType Events eventType notification type SSE owner message
muse_member_entitlement_audit_log quota_adjustment notification quota_alert accountUserId 额度已调整

多 resourceType 同一 command 时,必须按 entitlement audit log 逐条发布,不允许同时做 command 聚合。

幂等合同

  • 每条 entitlement audit log 只生成一条 outbox。
  • commandId 格式:acct_evt: + SHA-256(tenantId|auditLogId|accountUserId|quota_alert) 前 32 位 hex。
  • outboxId 格式:acct_out: + SHA-256(tenantId|auditLogId|accountUserId|quota_alert) 前 32 位 hex。
  • Events source tuple
    • sourceOwner = account
    • sourceType = account_quota_adjustment
    • sourceId = entitlementAuditLog.id
    • sourceRevision = __none__
    • eventType = notification
  • 同一 entitlement audit log 重放必须得到同一 Events eventId/sequenceNo
  • 同一 command 下不同 resourceType 的 entitlement audit log 必须生成不同 commandId 和不同 source tuple。
  • Command replay 策略:adminCreateQuotaAdjustment 命中 Account command replay 时不补历史 outbox只有首次成功写入 entitlement audit log 的事务内创建 outbox。历史 backfill 若需要,必须另起任务。

Payload 合同

Internal outbox 允许保存以下字段:

  • auditLogId
  • sourceCommandId
  • accountUserId
  • operatorUserId
  • resourceType
  • deltaAmount
  • sourceOwner
  • auditEventId
  • requestHash
  • sourceRevision
  • message
  • resourceRefType
  • resourceRefId

Events payloadSummary 只能发布:

{
  "type": "quota_alert",
  "message": "额度已调整",
  "resourceRef": {
    "resourceType": "account_quota",
    "resourceId": 8001
  },
  "timestamp": "2026-06-07T12:00:00"
}

约束:

  • resourceRef.resourceId 必须是数值型 Long / Integer 语义,值为 entitlement audit log id。
  • DTO EventsPublishReqDTO.resourceId 是 String可以传 String.valueOf(auditLogId)payload resourceRef.resourceId 必须保持 Number不能是 String。
  • message 第一切片只允许 额度已调整
  • timestamp 使用 entitlement audit log 的 createTime,缺失时才回退 outbox createTime
  • 不得发布 reasonMessagereasonCodebeforeValueSnapshotdeltaValueSnapshotafterValueSnapshot、operatorUserId、correlationId、external provider detail、requestHash、error stack。
  • payloadSummary 必须是 exact allowlist顶层只能包含 typemessageresourceReftimestampresourceRef 只能包含 resourceTyperesourceId

状态机合同

Outbox 本域状态只允许:

  • queued
  • running
  • retryable
  • published
  • dead_letter

状态流转:

stateDiagram-v2
    [*] --> queued
    queued --> running: claim
    retryable --> running: retry due
    running --> running: expired lease reclaim
    running --> published: Events accepted
    running --> dead_letter: Events rejected/blocked
    running --> retryable: temporary failure and attempts remain
    running --> dead_letter: retry exhausted

Worker claim 要求:

  • 使用 PostgreSQL FOR UPDATE SKIP LOCKED 或等价 UPDATE ... RETURNING 原子领取。
  • 调度线程无请求租户上下文时必须先忽略租户拦截器领取,再使用 outbox 行内 tenantId 恢复租户上下文发布。
  • Claim 阶段唯一递增 attemptCount
  • 临时失败只释放 claim、设置 nextRetryAt 和安全错误摘要,不二次递增 attempt count。
  • markPublished / markRetryable / markDeadLetter 必须绑定 publish_status='running' AND attempt_count=#{claimedAttemptCount}
  • terminal update 返回 0 时必须记录 stale claim 诊断日志,包含 tenantId、accountUserId、outboxId、auditLogId、attempt、targetStatus、errorCode。
  • Worker 默认关闭:muse.account.events.publish-worker.enabled=false
  • 调度配置键:
    • muse.account.events.publish-worker.initial-delay-ms
    • muse.account.events.publish-worker.fixed-delay-ms
    • muse.account.events.publish-worker.max-attempt
    • muse.account.events.publish-worker.claim-timeout-seconds

失败路径合同

路径 结果
无 entitlement audit log 不创建 outbox
changeType 非 quota_adjustment 不创建 outbox
accountUserId null 或 <= 0 不创建 outbox记录安全日志
audit log id null 不创建 outbox记录安全日志
payload 构造缺 required 字段 创建 outbox 前拒绝并记录安全日志,不创建 outbox不发布错误码固定为 ACCOUNT_EVENTS_PAYLOAD_INVALID
Events accepted outbox -> published回写 eventId / sequenceNo
Events duplicate accepted outbox -> published回写既有 eventId / sequenceNo
Events rejected / blocked outbox -> dead_letter
CommonResult error / exception attempt 未耗尽 -> retryable耗尽 -> dead_letter
command replay 不补历史 outbox不重复发布

涉及文件

生产代码

  • Modify: muse-cloud/muse-module-member/muse-module-member-server/pom.xml
  • Modify: muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/application/account/AccountQuotaServiceImpl.java
  • Create: muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/application/account/AccountEventPublishOutboxService.java
  • Create: muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/application/account/AccountEventPublishOutboxServiceImpl.java
  • Create: muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/application/account/AccountEventPublishWorker.java
  • Create: muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/application/account/AccountEventPayloads.java
  • Create: muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/dal/dataobject/account/AccountEventPublishOutboxDO.java
  • Create: muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/dal/mysql/account/AccountEventPublishOutboxMapper.java
  • Create: muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/framework/config/MuseAccountEventsConfiguration.java
  • Create: muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/framework/config/MuseAccountEventsProperties.java
  • Create: muse-cloud/sql/muse/V20__extend_account_events_publish_outbox.sql

测试代码

  • Modify: muse-cloud/muse-module-member/muse-module-member-server/src/test/java/cn/iocoder/muse/module/member/application/account/AccountQuotaServiceTest.java
  • Create: muse-cloud/muse-module-member/muse-module-member-server/src/test/java/cn/iocoder/muse/module/member/application/account/AccountEventPublishOutboxServiceTest.java
  • Create: muse-cloud/muse-module-member/muse-module-member-server/src/test/java/cn/iocoder/muse/module/member/application/account/AccountEventPublishWorkerTest.java
  • Create: muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountEventsPublishDependencyTest.java
  • Create: muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountEventsPublishEndToEndTest.java
  • Create: muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountEventsPublishFlywayMigrationIT.java
  • Create: muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountEventsPublishMigrationSqlTest.java

文档

  • Modify: docs/agent-specs/.agent
  • Create: docs/memorys/YYYY-MM-DD-P1R7eAccountSourceOwnerPropagation真实链路.md

实施任务

Task 0fresh implementer preflight

  • 在 worktree 根执行:
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
git status --short --branch
git log --oneline -5
git pull --ff-only origin dev/1.0.0
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
ls muse-cloud/sql/muse/V*__*.sql | sort -V | tail
  • 若最新 migration 已不是 V19停止并修订 V20 编号。
  • 若 protected diff 非空,停止并汇报。
  • 若远端有更新且无法 fast-forward停止并汇报。

完成条件:

  • 记录 dirty baseline。
  • 确认 protected diff 为空。
  • 确认 V20 编号仍可用。

Task 1依赖与配置骨架

目标:

  • Member server 只新增 muse-module-events-api direct dependency。
  • 新增 Account Events worker 配置,默认关闭。

文件:

  • Modify: muse-cloud/muse-module-member/muse-module-member-server/pom.xml
  • Create: MuseAccountEventsConfiguration.java
  • Create: MuseAccountEventsProperties.java

要求:

  • 不新增 muse-module-events-server
  • 配置 prefixmuse.account.events
  • 默认:
    • publish-worker.enabled=false
    • publish-worker.max-attempt=5
    • publish-worker.claim-timeout-seconds=60
  • 配置类必须可被 Spring 扫描。

Focused tests

  • 配置默认值。
  • worker disabled 时 dispatchOnce() 不 claim。

Task 2V20 outbox schema

目标:

  • 新增 muse_account_event_publish_outbox,只服务 Account -> Events publish compensation。

文件:

  • Create: muse-cloud/sql/muse/V20__extend_account_events_publish_outbox.sql
  • Create: AccountEventPublishOutboxDO.java
  • Create: AccountEventPublishOutboxMapper.java
  • Create: P1rAccountEventsPublishMigrationSqlTest.java

表字段必须包含:

  • id
  • tenant_id
  • outbox_id
  • command_id
  • source_command_id
  • audit_log_id
  • account_user_id
  • operator_user_id
  • resource_type
  • delta_amount
  • source_owner
  • audit_event_id
  • request_hash
  • source_revision
  • event_type
  • notification_type
  • resource_ref_type
  • resource_ref_id
  • payload_summary
  • publish_status
  • attempt_count
  • max_attempt
  • next_retry_at
  • claimed_at
  • claim_expires_at
  • last_error_code
  • last_error_message
  • published_event_id
  • published_sequence_no
  • creator
  • updater
  • create_time
  • update_time
  • deleted

唯一约束:

  • (tenant_id, outbox_id)
  • (tenant_id, command_id)
  • (tenant_id, audit_log_id, notification_type)

Check 约束:

  • event_type = 'notification'
  • notification_type = 'quota_alert'
  • publish_status in ('queued','running','retryable','published','dead_letter')
  • account_user_id > 0
  • audit_log_id > 0
  • attempt_count >= 0
  • max_attempt > 0
  • resource_ref_id > 0

索引:

  • claim indexpublish_status, next_retry_at, claim_expires_at
  • owner status indexaccount_user_id, publish_status
  • audit log indexaudit_log_id

Trigger

  • update_time trigger。

Mapper 方法:

  • insertIgnore
  • selectByTenantIdAndOutboxId
  • selectByTenantIdAndCommandId
  • selectByTenantIdAndAuditLogIdAndNotificationType
  • claimNextPublishOutbox
  • markPublished
  • markRetryable
  • markDeadLetter

SQL gate

  • 断言字段、唯一约束、check 约束、索引、trigger。
  • 断言非法 event_typenotification_typepublish_statusaccount_user_idaudit_log_idresource_ref_id 在 Flyway _test 中被 DB 拒绝。

Task 3Account outbox service

目标:

  • 在 Account quota adjustment 首次成功事务内,为每条 entitlement audit log 创建一条 outbox。

文件:

  • Create: AccountEventPublishOutboxService.java
  • Create: AccountEventPublishOutboxServiceImpl.java
  • Create: AccountEventPayloads.java
  • Modify: AccountQuotaServiceImpl.java
  • Test: AccountEventPublishOutboxServiceTest.java
  • Test: AccountQuotaServiceTest.java

接入点:

  • AccountQuotaServiceImpl.adminCreateQuotaAdjustment
  • 在每个 MemberEntitlementAuditLogDO insert 后调用 outbox service。
  • command replay 返回时不得创建 outbox。

Outbox service 输入:

  • MemberEntitlementAuditLogDO log
  • source command id必须优先由调用方传入原始 reqVO.getCommandId();只有回查历史 log 且调用方无法传入时,才允许从 log.idempotencyKeylastIndexOf(':') 切出 commandId禁止用第一个冒号切分。
  • request hash允许 outbox 内部保存,不允许 Events payload 发布。

Payload 构造:

{
  "type": "quota_alert",
  "message": "额度已调整",
  "resourceRef": {
    "resourceType": "account_quota",
    "resourceId": 8001
  },
  "timestamp": "2026-06-07T12:00:00"
}

Payload validator

  • 顶层 exact keystype/message/resourceRef/timestamp
  • resourceRef exact keysresourceType/resourceId
  • type=quota_alert
  • message 只允许 额度已调整
  • resourceType=account_quota
  • resourceId 必须是 Number。
  • 不允许 requestHash、reason、snapshot、operator、correlation、error 等字段。

错误码:

  • payload invalid 固定为 ACCOUNT_EVENTS_PAYLOAD_INVALID

Focused tests

  • 首次 successful quota adjustment 为每个 audit log 创建 outbox。
  • command replay 不创建 outbox。
  • invalid owner 不创建 outbox并记录安全日志。
  • payload exact allowlist敏感字段负断言。
  • 多 resourceType 产生多条不同 outbox。
  • configured maxAttempt 生效。
  • timestamp 使用 audit log createTime缺失时回退 outbox createTime。

Task 4Account publish worker

目标:

  • Claim Account outbox构造 EventsPublishReqDTO,调用 EventsPublishApi,按结果更新 outbox 状态。

文件:

  • Create: AccountEventPublishWorker.java
  • Test: AccountEventPublishWorkerTest.java

Events request

  • commandId = outbox.commandId
  • tenantId = outbox.tenantId
  • ownerUserId = outbox.accountUserId
  • sourceOwner = account
  • sourceType = account_quota_adjustment
  • sourceId = String.valueOf(outbox.auditLogId)
  • sourceRevision = __none__
  • eventType = notification
  • resourceType = outbox.resourceRefType
  • resourceId = String.valueOf(outbox.resourceRefId)
  • payloadSummary = parsed exact allowlist payload
  • emittedAt = outbox.createTime

状态处理:

  • accepted -> published
  • duplicate accepted -> published
  • rejected / blocked -> dead_letter
  • CommonResult error -> retryable 或耗尽后 dead_letter
  • exception -> retryable 或耗尽后 dead_letter
  • payload invalid -> dead_letter,不调用 Events
  • unknown status -> dead_letter

Focused tests

  • disabled 不 claim。
  • enabled claim 使用配置 claim timeout。
  • accepted 回写 eventId / sequenceNo。
  • duplicate accepted 回写既有 eventId / sequenceNo。
  • rejected / blocked dead_letter。
  • CommonResult error 非最后一次 retryable最后一次 dead_letter。
  • exception 非最后一次 retryable最后一次 dead_letter。
  • invalid payload dead_letter 且不调用 Events。
  • owner invalid dead_letter 且不调用 Events。
  • terminal update 使用 claimed attempt ownership。
  • stale terminal update 返回 0 时记录诊断日志。

Task 5P1R executable gates

目标:

  • 用 server 侧 gate 证明依赖方向、focused E2E、V20 migration 和既有 owner gate 不回退。

文件:

  • Create: P1rAccountEventsPublishDependencyTest.java
  • Create: P1rAccountEventsPublishEndToEndTest.java
  • Create: P1rAccountEventsPublishFlywayMigrationIT.java
  • Create: P1rAccountEventsPublishMigrationSqlTest.java

Dependency test

  • Member server 有 direct muse-module-events-api
  • Member server 无 muse-module-events-server
  • Events server 无 AI / Knowledge / Market / Member / Content server。

E2E focused test

  • 构造 quota adjustment source fact。
  • Account outbox service 创建 outbox。
  • Worker 发布到 fake / in-memory Events publish API 或真实 Events service harness。
  • muse_unified_event 中 owner 为 accountUserId。
  • SSE visible query 对 account owner 可见,对其他 owner 不可见。
  • payload 不含 requestHash、reason、snapshot、operator、correlation。
  • 多 resourceType 产生多条独立通知。
  • rejected -> outbox dead_lettervisible query 为空。
  • stale claim terminal update 被忽略。

Flyway _test

  • 使用真实 PostgreSQL _test 数据库。
  • 跑到 V20。
  • 验证 table / indexes / constraints / trigger。
  • 验证非法 insert 被 DB 拒绝。

Task 6最终验证、review 和留痕

最终验证顺序必须先 build再 tests避免 clean 清理 surefire XML。

1. Full reactor build

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud && \
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o clean install -DskipTests -Dspring-boot.repackage.skip=true

期望62/62 或 live reactor 全量 modules success。

2. Account focused tests

先断言新增测试文件存在:

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
test -f muse-cloud/muse-module-member/muse-module-member-server/src/test/java/cn/iocoder/muse/module/member/application/account/AccountEventPublishOutboxServiceTest.java && \
test -f muse-cloud/muse-module-member/muse-module-member-server/src/test/java/cn/iocoder/muse/module/member/application/account/AccountEventPublishWorkerTest.java && \
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountEventsPublishMigrationSqlTest.java && \
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountEventsPublishDependencyTest.java && \
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountEventsPublishEndToEndTest.java && \
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountEventsPublishFlywayMigrationIT.java

运行:

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud && \
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o test \
  -pl muse-module-member/muse-module-member-server -am \
  -Dtest=AccountQuotaServiceTest,AccountEventPublishOutboxServiceTest,AccountEventPublishWorkerTest \
  -Dsurefire.failIfNoSpecifiedTests=false

必须检查 surefire XML目标测试类 tests>0 且 failures/errors/skipped 均为 0。

3. P1R mixed executable gates

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud && \
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

必须检查 surefire XML新增 Account 三个 P1R gate 和既有 Account owner gate 测试类 tests>0 且 failures/errors/skipped 均为 0

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
python3 - <<'PY'
from pathlib import Path
import xml.etree.ElementTree as ET

targets = [
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountEventsPublishMigrationSqlTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountEventsPublishDependencyTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountEventsPublishEndToEndTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountRealApiGateTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountRouteOwnershipTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMarketEventsPublishMigrationSqlTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMarketEventsPublishDependencyTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMarketEventsPublishEndToEndTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rKnowledgeEventsPublishMigrationSqlTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rKnowledgeEventsPublishDependencyTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rKnowledgeEventsPublishEndToEndTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiEventsPublishMigrationSqlTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiEventsPublishDependencyTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiEventsPublishEndToEndTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rApiCoverageReportTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rEventsRealApiGateTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rEventsRouteOwnershipTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiRealApiGateTest.xml",
    "muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiRouteOwnershipTest.xml",
]
for target in targets:
    path = Path(target)
    if not path.exists():
        raise SystemExit(f"missing surefire xml: {target}")
    root = ET.parse(path).getroot()
    tests = int(root.attrib.get("tests", "0"))
    failures = int(root.attrib.get("failures", "0"))
    errors = int(root.attrib.get("errors", "0"))
    skipped = int(root.attrib.get("skipped", "0"))
    if tests <= 0 or failures or errors or skipped:
        raise SystemExit(f"bad surefire result: {target} tests={tests} failures={failures} errors={errors} skipped={skipped}")
PY

4. Flyway _test

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud && \
. ~/.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

期望:

  • migrations_executed = 20。
  • target_schema_version = 20。
  • muse_account_event_publish_outbox 存在。
  • 目标测试 XML tests > 0failures/errors/skipped 均为 0。

5. Dependency tree gate

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud && \
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 && \
rg "muse-module-events-api" /tmp/p1r7e-member-server-dependency-tree.txt && \
! rg "muse-module-events-server" /tmp/p1r7e-member-server-dependency-tree.txt && \
! rg "muse-module-(ai|knowledge|market|member|content)-server" /tmp/p1r7e-events-server-dependency-tree.txt

6. Isolated coverage scanner

只允许在 /tmp 隔离副本运行 scanner

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
tmpdir=$(mktemp -d /tmp/p1r7e-coverage-scan.XXXXXX) && \
rsync -a --delete --exclude .git --exclude 'muse-cloud/**/target' ./ "$tmpdir"/ && \
cd "$tmpdir" && \
python3 -m venv .venv && \
. .venv/bin/activate && \
python -m pip install --quiet PyYAML && \
python muse-cloud/scripts/p1r-audit-api-coverage.py --check

期望:

  • scanner exit 0。
  • completed=100。
  • needsVerification=133。
  • incomplete=0。
  • genericPersistence=0。
  • ssePlaceholder=0。
  • Events streamEvents=dedicated / needs_verification
  • Account 33 operations 仍为 dedicated / needs_verification

7. Protected diff and whitespace

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
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

8. Memory

新增:

  • docs/memorys/2026-06-07-P1R7eAccountSourceOwnerPropagation真实链路.md

必须记录:

  • 目标和非目标。
  • 关键实现文件。
  • focused tests 数量。
  • P1R mixed gates 数量。
  • Flyway _test 结果。
  • dependency tree gate。
  • isolated coverage scanner 目录和 summary。
  • protected diff。
  • fresh implementation review 结果。
  • 明确说明仍不代表 Events / P1R-7 / Account completed。

9. Fresh implementation review

实现完成和最终验证后必须重新派发:

  • fresh spec / correctness review。
  • fresh quality / data-integrity / testing review。

双 PASS 前不得提交、push 或推进 completed。

完成条件

P1R-7e 实现完成条件:

  • Account quota adjustment terminal fact -> Account outbox -> worker -> EventsPublishApi -> muse_unified_event -> SSE owner-visible focused evidence 成立。
  • Member server 只依赖 events-api不依赖 events-server。
  • Events server 不反向依赖 AI / Knowledge / Market / Member / Content server。
  • V20 SQL 静态 gate 和 Flyway _test 通过。
  • focused tests、P1R mixed gates、dependency tree、isolated coverage scanner、protected diff、git diff --check 均通过。
  • fresh implementation spec review + quality review 双 PASS。

仍不得称为 completed

  • Account 33 operations。
  • Events streamEvents
  • 总 P1R-7。
  • Market / Knowledge / AI 之外任何未单独批准的域。