oh-my-muse/docs/agent-specs/2026-06-07-P1R7eAccountSourceOwnerPropagation审阅版.md

15 KiB
Raw Blame History

P1R7e Account Source Owner Propagation 审阅版

结论

推荐进入 P1R-7e但第一切片只覆盖 Account 配额调整成功后的用户可见 quota_alert 通知,不覆盖全部 Account 安全事件、导出任务、New-API binding、quota request、usage attribution 或 completed approval。

推荐方案:

  1. 选择 AccountQuotaServiceImpl.adminCreateQuotaAdjustment 成功后写入的 muse_member_entitlement_audit_log(change_type=quota_adjustment) 作为 Account 第一批 source fact。
  2. 新增 Member(Account) 本域 Events publish outbox / worker调用 EventsPublishApi,发布 Events 已声明的 notification/quota_alert
  3. 不复用 muse_account_integration_callmuse_account_quota_requestmuse_account_auditmuse_member_security_event 作为 Events publish outbox。
  4. 不修改 OpenAPI、scanner 或 coverage report不推进 Account / Events / P1R-7 completed。
flowchart LR
    Admin["Admin quota adjustment command"] --> Quota["muse_member_quota<br/>limit / used / revision"]
    Admin --> Audit["muse_member_entitlement_audit_log<br/>quota_adjustment"]
    Audit --> Outbox["Account events publish outbox<br/>queued / running / retryable / published / dead_letter"]
    Outbox --> Worker["Account worker<br/>claim + retry + stale-claim guard"]
    Worker --> Api["EventsPublishApi<br/>events-api only"]
    Api --> Unified[("muse_unified_event<br/>notification / quota_alert")]
    Unified --> SSE["/app-api/muse/events<br/>account owner visible"]

本审阅版只冻结目标、非目标、取舍、风险和验收标准。执行版、实现和提交必须在本审阅版 fresh spec review + fresh quality / feasibility review 双 PASS 后再继续。

Review Gate

旧 feasibility reviewGoodall对早期候选 MemberSecurityEventDO 安全事件通知给出 FAIL。该 FAIL 已验证有效,并已用于排除 security event 第一切片:

  • 安全事件生产写入当前只在高敏导出路径后段,但高敏导出会在 normalizeRequest 中 fail-closed无法形成当前可达 source fact。
  • ack 是用户处理轨迹,不是新安全事实产生。
  • Events 当前 notification subtype 不包含 security_event,在不改 OpenAPI 的约束下不能发布安全事件通知。

修订后的最终审阅版已改为 quota adjustment -> notification/quota_alert,并完成 fresh 双 review

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

非阻塞风险已纳入执行版前置决策:

  • 执行版必须冻结为每条 entitlement audit log 一条通知。
  • resourceRef.resourceId 必须使用数值型 audit log id不能使用 account user id。
  • sourceRevision 固定为 Events 现有 __none__,不为 audit log 新增 revision。

已验证事实

工作区与状态

  • 正确 worktree/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
  • 分支:dev/1.0.0
  • git pull --ff-only origin dev/1.0.0 返回 Already up to date.
  • 当前 HEAD 包含 P1R-7d 两个已推送提交:
    • b36b153 feat(p1r): 接入 Market 事件传播真实链路
    • e8ed7d7 test(p1r): 收口 Market 事件传播真实链路门禁
  • 受保护文件 diff 为空:
    • 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
  • 当前未提交内容包括 P1R-7 completed approval 预检审阅文档和 .agent 状态补记;本阶段不回退、不清理。

全局 P1R-7 边界

  • P1R-7b AI、P1R-7c Knowledge、P1R-7d Market source owner propagation 已形成 needs_verification evidence不代表 Events / P1R-7 / 对应业务域 completed。
  • P1R-7 completed approval 必须另起任务并由用户明确批准。
  • source owner 只能依赖 muse-module-events-apiEvents server 不得反向依赖 AI / Knowledge / Market / Member / Content server。
  • coverage 当前边界仍保持:
    • completedOperations=100
    • needsVerificationOperations=133
    • incompleteOperations=0
    • genericPersistenceOperations=0
    • ssePlaceholderOperations=0
    • Events streamEvents=dedicated / needs_verification
    • Account 33 operations dedicated / needs_verification

Events 可复用合同

  • EventsPublishReqDTO 已包含 commandIdtenantIdownerUserIdsourceOwnersourceTypesourceIdsourceRevisioneventTyperesourceTyperesourceIdpayloadSummaryemittedAt
  • EventsPublishServiceImpl 先按 commandId 回放,再按 source tuple 回放。
  • EventsPublishServiceImpl 已声明事件类型 notification,并允许 notification subtype quota_alert
  • docs/api-contracts/events/openapi.yamlSSENotificationEvent.data.type 已声明 quota_alert
  • EventsStreamServiceImpl.notificationData 支持 type/message/resourceRef/timestamp,并对 resourceRef.resourceId 做 int64 语义过滤。

