diff --git a/src/application/binary/BinarySessionRecords.ts b/src/application/binary/BinarySessionRecords.ts index edd9e15cfb..432283b3aa 100644 --- a/src/application/binary/BinarySessionRecords.ts +++ b/src/application/binary/BinarySessionRecords.ts @@ -107,9 +107,9 @@ export abstract class BinarySessionRecords { return this.#records.exportEvidenceBundle(); } - /** Borrow a sealed bundle for complete serialization without cloning retained graphs. */ - evidenceBundleForSerialization(): EvidenceBundle { - return this.#records.evidenceBundleForSerialization(); + /** Borrow the record owner's immutable bundle for read-only workflows. */ + immutableEvidenceBundle(): EvidenceBundle { + return this.#records.immutableEvidenceBundle(); } /** Persist one provider-neutral snapshot under this session's resource owner. */ diff --git a/src/application/investigation/EvidenceLedger.test.ts b/src/application/investigation/EvidenceLedger.test.ts index 9955801948..9a61df3ad9 100644 --- a/src/application/investigation/EvidenceLedger.test.ts +++ b/src/application/investigation/EvidenceLedger.test.ts @@ -138,13 +138,13 @@ describe("evidence ledger recording", () => { expect(Reflect.set(detached, "operation", "forged")).toBe(true); expect(ledger.record(detached).ok).toBe(false); expect(ledger.forAnalysis(evidence.evidence_id)).toBe(borrowed); - const snapshot = ledger.forSerialization(); + const snapshot = ledger.immutableBundle(); expect(snapshot).toEqual(ledger.export()); expect(snapshot.records[0]).toBe(borrowed); expect(Object.isFrozen(snapshot.records)).toBe(true); ledger.clear(); expect(ledger.forAnalysis(evidence.evidence_id)).toBeUndefined(); - expect(ledger.forSerialization().records).toEqual([]); + expect(ledger.immutableBundle().records).toEqual([]); expect(snapshot.records).toEqual([evidence]); }); }); diff --git a/src/application/investigation/EvidenceLedger.ts b/src/application/investigation/EvidenceLedger.ts index f8c6535fdd..e2dfa2104d 100644 --- a/src/application/investigation/EvidenceLedger.ts +++ b/src/application/investigation/EvidenceLedger.ts @@ -187,8 +187,8 @@ export class EvidenceLedger { ); } - /** Borrow a sealed canonical bundle for serialization without copying its records. */ - forSerialization(): EvidenceBundle { + /** Borrow an authenticated immutable bundle without copying its records. */ + immutableBundle(): EvidenceBundle { return createImmutableEvidenceBundle( [...this.#records.values()], [...this.#unknownRevisions.values()], diff --git a/src/application/investigation/InvestigationEvidence.test.ts b/src/application/investigation/InvestigationEvidence.test.ts new file mode 100644 index 0000000000..571f4bfe5f --- /dev/null +++ b/src/application/investigation/InvestigationEvidence.test.ts @@ -0,0 +1,111 @@ +import { expect, it } from "vitest"; + +import { createEvidence, type Evidence } from "../../domain/evidence.js"; +import { createEvidenceBundle } from "../../domain/evidenceBundle.js"; +import { InvestigationRecords } from "./InvestigationRecords.js"; +import { + investigationEvidenceBundle, + investigationEvidenceLookup, + resolveInvestigationEvidenceClosure, +} from "./InvestigationEvidence.js"; + +const observation = (path: string, links: readonly string[] = []): Evidence => + createEvidence( + { path, sha256: "a".repeat(64), format: "elf" }, + { id: "fixture", name: "Fixture", version: "1" }, + { + operation: "observe", + parameters: {}, + result: { value: "observed" }, + evidenceLinks: [...links], + }, + ); + +it.each(["immutable", "detached"] as const)( + "reads current metadata through a %s reader while preserving earlier snapshots", + (mode) => { + const records = new InvestigationRecords(); + const first = observation("/original"); + const added = records.recordEvidence(first); + if (!added.ok) throw added.error; + const reader = + mode === "immutable" + ? records + : { + evidenceById: records.evidenceById.bind(records), + exportEvidenceBundle: records.exportEvidenceBundle.bind(records), + }; + const lookup = investigationEvidenceLookup(reader); + const snapshot = investigationEvidenceBundle(reader); + const replacement = observation("/moved"); + expect(replacement.evidence_id).toBe(first.evidence_id); + const merged = records.mergeEvidenceBundle( + createEvidenceBundle([replacement]), + ); + if (!merged.ok) throw merged.error; + expect(merged.value.metadataChanged).toBe(true); + expect(lookup(first.evidence_id)?.subject?.local_path).toBe("/moved"); + expect( + investigationEvidenceBundle(reader).records[0]?.subject?.local_path, + ).toBe("/moved"); + expect(snapshot.records[0]?.subject?.local_path).toBe("/original"); + if (mode === "immutable") { + expect(Object.isFrozen(snapshot.records[0]?.subject)).toBe(true); + } else { + const subject = snapshot.records[0]?.subject; + if (subject === undefined || subject === null) + throw new Error("Expected detached subject"); + subject.local_path = "/caller-edited"; + expect(lookup(first.evidence_id)?.subject?.local_path).toBe("/moved"); + } + records.clear(); + expect(lookup(first.evidence_id)).toBeUndefined(); + expect(investigationEvidenceBundle(reader).records).toEqual([]); + expect(snapshot.records).toHaveLength(1); + }, +); + +it("keeps a selected transitive closure distinct from unrelated records and rejects a missing dependency", () => { + const records = new InvestigationRecords(); + const leaf = observation("/leaf"); + const middle = observation("/middle", [leaf.evidence_id]); + const root = observation("/root", [middle.evidence_id, leaf.evidence_id]); + const unrelated = observation("/unrelated", [root.evidence_id]); + for (const record of [leaf, middle, root, unrelated]) { + const added = records.recordEvidence(record); + if (!added.ok) throw added.error; + } + expect( + resolveInvestigationEvidenceClosure(records, [root.evidence_id]), + ).toEqual({ + ok: true, + value: [leaf.evidence_id, middle.evidence_id, root.evidence_id].sort(), + }); + records.clear(); + const added = records.recordEvidence(root); + if (!added.ok) throw added.error; + expect( + resolveInvestigationEvidenceClosure(records, [root.evidence_id]), + ).toMatchObject({ + ok: false, + error: { _tag: "EvidenceIntegrityError" }, + }); +}); + +it("rejects a lookup that returns another semantic Evidence identity", () => { + const record = observation("/source"); + const selectedId = `ev_${"0".repeat(64)}`; + expect( + resolveInvestigationEvidenceClosure({ evidenceById: () => record }, [ + selectedId, + ]), + ).toMatchObject({ + ok: false, + error: { + _tag: "EvidenceIntegrityError", + reason: "identity_mismatch", + evidenceId: selectedId, + actual: record.evidence_id, + }, + }); +}); diff --git a/src/application/investigation/InvestigationEvidence.ts b/src/application/investigation/InvestigationEvidence.ts new file mode 100644 index 0000000000..3837d554e5 --- /dev/null +++ b/src/application/investigation/InvestigationEvidence.ts @@ -0,0 +1,51 @@ +import { + resolveEvidenceInput, + type EvidenceLookup, +} from "../EvidenceInputResolver.js"; +import type { EvidenceReader } from "./InvestigationRecordPort.js"; +import type { EvidenceBundle } from "../../domain/evidenceBundle.js"; +import { uniqueSorted } from "../../domain/canonicalOrdering.js"; +import type { EvidenceIntegrityError } from "../../domain/evidenceErrors.js"; +import { ok, type Result } from "../../domain/result.js"; + +type InvestigationLookup = Pick< + EvidenceReader, + "evidenceForAnalysis" | "evidenceById" +>; + +/** Prefer owned immutable records; detached-only readers retain their contract. */ +export const investigationEvidenceLookup = + (reader: InvestigationLookup): EvidenceLookup => + (id) => + reader.evidenceForAnalysis?.(id) ?? reader.evidenceById(id); + +/** Borrow the owner's immutable snapshot for read-only projection or delivery. */ +export const investigationEvidenceBundle = ( + reader: Pick< + EvidenceReader, + "immutableEvidenceBundle" | "exportEvidenceBundle" + >, +): EvidenceBundle => + reader.immutableEvidenceBundle?.() ?? reader.exportEvidenceBundle(); + +/** Follow exact session references without exporting unrelated records or Unknowns. */ +export const resolveInvestigationEvidenceClosure = ( + reader: InvestigationLookup, + seedIds: readonly string[], +): Result => { + const lookup = investigationEvidenceLookup(reader); + const visited = new Set(); + const pending = [...seedIds]; + while (pending.length > 0) { + const evidenceId = pending.pop(); + if (evidenceId === undefined || visited.has(evidenceId)) continue; + const evidence = resolveEvidenceInput( + { kind: "retained-evidence", evidence_id: evidenceId }, + lookup, + ); + if (!evidence.ok) return evidence; + visited.add(evidenceId); + pending.push(...evidence.value.evidence_links); + } + return ok(uniqueSorted([...visited])); +}; diff --git a/src/application/investigation/InvestigationRecordPort.ts b/src/application/investigation/InvestigationRecordPort.ts index 2ac18a207b..0ceaf21a64 100644 --- a/src/application/investigation/InvestigationRecordPort.ts +++ b/src/application/investigation/InvestigationRecordPort.ts @@ -12,14 +12,14 @@ import type { } from "../../domain/residualUnknown.js"; import type { Result } from "../../domain/result.js"; -/** Detached investigation Evidence reads without target execution or lifecycle authority. */ +/** Read-only investigation Evidence access without target execution or lifecycle authority. */ export interface EvidenceReader { hasEvidence(evidenceId: string): boolean; evidenceById(evidenceId: string): Evidence | undefined; /** Optional immutable analysis read; older adapters can keep detached reads. */ evidenceForAnalysis?(evidenceId: string): Evidence | undefined; - /** Optional immutable bundle snapshot for complete serialization without graph copies. */ - evidenceBundleForSerialization?(): EvidenceBundle; + /** Optional immutable bundle snapshot for read-only workflows and serialization. */ + immutableEvidenceBundle?(): EvidenceBundle; exportEvidenceBundle(): EvidenceBundle; } diff --git a/src/application/investigation/InvestigationRecords.ts b/src/application/investigation/InvestigationRecords.ts index ea6edb46f3..2e0e6d6a98 100644 --- a/src/application/investigation/InvestigationRecords.ts +++ b/src/application/investigation/InvestigationRecords.ts @@ -60,9 +60,9 @@ export class InvestigationRecords return this.#evidence.export(); } - /** Borrow a sealed bundle snapshot for complete JSON serialization. */ - evidenceBundleForSerialization(): EvidenceBundle { - return this.#evidence.forSerialization(); + /** Borrow an authenticated immutable bundle snapshot without copying its records. */ + immutableEvidenceBundle(): EvidenceBundle { + return this.#evidence.immutableBundle(); } /** Atomically merge a bundle and report changes for the owner's post-commit observers. */ diff --git a/src/server/createServer.ts b/src/server/createServer.ts index a84b2e7fe2..6e647fbbd1 100644 --- a/src/server/createServer.ts +++ b/src/server/createServer.ts @@ -95,6 +95,7 @@ import type { CompactInputSchemaPresentation } from "./EvidenceMcpServer.js"; import { isAbsolute } from "node:path"; import type { BinarySessionPort } from "../application/binary/BinarySessionPort.js"; +import { investigationEvidenceLookup } from "../application/investigation/InvestigationEvidence.js"; import type { BrowserObservationPort } from "../application/BrowserObservationPort.js"; import type { BrowserScenarioCapturePort } from "../application/BrowserScenarioCapturePort.js"; import type { ElectronActiveObservationPort } from "../application/javascript/ElectronActiveObservationPort.js"; @@ -1078,11 +1079,7 @@ const createSessionRecorders = ( session: BinarySessionPort | undefined, ) => ({ evidenceById: - session === undefined - ? undefined - : (evidenceId: string) => - session.evidenceForAnalysis?.(evidenceId) ?? - session.evidenceById(evidenceId), + session === undefined ? undefined : investigationEvidenceLookup(session), activeTarget: session === undefined ? undefined : () => session.activeTarget(), recordEvidence: diff --git a/src/server/promptCompletion.ts b/src/server/promptCompletion.ts index f23b30ce03..ce79439777 100644 --- a/src/server/promptCompletion.ts +++ b/src/server/promptCompletion.ts @@ -1,4 +1,3 @@ -import type { EvidenceReader } from "../application/investigation/InvestigationRecordPort.js"; import { z } from "zod"; import { uniqueSorted } from "../domain/canonicalOrdering.js"; @@ -11,6 +10,8 @@ import { addressedValueSchema } from "../domain/native/nativeInventory.js"; import { currentMcpRequestSignal } from "./EvidenceMcpServerCore.js"; import { parseArtifactInventoryEvidence } from "../domain/artifactInventoryEvidence.js"; import { EvidenceIntegrityError } from "../domain/evidenceErrors.js"; +import type { Evidence } from "../domain/evidence.js"; +import { investigationEvidenceBundle } from "../application/investigation/InvestigationEvidence.js"; const documentListSchema = z.array(z.string().min(1)); const procedureInventorySchema = z.array( @@ -171,18 +172,14 @@ const evidenceCandidates = ( ): readonly string[] => { if (session === undefined) return []; const candidates: string[] = []; - for (const evidence of session.exportEvidenceBundle().records) + for (const evidence of investigationEvidenceBundle(session).records) candidates.push(...evidenceValues(kind, evidence)); return candidates; }; -type LedgerEvidence = ReturnType< - EvidenceReader["exportEvidenceBundle"] ->["records"][number]; - const evidenceValues = ( kind: "evidence" | "capture" | "manifest" | "occurrence", - evidence: LedgerEvidence, + evidence: Evidence, ): readonly string[] => { if (kind === "evidence") return [evidence.evidence_id]; if (kind === "capture") @@ -210,7 +207,7 @@ const artifactInventoryValues = ( } }; -const isProcessCaptureEvidence = (evidence: LedgerEvidence): boolean => { +const isProcessCaptureEvidence = (evidence: Evidence): boolean => { const parsed = parseProcessCaptureEvidence(evidence); if (parsed.ok) return true; switch (parsed.error.kind) { diff --git a/src/server/registerArtifactComparisonTool.ts b/src/server/registerArtifactComparisonTool.ts index dcb8501af1..39fc1312e9 100644 --- a/src/server/registerArtifactComparisonTool.ts +++ b/src/server/registerArtifactComparisonTool.ts @@ -8,6 +8,7 @@ import { createEvidence } from "../domain/evidence.js"; import { readEvidence } from "../domain/evidenceValidation.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; import { resolveArtifactComparisonEvidence } from "../application/artifacts/ArtifactComparisonEvidence.js"; +import { investigationEvidenceLookup } from "../application/investigation/InvestigationEvidence.js"; import type { RecordUnknownInput } from "../domain/residualUnknown.js"; import { recordDerivedEvidence } from "./recordDerivedEvidence.js"; import { runDerivedOperation } from "./runDerivedOperation.js"; @@ -23,9 +24,7 @@ export const registerArtifactComparisonTool = ( contract: ReturnType>, ): void => { const admission = withAdmittedAnalysis({ kind: "session", session }); - const evidenceById = (evidenceId: string) => - session.evidenceForAnalysis?.(evidenceId) ?? - session.evidenceById(evidenceId); + const evidenceById = investigationEvidenceLookup(session); server.registerTool( contract.name, toolRegistrationOptions(contract), diff --git a/src/server/registerFunctionComparisonTool.ts b/src/server/registerFunctionComparisonTool.ts index eabd80f2d4..1f48465d66 100644 --- a/src/server/registerFunctionComparisonTool.ts +++ b/src/server/registerFunctionComparisonTool.ts @@ -17,6 +17,7 @@ import { runAdmittedToolOperation } from "./admittedToolOperation.js"; import { compareFunctions } from "../domain/functionComparison.js"; import { uniqueSorted } from "../domain/canonicalOrdering.js"; +import { investigationEvidenceLookup } from "../application/investigation/InvestigationEvidence.js"; /** Register explicit Evidence-backed function comparison. */ export const registerFunctionComparisonTool = ( server: EvidenceMcpServer, @@ -36,8 +37,7 @@ export const registerFunctionComparisonTool = ( async () => { const resolved = resolvePairedEvidenceRequest( input, - (id) => - session.evidenceForAnalysis?.(id) ?? session.evidenceById(id), + investigationEvidenceLookup(session), ); if (!resolved.ok) return server.delivery.toCallToolResult(resolved, contract); diff --git a/src/server/registerInvestigationTools.ts b/src/server/registerInvestigationTools.ts index 0329d991f7..fe1ab7b297 100644 --- a/src/server/registerInvestigationTools.ts +++ b/src/server/registerInvestigationTools.ts @@ -1,3 +1,7 @@ +import { + investigationEvidenceBundle, + resolveInvestigationEvidenceClosure, +} from "../application/investigation/InvestigationEvidence.js"; import type { EvidenceMcpServer } from "./EvidenceMcpServer.js"; import type { BinarySessionPort } from "../application/binary/BinarySessionPort.js"; @@ -9,7 +13,6 @@ import { jsonValueSchema } from "../domain/jsonValue.js"; import { correlateStaticAndRuntime } from "../domain/staticRuntimeCorrelation.js"; import { comparisonClosure, - evidenceClosure, isIncomplete, recordWorkflowEvidence, } from "./registerInvestigationTools/helpers.js"; @@ -75,7 +78,10 @@ const registerChangedBehavior = ( if (!computed.ok) return server.delivery.toCallToolResult(computed, contract); const { result, comparisonEvidenceIds } = computed.value; - const closure = evidenceClosure(session, result.evidence_links); + const closure = resolveInvestigationEvidenceClosure( + session, + result.evidence_links, + ); if (!closure.ok) return server.delivery.toCallToolResult(closure, contract); const links = closure.value; @@ -136,7 +142,7 @@ const registerCallPath = ( contract.name, context.mcpReq.signal, async () => { - const closure = evidenceClosure( + const closure = resolveInvestigationEvidenceClosure( session, input.functions.map(({ evidence_id }) => evidence_id), ); @@ -209,7 +215,7 @@ const registerStaticRuntime = ( contract.name, context.mcpReq.signal, async () => { - const closure = evidenceClosure( + const closure = resolveInvestigationEvidenceClosure( session, comparisonClosure([ ...input.static_comparisons, @@ -283,7 +289,7 @@ const registerReconstruction = ( contract.name, context.mcpReq.signal, async () => { - const owned = session.exportEvidenceBundle(); + const owned = investigationEvidenceBundle(session); const computed = await runDerivedOperation( context, contract.name, @@ -292,7 +298,10 @@ const registerReconstruction = ( if (!computed.ok) return server.delivery.toCallToolResult(computed, contract); const result = computed.value; - const closure = evidenceClosure(session, result.evidence_links); + const closure = resolveInvestigationEvidenceClosure( + session, + result.evidence_links, + ); if (!closure.ok) return server.delivery.toCallToolResult(closure, contract); const links = closure.value; diff --git a/src/server/registerInvestigationTools/helpers.ts b/src/server/registerInvestigationTools/helpers.ts index df3ac3efd4..9d1a8ea0d2 100644 --- a/src/server/registerInvestigationTools/helpers.ts +++ b/src/server/registerInvestigationTools/helpers.ts @@ -1,56 +1,23 @@ import type { BinarySessionPort } from "../../application/binary/BinarySessionPort.js"; import type { Evidence } from "../../domain/evidence.js"; -import { EvidenceIntegrityError } from "../../domain/evidenceErrors.js"; import type { AnalysisError } from "../../domain/analysisErrorBase.js"; -import { err, ok, type Result } from "../../domain/result.js"; +import type { Result } from "../../domain/result.js"; import { recordDerivedEvidence } from "../recordDerivedEvidence.js"; import { workflowUnknownInput } from "../../application/investigation/WorkflowUnknown.js"; import type { WorkflowUnknownInput } from "./types.js"; -import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js"; +import { uniqueSorted } from "../../domain/canonicalOrdering.js"; export const isIncomplete = (status: string): boolean => status === "unknown" || status === "truncated"; export const comparisonClosure = (comparisons: readonly Evidence[]): string[] => - uniqueIds( + uniqueSorted( comparisons.flatMap((evidence) => [ evidence.evidence_id, ...evidence.evidence_links, ]), ); -const uniqueIds = (ids: readonly string[]): string[] => - [...new Set(ids)].sort((left, right) => - compareUnicodeCodePoints(left, right), - ); - -export const evidenceClosure = ( - session: BinarySessionPort, - seedIds: readonly string[], -): Result => { - const records = new Map( - session - .exportEvidenceBundle() - .records.map((evidence) => [evidence.evidence_id, evidence]), - ); - const visited = new Set(); - const pending = [...seedIds]; - while (pending.length > 0) { - const evidenceId = pending.pop(); - if (evidenceId === undefined || visited.has(evidenceId)) continue; - const evidence = records.get(evidenceId); - if (evidence === undefined) - return err( - new EvidenceIntegrityError( - "Investigation input has a dangling Evidence link", - ), - ); - visited.add(evidenceId); - pending.push(...evidence.evidence_links); - } - return ok(uniqueIds([...visited])); -}; - export const recordWorkflowEvidence = ( ...[session, evidence, unresolved, input]: readonly [ session: BinarySessionPort, diff --git a/src/server/registerNativeFunctionMatchingTool.ts b/src/server/registerNativeFunctionMatchingTool.ts index 128f885688..73c8bd6346 100644 --- a/src/server/registerNativeFunctionMatchingTool.ts +++ b/src/server/registerNativeFunctionMatchingTool.ts @@ -8,6 +8,7 @@ import { createEvidenceToolHandler } from "./contractToolHandler.js"; import { toolRegistrationOptions } from "./toolRegistrationOptions.js"; import { recordSessionEvidenceSources } from "./sessionEvidence.js"; import { ok } from "../domain/result.js"; +import { investigationEvidenceLookup } from "../application/investigation/InvestigationEvidence.js"; import type { Logger } from "pino"; /** Bind one named contract to the same retained-Evidence workflow used by CLI. */ @@ -17,8 +18,7 @@ export const registerNativeFunctionMatchingTool = ( logger: Logger, ): void => { const contract = toolContract("match_native_functions"); - const evidenceById = (id: string) => - session.evidenceForAnalysis?.(id) ?? session.evidenceById(id); + const evidenceById = investigationEvidenceLookup(session); const recordEvidence: BinarySessionPort["recordEvidence"] = (evidence) => session.recordEvidence(evidence); const handler = createEvidenceToolHandler( diff --git a/src/server/registerSessionRecordTools.ts b/src/server/registerSessionRecordTools.ts index 2b92d67e96..295d31ebbb 100644 --- a/src/server/registerSessionRecordTools.ts +++ b/src/server/registerSessionRecordTools.ts @@ -11,6 +11,7 @@ import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.j import { err, ok } from "../domain/result.js"; import { AnalysisCancelledError } from "../domain/analysisErrorCore.js"; import { inspectEvidenceBundle } from "../application/investigation/InspectEvidenceBundle.js"; +import { investigationEvidenceBundle } from "../application/investigation/InvestigationEvidence.js"; import { toolRegistrationOptions } from "./toolRegistrationOptions.js"; import { withAdmittedAnalysis } from "./analysisAdmission.js"; import { runAdmittedToolOperation } from "./admittedToolOperation.js"; @@ -49,7 +50,7 @@ const registerExportEvidenceTool = ({ exportContract.name, toolRegistrationOptions(exportContract), async (input, context) => { - const bundle = bundleForSerialization(session); + const bundle = investigationEvidenceBundle(session); const written = await writeEvidenceBundle( bundle, input.path, @@ -82,7 +83,7 @@ const registerSnapshotEvidenceTool = ({ toolRegistrationOptions(snapshotContract), (input) => server.delivery.toCallToolResult( - inspectEvidenceBundle(bundleForSerialization(session), input), + inspectEvidenceBundle(investigationEvidenceBundle(session), input), snapshotContract, ), ); @@ -116,7 +117,7 @@ const registerImportEvidenceTool = ({ if (!loaded.ok) return server.delivery.toCallToolResult(loaded, importContract); const retainedUnknownRevisions = new Set( - bundleForSerialization(session).unknowns.map((unknown) => + investigationEvidenceBundle(session).unknowns.map((unknown) => unknownRevisionKey(unknown), ), ); @@ -136,7 +137,7 @@ const registerImportEvidenceTool = ({ unknownRevisionKey(unknown), ), ).length, - total: bundleForSerialization(session).records.length, + total: investigationEvidenceBundle(session).records.length, }), importContract, ) @@ -146,9 +147,6 @@ const registerImportEvidenceTool = ({ ); }; -const bundleForSerialization = (session: BinarySessionPort): EvidenceBundle => - session.evidenceBundleForSerialization?.() ?? session.exportEvidenceBundle(); - const unknownRevisionKey = ( unknown: EvidenceBundle["unknowns"][number], ): string => `${unknown.unknown_id}:${String(unknown.revision)}`;