feat(graph): add diagnosis real nodes

This commit is contained in:
zhuyongxin
2026-07-17 16:44:05 +08:00
parent 42ba204532
commit 1460dd1e99
57 changed files with 4380 additions and 710 deletions
@@ -0,0 +1,42 @@
# Chat Diagnosis StateGraph Real Nodes 验收
## 结果
已接受。OpenSpec tasks 27/27 完成;真实 Nodes 可构造和测试,旧生产 Sequential 路径保持可用,阶段 2 未切换生产入口。
## 静态验证
- `openspec validate chat-diagnosis-stategraph-real-nodes --strict`:通过。
- `openspec validate --specs --strict`:13 passed,0 failed。
- `git diff --check`:通过;只有 LF/CRLF 转换提示,无 whitespace error。
- 源码/引用检查:`ChatService` Graph 引用 0;新增源码 forbidden refs 0;protocol 反向依赖 0;DB/Trace/Prompt diff 0。
- 装配对齐:无核心 TODO/FIXME/placeholder;Graph factory 继续拥有所有 retry counter 与 Planner reset。
## 脚本验证
- 新 protocol/Node/CompiledGraph/Router/Trace focused suite:通过。
- `mvn -q "-Dtest=ChatServiceSequentialAgentTest,VerifierInputHookTest,ExecutorGatekeeperServiceTest" test`:通过。
- 两组测试合计:100 tests,0 failures,0 errors,0 skipped。
- `mvn -q -DskipTests test-compile`:通过。
## 浏览器/人工验证
- 未运行。阶段 2 不改变用户入口或 UI,没有独立人工验收价值。
## 未验证
- Maven live E2E、`logs/` 日志和 `scripts/query_mysql.py` 数据库核验未运行。
- 原因:用户明确要求只在阶段 5 全部实现后统一做最终 E2E;阶段 2 尚未切换生产入口。
- 风险:当前验收只证明组件/Graph 契约与旧路径回归,不证明真实生产装配、模型、DB 和 Trace 全链路。
## 已完成范围
- 中立共享 protocol 与旧路径行为保持型委托。
- 四个 Agent adapters、显式 Gatekeeper、可信投影、关键证据补查、Composer 与两类 Fallback。
- 真实 CompiledGraph 装配、critical gap 路由修复和完整 focused test 证据。
## 交接
- 下一步:归档本 change、独立提交阶段 2,然后启动阶段 3 `chat-diagnosis-stategraph-chatservice-cutover`。
- OpenSpec 归档:已同步主 specs,并归档到 `openspec/changes/archive/2026-07-17-chat-diagnosis-stategraph-real-nodes/`。
- 归档授权:用户已明确“直接实现吧,不用找我授权了”,授权后续阶段在门禁通过后直接归档和提交。
@@ -0,0 +1,21 @@
# Chat Diagnosis StateGraph Real Nodes Brief
## 背景
- 用户目标:将 ISS-011 阶段 2 作为独立 sm-flow,接入真实 Agent/Java Nodes,并在归档、验收和独立 Git 提交后才进入阶段 3。
- 当前问题:阶段 1 只有 Fake Node 路由骨架;Executor/Verifier/Composer 协议逻辑分散在 Hook 与 ChatService,真实 Graph 尚不能安全调用 Agent、Gatekeeper 或投影可信材料。
- 关联 OpenSpec:`openspec/changes/archive/2026-07-17-chat-diagnosis-stategraph-real-nodes/`
- devflow 分档:complex
## 范围
- 本次要做:共享无状态 protocol 组件;Planner/Executor/Verifier/Composer adapters;显式 Gatekeeper、Verified Input、Evidence Retry、Fallback Nodes;真实 CompiledGraph 装配和 focused tests。
- 本次不做:不切换 ChatService 生产入口,不修改 DB、Trace API、Prompt 契约,不删除旧 Sequential/Hook,不运行 live E2E。
- 影响区域:`diagnosis.protocol`、`graph.diagnosis`、`VerifierInputHook`、`ChatService` 共享逻辑委托及对应测试。
## OpenSpec 对齐
- proposal 覆盖状态:已覆盖。
- design 覆盖状态:已覆盖;接口影响为 L2,生产切换明确延期到阶段 3。
- specs 覆盖状态:已覆盖真实 Nodes 安全边界,并修正 critical evidence gap 路由条件。
- tasks 覆盖状态:27/27 完成。
@@ -0,0 +1,218 @@
# Chat Diagnosis StateGraph Real Nodes Decisions
## Question Pool
| # | 维度 | 问题 | 模式 | 状态 |
|---|---|---|---|---|
| Q1 | 边界 | 阶段 2 是否切换 ChatService/DB/Trace? | user-interview(六阶段已确认) | 已解决 |
| Q2 | 复用 | 新 Nodes 如何避免复制 Hook/ChatService 解析与安全渲染? | evidence-driven | 已解决 |
| Q3 | Agent | Adapter 如何调用真实 ReactAgent 并保留 Hook/ToolCallback/run config? | evidence-driven | 已解决 |
| Q4 | Gatekeeper | 显式 Node 如何保证当前 run、单次调用和 fail-closed 标准化? | evidence-driven | 已解决 |
| Q5 | 安全 | passed checked bindings 如何投影为 verified output/evidence? | evidence-driven | 已解决 |
| Q6 | 补证据 | 哪些 facts 可触发 evidence retry,completed queries 如何表达? | evidence-driven | 已解决 |
| Q7 | Fallback | 前置验证失败与 Composer 后置失败可分别使用哪些材料? | evidence-driven | 已解决 |
| Q8 | Prompt | 阶段 2 是否立即修改共享 Verifier Prompt? | evidence-driven | 已解决 |
| Q9 | 验收 | 阶段 2 是否需要单元/集成测试与 E2E? | evidence-driven + user rule | 已解决 |
## Evidence-driven
| 结论 | 证据来源 | 是否已汇报用户 |
|---|---|---|
| ReactAgent.call(input, config) 返回 AssistantMessage,现有 AgentLoggingHook 从 config metadata 读取 sessionId/runId | 本地 1.1.2.0 javap、AgentLoggingHook | 已汇报 |
| Executor parser 当前在 VerifierInputHook,Verifier/Composer parser 与 renderer 当前在 ChatService | 定向源码阅读和 references | 已汇报 |
| checked_bindings 提供 claim_id/tool_name/source_invocation_id/raw_path/matched_text/status | ExecutorGatekeeperService | 已汇报 |
| Gatekeeper.validateRun 使用 run-scoped ToolInvocation repository | ExecutorGatekeeperService tests/code | 已汇报 |
| Graph path不能注册旧 VerifierInputHook,否则 Gatekeeper 会双执行且输入包含 full tool trace | 阶段 0 ADR、Hook 源码 | 已汇报 |
| evidence retry 只允许 critical no_evidence/indirect_support | 阶段 0 design/ISS-011 | 已汇报 |
| 阶段 1 router 未检查 is_critical,属于实现偏离 | Router 源码与阶段 0 baseline 对照 | 已汇报 |
| 共享 Prompt 暂时仍服务旧 Sequential path,本阶段直接修改会提前破坏生产 payload | ChatService agent builder + VerifierInputHook + prompt | 已汇报 |
| Node 输入/安全边界新增且旧解析会重构,单元和 focused regression 必要;E2E 不必要 | 阶段边界和用户规则 | 已汇报 |
## User-interview
| 问题原文 | 用户原话 | 确认状态 | OpenSpec 回写 |
|---|---|---|---|
| 阶段 2 是否独立 sm-flow? | “iss-011里每个阶段,都是一个sm-flow” | 已确认 | 独立 change |
| 是否可提前切生产入口? | “每个阶段需要归档完并提交才能进入下一个阶段” | 已确认不可提前阶段 3 | Out of Scope |
| 阶段 2 是否执行 E2E? | “端到端只在最后阶段全部完成后才验证” | 已确认不执行 | Acceptance |
| 是否添加单元测试? | “如果有必要添加单元测试验收的话,就加” | 已确认规则;本阶段判定必要 | Acceptance |
## Context And Handoff
- 阶段 0 archive/commit:design baseline / `581daff`
- 阶段 1 archive/commit:routing skeleton / `42ba204`
- 当前 change:`chat-diagnosis-stategraph-real-nodes`
- 后续 change:`chat-diagnosis-stategraph-chatservice-cutover`,只能在本阶段 archive + commit 后创建。
## Technical Decisions
### Shared protocol components
- 抽取 Executor、Verifier、Composer 解析器,旧 Hook/ChatService 委托新组件。
- 抽取 Composer safe input builder / renderer,旧 ChatService 保持相同输出。
- JSON sanitization 只存在一份共享实现,不在每个 Node copy。
- 抽取过程是行为保持 refactor;现有 focused tests 是回归门禁。
### Agent adapter boundary
- `DiagnosisAgentInvoker` 是最小 port:`invoke(String, RunnableConfig) -> String`。
- `ReactAgentDiagnosisInvoker` 只包装 `ReactAgent.call(...).getText()`。
- Planner/Executor/Verifier/Composer adapters 各自拥有白名单 input projector、parser 和 status event。
- tests 使用 fake invoker,另有 ReactAgent wrapper test。
### Legacy Hook coexistence
- 旧 Sequential path 在阶段 3 前仍通过 VerifierInputHook 执行 Gatekeeper。
- Graph Verifier Agent 不注册该 Hook;显式 Gatekeeper Node 是 Graph 中唯一 validation 入口。
- Parser/enricher 可共享,但 Hook 的 legacy payload/prompt 暂不改变。
- 阶段 3 切换生产入口并同步 Verifier Prompt;阶段 5 删除旧隐式结构。
### Gatekeeper normalization
- raw pass → PASS。
- raw fail + severity low_confid → LOW_CONFID。
- raw fail + severity reject → REJECT。
- 缺失、unknown、异常或不一致 → REJECT。
- verified_binding_count 只统计 checked_bindings.status=pass。
### Verified projection
- 通过项按 claim_id + source_invocation_id + tool_name + raw_path 与原 binding 精确匹配。
- verified_executor_output 只包含 answer_version 与至少一条 passed binding 的 filtered claims;合法零 claim 保持空列表。
- verified_evidence 只含 claim_id/source_invocation_id/tool_name/raw_path/matched_text。
- hypotheses、失败 binding、未引用 ToolInvocation、raw Executor output 不投影。
### Evidence retry
- extractor 要求 `is_critical=true` 且 verification 为 no_evidence/indirect_support。
- gap 字段:claim_id(从 `claim-id: text` 提取或空)、fact、verification、reason。
- completed queries 由 verified evidence 的 tool/invocation/path 去重生成。
- prior verified output/evidence 原样只读进入 retry_context。
- 约束固定:max_retry=1、do_not_repeat_successful_queries、only_execute_incremental_queries、preserve_prior_verified_claims。
- Executor 输入声明“增量执行、完整输出”,Java 不合并 claims。
### Interface impact
- 等级:L2 internal interface;共享 parser 委托保持旧行为。
- 新消费者:阶段 3 Graph orchestrator/Agent factory。
- 当前外部 API/DB/生产路由:无变化。
- 回滚:revert 本阶段提交;旧 Sequential path仍完整。
- 有意修正:Router 只允许 critical gap,属于对冻结基线的代码修复。
## Risks Accepted
- 真实模型尚未执行;Node contract 通过 fake invoker/real service tests证明,阶段 5 才 E2E。
- Prompt 输入说明与 Graph input 的最终同步推迟到阶段 3,避免当前旧生产 Hook 提前不兼容。
- 旧 Hook 暂时仍存在,但不进入 Graph action graph;阶段 5 必须删除。
## Architecture Audit
### Module and ownership map
`Diagnosis Context + RunnableConfig` → Agent adapters / deterministic Nodes → owned Graph State fields → `DiagnosisGraphRouter` → next Node or END → `final_answer + orchestration_events`。阶段 2 只提供这条可构造链,阶段 3 才由 `ChatService` 创建 Run、构造 Agent 实例并调用 Graph。
| 模块 | 数据所有权 | 允许依赖 |
|---|---|---|
| `diagnosis.protocol` | JSON contract、纯解析结果、安全 input/rendering | Jackson 与纯 DTO;不依赖 Graph/Hook/ChatService/ThreadLocal |
| `graph.diagnosis` Agent adapters | 白名单输入、Agent attempt status/event | protocol、注入的 invoker、Graph API |
| Gatekeeper Node | raw Gatekeeper result、normalized status/count | ExecutorGatekeeperService、当前 run config |
| Verified Input / Retry Prepare | verified projection、critical gaps、retry context | raw result 的只读投影与 protocol DTO |
| Router / Factory | 条件边、有限计数、调用次序 | Graph State;不解析 Agent raw output |
| legacy Hook / ChatService | 阶段 3 前的生产 Sequential 流程 | 只委托 protocol;不得消费真实 Graph action set |
### Lifecycle and coupling audit
Graph State 和 events 都是 invocation-scoped,runId 只从当前 RunnableConfig 获取,Nodes 不持有跨 Run 可变状态。旧路径与 Graph 路径阶段性共享的只有无状态 protocol 组件和 Gatekeeper service,不共享 ThreadLocal 或 Agent output。ReactAgent/invoker 必须构造注入,阶段 2 不复制 ChatService Prompt/Agent factory。唯一有意的阶段性耦合是 legacy consumers 改为委托 protocol,这由旧 focused tests 和完整 revert 保护。阶段 3 前生产入口、DB、Trace、Prompt 均保持隔离。
### Cross-artifact alignment
| 对齐链 | 结果 | 证据 |
|---|---|---|
| brief/proposal 目标、范围、非目标 → proposal | 已对齐 | 独立阶段边界、真实 Nodes、无生产切换均明确 |
| proposal 承诺与约束 → design | 已对齐 | invoker、共享组件、Gatekeeper、投影、retry、Fallback、L2 均有决策 |
| design 架构/接口结论 → specs/tasks | 已对齐 | 中立 protocol 包、构造注入、fail-closed 和生产隔离均有任务/行为 |
| specs 可观察行为 → tasks 可执行切片 | 已对齐 | 每个 requirement 至少由一个实现任务和一个测试/验收任务覆盖 |
### Audit result
审计发现共享协议组件包所有权与 Agent 实例装配边界需要显式化,已回写 design/tasks。未发现状态字段、路由计数、run 生命周期或阶段边界的新冲突。接口影响维持 L2,消费者都在本 change 与下一阶段明确范围内。剩余风险是共享抽取的旧行为漂移和 binding 投影泄漏,均有 focused regression 与 mixed-binding tests。架构风险可接受,无未解决问题。
## Commit Gate
- proposal/design/specs/tasks:全部存在,OpenSpec status `isComplete=true`。
- 当前 change strict validation:通过。
- 主 specs strict validation:13 passed,0 failed。
- 规格结构:9 requirements、22 scenarios;tasks:27 个 checkbox 切片。
- Cross-artifact:4/4 已对齐,gap=0。
- 接口影响:L2,已在 design 独立章节记录消费者、兼容和回滚。
- Question pool:所有 evidence-driven 已汇报;所有 user-interview 已确认;无未决项。
- Preflight scope:`git diff --check` 通过;Commit checkpoint 未修改业务代码。
- 结论:Draft OpenSpec 达到可执行状态,创建 `.committed`。
## Apply Authorization
- 用户原话:“直接实现吧,不用找我授权了”。
- 解释:阶段 2–5 后续 checkpoint 可在前置门禁通过后直接继续,不再因 Apply 或 Archive 授权暂停。
- 不扩大范围:每阶段仍须独立 OpenSpec、验收归档、Git commit;阶段 0–4 不做 E2E,阶段 5 才统一执行。
## Pre-apply Research
### Reference implementations read
- `src/main/java/com/superbiz/agent/hook/VerifierInputHook.java`:Executor JSON sanitization、parse status、tool-name normalization、唯一 invocation 回填与 legacy Gatekeeper payload。
- `src/main/java/com/superbiz/agent/service/ChatService.java`:Verifier claim/fact parser、Gatekeeper ceiling、Composer allowed-material builder、Composer parser/safe renderer、旧 retry context。
- `src/main/java/com/superbiz/agent/service/ExecutorGatekeeperService.java`:`validateRun`、severity normalization source、checked binding 与 matched_text 结构。
- `src/main/java/com/superbiz/agent/graph/diagnosis/DiagnosisGraphFactory.java`:config-aware action ports、technical retry/evidence retry counter 所有权和 recursion limit。
- `src/main/java/com/superbiz/agent/graph/diagnosis/OrchestrationEvent.java` 与 `src/test/java/com/superbiz/agent/graph/diagnosis/ScriptedDiagnosisGraphActions.java`:每 attempt 单 event 形态。
- `src/main/java/com/superbiz/agent/hook/AgentLoggingHook.java`:RunnableConfig metadata 中 sessionId/runId 的审计读取方式。
- `src/test/java/com/superbiz/agent/hook/VerifierInputHookTest.java`、`ChatServiceSequentialAgentTest.java`、`DiagnosisGraphRoutingTest.java`:JUnit 5、Mockito 边界替身和 CompiledGraph observable behavior 测试风格。
- 本地 `1.1.2.0` JAR `javap`:`ReactAgent.call(String,RunnableConfig)` 与 `RunnableConfig.threadId/metadata` 公共 API。
### Technology inventory
| 类别 | 项目标准 / 本阶段使用 |
|---|---|
| JSON contract | Jackson `ObjectMapper`、`LinkedHashMap` 保持稳定字段顺序;共享 sanitization 只存在一份 |
| Graph Node | `AsyncNodeActionWithConfig` 返回 `CompletableFuture<Map<String,Object>>`;state 默认 Replace、events Append |
| Agent boundary | 构造注入的 `DiagnosisAgentInvoker`;ReactAgent wrapper 透传同一个 RunnableConfig |
| Run scope | `config.metadata("runId")` 为 Gatekeeper 唯一运行边界;缺失即 fail closed |
| Error handling | 合法 contract/invalid output/temporary/permanent 分离;unknown exception 不推断为可重试 |
| Tests | JUnit 5;只在 ReactAgent、repository 等系统边界使用 fake/mock;Node/CompiledGraph 走公开 action/graph 接口 |
| Request/response | 本阶段不改 Controller/DTO/API,不适用 |
| MQ/Consumer | 本阶段不涉及,不适用 |
| DB/Trace/Prompt | 本阶段禁止修改,阶段 3 处理 |
### Reuse and new infrastructure
- 新建中立 `com.superbiz.agent.diagnosis.protocol`:`JsonPayloadSupport`、Executor/Verifier/Composer protocol、safe input/rendering、`EvidenceGapExtractor`;不得依赖 Graph/Hook/ChatService/ThreadLocal。
- 新建 `graph.diagnosis` Node 层:invoker wrapper、failure classifier、四个 Agent adapters、Gatekeeper、Verified Input、Retry Prepare、Fallback 和 action assembly。
- 不新建 Prompt/Agent factory;阶段 3 通过构造注入已有 ReactAgent 实例。
- 不新增 Maven dependency、数据库迁移、配置项或生产 consumer。
### Pre-apply conclusion
参考实现、API 签名、异常和测试标准已足以指导实现;未发现 devflow/OpenSpec 冲突。进入 TDD tracer bullet,先锁定共享 Executor parser 的合法 no-evidence 与 malformed 行为。
## Apply Completion And Verification
### Assembly alignment
- 首个共享 protocol 模块与最终真实 Graph action assembly 均逐项对照 design/specs:invoker 显式透传 RunnableConfig,Gatekeeper 单次 fail-closed,Verified Input 精确投影,Verifier/Composer 固定输入重试,critical evidence retry 与两类 Fallback 边界全部落地。
- `DiagnosisGraphFactory` 继续独占技术重试计数、evidence retry 计数和 Planner mode/reset;Node 不重复拥有编排计数。
- 新增源码中无 `ThreadLocal`、`VerifierInputHook`、`tool_trace_summary`、`raw_executor`、TODO 或 FIXME;protocol 包无 Graph/Hook/ChatService 反向依赖。
- `ChatService` 无 Diagnosis Graph/CompiledGraph 引用,生产切换保持在阶段 3;DB migration、Trace DTO/entity/repository 和 prompts 均无 diff。
### Automated verification
- 新 protocol/Node/真实 CompiledGraph/Router/Trace focused suite:通过。
- 旧 `ChatServiceSequentialAgentTest`、`VerifierInputHookTest`、`ExecutorGatekeeperServiceTest` 回归:通过。
- 合计 100 tests,0 failures,0 errors,0 skipped。
- `mvn -q -DskipTests test-compile`:通过。
- `openspec validate chat-diagnosis-stategraph-real-nodes --strict`:通过。
- `openspec validate --specs --strict`:13 passed,0 failed。
- `git diff --check`:通过;仅报告 Git 既有 LF/CRLF 转换提示,无 whitespace error。
### Deferred final verification
- 阶段 2 按用户确认不运行 Maven live E2E,不启动应用,不检查 `logs/`,不查询数据库。
- 上述端到端、日志和 `scripts/query_mysql.py` 数据库核验统一保留到阶段 5 全部实现完成后执行。
@@ -0,0 +1,25 @@
# Chat Diagnosis StateGraph Real Nodes Evidence
## 证据
| 来源 | 证据 | 结论 | 是否已汇报 |
|---|---|---|---|
| `VerifierInputHook.java`、`ChatService.java` | Executor/Verifier/Composer 解析与安全渲染原实现 | 抽取到中立 protocol 并让旧路径委托,避免双真理源 | 是 |
| `ExecutorGatekeeperService.java` | `validateRun` 与 checked binding/matched_text 契约 | Graph Gatekeeper 必须按当前 runId 单次调用并 fail closed | 是 |
| 本地 Graph/ReactAgent 1.1.2.0 API | `ReactAgent.call(String,RunnableConfig)` 与 config-aware Graph action | 最小 invoker 可精确透传输入和当前 Run metadata | 是 |
| 阶段 0/1 OpenSpec archives | 路由、计数、状态与安全边界冻结 | 阶段 2 不改变 Graph counter 所有权或生产入口 | 是 |
| 新 protocol/Node/CompiledGraph tests | PASS、REJECT、LOW_CONFID、critical retry、固定输入重试和安全 fallback | 真实 Nodes 的可观察路径和材料边界已覆盖 | 是 |
| 旧 Sequential/Hook/Gatekeeper tests | 共享抽取后的旧路径回归 | 阶段 2 未破坏当前生产控制流 | 是 |
## Evidence-driven 结论
- Graph Verifier 不得注册旧 `VerifierInputHook`,否则会双执行 Gatekeeper 并泄漏完整 tool trace。
- Verified Input 必须用 claim/invocation/tool/path 精确关联 passed binding;不能复刻 Gatekeeper 判断或读取未引用工具结果。
- Router 与 Retry Prepare 必须共用 critical-gap 提取规则:仅 `is_critical=true` 的 `no_evidence`/`indirect_support`。
- 技术重试输入必须字节一致,且不得重跑前序 Node;计数仍由 Graph factory 统一拥有。
- 当前 ChatService 无 Graph 引用,DB/Trace/Prompt 无 diff,满足阶段 2 的生产隔离要求。
## 风险与后续证据
- 本阶段使用 fake invoker 和 focused tests,不证明真实模型/外部基础设施联通;阶段 5 最终 E2E 统一补证。
- Prompt 输入说明、生产装配、Run/Trace 持久化属于阶段 3,不能提前从阶段 2 证据推断已完成。