feat(investigation): publish incremental reference graphs

Publish graph deltas using the Server-owned cumulative graph contract. Validate historical comparison chart publications and consolidate Agent documentation.
This commit is contained in:
2026-11-11 19:10:41 +08:00
parent 5ee93329a4
commit db87323b69
28 changed files with 2329 additions and 90 deletions
+8 -13
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from "bun:test";
import { taskReportSchema, taskProgressSchema } from "../../src/product/investigation-report.js";
import { taskPublicationDiagramSchema, taskPublicationProgressSchema, taskReportUiBlocksSchema } from "../../.opencode/tools/task-publication-schema.js";
import { taskReportUiBlocksSchema } from "../../.opencode/tools/task-publication-schema.js";
const report = { title: "调查报告", current_conclusion: "无法确定原因", conclusion_boundary: "仅有上游数据",
evidence_refs: ["query-1"], recommendations: [], pending_items: [], stage: "final", outcome: "insufficient_data",
@@ -12,11 +12,9 @@ describe("new investigation report writes", () => {
const diagram = { title: "状态图", mermaid: "flowchart TD\nQUERY:::running --> REVIEW:::pending",
node_refs: [{ node_id: "QUERY", label: "核查", status: "running" },
{ node_id: "REVIEW", label: "复核", status: "pending" }] };
expect(taskPublicationDiagramSchema.safeParse(diagram).success).toBe(true);
expect(taskReportSchema.safeParse({ ...report, diagram }).success).toBe(true);
for (const node_id of ["missing", "QUER"]) {
const invalid = { ...diagram, node_refs: [{ ...diagram.node_refs[0], node_id }] };
expect(taskPublicationDiagramSchema.safeParse(invalid).success).toBe(false);
expect(taskReportSchema.safeParse({ ...report, diagram: invalid }).success).toBe(false);
}
});
@@ -41,7 +39,6 @@ describe("new investigation report writes", () => {
}
const mismatched = { ...diagram, node_refs: [{ ...diagram.node_refs[0], node_id: "query" }] };
expect(() => taskReportSchema.parse({ ...report, diagram: mismatched })).toThrow("未在 Mermaid 源码中声明");
expect(taskPublicationDiagramSchema.safeParse(mismatched).success).toBe(false);
});
it("rejects downstream work or unsupported claims on insufficient-data exit", () => {
for (const changes of [{ recommendations: ["派采样工单"] }, { pending_items: ["等待人工"] },
@@ -50,13 +47,13 @@ describe("new investigation report writes", () => {
expect(() => taskReportSchema.parse({ ...report, ...changes })).toThrow();
}
});
it("shares explicit node activity and supply feature targets with progress", () => {
it("shares saved reference annotations and supply feature targets with progress", () => {
const diagram = { title: "当前调查", mermaid: "flowchart LR\nQUERY --> HUMAN",
node_refs: [{ node_id: "QUERY", label: "核对监测", status: "running", evidence_refs: ["query-1"],
map_targets: [{ source_id: "scada", feature_id: "MP01" }] },
{ node_id: "HUMAN", label: "现场核验", status: "waiting_human" }] };
const progress = taskProgressSchema.parse({ summary: "正在核对投诉时段的实际监测记录", diagram });
expect(progress.diagram?.node_refs[0]).toMatchObject({ status: "running", map_targets: [{ source_id: "scada", feature_id: "MP01" }] });
expect(progress.diagram?.node_refs[0]).toMatchObject({ status: "reference", map_targets: [{ source_id: "scada", feature_id: "MP01" }] });
expect(taskReportSchema.parse({ ...report, diagram }).diagram).toEqual(progress.diagram);
expect(taskProgressSchema.parse({ summary: "调整调查方向", diagram: null }).diagram).toBeNull();
for (const change of [{ status: "blocked" }, { map_targets: [{ source_id: "drainage", feature_id: "X" }] },
@@ -66,7 +63,7 @@ describe("new investigation report writes", () => {
} })).toThrow();
}
});
it("preserves waiting, unknown and evidence-confirmed states at every diagram boundary", () => {
it("normalizes legacy states into saved reference annotations at every read boundary", () => {
const diagram = { title: "等待系统结果", mermaid: "flowchart LR\nDONE --> WAIT --> UNKNOWN --> UNSET", node_refs: [
{ node_id: "DONE", label: "查询完成", status: "completed", evidence_refs: ["query-1"] },
{ node_id: "WAIT", label: "等待系统结果", status: "waiting" },
@@ -74,12 +71,10 @@ describe("new investigation report writes", () => {
{ node_id: "UNSET", label: "未确认" },
] };
const nodes = taskProgressSchema.parse({ summary: "等待系统结果", diagram }).diagram!.node_refs;
expect(nodes?.map((node) => node.status)).toEqual(["completed", "waiting", null, undefined]);
expect(nodes?.map((node) => node.status)).toEqual(["recorded", "needs_input", null, undefined]);
expect(taskReportSchema.parse({ ...report, diagram }).diagram?.node_refs).toEqual(nodes);
expect(taskPublicationDiagramSchema.parse(diagram).node_refs).toEqual(nodes);
expect(taskPublicationProgressSchema.parse({ summary: "等待系统结果", diagram }).diagram?.node_refs).toEqual(nodes);
});
it("preserves parallel branches with independent activity and a pending join", () => {
it("preserves parallel branches and recorded findings without live activity assertions", () => {
const mermaid = 'flowchart TD\nSTART[受理投诉] --> SPLIT{并行核查}\nSPLIT --> TICKETS[工单核查]\nSPLIT --> SCADA[监测核查]\nTICKETS --> JOIN[汇总证据]\nSCADA --> JOIN';
const diagram = { title: "并行调查", mermaid, node_refs: [
{ node_id: "TICKETS", label: "工单核查", status: "running" },
@@ -88,11 +83,11 @@ describe("new investigation report writes", () => {
] };
const progress = taskProgressSchema.parse({ summary: "两个独立核查正在执行", diagram });
expect(progress.diagram?.mermaid).toBe(mermaid);
expect(progress.diagram?.node_refs.map((node) => node.status)).toEqual(["running", "running", "pending"]);
expect(progress.diagram?.node_refs.map((node) => node.status)).toEqual(["reference", "reference", "reference"]);
const updated = taskProgressSchema.parse({ summary: "工单核查已完成,监测核查仍在执行", diagram: {
...diagram, node_refs: [{ ...diagram.node_refs[0], status: "completed", evidence_refs: ["query-1"] }, ...diagram.node_refs.slice(1)],
} });
expect(updated.diagram?.node_refs.map((node) => node.status)).toEqual(["completed", "running", "pending"]);
expect(updated.diagram?.node_refs.map((node) => node.status)).toEqual(["recorded", "reference", "reference"]);
});
});