# 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_call`、`muse_account_quota_request`、`muse_account_audit` 或 `muse_member_security_event` 作为 Events publish outbox。 4. 不修改 OpenAPI、scanner 或 coverage report;不推进 Account / Events / P1R-7 completed。 ```mermaid flowchart LR Admin["Admin quota adjustment command"] --> Quota["muse_member_quota
limit / used / revision"] Admin --> Audit["muse_member_entitlement_audit_log
quota_adjustment"] Audit --> Outbox["Account events publish outbox
queued / running / retryable / published / dead_letter"] Outbox --> Worker["Account worker
claim + retry + stale-claim guard"] Worker --> Api["EventsPublishApi
events-api only"] Api --> Unified[("muse_unified_event
notification / quota_alert")] Unified --> SSE["/app-api/muse/events
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.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-api`;Events 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` 已包含 `commandId`、`tenantId`、`ownerUserId`、`sourceOwner`、`sourceType`、`sourceId`、`sourceRevision`、`eventType`、`resourceType`、`resourceId`、`payloadSummary`、`emittedAt`。 - `EventsPublishServiceImpl` 先按 `commandId` 回放,再按 source tuple 回放。 - `EventsPublishServiceImpl` 已声明事件类型 `notification`,并允许 notification subtype `quota_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_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-server;Events 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 只允许: ```json { "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 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:`。 - 状态机:`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 同步返回语义。 ## 验收标准 审阅版通过后,执行版必须包含: 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 gate:Member server 有 events-api、无 events-server;Events 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 暴露。