feat(chat): cut over to single SSE endpoint

This commit is contained in:
zhuyongxin
2026-07-22 10:01:12 +08:00
parent f8809cb7dd
commit bc36248cd8
36 changed files with 2187 additions and 945 deletions
@@ -0,0 +1,110 @@
# Decisions: single-react-chat-sse-cutover
## Discover Status
- Checkpoint: Discover
- Capability source: `sm-flow` + `grill-with-docs`;`codebase-retrieval`、LSP 和 GitNexus MCP 当前不可用,使用 `rg` 引用核对、源码阅读和 focused tests 降级。
- Scale: complex。涉及 L4 HTTP/SSE 协议、前端消费者、异步连接生命周期、生产 Bean 装配和阶段 7 删除边界。
- `devflow/index.md` 命中阶段 0-6A、ISS-013 和 session-run-trace-isolation;ISS-014 是更新且已冻结的最终协议源。
## Question Pool
| # | 维度 | 问题 | 模式 | 状态 |
|---|---|---|---|---|
| Q1 | 术语 | “真正 SSE”是 Token streaming,还是过程事件实时 + 最终内容一次释放? | evidence-driven | 已解决 |
| Q2 | 协议 | 唯一 endpoint、事件名称、payload、顺序、互斥和终态是什么? | evidence-driven | 已解决 |
| Q3 | 边界 | Controller、Application Use Case、Harness 和 SSE adapter 各自拥有何种职责? | evidence-driven | 已解决 |
| Q4 | 生命周期 | disconnect/timeout/send failure 如何取消同一个 Run,正常 complete 如何避免误取消? | evidence-driven | 已解决 |
| Q5 | 执行器 | 如何消除 Controller 自建无界线程池并处理饱和? | evidence-driven | 已解决 |
| Q6 | 装配 | 阶段 2-6A plain Java components 如何形成可启动的生产 Bean graph? | evidence-driven | 已解决 |
| Q7 | 前端 | 快速/流式双模式如何迁移到唯一 SSE consumer? | evidence-driven | 已解决 |
| Q8 | 安全 | 哪些内部状态/内容禁止进入 SSE? | evidence-driven | 已解决 |
| Q9 | 兼容 | 是否保留同步 `/api/chat` 或 `/api/chat_stream` 兼容? | evidence-driven | 已解决 |
| Q10 | 范围 | `/api/ai_ops` 和旧多 Agent 何时处理? | evidence-driven | 已解决 |
| Q11 | 验收 | 如何证明 event order、single terminal、same IDs、cancel 和 production wiring? | evidence-driven | 已解决 |
## Evidence-driven
| 结论 | 证据来源 | 是否已汇报用户 |
|---|---|---|
| 真正 SSE 固定为状态实时发送、最终 typed content 一次释放,不做 Token/字符切片。 | ISS-014 4.7 | 已汇报 |
| 唯一入口是 `POST /api/chat`;顺序为 `metadata -> status* -> content|failure -> done`。 | ISS-014 4.7、阶段 6B | 已汇报 |
| metadata/status/content/failure/done 使用 named SSE event;payload 不再包含旧 `type/data` 包装。 | ISS-014 最小事件结构 | 已汇报 |
| 当前 Controller 同时拥有旧 ChatService、模型、Tools、SessionManager、同步/伪流式流程和 cached thread pool。 | `ChatController.java` | 已汇报 |
| 当前前端 quick 调 `/chat` JSON,stream 调 `/chat_stream` 并保留大量旧格式 fallback。 | `static/app.js` | 已汇报 |
| `ChatApplicationUseCase` 已提供 observer、同一 run control、typed content 和 stable failure code,但组件尚无完整生产 Bean graph。 | 阶段 6A code + Spring annotation scan | 已汇报 |
| 客户端断开必须通过 observer 得到的 `ChatRunControl` 取消,同一引用需覆盖断开早于 onStarted 的竞态。 | 阶段 6A design + `SseEmitter` lifecycle | 已汇报 |
| `/api/ai_ops` 是独立公开入口,不属于阶段 6B Chat 原子切换;旧实现阶段 7 清理/处置。 | ISS-014 阶段 6B/7 | 已汇报 |
## User-interview
- 无新增 user-interview。唯一 endpoint、破坏性迁移、事件 schema、非 Token 流、自动 Apply/Archive/commit 均由 ISS-014 与用户持续授权冻结。
## Key Decisions
- Controller 使用构造注入的 `ChatApplicationUseCase` 与受控 `TaskExecutor`;不注入 ChatModel、Tools、ChatService 或 SessionManager 来处理 Chat。
- SSE adapter 为每个请求维护单一 state machine 和 `AtomicReference<ChatRunControl>`;disconnect 标记先于 onStarted 时,onStarted 立即取消。
- 正常 result 只产生一次 typed content 和 done;异常只产生 failure 和 done;IOException/timeout/disconnect 只取消,不尝试补发终态。
- executor rejection 在没有 Run 时发送稳定 failure/done;worker 启动后所有 failure code 来自 `ChatApplicationException`,不暴露 cause。
- 生产装配使用集中 `harness.chat` properties 和 Spring-managed bounded executors;所有模型调用复用同一 `ChatModel` 和 `GuardModelCall`。
- 前端移除 mode selector 和 quick path,只保留一个严格 named-event parser;unknown event/schema fail closed。
- 不创建 ADR:L4 方案已经在 ISS-014 设计冻结,本 change 负责原子落地和迁移说明。
## OpenSpec Backfill
- 需进入 proposal/design/spec/tasks:唯一 SSE、五事件 state machine、typed payload、安全 failure、same IDs、disconnect cancellation、bounded executors、production assembly、frontend migration、L4 rollback。
- 非目标:AiOps 协议、旧类物理删除、live E2E。
## Cross-artifact Alignment
| 上游 -> 下游 | 检查内容 | 状态 |
|---|---|---|
| ISS-014/brief -> proposal | 唯一 SSE、五事件、断开取消、前端迁移、生产装配、阶段 7 边界 | 已对齐 |
| proposal -> design | named events、state machine、bounded executors、Bean graph、AiOps 隔离、L4 rollback | 已对齐 |
| design -> specs/tasks | 每项 ownership/lifecycle/safety/migration 均有可观察 requirement 和纵向切片 | 已对齐 |
| specs -> tasks | 10 组 requirements 覆盖 wiring、SSE、cancel、frontend、AiOps regression 和 verification | 已对齐 |
## Architecture Audit
- Capability source: `zoom-out`,使用 Chat Application Use Case、RunContext、Run Lifecycle、Diagnosis Harness 和 Chat SSE Contract 术语。
- 链路为 `browser -> Controller -> SSE session -> bounded worker -> Application Use Case -> Harness -> typed release -> SSE session`;业务真理源不进入 Controller。
- SSE session 独占协议状态,Application 独占 Run/dispatch/persistence,Core 独占 cancel/budget,configuration 独占 infrastructure graph。
- 最大风险是 disconnect/onStarted 与 terminal callback 竞态,design/tasks 已固定 pending-disconnect、atomic terminal 和 no-send-after-close tests。
- L4 部署必须 Controller/frontend 同版本;回滚成对返回阶段 6A commit,V012 可保留。
## Interface Impact
- Level: L4 breaking HTTP/frontend contract。
- 删除同步 JSON `POST /api/chat`、`POST /api/chat_stream` 和旧 Chat SSE wrapper;新增 named-event SSE `POST /api/chat`。
- 消费者:bundled `static/app.js`、外部 Chat callers、Controller/MockMvc tests;Trace/feedback 继续使用 metadata IDs。
- 迁移/回滚:前后端同 commit 原子部署/回滚;不提供兼容开关、双 endpoint 或旧 parser。
## Commit Gate Preflight
- proposal、design、specs、tasks 完整,change strict validation 通过。
- Question pool 全部 evidence-driven 解决并已汇报,无 user-interview、未判级接口或未接受架构风险。
- Cross-artifact 四段对齐无 gap;L4 migration/rollback、production wiring、disconnect race 和阶段 7 边界均进入 design/spec/tasks。
- `openspec-propose` 补全产物,`zoom-out` 完成架构审计;可提交为 Committed OpenSpec。
## Apply Progress
- 1.1-1.4 完成:新增 `ChatHarnessProperties`、bounded worker/model executors 和显式 `HarnessChatConfiguration` graph;空 MySQL datasource 只允许 fail-closed unknown logical ID。
- 2.1-2.4 完成:新增 named-event `ChatSseEvent`/`ChatSseSession`,Chat 唯一 SSE Controller,移除 `/chat_stream` 和同步 Chat;AiOps 模型/Tool 依赖下沉到 service。
- 3.1-3.3 完成:前端移除 quick/stream 双轨,统一 named-event parser、typed renderer 和静态 contract test。
- `JpaChatRunStore` 生产构造器改为注入集中 `PublishedResultPolicy`,避免读写边界漂移;这是实现偏差修复,无需改需求方向。
## Apply Review
- Review 发现 `ChatController` 虽然 Chat path 已经只调用应用用例,但类本身仍承载 AiOps 和 Session 管理依赖,不完全满足“Chat Controller 只负责 Chat 协议”的 ownership 要求。
- 用户确认将职责拆分为 `ChatController`(仅 `/api/chat`)、`AiOpsController`(保持 `/api/ai_ops`)和 `ChatSessionController`(保持 clear/session/runs URL)。
- 该修正保持所有公开 URL、AiOps message-wrapper payload 和 Session observable behavior,不修改 Committed OpenSpec 的范围或方向。
- `styles.css` 中 mode selector/dropdown 死样式随前端双轨删除一并移除。
- AGENTS.md 指定的 `codebase-retrieval` 和 LSP 工具在当前环境不可用;使用 OpenSpec 全量上下文、`rg` 引用检查、Java 编译、结构测试和综合回归完成等价影响面确认。
## Verification and Migration
- Stage 2-6B + AiOps regression:24 suites / 101 tests,0 failure/error/skipped;包含 `HarnessChatConfigurationTest` Spring wiring。
- `mvn -q -DskipTests compile`、`openspec validate single-react-chat-sse-cutover --strict`、`node --check src/main/resources/static/app.js` 均通过。
- 生产 Controller/前端遗留 token 静态扫描为 0;`ChatController` 中模型、Tool、ChatService、AiOps、Session 和 Trace service 禁止依赖扫描为 0。
- L4 部署必须将后端 `/api/chat` SSE 与 bundled frontend consumer 同版本原子部署;回滚必须成对回滚到阶段 6A commit,不提供兼容 endpoint、旧 parser 或双轨开关。
- live 模型、Redis、日志和 MySQL E2E 按 ISS-014 门禁明确延后到阶段 7,本阶段没有把 focused/Mock 验证表述为 live 验收。