15 KiB
15 KiB
P1R7e Account Source Owner Propagation 审阅版
结论
推荐进入 P1R-7e,但第一切片只覆盖 Account 配额调整成功后的用户可见 quota_alert 通知,不覆盖全部 Account 安全事件、导出任务、New-API binding、quota request、usage attribution 或 completed approval。
推荐方案:
- 选择
AccountQuotaServiceImpl.adminCreateQuotaAdjustment成功后写入的muse_member_entitlement_audit_log(change_type=quota_adjustment)作为 Account 第一批 source fact。 - 新增 Member(Account) 本域 Events publish outbox / worker,调用
EventsPublishApi,发布 Events 已声明的notification/quota_alert。 - 不复用
muse_account_integration_call、muse_account_quota_request、muse_account_audit或muse_member_security_event作为 Events publish outbox。 - 不修改 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 review(Goodall)对早期候选 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 review:PASS,无 P0/P1/P2。
- Ampere quality / feasibility review:PASS,无 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.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
- 当前未提交内容包括 P1R-7 completed approval 预检审阅文档和
.agent状态补记;本阶段不回退、不清理。
全局 P1R-7 边界
- P1R-7b AI、P1R-7c Knowledge、P1R-7d Market source owner propagation 已形成
needs_verificationevidence,不代表 Events / P1R-7 / 对应业务域 completed。 - P1R-7 completed approval 必须另起任务并由用户明确批准。
- source owner 只能依赖
muse-module-events-api;Events server 不得反向依赖 AI / Knowledge / Market / Member / Content server。 - coverage 当前边界仍保持:
completedOperations=100needsVerificationOperations=133incompleteOperations=0genericPersistenceOperations=0ssePlaceholderOperations=0- Events
streamEvents=dedicated / needs_verification - Account 33 operations
dedicated / needs_verification
Events 可复用合同
EventsPublishReqDTO已包含commandId、tenantId、ownerUserId、sourceOwner、sourceType、sourceId、sourceRevision、eventType、resourceType、resourceId、payloadSummary、emittedAt。EventsPublishServiceImpl先按commandId回放,再按 source tuple 回放。EventsPublishServiceImpl已声明事件类型notification,并允许 notification subtypequota_alert。docs/api-contracts/events/openapi.yaml的SSENotificationEvent.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.reserveCommand做commandId幂等。 - 按
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 第一切片事实源。AccountQuotaRequestDO和AccountIntegrationCallDO当前表达 quota request / 外部归因等待状态,不是终态配额变化事实。
推断
- P1R-7e 第一切片应选 quota adjustment,而不是 security event。原因是 quota adjustment 当前真实可达,且成功后已有 quota、command、audit、entitlement audit log 和 ownerUserId;security 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_alertpayload 可以使用 Events 现有SSENotificationEvent字段,不需要新增 OpenAPI 字段。
目标
- 证明 Account source owner 的真实 quota adjustment 事实可以通过 Account 本域 publish outbox / worker 发布到 Events。
- 让被调整账户用户可以通过统一 SSE 看到自己的额度变更通知。
- 建立 Account 与 Events 的单向依赖证据:Member server 依赖 events-api,不依赖 events-server;Events server 不反向依赖 Member server。
- 为后续 Account security event、quota request、integration call、export task 等事件扩展保留清晰边界。
非目标
- 不把 Account 33 operations 从
needs_verification推进到completed。 - 不把 Events
streamEvents或总 P1R-7 推进到completed。 - 不修改 Account / Events OpenAPI。
- 不修改 coverage scanner 或 coverage report。
- 不发布全部 Account security event,不发布高敏导出安全事件。
- 不发布 New-API binding、quota request、integration call、call attribution job、usage record 或 export download credential。
- 不发布 admin reason 原文、requestHash、before/after 完整快照、operatorUserId、integration correlationId、外部调用详情、错误堆栈或文件凭证。
- 不让 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:
requestHashreasonMessageoperatorUserIdbeforeValueSnapshotdeltaValueSnapshotafterValueSnapshotcorrelationIdintegrationCallId- 外部 provider 返回值
- 错误堆栈
Outbox 边界
推荐新增独立 Account Events publish outbox:
- source table:
muse_member_entitlement_audit_log。 - source tuple:
sourceOwner=account,sourceType=account_quota_adjustment,sourceId=entitlementAuditLog.id,sourceRevision=__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 type:Events 合同已经声明 subtype,足够承载第一批摘要。 - 不复用 entitlement audit log 作为 publish queue:source 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 同步返回语义。
验收标准
审阅版通过后,执行版必须包含:
- Account outbox DO / Mapper / Service / Worker / 配置 / V20 migration 的文件边界。
AccountQuotaServiceImpl.adminCreateQuotaAdjustment在 entitlement audit log 写入后同事务创建 outbox 的事务边界。- payload exact allowlist 与敏感字段拒绝策略。
- worker success / duplicate / retryable / CommonResult error / rejected / blocked / dead_letter / stale claim tests。
- Account quota adjustment source fact -> Account outbox -> worker -> EventsPublishApi ->
muse_unified_event-> SSE visible focused E2E。 - dependency gate:Member server 有 events-api、无 events-server;Events server 无 AI / Knowledge / Market / Member / Content server。
- V20 SQL 静态 gate 与 Flyway
_test。 - P1R mixed gates 继续确认 AI / Knowledge / Market source owner gates 不回退,Account 仍为
dedicated / needs_verification。 - coverage scanner 只在
/tmp隔离副本运行,真实 worktree protected diff 为空。 git diff --check和 protected diff gate。
待确认项
- P1R-7e 第一切片是否确认只做 Account quota adjustment
quota_alert。 - 多个 resourceType 同一 command 时,是每条 audit log 一条通知,还是按 command 聚合一条通知。
resourceRef.resourceId是否使用 entitlement audit logid,还是使用 account user id;推荐使用 audit log id,避免把用户 ID 当业务资源 ID 暴露。