Account 当前事实模型

  • Account owner 位于 muse-module-member 的 Account 子域,没有独立 muse-module-account
  • muse-module-member-server/pom.xml 当前没有依赖 muse-module-events-api
  • AccountQuotaServiceImpl.adminCreateQuotaAdjustment 在同一事务内:
    • 校验 Account 用户存在。
    • AccountCommandService.reserveCommandcommandId 幂等。
    • MemberQuotaDO.revision 更新或创建 quota。
    • 写入 AccountAuditDO
    • 每个调整项写入 MemberEntitlementAuditLogDO(changeType=quota_adjustment)
    • MemberEntitlementAuditLogDO.accountUserId 是被调整账户 owner。
    • MemberEntitlementAuditLogDO.idempotencyKey 使用 commandId:resourceType
    • commandService.recordSucceeded 保存结果快照。
  • MemberEntitlementAuditLogMapper.selectPageByAccountUserId 已把 Account 配额 ledger 固定在 quota_adjustment 语义,并按 OpenAPI source owner allowlist 过滤。
  • Events 合同已有 quota_alert,因此本阶段不需要修改 Events OpenAPI。
  • MemberSecurityEventDO 存在并有 App 读侧、ack 和脱敏转换;但 AccountExportServiceImpl 的高敏导出当前在 normalizeRequest 中 fail-closed生产路径不会走到 insertSensitiveExportEvent。因此它不适合作为 P1R-7e 第一切片事实源。
  • AccountQuotaRequestDOAccountIntegrationCallDO 当前表达 quota request / 外部归因等待状态,不是终态配额变化事实。

推断

  • P1R-7e 第一切片应选 quota adjustment而不是 security event。原因是 quota adjustment 当前真实可达,且成功后已有 quota、command、audit、entitlement audit log 和 ownerUserIdsecurity event 的高敏导出生产入口当前被 fail-closed 阻断。
  • notification/quota_alert 已由 Events 合同声明,适合承载“额度已调整”的用户可见摘要。
  • muse_member_entitlement_audit_log 是 source fact不应直接兼任 publish outbox。它没有 claim lease、attempt、retry、published event、dead_letter、last_error 等发布队列语义。
  • 每个 quota adjustment command 可能写多条资源类型调整;第一切片可以按每条 entitlement audit log 发布一条 quota_alert,也可以在执行版中聚合为每个 command 一条通知。推荐每条 log 一条通知,因为它已有 idempotencyKey=commandId:resourceType,与 source tuple 更自然闭合。

假设

  • P1R-7e 允许在 Member(Account) owner 内新增独立 Events publish outbox 表或等价状态表。
  • P1R-7e 允许 muse-module-member-server 新增对 muse-module-events-api 的直接依赖,但不允许依赖 muse-module-events-server
  • Account quota alert 的 SSE owner 是 MemberEntitlementAuditLogDO.accountUserId,不是 admin operator。
  • quota_alert payload 可以使用 Events 现有 SSENotificationEvent 字段,不需要新增 OpenAPI 字段。

目标

  1. 证明 Account source owner 的真实 quota adjustment 事实可以通过 Account 本域 publish outbox / worker 发布到 Events。
  2. 让被调整账户用户可以通过统一 SSE 看到自己的额度变更通知。
  3. 建立 Account 与 Events 的单向依赖证据Member server 依赖 events-api不依赖 events-serverEvents server 不反向依赖 Member server。
  4. 为后续 Account security event、quota request、integration call、export task 等事件扩展保留清晰边界。

非目标

  1. 不把 Account 33 operations 从 needs_verification 推进到 completed
  2. 不把 Events streamEvents 或总 P1R-7 推进到 completed
  3. 不修改 Account / Events OpenAPI。
  4. 不修改 coverage scanner 或 coverage report。
  5. 不发布全部 Account security event不发布高敏导出安全事件。
  6. 不发布 New-API binding、quota request、integration call、call attribution job、usage record 或 export download credential。
  7. 不发布 admin reason 原文、requestHash、before/after 完整快照、operatorUserId、integration correlationId、外部调用详情、错误堆栈或文件凭证。
  8. 不让 Events server 查询 Member / Account 表。

推荐方案

事件选择

第一批只允许:

Account source fact 触发入口 Events eventType notification type SSE owner
muse_member_entitlement_audit_log(change_type=quota_adjustment) adminCreateQuotaAdjustment notification quota_alert account_user_id

暂不选择:

候选事实 暂不选择原因
muse_member_security_event 高敏导出创建入口当前 fail-closed第一切片没有真实可达生产事实且安全事件 payload 脱敏要求更高
muse_account_security_event_ack ack 是用户处理轨迹,不是新安全事实产生
muse_account_quota_request 状态是 request queued / pending external attribution不是额度已变化终态
muse_account_integration_call 内部外部调用跟踪,不适合直接推给 App SSE
muse_member_usage_record 用量流水可能高频且含 attribution / gateway 信息,需单独节流和脱敏设计

Payload allowlist

第一批 payload 只允许:

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

