Files
rea/src/server/registerApplicationTools/compareSourceToBundle.ts
T
morluto 835904e4ee refactor(evidence): carry producer-owned source records
Carry the final artifact inspection's exact nested source Evidence through
AnalysisExecution. Derive links in the shared Evidence builder instead of
reparsing the result in CLI and MCP adapters. Retain validation for unknown
and historical Evidence inputs.

Move the shared source recorder into application ownership and migrate its
consumers. Record sources before parents; keep source recording failures
from publishing summaries. Decline snapshot entries whose source closure the
current entry format cannot represent, while retaining investigation Evidence.

Validation: actual ASAR regression failed before the change with missing
links and passes afterward; exact nested identity, source recorder refusal,
final-close cancellation and cleanup failure, historical authentication,
snapshot controls and public CLI/MCP workflows. Fresh runtime/catalog build,
full static/docs, discovery and duplication checks pass. Installed native
providers and platform-specific skipped cases remain unverified.
2026-10-25 04:37:11 +08:00

125 lines
4.8 KiB
TypeScript

import { setImmediate } from "node:timers/promises";
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
import { ok } from "../../domain/result.js";
import { resolveSourceToBundleReference } from "../../application/javascript/SourceToBundleReference.js";
import {
requireAnalysisViewRetention,
inspectAnalysisViewValidated,
} from "../../application/analysisView/AnalysisViewService.js";
import type { EvidenceMcpServer } from "../EvidenceMcpServer.js";
import { runAdmittedToolOperation } from "../admittedToolOperation.js";
import { resolveApplicationEvidenceRequest } from "../../application/EvidenceInputResolver.js";
import { recordEvidenceSources } from "../../application/investigation/recordEvidenceSources.js";
import { compareSourceToBundleEvidence } from "../../application/javascript/JavaScriptApplicationWorkflowService.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 = toolContract("compare_source_to_bundle");
/** Register conservative historical-source to bundle comparison. */
export const registerCompareSourceToBundleTool = (
server: EvidenceMcpServer,
options: ApplicationToolRegistration,
): void => {
server.registerTool(
contract.name,
toolRegistrationOptions(contract),
async (input, context) =>
runAdmittedToolOperation(
server,
options.withAdmittedAnalysis,
contract.name,
context.mcpReq.signal,
async () => {
if (input.view !== undefined) {
const retention = requireAnalysisViewRetention(
contract.name,
options,
);
if (!retention.ok)
return server.delivery.toCallToolResult(retention, contract);
}
const resolved = resolveApplicationEvidenceRequest(
input,
options.evidenceById,
);
if (!resolved.ok)
return server.delivery.toCallToolResult(resolved, contract);
const reference = await resolveSourceToBundleReference(
resolved.value.reference,
context.mcpReq.signal,
options.artifactResources,
);
if (!reference.ok)
return server.delivery.toCallToolResult(reference, contract);
const parsed = {
...reference.value,
application: resolved.value.application,
};
const result = await logToolExecution(
options.logger,
contract.name,
() => Promise.resolve(compareSourceToBundleEvidence(parsed)),
);
if (!result.ok)
return server.delivery.toCallToolResult(result, contract);
const parent = result.value.evidence;
const comparison = result.value.result;
// Stage the projection against the owned parent before committing any session facts.
const selected =
input.view === undefined
? ok(parent)
: inspectAnalysisViewValidated(
{
source: {
kind: "retained-evidence",
evidence_id: parent.evidence_id,
},
view: input.view,
},
(id) =>
id === parent.evidence_id
? parent
: options.evidenceById?.(id),
);
await setImmediate();
if (context.mcpReq.signal.aborted)
return server.delivery.toErrorToolResult(
new AnalysisCancelledError(contract.name, {
cause: context.mcpReq.signal.reason,
}),
contract.returnsTargetContent,
);
if (!selected.ok)
return server.delivery.toCallToolResult(selected, contract);
const recorded = recordEvidenceSources(
options.recordEvidence,
result.value.sources,
);
if (!recorded.ok)
return server.delivery.toCallToolResult(recorded, contract);
const saved = recordApplicationResult(
options,
parent,
comparison.summary.unknown > 0
? "source-to-bundle-comparison"
: undefined,
);
if (saved !== undefined && !saved.ok)
return server.delivery.toCallToolResult(saved, contract);
return server.delivery.toEvidenceToolResult(
selected.value,
contract,
input.view === undefined
? saved
: options.recordEvidence?.(selected.value),
);
},
),
);
};