## Context 公开 Chat 仍有同步 `/api/chat` 和伪流式 `/api/chat_stream` 两条旧 `ChatService` 链路;Controller 直接读取 Session 历史、获取模型/Tools、选择策略并维护 cached thread pool。阶段 6A 已完成 protocol-neutral `ChatApplicationUseCase`、observer 和 `ChatRunControl`,但阶段 2-6A 的 plain Java components 尚未形成生产 Bean graph。前端同时保留 quick JSON 与 stream SSE 两套消费者。 本 change 是 L4 破坏性协议迁移。ISS-014 已冻结 event schema、无兼容分支和阶段 7 清理边界;本阶段必须原子切换 Controller、生产装配与前端,不能出现旧路径绕过 release boundary。 ## Goals / Non-Goals **Goals:** - 唯一 `POST /api/chat` 返回 named SSE events,固定顺序和互斥规则。 - Controller 只负责校验、SSE/HTTP 和连接生命周期,业务执行只调用 `ChatApplicationUseCase`。 - 使用同一个 Run control 处理断开/timeout/send failure,并禁止断开后的终态发送。 - 生产装配阶段 2-6A 的 Core、Tools、Agent、Guards、executors、store 和 use case。 - 使用集中配置与 Spring-managed bounded executors。 - 前端唯一 consumer 严格消费新契约并保存 exact session/run IDs。 **Non-Goals:** - 不做 Token streaming 或最终内容切片。 - 不修改 `/api/ai_ops` 的 URL、请求或事件协议。 - 不物理删除旧多 Agent、ChatService、Hooks 或 ThreadLocal;阶段 7 处理。 - 不做断线续传、event replay、WebSocket、轮询或 live E2E。 ## Decisions ### 1. Named SSE event 是唯一协议层类型 新增 immutable SSE payload:`metadata(session_id,run_id)`、`status(code,message)`、`content(content_type,payload)`、`failure(code,message)`、`done(outcome)`。`SseEmitter.event().name(type).data(payload)` 直接使用业务 event name,不再发送旧 `message + {type,data}` 包装。 替代继续用统一 `message` event;拒绝,因为它允许 type 漂移并让浏览器保留旧兼容分支。`content.payload` 直接引用阶段 6A sealed typed content,禁止 Map/Object 临时协议。 ### 2. 每个连接使用显式 first-terminal-wins SSE session 新增 protocol adapter 持有 emitter、`AtomicReference`、disconnect flag、metadata flag 和 terminal flag。`onStarted` 固定发送 metadata;若 disconnect 已先发生则立即取消新得到的 Run control。status 只能在 metadata 后、terminal 前发送。success 原子发送 content + done 后 complete;failure 原子发送 failure + done 后 complete。 `onTimeout`、`onError`、异常 send 和非正常 `onCompletion` 只标记 disconnected 并调用 `cancelClientDisconnect()`;不尝试补发 failure/done。正常 complete 在回调前标记 terminal,避免把成功误判为断开。替代仅在 IOException catch 取消;拒绝,因为 timeout 和 disconnect-before-onStarted 会漏掉。 ### 3. 请求接纳失败不进入 SSE 事件序列 空 query 在创建 emitter/Run 前返回 HTTP 400;bounded worker rejection 在创建 Run 前返回 HTTP 503。五事件顺序只适用于已接纳并成功持久化 Run 的请求,因而 metadata 始终是第一个 SSE event。Session ID 格式错误由 use case 形成有 Run 的稳定 failure,除非它在 startRun 前失败;Controller 只做 query shape 校验。 ### 4. Chat Controller 不拥有模型、Tools、Session 或业务执行 删除同步 Chat 方法、`/chat_stream`、ChatResponse、历史 helper、内容切片和 Chat 使用的 `ChatService/SessionManager/ChatModel/ToolCallbackProvider`。Controller 构造注入 `ChatApplicationUseCase` 和 qualifier Chat TaskExecutor。 同一类中的 `/api/ai_ops` 仍需旧模型/Tools。为保持其公开协议不变又清理 Controller 依赖,向 `AiOpsService` 增加内部 overload,由 service 注入 ChatModel/ToolCallbackProvider 并调用既有执行方法;Controller 只传 request/session/run。替代本阶段迁移 AiOps 到新 use case;拒绝,因为超出阶段 6B。 ### 5. Production assembly 使用一个显式 Bean graph 新增 `HarnessChatConfiguration`,按依赖顺序装配: ```text properties/Clock/executors -> DiagnosisHarnessCore + HarnessRetryExecutor + GuardModelCall -> RedisCanonicalInvocationStore + ToolBoundary -> RAG/log/MySQL adapters + HarnessEvidenceTools -> DiagnosisAgentFactory/UseCase -> EvidenceGuard/Repair + SemanticGuard + ReleaseUseCase -> Router + System/Knowledge/Diagnosis executors -> ChatApplicationUseCase(JpaChatRunStore) ``` 所有模型入口复用同一 Spring `ChatModel` 和 model executor。RAG/log adapters 复用现有 `LookupKnowledgeTool`/`QueryLogsTools` legacy executors,但所有 Agent-facing output 仍经过 ToolBoundary projection。MySQL 从 `MysqlToolProperties` 生成独立 datasource map;空配置时 Tool 仍注册但 fail closed 为 unknown datasource。 ### 6. 并发与预算全部集中配置 新增 `harness.chat` configuration properties,覆盖 Chat worker/model pool、Run deadline/budget、canonical TTL/bytes、Agent/Router/Guard/single-turn/Knowledge limits 和 key prefix。worker 与 model executor 均使用 bounded `ThreadPoolExecutor + AbortPolicy`,由 Spring 负责 shutdown;Controller 不持有或创建 executor。 Retry attempt audit 首版写结构化 debug log,不新增持久化表。替代硬编码构造参数;拒绝,因为阶段 0 已要求上限集中可校准。 ### 7. 前端删除 quick/stream 双轨 移除 mode selector DOM、`currentMode`、`sendQuickMessage` 和旧 `/chat_stream` parser。`sendMessage` 只调用 `sendChatMessage`,POST `/api/chat` 并按空行分隔完整 SSE frame,解析 named event 与 JSON payload。metadata 更新 trace target;status 更新等待说明;content 一次渲染 typed payload;failure 显示安全消息;done 校验 outcome 并结束。 typed payload 使用专用 renderer 转换 SYSTEM_CHAT、KNOWLEDGE、DIAGNOSIS_REPORT、FALLBACK,不把对象隐式拼接为字符串。unknown event、重复 metadata/content/failure/done、content/failure 同时出现或流结束前无 done 均 fail closed。 ## Module and Ownership Audit ```text Browser fetch POST /api/chat -> ChatController (validation + connection admission) -> ChatSseSession (event order + connection cancellation) -> bounded chat worker -> ChatApplicationUseCase (session/run/routing/dispatch/terminal persistence) -> Harness Core / Agent / Tools / Guards -> typed content or stable failure -> ChatSseSession (single content|failure + done) ``` - Controller owns HTTP/SSE only;SSE session owns event state;Application owns business Run;Core owns cancellation/budget;Store owns MySQL;Harness configuration owns infrastructure graph。 - 数据真理源仍为 `diagnosis_run`;SSE metadata 只透出同一 use case 生成的 IDs。 - 最大生命周期风险是 disconnect 与 onStarted 竞态;atomic control + pending disconnect 解决。 - 最大部署风险是 L4 前后端不同步;Controller/前端必须同一 commit 原子部署,回滚也必须成对。 - 无 ADR 冲突;ISS-013 的 token-like “incremental content”被更新的 ISS-014 明确收敛为实时 status + 单次安全 content。 ## Interface Impact - Level: L4 breaking HTTP/frontend contract。 - Removed: synchronous JSON `POST /api/chat`、`POST /api/chat_stream`、旧 `ChatResponse` 和 message-wrapper Chat SSE。 - Added: SSE `POST /api/chat` with five named events and exact snake_case payload fields。 - Consumers: bundled `static/app.js`、外部 Chat API callers、Controller tests/MockMvc;Trace/feedback continue using metadata IDs。 ## Risks / Trade-offs - [客户端仍调用旧 endpoint] -> 同 commit 迁移 bundled frontend;外部消费者必须按 L4 文档迁移,不提供兼容层。 - [发送中断后 worker 继续] -> every send failure/timeout/error cancels exact Run control;late result blocked by Core。 - [executor 饱和] -> bounded queue + HTTP 503 before Run;不 caller-runs、不无界排队。 - [Spring graph 缺 Bean/歧义] -> focused configuration context test + Maven compile。 - [legacy Tool side effects] -> Agent-facing output 仍经新 boundary;legacy classes stage 7 physically removed。 - [typed diagnosis payload frontend rendering复杂] -> deterministic renderer and JS contract tests/static assertions。 ## Migration Plan 1. 部署前确保 V012 已迁移,Redis 与模型配置可用。 2. 同一版本注册 Harness Bean graph、切换 `/api/chat` SSE、删除 `/chat_stream` 并更新 bundled frontend。 3. smoke/focused tests 验证 metadata/status/content|failure/done、same IDs 和 cancellation;live E2E 延后阶段 7。 4. 回滚必须同时回滚 Controller 与 frontend 到阶段 6A commit;数据库 V012 nullable additive 可保留。 ## Open Questions - None。初始数值为可配置默认值,阶段 7 根据 live trace 校准。