执行版可以把 message 收窄为安全固定枚举,例如:

  • 额度已调整
  • AI 调用额度已调整
  • 导出额度已调整

不得把以下字段放入 Events payload

  • requestHash
  • reasonMessage
  • operatorUserId
  • beforeValueSnapshot
  • deltaValueSnapshot
  • afterValueSnapshot
  • correlationId
  • integrationCallId
  • 外部 provider 返回值
  • 错误堆栈

Outbox 边界

推荐新增独立 Account Events publish outbox

  • source tablemuse_member_entitlement_audit_log
  • source tuplesourceOwner=accountsourceType=account_quota_adjustmentsourceId=entitlementAuditLog.idsourceRevision=__none__ 或 audit log revision 等价值。
  • commandId稳定短幂等键建议 acct_evt:<sha256(tenantId|auditLogId|accountUserId|quota_alert)>
  • 状态机:queued/running/retryable/published/dead_letter
  • claim必须原子领取具备 lease / retry / attempt ownership。
  • 终态回写:必须防 stale worker 覆盖新 claim 终态。
  • worker 默认关闭,配置启用后才 claim。
  • invalid owner 或 invalid payload 必须 fail-closed不得发布给 owner 0 或 admin operator。

依赖边界

  • Member server 可以依赖 muse-module-events-api
  • Member server 不得依赖 muse-module-events-server
  • Events server 不得依赖 Member server。
  • muse-server 作为装配层可以同时包含 Member server 与 Events server。

关键取舍

  • 选择 quota adjustment不选择 security event前者当前真实可达且 Events 合同有 quota_alert;后者虽然有读侧和脱敏转换,但首个生产写入路径被 fail-closed 阻断。
  • 选择 account owner 可见,不选择 admin operator 可见:统一 SSE 是 App 侧 owner stream管理员操作应继续由 admin audit / ledger 承载。
  • 使用 notification/quota_alert,不新增 event typeEvents 合同已经声明 subtype足够承载第一批摘要。
  • 不复用 entitlement audit log 作为 publish queuesource fact 与 publish compensation 是两个职责,混用会缺 claim、retry 和 dead_letter 证据。

影响范围

  • 直接影响:
    • Account quota adjustment source fact 发布链路。
    • Member server 依赖 events-api。
    • Member(Account) 本域 outbox / worker / migration / focused tests。
    • Events unified event focused E2E。
  • 间接影响:
    • P1R-7 completed approval 的 source owner evidence 更完整。
  • 不影响:
    • OpenAPI 合同。
    • coverage scanner / coverage report。
    • Account 33 operations completion status。
    • Account quota adjustment 同步返回语义。
    • New-API、FileService、Market、Content 或 security event 业务逻辑。

风险与兼容性

  • 事件重复风险:同一 entitlement audit log retry 不能创建多条 unified event必须用 commandId 与 source tuple 双重幂等。
  • 多调整项风险:一个 command 可能产生多条 audit log执行版必须明确按 log 逐条发布或按 command 聚合,不能两种同时存在。
  • 并发风险worker terminal update 必须绑定本次 claim attempt避免 stale worker 覆盖新 claim。
  • 敏感信息风险quota adjustment 的 reason、requestHash、before/after snapshot 和 operatorUserId 不得进入 payload。
  • owner 风险SSE owner 必须是 accountUserId不能是 operatorUserId。
  • 可达性风险security event 不能在本阶段被包装成已可达 production fact。
  • 兼容性:新增 outbox 与 worker 默认关闭,不改变现有 Account API 同步返回语义。

验收标准

审阅版通过后,执行版必须包含:

  1. Account outbox DO / Mapper / Service / Worker / 配置 / V20 migration 的文件边界。
  2. AccountQuotaServiceImpl.adminCreateQuotaAdjustment 在 entitlement audit log 写入后同事务创建 outbox 的事务边界。
  3. payload exact allowlist 与敏感字段拒绝策略。
  4. worker success / duplicate / retryable / CommonResult error / rejected / blocked / dead_letter / stale claim tests。
  5. Account quota adjustment source fact -> Account outbox -> worker -> EventsPublishApi -> muse_unified_event -> SSE visible focused E2E。
  6. dependency gateMember server 有 events-api、无 events-serverEvents server 无 AI / Knowledge / Market / Member / Content server。
  7. V20 SQL 静态 gate 与 Flyway _test
  8. P1R mixed gates 继续确认 AI / Knowledge / Market source owner gates 不回退Account 仍为 dedicated / needs_verification
  9. coverage scanner 只在 /tmp 隔离副本运行,真实 worktree protected diff 为空。
  10. git diff --check 和 protected diff gate。

待确认项

  1. P1R-7e 第一切片是否确认只做 Account quota adjustment quota_alert
  2. 多个 resourceType 同一 command 时,是每条 audit log 一条通知,还是按 command 聚合一条通知。
  3. resourceRef.resourceId 是否使用 entitlement audit log id,还是使用 account user id推荐使用 audit log id避免把用户 ID 当业务资源 ID 暴露。