# 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 暴露。