mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
refactor(investigation): share immutable evidence reads
This commit is contained in:
1 parent
b7f8bbb49f
commit
74ed67da7f
15 files changed
+211
-82
No files matched your search
@@ -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. */
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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()],
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -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<string[], EvidenceIntegrityError> => {
|
||||
const lookup = investigationEvidenceLookup(reader);
|
||||
const visited = new Set<string>();
|
||||
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]));
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<typeof toolContract<"compare_artifacts">>,
|
||||
): 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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string[], EvidenceIntegrityError> => {
|
||||
const records = new Map(
|
||||
session
|
||||
.exportEvidenceBundle()
|
||||
.records.map((evidence) => [evidence.evidence_id, evidence]),
|
||||
);
|
||||
const visited = new Set<string>();
|
||||
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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)}`;
|
||||
|
||||
Reference in new issue
Block a user