fix(workflows): retain residual unknowns through CLI and MCP

This commit is contained in:
morluto 2026-10-09 00:20:46 +08:00
parent 65a6288257
commit 0136ee8c22
4 changed files with 135 additions and 62 deletions

View File

@ -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" &&

View File

@ -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<InvestigationRecords["recordUnknown"]>[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));
});
});

View File

@ -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<UnknownRegistryPort["recordUnknown"]>
| { 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 };
};

View File

@ -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<UnknownRegistryPort["recordUnknown"]>
| { 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 };
};