From 0136ee8c22300ed351af5eab6329ab4d271b5bb6 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Fri, 9 Oct 2026 00:20:46 +0800 Subject: [PATCH] fix(workflows): retain residual unknowns through CLI and MCP --- src/application/DirectAnalysis.ts | 15 +++++- src/application/WorkflowEvidence.test.ts | 57 +++++++++++++++++++++ src/application/WorkflowEvidence.ts | 63 ++++++++++++++++++++++++ src/server/registerEnhancedTools.ts | 62 +---------------------- 4 files changed, 135 insertions(+), 62 deletions(-) create mode 100644 src/application/WorkflowEvidence.test.ts diff --git a/src/application/DirectAnalysis.ts b/src/application/DirectAnalysis.ts index a798a835b..0adce674c 100644 --- a/src/application/DirectAnalysis.ts +++ b/src/application/DirectAnalysis.ts @@ -44,6 +44,7 @@ import { import { createWorkflowEvidence, workflowSnapshotRecord, + recordWorkflowUnknowns, } from "./WorkflowEvidence.js"; import type { AnalysisProviderSelector } from "../contracts/providerSelection.js"; import { artifactInspectionResultSchema } from "../domain/artifactInspection.js"; @@ -262,7 +263,19 @@ const runAnalysis = async ( signal, evidenceProfile, }); - if (evidence !== undefined) session.recordEvidence(evidence); + if (evidence !== undefined) { + const recorded = session.recordEvidence(evidence); + if (!recorded.ok) return cliError(recorded.error); + if (isWorkflowEvidenceTool(tool)) { + const unknowns = recordWorkflowUnknowns({ + name: tool, + result: evidence.normalized_result, + evidenceId: evidence.evidence_id, + recordUnknown: (unknown) => session.recordUnknown(unknown), + }); + if (!unknowns.ok) return cliError(unknowns.error); + } + } if ( isWorkflowEvidenceTool(tool) && tool !== "trace_native_ui_action" && diff --git a/src/application/WorkflowEvidence.test.ts b/src/application/WorkflowEvidence.test.ts new file mode 100644 index 000000000..5beec7b56 --- /dev/null +++ b/src/application/WorkflowEvidence.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from "vitest"; + +import { + recordWorkflowUnknowns, + createWorkflowEvidence, +} from "./WorkflowEvidence.js"; +import { InvestigationRecords } from "./investigation/InvestigationRecords.js"; +import { UnknownRegistryError } from "../domain/unknownRegistryError.js"; +import { err } from "../domain/result.js"; + +describe("workflow residual question retention", () => { + it("keeps questions linked to workflow Evidence and records repeats idempotently", () => { + const records = new InvestigationRecords(); + const result = { + residual_unknowns: ["Which provider resolves this boundary?"], + }; + const evidence = createWorkflowEvidence({ + target: undefined, + operation: "inspect_native_api", + parameters: { procedure: "0x1000" }, + result, + upstreamProfile: undefined, + }); + expect(records.recordEvidence(evidence).ok).toBe(true); + const input = { + name: "inspect_native_api", + result, + evidenceId: evidence.evidence_id, + recordUnknown: ( + unknown: Parameters[0], + ) => records.recordUnknown(unknown), + }; + expect(recordWorkflowUnknowns(input).ok).toBe(true); + expect(recordWorkflowUnknowns(input).ok).toBe(true); + expect(records.listUnknowns()).toMatchObject([ + { + question: result.residual_unknowns[0], + domain: "native-api", + supporting_evidence_ids: [evidence.evidence_id], + required_authority: "shipped-artifact", + }, + ]); + expect(records.listUnknowns()).toHaveLength(1); + }); + + it("preserves registry failures rather than presenting a completed workflow", () => { + const error = new UnknownRegistryError("limit"); + expect( + recordWorkflowUnknowns({ + name: "trace_feature", + result: { residual_unknowns: ["Which branch handles the request?"] }, + evidenceId: "unretained", + recordUnknown: () => err(error), + }), + ).toEqual(err(error)); + }); +}); diff --git a/src/application/WorkflowEvidence.ts b/src/application/WorkflowEvidence.ts index 7944206a9..070a6ba58 100644 --- a/src/application/WorkflowEvidence.ts +++ b/src/application/WorkflowEvidence.ts @@ -12,6 +12,8 @@ import { import type { BinaryTarget } from "../domain/binaryTarget.js"; import { createEvidence, type Evidence } from "../domain/evidence.js"; import type { JsonValue } from "../domain/jsonValue.js"; +import type { UnknownRegistryPort } from "./investigation/InvestigationRecordPort.js"; +import { UnknownRegistryError } from "../domain/unknownRegistryError.js"; /** Build the shared Evidence record for a successful composed binary workflow. */ export const createWorkflowEvidence = (input: { @@ -62,3 +64,64 @@ export const workflowSnapshotRecord = ( }, }; }; + +interface WorkflowUnknownInput { + readonly name: string; + readonly result: JsonValue; + readonly evidenceId: string; + readonly recordUnknown: UnknownRegistryPort["recordUnknown"] | undefined; +} + +/** Retain residual workflow questions using the shared investigation policy. */ +export const recordWorkflowUnknowns = ({ + name, + result, + evidenceId, + recordUnknown, +}: WorkflowUnknownInput): + | ReturnType + | { readonly ok: true; readonly value: null } => { + if ( + !["trace_feature", "trace_call_path", "inspect_native_api"].includes( + name, + ) || + recordUnknown === undefined || + typeof result !== "object" || + result === null || + Array.isArray(result) || + !Array.isArray(result.residual_unknowns) + ) + return { ok: true, value: null }; + for (const question of result.residual_unknowns) { + if (typeof question !== "string") continue; + const recorded = recordUnknown({ + question, + severity: "medium", + domain: name === "inspect_native_api" ? "native-api" : "control-flow", + supporting_evidence_ids: [evidenceId], + contradicting_evidence_ids: [], + required_authority: "shipped-artifact", + required_confidence: "observed", + required_environment: null, + recommended_probes: [ + { + operation: name, + rationale: + name === "inspect_native_api" + ? "Confirm the unsupported boundary with a capable provider or ABI probe." + : "Continue with a focused query or another available provider.", + }, + ], + relationships: [], + }); + if ( + !recorded.ok && + !( + recorded.error instanceof UnknownRegistryError && + recorded.error.reason === "already-exists" + ) + ) + return recorded; + } + return { ok: true, value: null }; +}; diff --git a/src/server/registerEnhancedTools.ts b/src/server/registerEnhancedTools.ts index 4797998a9..b6d075bb6 100644 --- a/src/server/registerEnhancedTools.ts +++ b/src/server/registerEnhancedTools.ts @@ -17,11 +17,11 @@ import { import { createWorkflowEvidence, workflowSnapshotRecord, + recordWorkflowUnknowns, } from "../application/WorkflowEvidence.js"; import { toolContract, type ToolContract } from "../contracts/toolContracts.js"; import type { AnalysisProfileCommitment } from "../domain/analysisProfile.js"; import type { BinaryTarget } from "../domain/binaryTarget.js"; -import { UnknownRegistryError } from "../domain/unknownRegistryError.js"; import type { JsonValue } from "../domain/jsonValue.js"; import type { Logger } from "../logger.js"; import { mcpProgressReporter } from "./mcpProgress.js"; @@ -324,63 +324,3 @@ const jsonParameters = ( (entry): entry is [string, JsonValue] => entry[1] !== undefined, ), ); - -interface WorkflowUnknownInput { - readonly name: string; - readonly result: JsonValue; - readonly evidenceId: string; - readonly recordUnknown: UnknownRegistryPort["recordUnknown"] | undefined; -} - -const recordWorkflowUnknowns = ({ - name, - result, - evidenceId, - recordUnknown, -}: WorkflowUnknownInput): - | ReturnType - | { readonly ok: true; readonly value: null } => { - if ( - !["trace_feature", "trace_call_path", "inspect_native_api"].includes( - name, - ) || - recordUnknown === undefined || - typeof result !== "object" || - result === null || - Array.isArray(result) || - !Array.isArray(result.residual_unknowns) - ) - return { ok: true, value: null }; - for (const question of result.residual_unknowns) { - if (typeof question !== "string") continue; - const recorded = recordUnknown({ - question, - severity: "medium", - domain: name === "inspect_native_api" ? "native-api" : "control-flow", - supporting_evidence_ids: [evidenceId], - contradicting_evidence_ids: [], - required_authority: "shipped-artifact", - required_confidence: "observed", - required_environment: null, - recommended_probes: [ - { - operation: name, - rationale: - name === "inspect_native_api" - ? "Confirm the unsupported boundary with a capable provider or ABI probe." - : "Continue with a focused query or another available provider.", - }, - ], - relationships: [], - }); - if ( - !recorded.ok && - !( - recorded.error instanceof UnknownRegistryError && - recorded.error.reason === "already-exists" - ) - ) - return recorded; - } - return { ok: true, value: null }; -};