feat(harness): add chat application use case

This commit is contained in:
zhuyongxin
2026-07-22 00:57:39 +08:00
parent ee0949d464
commit f8809cb7dd
56 changed files with 2815 additions and 6 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-21
@@ -0,0 +1,132 @@
## Context
阶段 2-5 已形成内部 Harness 核心:`RunContext`、canonical evidence tools、唯一 Diagnosis Agent、EvidenceGuard/SemanticGuard 和 release policy。当前生产 Chat 仍由旧 `ChatService`/Controller 创建 Run、选择 Agent、维护历史并生成答案,新的组件没有统一入口,也没有 `intent/release_outcome/published_result` 持久化,阶段 6B 无法只做协议切换。
阶段 6A 建立同步的内部 application use case 和 typed output。阶段 6B 将在受控任务执行器中调用它并把 observer 转成 SSE;本阶段不触碰 HTTP、前端或旧公开行为。
## Goals / Non-Goals
**Goals:**
- 应用用例拥有 session/run 创建、路由、固定 executor dispatch、最终终态和安全持久化。
- Router 只看最小输入并按 Harness policy 进行最多两次技术 attempt。
- SYSTEM_CHAT、KNOWLEDGE_QUERY、DIAGNOSIS 三条路径接收同一个未改写 Query,能力严格隔离。
- 从 MySQL `diagnosis_run` 确定性读取有界 PreviousTurn。
- 提供 protocol-neutral observer/run control,使阶段 6B 能发送 metadata/status 和处理断开取消。
- 新增可前向迁移、可回滚的 L3 数据库契约。
**Non-Goals:**
- 不修改 Controller、SSE endpoint、前端消费者和 HTTP DTO。
- 不删除旧 ChatService、多 Agent、ThreadLocal 或旧 session storage。
- 不实现 token streaming、事件 schema 或 Controller 线程池。
- 不执行 live E2E;阶段 7 统一启动项目验证。
## Decisions
### 1. Application Use Case 是唯一业务入口
`ChatApplicationUseCase.execute(request, observer)` 固定流程:
```text
resolve session
-> read prior safe routing/diagnosis context
-> start one RunContext and persist RUNNING
-> observer.onStarted(run control)
-> IntentRouter
-> persist intent
-> fixed executor
-> complete Harness lifecycle
-> persist release outcome/public content/published result
-> typed ChatApplicationResult
```
它通过构造器接收 Router、三个 executor、Run store、Core 和 session ID supplier,不接收 Controller 注入的 ChatModel/Tool。Controller 在阶段 6B 只调用这一接口。
读取 prior context 必须早于当前 Run 持久化,避免 latest query 命中当前 PENDING/RUNNING 行。替代在 Controller 中先查历史;拒绝,因为会拆散生命周期所有权并制造 session/run 竞态。
### 2. Protocol-neutral observer 与取消控制
Observer 只接收 `ChatRunControl(sessionId, runId, cancelClientDisconnect)` 和固定 `ChatApplicationStatus`,不依赖 Spring MVC/SSE 类型。应用用例先成功写入 Run,再发送 started;阶段 6B 将 started 映射为 metadata,将 status 映射为安全状态事件。
Run control 只暴露客户端断开取消,不暴露 Core/RunContext。Observer 异常按执行失败处理并进入 Run 终态,避免协议层静默丢失连接后继续运行。
### 3. Router 是直接单轮模型调用
`IntentRouterInput` 只含原始 query、可选 last intent 和 last user query。Router 使用新的 system prompt 和共享 `GuardModelCall`,无 Tool/记忆/ReactAgent;严格解析唯一字段 `intent`。timeout/transport/任何非法输出映射到 `HarnessRetryPolicies.intentRouter()` 支持的失败类型,最多两次使用同一 JSON 输入。
两次失败抛出稳定 `ChatApplicationException(ROUTING_UNAVAILABLE)`;不得默认 DIAGNOSIS。取消/预算耗尽继续传播为 Run 终态。
### 4. 固定执行器与 typed public content
- SYSTEM_CHAT:一次 `GuardModelCall`,system prompt 只含受控产品能力说明;不接历史、Tool 或 evidence guard。
- KNOWLEDGE_QUERY:生成当前 direct invocation 的唯一 ID,调用 `HarnessEvidenceTools.lookup_knowledge` 一次。`NO_EVIDENCE` 返回固定安全说明;正证据只调用模型一次并严格解析 `KnowledgeAnswerDraft`,校验 exact call ID 和 document ID subset,再发布移除 Tool ID 的 `KnowledgeContent`。
- DIAGNOSIS:调用 `DiagnosisAgentUseCase(query, previousTurn)`,再调用 `DiagnosisReleaseUseCase`。SUCCESS 发布 `SemanticDraftView + verified sources`,Fallback 只发布 `SafeFallback`。
`ChatApplicationResult` 使用 `ChatContentType` 与 sealed typed content,不使用 Map/Object payload。阶段 6B 可直接序列化 content;Diagnosis public view 不含 Tool Call ID 或完整 evidence snapshot。
### 5. PreviousTurn policy 同时负责写入和读取边界
`PublishedResultPolicy` 从 SUCCESS Diagnosis 的 Query、Conclusion、Limitations/Scope 和 verified RAG metadata 构造有界 `PublishedResult`,并从持久化结果构造 `PreviousTurn`。每个字符串、list 和 source document 都按集中 limits 在字段边界截断;不调用 LLM。
只有 conclusion 非空才写 `published_result`。Repository 查询必须精确为同一 Session、`intent=DIAGNOSIS`、`release_outcome=SUCCESS`、JSON 非空,按 `created_at,id` 倒序一条。解析失败或字段无效时 fail closed 为 `previousTurn=null`。
### 6. Run store 和数据库契约
`DiagnosisRun` 新增:
- `intent VARCHAR(32)`,枚举 `SYSTEM_CHAT|KNOWLEDGE_QUERY|DIAGNOSIS`;
- `release_outcome VARCHAR(16)`,枚举 `SUCCESS|FALLBACK|FAILED|CANCELLED`;
- `published_result JSON`,只存固定安全结构。
V012 添加三列及索引 `session_id,intent,release_outcome,created_at,id`。`JpaChatRunStore` 负责 ensure ChatSession、start、markIntent、finish、latest routing context 和 safe PreviousTurn;JSON 使用注入的 ObjectMapper,禁止 Java serialization。
Run `status` 保持既有字符串:安全 SUCCESS/FALLBACK 都标记 `SUCCESS`,技术失败 `FAILED`,客户端/用户取消 `CANCELLED`;`release_outcome` 保留精确发布语义。finish 同时写安全 public content、budget tokens/tool count 和 duration。
### 7. 异常与 first-terminal-wins
应用用例 catch 所有执行异常后先读取 Run lifecycle:CANCELLED 映射 `release_outcome=CANCELLED`,其余未完成异常调用 `core.completeFailure` 并写 FAILED。成功/Fallback 都调用 `core.completeSuccess`。持久化错误不伪造成功;异常只通过稳定 `ChatFailureCode` 向阶段 6B 暴露,不携带供应商/JPA/Prompt 详情。
## Module and Ownership Audit
```text
ChatApplicationRequest
-> ChatApplicationUseCase (session/run/dispatch/terminal owner)
-> ChatRunStore (MySQL truth)
-> IntentRouter (minimal direct ChatModel)
-> SystemChatExecutor | KnowledgeQueryExecutor | DiagnosisChatExecutor
-> GuardModelCall | HarnessEvidenceTools | DiagnosisAgent/Release
-> ChatApplicationResult + Observer statuses
```
- Core owns deadline/cancel/budget/lifecycle;Application owns business dispatch and persistence;Store owns MySQL mapping;executors own path-specific behavior;Controller remains protocol-only.
- 最大风险是应用终态和 DB 写入分离;用例使用单一 finish path 和 first-terminal-wins lifecycle,focused tests 覆盖 success/fallback/failure/cancel。
- 最大兼容风险是 V012;Entity/migration/repository query 必须同步,阶段 6B 前不读取新入口不会改变公开行为。
- 不复用旧 `ChatService` 私有 routing/session helpers,避免新用例依赖旧多 Agent 结构。
## Interface Impact
- Level: L3 collaboration/database contract。
- Consumers: 阶段 6B Controller/SSE adapter、DiagnosisRunRepository、MySQL schema。
- Public HTTP/frontend: 本阶段无变化。
- Independent contract: 本 design 的 Database Contract/Migration Plan 章节作为独立接口说明。
## Risks / Trade-offs
- [Router hallucinated value] -> exact one-field JSON + enum parser + two-attempt policy + fail closed。
- [Knowledge model fabricates reference] -> exact invocation ID/document subset validation before public projection。
- [Current Run shadows previous context] -> prior reads before start/persist current Run。
- [PublishedResult leaks evidence] -> dedicated typed contract/policy and serialized-field negative tests。
- [Observer disconnect leaves work running] -> run control directly cancels shared Core context。
- [DB/entity drift] -> V012 migration + entity/repository/static schema tests + compile。
## Migration Plan
1. Apply V012 additive columns/index; all new columns nullable, old writers remain compatible。
2. Deploy stage 6A code while public Controller continues using old path;new fields remain unused except internal tests。
3. Stage 6B wires new application use case and begins writing fields atomically per Run。
4. Rollback: switch Controller back to old path, then optionally drop V012 index and three columns;V011 data and old writers remain valid。
## Open Questions
- None。具体模型/byte/PreviousTurn 数值由构造配置提供默认值,阶段 7 根据真实 Trace 校准。
@@ -0,0 +1,31 @@
## Why
阶段 2-5 已具备 RunContext、单一 Diagnosis Agent 和安全释放门禁,但仍没有统一的 Chat 应用用例负责创建 Run、三类意图路由、执行器选择、PreviousTurn 组装和最终持久化。阶段 6A 需要先完成这层内部应用边界,使阶段 6B 只做 HTTP/SSE 协议切换。
## What Changes
- 新增内部 `ChatApplicationUseCase`,接收原始 Query 和可选 sessionId,创建并显式传播同一个 `RunContext`。
- 新增无 Tool、无记忆、无 ReAct 的三分类 Intent Router;输入只含 Query、可选 last intent 和 last user query,技术/非法输出最多两次 attempt,最终失败不默认进入 Diagnosis。
- 新增固定 SYSTEM_CHAT、KNOWLEDGE_QUERY、DIAGNOSIS 执行器映射;三条路径都接收未改写 Query,互不调用。
- SYSTEM_CHAT 使用受控单轮 ChatModel;KNOWLEDGE_QUERY 只调用一次 `lookup_knowledge`,基于有界 RAG projection 生成并验证引用;DIAGNOSIS 串联阶段 4 Agent 和阶段 5 release boundary。
- 从同一 Session 最近一个 `DIAGNOSIS + SUCCESS + published_result` 的 Run 确定性组装有界 PreviousTurn,不调用摘要模型、不复用历史日志/MySQL。
- 扩展 `diagnosis_run` 持久化 `intent`、`release_outcome` 和 JSON `published_result`,新增 JPA store 和 V012 Flyway migration。
- 新增应用执行 observer,使阶段 6B 能发送 metadata/status 并在断开时取消同一个 Run;本阶段只在内部测试入口使用。
- 不修改公开 Controller、`/api/chat`、`/api/chat_stream`、前端或现有 SSE 行为。
## Capabilities
### New Capabilities
- `single-react-chat-application-usecase`: 定义 Run/session 生命周期、三类意图路由、固定执行器、PreviousTurn、知识回答引用验证、持久化和内部 observer 行为。
### Modified Capabilities
- None. 公开 Chat/SSE capability 在阶段 6B 才改变。
## Impact
- 新增 `com.superbiz.agent.harness.application` 内部包、Router/System/Knowledge prompts、focused tests、JPA store 和 V012 migration。
- 修改 `DiagnosisRun`/`DiagnosisRunRepository`,新增数据库字段和安全 previous-turn 查询;复用 `DiagnosisHarnessCore`、`GuardModelCall`、`HarnessRetryExecutor`、`HarnessEvidenceTools`、`DiagnosisAgentUseCase` 和 `DiagnosisReleaseUseCase`。
- 接口影响为 L3:数据库 schema 和内部 application/store contract 变化,需要独立 migration/rollback 说明;HTTP/SSE/前端保持不变。
- 主要风险是当前 Run 污染上一回合查询、Router 失败误入 Diagnosis、Knowledge answer 引用伪造、PublishedResult 泄漏内部证据,以及异常路径没有持久化终态。
@@ -0,0 +1,112 @@
## ADDED Requirements
### Requirement: Application-owned session and Run lifecycle
The Chat Application Use Case SHALL resolve or generate the session ID, create exactly one RunContext, persist one matching Diagnosis Run, and propagate the same session ID, run ID, original Query, cancellation, budget, and terminal outcome through routing and the selected executor.
#### Scenario: Existing session request
- **WHEN** an internal request supplies a valid session ID
- **THEN** the use case preserves it, generates one run ID, and returns/persists the same identifiers
#### Scenario: New session request
- **WHEN** an internal request omits the session ID
- **THEN** the use case generates one valid session ID and uses it for all Run operations
### Requirement: Minimal isolated Intent Router
The Router SHALL execute a direct no-Tool, no-memory, no-ReAct ChatModel call whose input contains only the unchanged Query, optional last intent, and optional last user Query. It SHALL accept only `SYSTEM_CHAT`, `KNOWLEDGE_QUERY`, or `DIAGNOSIS`.
#### Scenario: Three valid routes
- **WHEN** the model returns each supported enum in the strict output schema
- **THEN** the use case dispatches to exactly the corresponding fixed executor
#### Scenario: New topic overrides history
- **WHEN** the current Query identifies a new topic while prior routing context exists
- **THEN** the model input still preserves the original current Query and prior fields are only optional context
### Requirement: Router technical retry fails closed
The Router SHALL use `HarnessRetryPolicies.intentRouter()` and SHALL retry timeout, transport, or invalid output at most once with identical input. A second failure MUST produce `ROUTING_UNAVAILABLE` and MUST NOT dispatch Diagnosis.
#### Scenario: Invalid output then valid route
- **WHEN** the first output is not a supported enum and the second output is valid
- **THEN** exactly two attempts use the same input and the valid route is executed
#### Scenario: Two invalid outputs
- **WHEN** both permitted attempts return invalid output
- **THEN** the Run ends FAILED and no intent executor is called
### Requirement: Fixed isolated executors
The Application Use Case SHALL map SYSTEM_CHAT to one no-Tool model response, KNOWLEDGE_QUERY to exactly one lookup-knowledge invocation plus one bounded answer model call, and DIAGNOSIS to the single Diagnosis Agent followed by the release boundary. Executors MUST NOT call one another or rewrite the Query.
#### Scenario: System Chat
- **WHEN** intent is SYSTEM_CHAT
- **THEN** no evidence Tool, Diagnosis Agent, EvidenceGuard, or SemanticGuard is invoked
#### Scenario: Knowledge Query
- **WHEN** intent is KNOWLEDGE_QUERY
- **THEN** only lookup_knowledge is invoked once and query_logs/query_mysql/Diagnosis ReAct are unavailable
#### Scenario: Diagnosis
- **WHEN** intent is DIAGNOSIS
- **THEN** the original Query and bounded PreviousTurn enter DiagnosisAgentUseCase and the Draft cannot publish before DiagnosisReleaseUseCase
### Requirement: Knowledge references are physically validated
The Knowledge executor SHALL accept only a bounded READY RAG projection, SHALL validate every model answer item against the exact direct invocation ID and returned document ID set, and SHALL remove Tool Call IDs from public content.
#### Scenario: Supported knowledge answer
- **WHEN** answer items reference the exact lookup call and only returned documents
- **THEN** public content contains answer text, stable document references, and limitations without Tool Call IDs
#### Scenario: Fabricated knowledge reference
- **WHEN** the model returns another call ID or an unknown document ID
- **THEN** the executor fails closed and no knowledge answer is published
#### Scenario: No knowledge evidence
- **WHEN** lookup returns READY `NO_EVIDENCE`
- **THEN** the executor returns a fixed bounded no-evidence answer without calling the answer model
### Requirement: Safe bounded PreviousTurn
The system SHALL load PreviousTurn only from the same Session's most recent Run with `intent=DIAGNOSIS`, `release_outcome=SUCCESS`, and non-null valid `published_result`. It SHALL deterministically bound fields and source documents without model summarization.
#### Scenario: Last safe Diagnosis exists
- **WHEN** a qualifying Run contains valid PublishedResult JSON
- **THEN** the next Diagnosis receives its bounded query, conclusion, scope, limitations, and RAG document metadata
#### Scenario: Last Run is fallback failed or cancelled
- **WHEN** newer Runs are not qualifying safe Diagnosis successes
- **THEN** they cannot become PreviousTurn and the query returns the most recent qualifying record or null
#### Scenario: Published result is corrupt
- **WHEN** stored JSON is invalid or required safe fields are blank
- **THEN** PreviousTurn is null and no raw stored value reaches a model
### Requirement: Diagnosis Run persistence contract
The `diagnosis_run` schema SHALL add nullable `intent`, `release_outcome`, and JSON `published_result`, and the JPA entity/repository/store SHALL write and query them consistently. PublishedResult MUST NOT contain Tool IDs, raw evidence, full Draft, or SemanticGuard reasons.
#### Scenario: Successful Diagnosis completion
- **WHEN** a Diagnosis report with non-null conclusion is safely released
- **THEN** the Run stores intent DIAGNOSIS, release outcome SUCCESS, safe public answer, bounded PublishedResult, duration and budget usage
#### Scenario: Fallback completion
- **WHEN** release returns SafeFallback
- **THEN** the Run status is SUCCESS, release outcome is FALLBACK, and published_result is null
#### Scenario: Failure or cancellation
- **WHEN** routing/execution fails or the client cancels the Run
- **THEN** the Run stores exactly one FAILED or CANCELLED release outcome and no PublishedResult
### Requirement: Protocol-neutral progress and cancellation
The use case SHALL notify a protocol-neutral observer after the Run is persisted, expose only session/run identifiers and client-disconnect cancellation, and emit only fixed safe application status codes.
#### Scenario: Observer start and status
- **WHEN** internal execution begins
- **THEN** observer start occurs once before routing status and contains the persisted session/run identifiers
#### Scenario: Client disconnect control
- **WHEN** the observer invokes client-disconnect cancellation
- **THEN** the same RunContext is cancelled and late executor results cannot complete successfully
### Requirement: Stage-six-A public isolation
Stage 6A SHALL NOT modify or switch public Chat Controller endpoints, SSE contracts, frontend consumers, or legacy ChatService behavior.
#### Scenario: Internal-only delivery
- **WHEN** stage 6A changes are inspected
- **THEN** Controller/frontend/public endpoint behavior has zero diff and stage 6B can consume the completed use case without rewriting it
@@ -0,0 +1,25 @@
## 1. Intent and single-turn executors
- [x] 1.1 Add typed Router input/limits/failure and strict three-enum direct ChatModel implementation with Harness retry auditing.
- [x] 1.2 Add fixed SYSTEM_CHAT executor and protocol-neutral application content/status contracts.
- [x] 1.3 Add KNOWLEDGE_QUERY executor with one lookup call, bounded RAG input, exact reference validation and ID-free public content.
- [x] 1.4 Add focused Router/System/Knowledge tests for mapping, identical retry input, fail-closed routing, one Tool call and fabricated references.
## 2. PreviousTurn and Run persistence
- [x] 2.1 Add PublishedResultPolicy and centralized limits for bounded write/read projection and RAG document extraction.
- [x] 2.2 Extend DiagnosisRun entity/repository and add V012 migration for intent, release_outcome, published_result and lookup index.
- [x] 2.3 Add ChatRunStore/JPA implementation for prior context, start, intent, safe finish and terminal metrics.
- [x] 2.4 Add focused policy/store/schema tests covering exact safe query, corrupt JSON, fallback exclusion and field leakage.
## 3. Application use case
- [x] 3.1 Add Diagnosis executor that composes stage 4 Agent and stage 5 release into ID-free typed content and optional PublishedResult.
- [x] 3.2 Implement ChatApplicationUseCase ordering prior reads, Run persistence, observer start/status, routing, dispatch and first-terminal-wins completion.
- [x] 3.3 Add protocol-neutral Run control whose client-disconnect cancellation targets the same RunContext.
- [x] 3.4 Add focused end-to-end use-case tests for all intents, original Query, previous turn, consistent IDs, success/fallback/failure/cancel persistence and observer order.
## 4. Verification and isolation
- [x] 4.1 Run stage 6A focused tests, relevant stage 2-5 regression tests and Maven compile.
- [x] 4.2 Validate strict OpenSpec, migration/entity alignment, no public Controller/frontend diff, no Router/ordinary executor Tool or Agent loop leakage, and no unsafe PublishedResult fields.
@@ -0,0 +1,115 @@
# single-react-chat-application-usecase Specification
## Purpose
TBD - created by archiving change single-react-chat-application-usecase. Update Purpose after archive.
## Requirements
### Requirement: Application-owned session and Run lifecycle
The Chat Application Use Case SHALL resolve or generate the session ID, create exactly one RunContext, persist one matching Diagnosis Run, and propagate the same session ID, run ID, original Query, cancellation, budget, and terminal outcome through routing and the selected executor.
#### Scenario: Existing session request
- **WHEN** an internal request supplies a valid session ID
- **THEN** the use case preserves it, generates one run ID, and returns/persists the same identifiers
#### Scenario: New session request
- **WHEN** an internal request omits the session ID
- **THEN** the use case generates one valid session ID and uses it for all Run operations
### Requirement: Minimal isolated Intent Router
The Router SHALL execute a direct no-Tool, no-memory, no-ReAct ChatModel call whose input contains only the unchanged Query, optional last intent, and optional last user Query. It SHALL accept only `SYSTEM_CHAT`, `KNOWLEDGE_QUERY`, or `DIAGNOSIS`.
#### Scenario: Three valid routes
- **WHEN** the model returns each supported enum in the strict output schema
- **THEN** the use case dispatches to exactly the corresponding fixed executor
#### Scenario: New topic overrides history
- **WHEN** the current Query identifies a new topic while prior routing context exists
- **THEN** the model input still preserves the original current Query and prior fields are only optional context
### Requirement: Router technical retry fails closed
The Router SHALL use `HarnessRetryPolicies.intentRouter()` and SHALL retry timeout, transport, or invalid output at most once with identical input. A second failure MUST produce `ROUTING_UNAVAILABLE` and MUST NOT dispatch Diagnosis.
#### Scenario: Invalid output then valid route
- **WHEN** the first output is not a supported enum and the second output is valid
- **THEN** exactly two attempts use the same input and the valid route is executed
#### Scenario: Two invalid outputs
- **WHEN** both permitted attempts return invalid output
- **THEN** the Run ends FAILED and no intent executor is called
### Requirement: Fixed isolated executors
The Application Use Case SHALL map SYSTEM_CHAT to one no-Tool model response, KNOWLEDGE_QUERY to exactly one lookup-knowledge invocation plus one bounded answer model call, and DIAGNOSIS to the single Diagnosis Agent followed by the release boundary. Executors MUST NOT call one another or rewrite the Query.
#### Scenario: System Chat
- **WHEN** intent is SYSTEM_CHAT
- **THEN** no evidence Tool, Diagnosis Agent, EvidenceGuard, or SemanticGuard is invoked
#### Scenario: Knowledge Query
- **WHEN** intent is KNOWLEDGE_QUERY
- **THEN** only lookup_knowledge is invoked once and query_logs/query_mysql/Diagnosis ReAct are unavailable
#### Scenario: Diagnosis
- **WHEN** intent is DIAGNOSIS
- **THEN** the original Query and bounded PreviousTurn enter DiagnosisAgentUseCase and the Draft cannot publish before DiagnosisReleaseUseCase
### Requirement: Knowledge references are physically validated
The Knowledge executor SHALL accept only a bounded READY RAG projection, SHALL validate every model answer item against the exact direct invocation ID and returned document ID set, and SHALL remove Tool Call IDs from public content.
#### Scenario: Supported knowledge answer
- **WHEN** answer items reference the exact lookup call and only returned documents
- **THEN** public content contains answer text, stable document references, and limitations without Tool Call IDs
#### Scenario: Fabricated knowledge reference
- **WHEN** the model returns another call ID or an unknown document ID
- **THEN** the executor fails closed and no knowledge answer is published
#### Scenario: No knowledge evidence
- **WHEN** lookup returns READY `NO_EVIDENCE`
- **THEN** the executor returns a fixed bounded no-evidence answer without calling the answer model
### Requirement: Safe bounded PreviousTurn
The system SHALL load PreviousTurn only from the same Session's most recent Run with `intent=DIAGNOSIS`, `release_outcome=SUCCESS`, and non-null valid `published_result`. It SHALL deterministically bound fields and source documents without model summarization.
#### Scenario: Last safe Diagnosis exists
- **WHEN** a qualifying Run contains valid PublishedResult JSON
- **THEN** the next Diagnosis receives its bounded query, conclusion, scope, limitations, and RAG document metadata
#### Scenario: Last Run is fallback failed or cancelled
- **WHEN** newer Runs are not qualifying safe Diagnosis successes
- **THEN** they cannot become PreviousTurn and the query returns the most recent qualifying record or null
#### Scenario: Published result is corrupt
- **WHEN** stored JSON is invalid or required safe fields are blank
- **THEN** PreviousTurn is null and no raw stored value reaches a model
### Requirement: Diagnosis Run persistence contract
The `diagnosis_run` schema SHALL add nullable `intent`, `release_outcome`, and JSON `published_result`, and the JPA entity/repository/store SHALL write and query them consistently. PublishedResult MUST NOT contain Tool IDs, raw evidence, full Draft, or SemanticGuard reasons.
#### Scenario: Successful Diagnosis completion
- **WHEN** a Diagnosis report with non-null conclusion is safely released
- **THEN** the Run stores intent DIAGNOSIS, release outcome SUCCESS, safe public answer, bounded PublishedResult, duration and budget usage
#### Scenario: Fallback completion
- **WHEN** release returns SafeFallback
- **THEN** the Run status is SUCCESS, release outcome is FALLBACK, and published_result is null
#### Scenario: Failure or cancellation
- **WHEN** routing/execution fails or the client cancels the Run
- **THEN** the Run stores exactly one FAILED or CANCELLED release outcome and no PublishedResult
### Requirement: Protocol-neutral progress and cancellation
The use case SHALL notify a protocol-neutral observer after the Run is persisted, expose only session/run identifiers and client-disconnect cancellation, and emit only fixed safe application status codes.
#### Scenario: Observer start and status
- **WHEN** internal execution begins
- **THEN** observer start occurs once before routing status and contains the persisted session/run identifiers
#### Scenario: Client disconnect control
- **WHEN** the observer invokes client-disconnect cancellation
- **THEN** the same RunContext is cancelled and late executor results cannot complete successfully
### Requirement: Stage-six-A public isolation
Stage 6A SHALL NOT modify or switch public Chat Controller endpoints, SSE contracts, frontend consumers, or legacy ChatService behavior.
#### Scenario: Internal-only delivery
- **WHEN** stage 6A changes are inspected
- **THEN** Controller/frontend/public endpoint behavior has zero diff and stage 6B can consume the completed use case without rewriting it