spec: PJ2026-0104010803 P0 durable outbox/reconciler + SSE cursor replay + DB constraints + 0读侧推理验收矩阵

新增 SPEC 章节:
- 5.6 durable projection outbox 与 reconciler 架构图
- 5.7 SSE cursor replay 架构与时序图
- 5.8 DB 唯一约束与幂等保障
- 5.9 0读侧推理验收矩阵

更新实现引用版本为 draft-2026-06-20-p1-zero-split-durable-realtime
同步 web-workbench 和 api-contract SPEC 引用版本

Closes #1746
Refs #1742
This commit is contained in:
Codex
2026-06-20 11:23:25 +00:00
parent 6d0054c78c
commit 624a1307fc
3 changed files with 75 additions and 5 deletions
@@ -19,7 +19,7 @@
| 短名 | Web工作台 |
| 层级 | L2 课题 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-06-20-p0-long-running-workbench; draft-2026-06-20-p0-error-diagnostics; draft-2026-06-20-p1-view-local-timing-ticker; PJ2026-0104010803 唯一投影 draft-2026-06-20-p0-durable-facts-model |
| 实现引用版本 | draft-2026-06-20-p0-long-running-workbench; draft-2026-06-20-p0-error-diagnostics; draft-2026-06-20-p1-view-local-timing-ticker; PJ2026-0104010803 唯一投影 draft-2026-06-20-p0-durable-facts-model; draft-2026-06-20-p1-zero-split-durable-realtime |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-0104 客户端](PJ2026-0104-client.md) |
| 规格治理索引 | [规格治理](spec-governance.md) |
@@ -19,7 +19,7 @@
| 短名 | Workbench唯一投影 |
| 层级 | L4 专项规格切片 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-06-20-p0-durable-facts-model; draft-2026-06-20-p1-view-local-timing-ticker |
| 实现引用版本 | draft-2026-06-20-p0-durable-facts-model; draft-2026-06-20-p1-view-local-timing-ticker; draft-2026-06-20-p1-zero-split-durable-realtime |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-010401 Web工作台](PJ2026-010401-web-workbench.md) |
| 关联规格 | [PJ2026-010403 API契约](PJ2026-010403-api-contract.md)、[PJ2026-010205 HWLAB接入](PJ2026-010205-hwlab-dispatch.md)、[PJ2026-0102 Agent编排](PJ2026-0102-agent-orchestration.md) |
@@ -250,6 +250,76 @@ Workbench durable facts 是唯一投影的持久对象模型。后续实现可
对象模型必须支持下列 DTO 投影:session rail summary、session detail、message/part page、turn snapshot、trace event page、projection diagnostic 和 transport diagnostic。DTO 可以裁剪字段,但不得合并 authority;例如 trace detail 可展示 `sourceSeq/sourceEventId` 审计信息,主 timeline 的 finalResponse 只能来自 sealed message/part/turn facts。
### 5.6 durable projection outbox 与 reconciler
Projection outbox 是 durable commit log,与 projection facts 在同一数据库事务提交。outbox 的作用是让 SSE、CLI 和其他消费者从 durable cursor 恢复实时通知,不依赖进程内内存 listener。
```mermaid
flowchart LR
AR[AgentRun events/result] --> AD[Adapter normalize]
AD --> WR[ProjectionWriter]
WR --> TX[(DB transaction: facts + outbox + checkpoint)]
TX --> FACTS[durable facts]
TX --> OUTBOX[durable outbox rows]
TX --> CP[checkpoint]
OUTBOX --> SSE[SSE cursor replay]
OUTBOX --> REC[reconciler worker]
FACTS --> RM[ReadModel]
RM --> REST[GET /v1/workbench/*]
SSE --> STREAM[SSE events]
CP --> BOOT[boot/resume scheduler]
BOOT --> FIN[Finalizer]
FIN --> WR
```
Outbox 每行至少包含 `outboxSeq`(全局单调)、`traceId``sessionId``turnId``projectedSeq``commitType`event/terminal/checkpoint/diagnostic)、`createdAt` 和可序列化 payload 摘要。SSE 连接建立时先从 durable outbox 按 `afterSeq` 查询历史 commit notification 进行 replay,再切换到 live tailcloud-api 重启后不需要内存 listener 即可恢复 SSE 输出。
Reconciler 是后台 worker,消费 outbox 和 checkpoint 驱动 finalizer 追平。它取代旧的请求/轮询式 `syncAgentRunChatResult` 推进模式:AgentRun result sync 只负责拉取/写入 projection source state,事件推进由 outbox/reconciler 管道统一处理。
### 5.7 SSE cursor replay 架构
```mermaid
sequenceDiagram
participant Client as Web/CLI
participant SSE as SSE handler
participant DB as durable outbox
participant WR as ProjectionWriter
Client->>SSE: GET /v1/workbench/events?afterSeq=N
SSE->>DB: SELECT outbox WHERE outboxSeq > N
DB-->>SSE: replay rows (terminal, events, diagnostics)
SSE-->>Client: replay events
Note over WR,DB: new projection commit
WR->>DB: INSERT outbox row
DB-->>SSE: live tail notification
SSE-->>Client: live event
```
SSE handler 不再直接订阅内存 `traceStore.subscribe`。它只从 durable outbox 读取:连接建立时执行 replay(按 `afterSeq` 查询历史 commit notification),replay 完成后切换到 live tailpoll 或 LISTEN/NOTIFY 等价机制)。cloud-api 滚动、多副本和多用户同看同 trace 时,SSE 从同一 durable outbox 恢复,不丢 terminal/sealed 状态。
### 5.8 DB 唯一约束与幂等保障
`workbench_trace_events` 表必须有 `(trace_id, source_event_id)` 唯一约束,防止并发或历史数据写入重复 source event。allocator 查询 sourceEventId 复用 projectedSeq 时,数据库层必须硬约束阻断重复写入。
`(trace_id, projected_seq)` 也应有唯一约束,确保同一 trace 内 projectedSeq 单调不重复。历史 collision 数据在 migration 时通过 collision blocker 策略处理:检测到重复时暴露 `projectionStatus=blocked``projectionHealth=degraded` 和稳定 blocker code,不把损坏序列交给 renderer 继续展示。
Projection outbox 的 `outboxSeq` 必须全局单调递增,可通过数据库 sequence 或等价机制生成。outbox 行与 projection facts 在同一事务提交,保证 commit notification 与 durable facts 一致。
### 5.9 0读侧推理验收矩阵
| 层 | 禁止模式 | 验收方式 |
| --- | --- | --- |
| 后端 turn projection | `result?.status ?? trace?.status ?? session?.status` | 源码扫描 + 单元测试构造多来源矛盾样本 |
| 后端 SSE handler | `traceStore.subscribe` / 内存 trace snapshot | 源码扫描 + cloud-api 重启后 SSE cursor 恢复验证 |
| 后端 trace snapshot | `events.at(-1)` 推导 status/lastEvent | 源码扫描 + 矛盾 events 样本测试 |
| 后端 compact result | result/traceSummary/agentRun/memoryTrace 混合 | 源码扫描 + GET 路径不访问旧 compact path 验证 |
| 前端 store | `events.at(-1)` 推导 activity/label/status | 源码扫描 + fake-server Playwright |
| 前端 subscription | turn poll + trace poll + event tail 合并 lifecycle | 源码扫描 + SSE gap/timeout Playwright |
| 前端 timing | `lastEventAgeMs` 作为相对时间权威 | 源码扫描 + fake clock Playwright |
| CLI renderer | `lastEvent.status` 推 trace lifecycle | 源码扫描 + CLI 同源语义验证 |
每层验收必须先用负向 fixture 形成红灯,再用修复后代码证明通过。负向源码扫描是辅助证据,不能替代 fake-server Playwright 与 D601 v0.3 web-probe。
## 6. 原子需求
### 6.1 WB-PROJ-REQ-001 durable facts store
@@ -389,7 +459,7 @@ Sealed final response 回归必须构造 completed assistant 主正文已经展
本专项范围内新增或重构的核心源码文件,文件头部必须标注遵循的 SPEC 编号、短名和实现引用版本,例如:
```text
SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-20-p0-durable-facts-model
SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-20-p1-zero-split-durable-realtime
```
同一文件同时承载 API契约、HWLAB接入、Web工作台或 Agent编排职责时,应在同一头部追加对应 SPEC 编号与实现引用版本。实现文件不得只写 issue 编号、`latest``current`。自动生成文件、第三方 vendored 文件、纯配置、锁文件和无法承载注释头的二进制产物可例外,但对应生成器、渲染器或配置入口必须能追溯到本 SPEC。
@@ -19,7 +19,7 @@
| 短名 | API契约 |
| 层级 | L2 课题 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-06-20-p0-workbench-pure-read-api; draft-2026-06-20-p0-error-diagnostics; draft-2026-06-20-p1-view-local-timing-ticker; PJ2026-0104010803 唯一投影 draft-2026-06-20-p0-durable-facts-model; PJ2026-01050105 Web鉴权 draft-2026-06-18-p0-auth |
| 实现引用版本 | draft-2026-06-20-p0-workbench-pure-read-api; draft-2026-06-20-p0-error-diagnostics; draft-2026-06-20-p1-view-local-timing-ticker; PJ2026-0104010803 唯一投影 draft-2026-06-20-p0-durable-facts-model; draft-2026-06-20-p1-zero-split-durable-realtime; PJ2026-01050105 Web鉴权 draft-2026-06-18-p0-auth |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-0104 客户端](PJ2026-0104-client.md) |
| 规格治理索引 | [规格治理](spec-governance.md) |
@@ -209,7 +209,7 @@ Trace resource 必须支持按事件位置继续读取,例如 `sinceSeq` / `cu
所有 GET path 必须无业务副作用。需要 repair、finalize、sync 或 migration 时,应由后台 finalizer、显式 mutation 或持久化投影入口执行,不能隐藏在 workspace、conversation、turn 或 trace GET 中。所有分页响应必须包含稳定 cursor 或 seq 边界;trace event page 的分页 cursor 使用 HWLAB `projectedSeq`,并保留 AgentRun `sourceSeq/sourceEventId` 作为来源审计字段,二者不得重叠或互相替代。所有错误响应必须包含 route、status、code/blocker、redacted actor、target resource 和下一步建议。
新增或重构的核心后端文件头部必须标注遵循的 SPEC 编号、短名和实现引用版本,例如 `SPEC: PJ2026-0104010803 唯一投影 draft-2026-06-20-p0-durable-facts-model; PJ2026-010403 API契约 draft-2026-06-20-p0-workbench-pure-read-api; PJ2026-010205 HWLAB接入 draft-2026-06-17-r0`,并简述文件职责。实现文件不得只写 issue 编号、`latest``current` 作为规格引用。
新增或重构的核心后端文件头部必须标注遵循的 SPEC 编号、短名和实现引用版本,例如 `SPEC: PJ2026-0104010803 唯一投影 draft-2026-06-20-p0-durable-facts-model; draft-2026-06-20-p1-zero-split-durable-realtime; PJ2026-010403 API契约 draft-2026-06-20-p0-workbench-pure-read-api; PJ2026-010205 HWLAB接入 draft-2026-06-17-r0`,并简述文件职责。实现文件不得只写 issue 编号、`latest``current` 作为规格引用。
### 6.4 CLIENT-API-REQ-004 资源与管理接口