refactor(mcp): consolidate contract and summary policy owners

Resolve application and managed workflow contracts through the canonical name-based resolver, removing catalog-position overloads and the obsolete local facade.

Use the existing retained-summary policy for eight offline registrations. Pass only validated delivery detail to that owner while preserving exact typed service inputs and validation, admission, logging, and Evidence recording order.

Align export-shape MCP parity coverage with the existing absolute-path contract using an absolute dot alias. Preserve exact caller-path parameters, canonical artifact parity, distinct Evidence identity, and the export comparison assertions.
This commit is contained in:
morluto
2026-10-24 07:09:42 +08:00
parent 0785223582
commit 3d4f55652b
23 changed files with 126 additions and 208 deletions
-42
View File
@@ -319,45 +319,3 @@ export const APPLICATION_TOOL_CONTRACTS = [
],
},
] as const satisfies readonly ToolContract[];
/** Resolve one named application contract without relying on array position. */
export function applicationToolContract(
name: "trace_application_feature",
): (typeof APPLICATION_TOOL_CONTRACTS)[0];
export function applicationToolContract(
name: "trace_javascript_semantics",
): (typeof APPLICATION_TOOL_CONTRACTS)[1];
export function applicationToolContract(
name: "compare_application_versions",
): (typeof APPLICATION_TOOL_CONTRACTS)[2];
export function applicationToolContract(
name: "compare_source_to_bundle",
): (typeof APPLICATION_TOOL_CONTRACTS)[3];
export function applicationToolContract(
name: "compare_javascript_export_shapes",
): (typeof APPLICATION_TOOL_CONTRACTS)[4];
export function applicationToolContract(
name: "build_reconstruction_obligation_ledger",
): (typeof APPLICATION_TOOL_CONTRACTS)[5];
export function applicationToolContract(
name: "evaluate_reconstruction_coverage",
): (typeof APPLICATION_TOOL_CONTRACTS)[6];
export function applicationToolContract(
name: "project_android_application_graph",
): (typeof APPLICATION_TOOL_CONTRACTS)[7];
export function applicationToolContract(
name: "project_apple_application_graph",
): (typeof APPLICATION_TOOL_CONTRACTS)[8];
export function applicationToolContract(
name: "project_harmony_application_graph",
): (typeof APPLICATION_TOOL_CONTRACTS)[9];
export function applicationToolContract(
name: (typeof APPLICATION_TOOL_CONTRACTS)[number]["name"],
): (typeof APPLICATION_TOOL_CONTRACTS)[number] {
const contract = APPLICATION_TOOL_CONTRACTS.find(
({ name: candidate }) => candidate === name,
);
if (contract === undefined)
throw new Error(`Missing application tool contract: ${name}`);
return contract;
}
@@ -3,13 +3,13 @@ import { runAdmittedToolOperation } from "../admittedToolOperation.js";
import { recordSessionEvidenceSources } from "../sessionEvidence.js";
import { compareJavaScriptExportShapesEvidence } from "../../application/javascript/JavaScriptApplicationWorkflowService.js";
import { applicationToolContract } from "../../contracts/applicationToolContracts.js";
import { toolContract } from "../../contracts/toolContracts.js";
import { logToolExecution } from "../toolLogging.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { recordResult } from "./helpers.js";
import type { ApplicationToolRegistration } from "./types.js";
const contract = applicationToolContract("compare_javascript_export_shapes");
const contract = toolContract("compare_javascript_export_shapes");
/** Register the execution-free exact JavaScript export-shape comparison. */
export const registerCompareJavaScriptExportShapesTool = (
@@ -12,13 +12,13 @@ import { resolveApplicationEvidenceRequest } from "../../application/EvidenceInp
import { recordSessionEvidenceSources } from "../sessionEvidence.js";
import { compareSourceToBundleEvidence } from "../../application/javascript/JavaScriptApplicationWorkflowService.js";
import { applicationToolContract } from "../../contracts/applicationToolContracts.js";
import { toolContract } from "../../contracts/toolContracts.js";
import { logToolExecution } from "../toolLogging.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { recordApplicationResult } from "./helpers.js";
import type { ApplicationToolRegistration } from "./types.js";
const contract = applicationToolContract("compare_source_to_bundle");
const contract = toolContract("compare_source_to_bundle");
/** Register conservative historical-source to bundle comparison. */
export const registerCompareSourceToBundleTool = (
@@ -3,13 +3,13 @@ import { runAdmittedToolOperation } from "../admittedToolOperation.js";
import { recordSessionEvidenceSources } from "../sessionEvidence.js";
import { compareApplicationVersionsEvidence } from "../../application/javascript/JavaScriptApplicationWorkflowService.js";
import { applicationToolContract } from "../../contracts/applicationToolContracts.js";
import { toolContract } from "../../contracts/toolContracts.js";
import { logToolExecution } from "../toolLogging.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { recordResult } from "./helpers.js";
import type { ApplicationToolRegistration } from "./types.js";
const compareContract = applicationToolContract("compare_application_versions");
const compareContract = toolContract("compare_application_versions");
/** Register the provider-neutral JavaScript version comparison tool. */
export const registerCompareApplicationVersionsTool = (
@@ -2,12 +2,12 @@ import type { EvidenceMcpServer } from "../EvidenceMcpServer.js";
import { runAdmittedToolOperation } from "../admittedToolOperation.js";
import { evaluateReconstructionCoverage } from "../../application/ReconstructionCoverageService.js";
import { applicationToolContract } from "../../contracts/applicationToolContracts.js";
import { toolContract } from "../../contracts/toolContracts.js";
import { logToolExecution } from "../toolLogging.js";
import { applicationToolRegistrationOptions } from "../toolRegistrationOptions.js";
import type { ApplicationToolRegistration } from "./types.js";
const contract = applicationToolContract("evaluate_reconstruction_coverage");
const contract = toolContract("evaluate_reconstruction_coverage");
/** Register the inline fail-closed reconstruction coverage evaluator. */
export const registerCoverageTools = (
@@ -5,15 +5,13 @@ import { recordSessionEvidenceSources } from "../sessionEvidence.js";
import { buildResolvedReconstructionObligationLedgerEvidence } from "../../application/ReconstructionObligationLedgerService.js";
import { resolveReconstructionObligationLedgerRequest } from "../../application/ReconstructionObligationLedgerRequest.js";
import { ok } from "../../domain/result.js";
import { applicationToolContract } from "../../contracts/applicationToolContracts.js";
import { toolContract } from "../../contracts/toolContracts.js";
import { logToolExecution } from "../toolLogging.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { recordResult } from "./helpers.js";
import type { ApplicationToolRegistration } from "./types.js";
const contract = applicationToolContract(
"build_reconstruction_obligation_ledger",
);
const contract = toolContract("build_reconstruction_obligation_ledger");
/** Register conservative reconstruction-obligation generation and closure. */
export const registerReconstructionObligationLedgerTool = (
@@ -5,10 +5,8 @@ import { runAdmittedToolOperation } from "../admittedToolOperation.js";
import { projectAndroidApplicationEvidence } from "../../application/android/AndroidApplicationService.js";
import { projectAppleApplicationEvidence } from "../../application/apple/AppleApplicationService.js";
import { projectHarmonyApplicationEvidence } from "../../application/harmony/HarmonyApplicationService.js";
import {
APPLICATION_TOOL_CONTRACTS,
applicationToolContract,
} from "../../contracts/applicationToolContracts.js";
import { APPLICATION_TOOL_CONTRACTS } from "../../contracts/applicationToolContracts.js";
import { toolContract } from "../../contracts/toolContracts.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import type { Result } from "../../domain/result.js";
import type { InventoryProjectionEvidenceResult } from "../../application/InventoryProjectionEvidence.js";
@@ -26,19 +24,19 @@ export const registerProjectMobileApplicationGraphTools = (
registerProjection({
server,
options,
contract: applicationToolContract("project_android_application_graph"),
contract: toolContract("project_android_application_graph"),
project: projectAndroidApplicationEvidence,
});
registerProjection({
server,
options,
contract: applicationToolContract("project_apple_application_graph"),
contract: toolContract("project_apple_application_graph"),
project: projectAppleApplicationEvidence,
});
registerProjection({
server,
options,
contract: applicationToolContract("project_harmony_application_graph"),
contract: toolContract("project_harmony_application_graph"),
project: projectHarmonyApplicationEvidence,
});
};
@@ -3,13 +3,13 @@ import { runAdmittedToolOperation } from "../admittedToolOperation.js";
import { recordSessionEvidenceSources } from "../sessionEvidence.js";
import { traceApplicationFeatureEvidence } from "../../application/javascript/JavaScriptApplicationWorkflowService.js";
import { applicationToolContract } from "../../contracts/applicationToolContracts.js";
import { toolContract } from "../../contracts/toolContracts.js";
import { logToolExecution } from "../toolLogging.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { recordResult } from "./helpers.js";
import type { ApplicationToolRegistration } from "./types.js";
const traceContract = applicationToolContract("trace_application_feature");
const traceContract = toolContract("trace_application_feature");
/** Register the provider-neutral JavaScript feature trace tool. */
export const registerTraceFeatureTool = (
@@ -4,13 +4,13 @@ import { resolveApplicationEvidenceRequest } from "../../application/EvidenceInp
import { recordSessionEvidenceSources } from "../sessionEvidence.js";
import { traceJavaScriptSemanticsEvidenceValidated } from "../../application/javascript/JavaScriptSemanticTraceService.js";
import { applicationToolContract } from "../../contracts/applicationToolContracts.js";
import { toolContract } from "../../contracts/toolContracts.js";
import { logToolExecution } from "../toolLogging.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { recordResult } from "./helpers.js";
import type { ApplicationToolRegistration } from "./types.js";
const contract = applicationToolContract("trace_javascript_semantics");
const contract = toolContract("trace_javascript_semantics");
/** Register the bounded JavaScript semantic relation trace tool. */
export const registerTraceJavaScriptSemanticsTool = (
+14 -16
View File
@@ -1,10 +1,7 @@
import type { ByteEntropyService } from "../application/binaryDiagnostics/ByteEntropyService.js";
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import {
requireAnalysisViewRetention,
summarizeRetainedAnalysis,
} from "../application/analysisView/AnalysisViewService.js";
import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js";
import type { BinaryLayoutService } from "../application/binaryDiagnostics/BinaryLayoutService.js";
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
@@ -38,6 +35,10 @@ export const registerBinaryDiagnosticsTools = (
withAdmittedAnalysis,
);
const contract = toolContract("inspect_binary_layout");
const delivery = retainedSummaryDelivery(new Set([contract.name]), {
recordEvidence,
evidenceById,
});
server.registerTool(
contract.name,
applicationToolRegistrationOptions(contract),
@@ -56,6 +57,7 @@ export const registerBinaryDiagnosticsTools = (
contract,
);
const { detail, ...request } = parsed.data;
const parameters = { detail };
return runAdmittedToolOperation(
server,
withAdmittedAnalysis,
@@ -66,21 +68,17 @@ export const registerBinaryDiagnosticsTools = (
logger,
contract.name,
async () => {
if (detail === "summary") {
const ready = requireAnalysisViewRetention(contract.name, {
recordEvidence,
evidenceById,
});
if (!ready.ok) return ready;
}
const ready = delivery.preflight(contract.name, parameters);
if (!ready.ok) return ready;
const inspected = await service.inspect(request, {
signal: context.mcpReq.signal,
});
return detail === "summary" && inspected.ok
? summarizeRetainedAnalysis(inspected.value, {
recordEvidence,
evidenceById,
})
return inspected.ok
? delivery.projectEvidence(
contract.name,
parameters,
inspected.value,
)
: inspected;
},
);
+14 -14
View File
@@ -1,10 +1,7 @@
import type { ByteEntropyService } from "../application/binaryDiagnostics/ByteEntropyService.js";
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import {
requireAnalysisViewRetention,
summarizeRetainedAnalysis,
} from "../application/analysisView/AnalysisViewService.js";
import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js";
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import { toolContract } from "../contracts/toolContracts.js";
@@ -25,7 +22,10 @@ export const registerByteEntropyTool = (
withAdmittedAnalysis: WithAdmittedAnalysis | undefined,
): void => {
const contract = toolContract("inspect_byte_entropy");
const retention = { recordEvidence, evidenceById };
const delivery = retainedSummaryDelivery(new Set([contract.name]), {
recordEvidence,
evidenceById,
});
server.registerTool(
contract.name,
toolRegistrationOptions(contract),
@@ -40,18 +40,18 @@ export const registerByteEntropyTool = (
logger,
contract.name,
async () => {
if (detail === "summary") {
const ready = requireAnalysisViewRetention(
contract.name,
retention,
);
if (!ready.ok) return ready;
}
const parameters = { detail };
const ready = delivery.preflight(contract.name, parameters);
if (!ready.ok) return ready;
const inspected = await entropyService.inspect(request, {
signal: context.mcpReq.signal,
});
return detail === "summary" && inspected.ok
? summarizeRetainedAnalysis(inspected.value, retention)
return inspected.ok
? delivery.projectEvidence(
contract.name,
parameters,
inspected.value,
)
: inspected;
},
);
+10 -11
View File
@@ -7,10 +7,7 @@ import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
import { toolContract } from "../contracts/toolContracts.js";
import { createEvidenceToolHandler } from "./contractToolHandler.js";
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
import {
requireAnalysisViewRetention,
summarizeRetainedAnalysis,
} from "../application/analysisView/AnalysisViewService.js";
import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { err } from "../domain/result.js";
@@ -24,7 +21,10 @@ export const registerDesktopApplicationTool = (
evidenceById: EvidenceLookup | undefined,
): void => {
const contract = toolContract("inspect_desktop_application");
const retention = { recordEvidence, evidenceById };
const delivery = retainedSummaryDelivery(new Set([contract.name]), {
recordEvidence,
evidenceById,
});
const handler = createEvidenceToolHandler(
server,
async (raw, options) => {
@@ -34,13 +34,12 @@ export const registerDesktopApplicationTool = (
analysisInputErrorFromIssues(contract.name, parsed.error.issues, raw),
);
const { detail, ...input } = parsed.data;
if (detail === "summary") {
const ready = requireAnalysisViewRetention(contract.name, retention);
if (!ready.ok) return ready;
}
const parameters = { detail };
const ready = delivery.preflight(contract.name, parameters);
if (!ready.ok) return ready;
const result = await service.inspect(input, options);
return result.ok && detail === "summary"
? summarizeRetainedAnalysis(result.value, retention)
return result.ok
? delivery.projectEvidence(contract.name, parameters, result.value)
: result;
},
logger,
+10 -11
View File
@@ -3,10 +3,7 @@ import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { EvmInterfaceService } from "../application/evm/EvmInterfaceService.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
import {
requireAnalysisViewRetention,
summarizeRetainedAnalysis,
} from "../application/analysisView/AnalysisViewService.js";
import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { err } from "../domain/result.js";
import { evmInterfaceModule } from "../composition/evm.js";
@@ -25,6 +22,10 @@ export const registerEvmTools = (
evidenceById?: EvidenceLookup,
): void => {
const contract = evmInterfaceModule.contract;
const delivery = retainedSummaryDelivery(new Set([contract.name]), {
recordEvidence,
evidenceById,
});
const handler = createEvidenceToolHandler(
server,
async (rawInput, options) => {
@@ -39,14 +40,12 @@ export const registerEvmTools = (
),
);
const { detail, ...input } = parsed.data;
const retention = { recordEvidence, evidenceById };
if (detail === "summary") {
const ready = requireAnalysisViewRetention(contract.name, retention);
if (!ready.ok) return ready;
}
const parameters = { detail };
const ready = delivery.preflight(contract.name, parameters);
if (!ready.ok) return ready;
const inspected = await service.inspect(input, options);
return detail === "summary" && inspected.ok
? summarizeRetainedAnalysis(inspected.value, retention)
return inspected.ok
? delivery.projectEvidence(contract.name, parameters, inspected.value)
: inspected;
},
logger,
@@ -6,10 +6,10 @@ import { recordSessionEvidenceSources } from "../sessionEvidence.js";
import { executeManagedMemberComparison } from "../../application/managed/ManagedMemberComparisonService.js";
import { logToolExecution } from "../toolLogging.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { managedWorkflowContract } from "./contract.js";
import { toolContract } from "../../contracts/toolContracts.js";
import type { ManagedWorkflowToolRegistration } from "./types.js";
const compareContract = managedWorkflowContract("compare_managed_members");
const compareContract = toolContract("compare_managed_members");
/** Register the managed member comparison workflow tool. */
export const registerCompareManagedMembers = (
@@ -1,24 +0,0 @@
import { MANAGED_WORKFLOW_TOOL_CONTRACTS } from "../../contracts/managed/managedWorkflowToolContracts.js";
/** Locate one managed workflow tool contract by exact name. */
export function managedWorkflowContract(
name: "compare_managed_members",
): (typeof MANAGED_WORKFLOW_TOOL_CONTRACTS)[0];
export function managedWorkflowContract(
name: "verify_managed_native_boundaries",
): (typeof MANAGED_WORKFLOW_TOOL_CONTRACTS)[1];
export function managedWorkflowContract(
name: "import_managed_reconstruction",
): (typeof MANAGED_WORKFLOW_TOOL_CONTRACTS)[2];
export function managedWorkflowContract(
name: "project_managed_application_graph",
): (typeof MANAGED_WORKFLOW_TOOL_CONTRACTS)[3];
export function managedWorkflowContract(
name: (typeof MANAGED_WORKFLOW_TOOL_CONTRACTS)[number]["name"],
) {
const found = MANAGED_WORKFLOW_TOOL_CONTRACTS.find(
(candidate) => candidate.name === name,
);
if (found === undefined) throw new Error(`Missing ${name} contract`);
return found;
}
@@ -7,12 +7,10 @@ import {
} from "../../application/managed/ManagedReconstructionService.js";
import { logToolExecution } from "../toolLogging.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { managedWorkflowContract } from "./contract.js";
import { toolContract } from "../../contracts/toolContracts.js";
import type { ManagedWorkflowToolRegistration } from "./types.js";
const reconstructionContract = managedWorkflowContract(
"import_managed_reconstruction",
);
const reconstructionContract = toolContract("import_managed_reconstruction");
/** Register the managed reconstruction import workflow tool. */
export const registerImportManagedReconstruction = (
@@ -8,12 +8,10 @@ import type { Evidence } from "../../domain/evidence.js";
import { ok } from "../../domain/result.js";
import { logToolExecution } from "../toolLogging.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { managedWorkflowContract } from "./contract.js";
import { toolContract } from "../../contracts/toolContracts.js";
import type { ManagedWorkflowToolRegistration } from "./types.js";
const graphContract = managedWorkflowContract(
"project_managed_application_graph",
);
const graphContract = toolContract("project_managed_application_graph");
/** Register the managed application graph projection workflow tool. */
export const registerProjectManagedApplicationGraph = (
@@ -6,10 +6,10 @@ import { recordSessionEvidenceSources } from "../sessionEvidence.js";
import { executeManagedNativeBoundaries } from "../../application/managed/ManagedNativeVerificationService.js";
import { logToolExecution } from "../toolLogging.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { managedWorkflowContract } from "./contract.js";
import { toolContract } from "../../contracts/toolContracts.js";
import type { ManagedWorkflowToolRegistration } from "./types.js";
const nativeVerificationContract = managedWorkflowContract(
const nativeVerificationContract = toolContract(
"verify_managed_native_boundaries",
);
+10 -11
View File
@@ -7,10 +7,7 @@ import { createEvidenceToolHandler } from "./contractToolHandler.js";
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
import {
requireAnalysisViewRetention,
summarizeRetainedAnalysis,
} from "../application/analysisView/AnalysisViewService.js";
import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { err } from "../domain/result.js";
@@ -24,6 +21,10 @@ export const registerNativeExportsTool = (
withAdmittedAnalysis?: WithAdmittedAnalysis,
): void => {
const contract = toolContract("inspect_native_exports");
const delivery = retainedSummaryDelivery(new Set([contract.name]), {
recordEvidence,
evidenceById,
});
const handler = createEvidenceToolHandler(
server,
async (rawInput, options) => {
@@ -38,14 +39,12 @@ export const registerNativeExportsTool = (
),
);
const { detail, ...input } = parsed.data;
const retention = { recordEvidence, evidenceById };
if (detail === "summary") {
const ready = requireAnalysisViewRetention(contract.name, retention);
if (!ready.ok) return ready;
}
const parameters = { detail };
const ready = delivery.preflight(contract.name, parameters);
if (!ready.ok) return ready;
const inspected = await service.inspect(input, options);
return detail === "summary" && inspected.ok
? summarizeRetainedAnalysis(inspected.value, retention)
return inspected.ok
? delivery.projectEvidence(contract.name, parameters, inspected.value)
: inspected;
},
logger,
+10 -11
View File
@@ -13,10 +13,7 @@ import {
applicationToolRegistrationOptions,
} from "./toolRegistrationOptions.js";
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
import {
requireAnalysisViewRetention,
summarizeRetainedAnalysis,
} from "../application/analysisView/AnalysisViewService.js";
import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { err } from "../domain/result.js";
@@ -43,6 +40,10 @@ export const registerPcapTools = (
handler(clientContract),
);
const contract = toolContract("inspect_pcap_capture");
const delivery = retainedSummaryDelivery(new Set([contract.name]), {
recordEvidence,
evidenceById,
});
const capture = createEvidenceToolHandler(
server,
async (rawInput, options) => {
@@ -57,14 +58,12 @@ export const registerPcapTools = (
),
);
const { detail, ...input } = parsed.data;
const retention = { recordEvidence, evidenceById };
if (detail === "summary") {
const ready = requireAnalysisViewRetention(contract.name, retention);
if (!ready.ok) return ready;
}
const parameters = { detail };
const ready = delivery.preflight(contract.name, parameters);
if (!ready.ok) return ready;
const result = await service.execute(contract.name, input, options);
return detail === "summary" && result.ok
? summarizeRetainedAnalysis(result.value, retention)
return result.ok
? delivery.projectEvidence(contract.name, parameters, result.value)
: result;
},
logger,
+10 -11
View File
@@ -7,10 +7,7 @@ import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { WasmArtifactService } from "../application/wasm/WasmArtifactService.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
import {
requireAnalysisViewRetention,
summarizeRetainedAnalysis,
} from "../application/analysisView/AnalysisViewService.js";
import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { err } from "../domain/result.js";
import { wasmArtifactModule } from "../composition/wasm.js";
@@ -64,6 +61,10 @@ export const registerWasmTools = (
compareHandler(comparison),
);
const contract = wasmArtifactModule.contract;
const delivery = retainedSummaryDelivery(new Set([contract.name]), {
recordEvidence,
evidenceById,
});
const handler = createEvidenceToolHandler(
server,
async (rawInput, options) => {
@@ -78,14 +79,12 @@ export const registerWasmTools = (
),
);
const { detail, ...input } = parsed.data;
const retention = { recordEvidence, evidenceById };
if (detail === "summary") {
const ready = requireAnalysisViewRetention(contract.name, retention);
if (!ready.ok) return ready;
}
const parameters = { detail };
const ready = delivery.preflight(contract.name, parameters);
if (!ready.ok) return ready;
const inspected = await service.inspect(input, options);
return detail === "summary" && inspected.ok
? summarizeRetainedAnalysis(inspected.value, retention)
return inspected.ok
? delivery.projectEvidence(contract.name, parameters, inspected.value)
: inspected;
},
logger,
+10 -11
View File
@@ -1,9 +1,6 @@
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
import {
requireAnalysisViewRetention,
summarizeRetainedAnalysis,
} from "../application/analysisView/AnalysisViewService.js";
import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { redactExplicitFailure } from "../domain/explicitSensitiveFailure.js";
import { captureSensitiveValues } from "../domain/webNetworkCapture.js";
@@ -26,6 +23,10 @@ export const registerWebNetworkCaptureTool = (
evidenceById?: EvidenceLookup,
): void => {
const contract = toolContract("inspect_web_network_capture");
const delivery = retainedSummaryDelivery(new Set([contract.name]), {
recordEvidence,
evidenceById,
});
const registration = applicationToolRegistrationOptions(contract);
// Keep the exact advertised contract; the shared service validates raw input
// before effects so caller-declared sensitivity also covers invalid arguments.
@@ -47,14 +48,12 @@ export const registerWebNetworkCaptureTool = (
),
);
const { detail, ...input } = parsed.data;
const retention = { recordEvidence, evidenceById };
if (detail === "summary") {
const ready = requireAnalysisViewRetention(contract.name, retention);
if (!ready.ok) return ready;
}
const parameters = { detail };
const ready = delivery.preflight(contract.name, parameters);
if (!ready.ok) return ready;
const inspected = await service.inspect(input, options);
return detail === "summary" && inspected.ok
? summarizeRetainedAnalysis(inspected.value, retention)
return inspected.ok
? delivery.projectEvidence(contract.name, parameters, inspected.value)
: inspected;
},
logger,
@@ -1,6 +1,6 @@
import { describe, expect, it, onTestFinished } from "vitest";
import { copyFile, mkdir, rm, writeFile } from "node:fs/promises";
import { join, relative, resolve } from "node:path";
import { join, resolve, sep } from "node:path";
import { Ajv2020 } from "ajv/dist/2020.js";
import { z } from "zod";
@@ -166,22 +166,22 @@ describe("application workflow MCP parity", () => {
right_export_name: "default",
};
try {
const relativePath = relative(process.cwd(), leftRoot);
const relativeApplication = await client.callTool({
const selectedPath = `${leftRoot}${sep}.`;
const selectedApplication = await client.callTool({
name: "analyze_javascript_application",
arguments: { input_path: relativePath },
arguments: { input_path: selectedPath },
});
expect(relativeApplication.isError).not.toBe(true);
const relativeEvidence = parseEvidence(
relativeApplication.structuredContent,
expect(selectedApplication.isError).not.toBe(true);
const selectedEvidence = parseEvidence(
selectedApplication.structuredContent,
);
expect(relativeEvidence.parameters.input_path).toBe(relativePath);
expect(selectedEvidence.parameters.input_path).toBe(selectedPath);
expect(left.value.parameters.input_path).toBe(leftRoot);
expect(relativeEvidence.normalized_result).toEqual(
expect(selectedEvidence.normalized_result).toEqual(
left.value.normalized_result,
);
expect(relativeEvidence.subject).toEqual(left.value.subject);
expect(relativeEvidence.evidence_id).not.toBe(left.value.evidence_id);
expect(selectedEvidence.subject).toEqual(left.value.subject);
expect(selectedEvidence.evidence_id).not.toBe(left.value.evidence_id);
const full = await client.callTool({
name: "compare_javascript_export_shapes",
arguments: { left: left.value, right: right.value, ...selectors },