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

266 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<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-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 和 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 只允许:
```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:<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 暴露。