perf(snapshot): restore large legacy caches within live memory bounds

Stream legacy snapshot cache rows independently, authenticate complete Evidence once, and restore exact immutable payloads without assembling duplicate full graphs. Preserve the existing file format, provenance, resource admission, cancellation and cache export isolation.

Verified through real Linux SDK MCP import of the original 83 MB Ghidra snapshot and full 41 MB bundle hash, actual Windows CLI/MCP PE recovery and the installed Windows large-file decoder boundary, focused tests and static checks.

Fixes #1680. Refs #1065.
This commit is contained in:
Ysopwnable authored and GitHub committed 2026-10-11 13:59:40 +08:00
1 parent 3f4ea32189
commit 092de8f4b3
9 files changed
+997 -302

No files matched your search

+1 -1
View File
@@ -451,7 +451,7 @@ const prepareSnapshot = async (options: {
const { path, snapshotPath } = options;
if (snapshotPath === undefined || !(await fileExists(snapshotPath)))
return ok({});
const loaded = await readAnalysisSnapshot(snapshotPath);
const loaded = await readAnalysisSnapshot(snapshotPath, options.signal);
if (!loaded.ok) return loaded;
const target = await parseBinaryTarget(path, {
signal: options.signal,
+44 -5
View File
@@ -16,7 +16,11 @@ import {
AnalysisResourceConstraintError,
} from "../domain/analysisErrorCore.js";
import { err, ok, type Result } from "../domain/result.js";
import { readJsonInputFile } from "./JsonInputFile.js";
import {
readJsonInputFile,
readJsonObjectFile,
type JsonObjectMemberReader,
} from "./JsonInputFile.js";
import { NonRegularFileReadError } from "../filesystem/RegularFile.js";
/** Request control and its owning operation for an interruptible atomic write. */
@@ -33,13 +37,47 @@ export const readJsonFile = async (
path: string,
): Promise<
Result<unknown, EvidenceFileError | AnalysisResourceConstraintError>
> =>
readJsonFileInput(path, (selected) =>
readJsonInputFile(selected, "read_evidence_file"),
);
/** Read selected JSON members through the same regular-file error boundary. */
export const readJsonObjectMembersFile = (
path: string,
scans: readonly ReadonlyMap<string, JsonObjectMemberReader>[],
signal?: AbortSignal,
): Promise<
Result<
void,
EvidenceFileError | AnalysisResourceConstraintError | AnalysisCancelledError
>
> =>
readJsonFileInput(
path,
(selected) =>
readJsonObjectFile(selected, "read_evidence_file", scans, signal),
signal,
);
const readJsonFileInput = async <Value>(
path: string,
read: (
selected: string,
) => Promise<
Result<
Value,
| import("../domain/analysisErrorCore.js").AnalysisInputError
| AnalysisResourceConstraintError
>
>,
signal?: AbortSignal,
): Promise<
Result<Value, EvidenceFileError | AnalysisResourceConstraintError>
> => {
const requestedPath = resolve(path);
try {
const decoded = await readJsonInputFile(
requestedPath,
"read_evidence_file",
);
const decoded = await read(requestedPath);
if (!decoded.ok) {
if (decoded.error instanceof AnalysisResourceConstraintError)
return err(decoded.error);
@@ -57,6 +95,7 @@ export const readJsonFile = async (
return ok(decoded.value);
} catch (cause: unknown) {
if (cause instanceof AnalysisResourceConstraintError) return err(cause);
if (signal?.aborted === true) throw cause;
return err(
new EvidenceFileError(
"read",
+270 -56
View File
@@ -1,4 +1,5 @@
import { constants } from "node:buffer";
import { createHash } from "node:crypto";
import type { FileHandle } from "node:fs/promises";
import { pipeline } from "node:stream/promises";
import { getHeapStatistics } from "node:v8";
@@ -7,6 +8,7 @@ import { asStream, none } from "stream-chain";
import fun from "stream-chain/fun.js";
import parser from "stream-json/parser.js";
import streamValues from "stream-json/streamers/stream-values.js";
import streamArray from "stream-json/streamers/stream-array.js";
import { RegularFileChangedError } from "../filesystem/RegularFile.js";
import { withRegularFile } from "./RegularFileRead.js";
@@ -39,6 +41,196 @@ const nativeParseFits = (inputBytes: number): boolean =>
inputBytes * 72 + READ_CHUNK_BYTES <=
getHeapStatistics().total_available_size;
/** One selected root member; repeated keys begin again with last-value semantics. */
export interface JsonObjectMemberReader {
readonly mode: "value" | "array";
readonly begin: () => void;
readonly accept: (value: unknown) => void | Promise<void>;
readonly reject: (cause: unknown) => void;
}
/** Scan selected members without retaining skipped subtrees or prior array items. */
export const readJsonObjectFile = (
path: string,
operation: string,
scans: readonly ReadonlyMap<string, JsonObjectMemberReader>[],
signal?: AbortSignal,
): Promise<Result<void, JsonInputFileFailure>> =>
withRegularFile(
path,
async (handle, stats) => {
let previousDigest: string | undefined;
try {
const native = await readNativeJson(
handle,
stats.size,
path,
operation,
signal,
);
if (native !== undefined) {
if (!native.ok) return native;
const value = native.value;
if (
typeof value !== "object" ||
value === null ||
Array.isArray(value)
)
throw new TypeError("JSON document root must be an object");
for (const members of scans)
for (const [key, member] of members) {
if (!Object.hasOwn(value, key)) continue;
signal?.throwIfAborted();
member.begin();
const selected: unknown = Reflect.get(value, key);
if (member.mode === "array" && !Array.isArray(selected)) {
member.reject(
new TypeError(`JSON member ${key} must be an array`),
);
continue;
}
for (const item of member.mode === "array" &&
Array.isArray(selected)
? selected
: [selected]) {
signal?.throwIfAborted();
try {
await member.accept(item);
} catch (cause: unknown) {
member.reject(cause);
}
}
}
return ok(undefined);
}
for (const members of scans) {
const hash = createHash("sha256");
let rootObject = false;
let depth = 0;
let key: string | undefined;
let consume:
| ((token: parser.Token) => typeof none | Promise<typeof none>)
| undefined;
await pipeline(
decodedChunks(handle, stats.size, path, signal, (bytes) => {
hash.update(bytes);
}),
asStream(
fun(
parser({
jsonStreaming: false,
streamValues: false,
packKeys: false,
packStrings: false,
packNumbers: false,
}),
packJsonScalars(operation, stats.size, path),
(token: parser.Token): typeof none | Promise<typeof none> => {
const opens =
token.name === "startObject" || token.name === "startArray";
const closes =
token.name === "endObject" || token.name === "endArray";
if (depth === 0) {
rootObject = token.name === "startObject";
} else if (
rootObject &&
depth === 1 &&
token.name === "keyValue"
) {
key = token.value;
} else if (rootObject && depth === 1 && !closes) {
const member =
key === undefined ? undefined : members.get(key);
consume = undefined;
if (member !== undefined) {
member.begin();
if (
member.mode === "array" &&
token.name !== "startArray"
) {
member.reject(
new TypeError(`JSON member ${key} must be an array`),
);
} else {
consume = memberConsumer(
member,
operation,
stats.size,
path,
);
}
}
key = undefined;
}
const selected = consume;
if (opens) depth += 1;
if (closes) depth -= 1;
if (rootObject && depth === 1 && token.name !== "keyValue")
consume = undefined;
if (selected === undefined) return none;
return selected(token);
},
),
),
signal === undefined ? {} : { signal },
);
if (!rootObject)
throw new TypeError("JSON document root must be an object");
const digest = hash.digest("hex");
if (previousDigest !== undefined && digest !== previousDigest)
throw new RegularFileChangedError(path);
previousDigest = digest;
}
return ok(undefined);
} catch (cause: unknown) {
const failure = jsonReadFailure(cause, operation, stats.size, path);
if (failure !== undefined) return err(failure);
throw cause;
}
},
{ signal },
);
const memberConsumer = (
member: JsonObjectMemberReader,
operation: string,
size: number,
path: string,
) => {
let admit = admitJsonAssembly(operation, size, path);
const streamer: parser.TokenConsumer<{ readonly value: unknown }> =
member.mode === "array" ? streamArray() : streamValues();
const consume = fun(
(token: parser.Token) => admit(token),
streamer,
(row: unknown): typeof none | Promise<typeof none> => {
if (typeof row !== "object" || row === null || !("value" in row))
throw new Error("Unexpected JSON member parser result");
// The preceding array item has left the assembler; retain only what the
// consumer owns and use live headroom when admitting the next item.
admit = admitJsonAssembly(operation, size, path);
try {
const accepted = member.accept(row.value);
if (accepted instanceof Promise)
return accepted.then(
() => none,
(cause: unknown) => {
member.reject(cause);
return none;
},
);
} catch (cause: unknown) {
member.reject(cause);
}
return none;
},
);
return (token: parser.Token): typeof none | Promise<typeof none> => {
const consumed = consume(token);
return consumed instanceof Promise ? consumed.then(() => none) : none;
};
};
/** Parse strict UTF-8 JSON without requiring a whole-document string. */
export const readJsonInputFile = (
path: string,
@@ -51,22 +243,14 @@ export const readJsonInputFile = (
let found = false;
let value: unknown;
try {
const prefix = nativeParseFits(stats.size)
? await readPrefix(handle, stats.size + 1, signal)
: undefined;
if (prefix !== undefined) {
if (!prefix.complete || prefix.bytes.length !== stats.size)
throw new RegularFileChangedError(path);
const parsed = parseUtf8Json(
prefix.bytes,
operation,
path,
"json-file-input",
);
return parsed.ok
? ok(parsed.value)
: err(invalidJson(operation, parsed.error, parsed.cause));
}
const native = await readNativeJson(
handle,
stats.size,
path,
operation,
signal,
);
if (native !== undefined) return native;
await pipeline(
decodedChunks(handle, stats.size, path, signal),
// Fuse synchronous token stages so only file chunks cross the async
@@ -105,48 +289,55 @@ export const readJsonInputFile = (
? ok(value)
: err(invalidJson(operation, "JSON document is empty"));
} catch (cause: unknown) {
if (cause instanceof AnalysisResourceConstraintError) return err(cause);
if (
cause instanceof TypeError &&
"code" in cause &&
cause.code === "ERR_ENCODING_INVALID_ENCODED_DATA"
)
return err(
invalidJson(operation, "JSON input is not valid UTF-8", cause),
);
if (
cause instanceof SyntaxError ||
(cause instanceof Error && cause.message.startsWith("Parser "))
)
return err(invalidJson(operation, cause.message, cause));
if (
cause instanceof Error &&
((cause instanceof RangeError &&
cause.message === "Invalid string length") ||
("code" in cause && cause.code === "ERR_STRING_TOO_LONG"))
)
return err(
new AnalysisResourceConstraintError(
operation,
"memory",
"An individual JSON string or number token exceeds the runtime string limit",
{
input_path: path,
input_file_bytes: stats.size,
max_string_code_units: constants.MAX_STRING_LENGTH,
},
{
cause,
remediationAction: JSON_INPUT_RESOURCE_REMEDIATION,
},
),
);
const failure = jsonReadFailure(cause, operation, stats.size, path);
if (failure !== undefined) return err(failure);
throw cause;
}
},
{ signal },
);
const jsonReadFailure = (
cause: unknown,
operation: string,
size: number,
path: string,
): JsonInputFileFailure | undefined => {
if (cause instanceof AnalysisResourceConstraintError) return cause;
if (
cause instanceof TypeError &&
"code" in cause &&
cause.code === "ERR_ENCODING_INVALID_ENCODED_DATA"
)
return invalidJson(operation, "JSON input is not valid UTF-8", cause);
if (
cause instanceof SyntaxError ||
(cause instanceof Error && cause.message.startsWith("Parser "))
)
return invalidJson(operation, cause.message, cause);
if (
cause instanceof Error &&
((cause instanceof RangeError &&
cause.message === "Invalid string length") ||
("code" in cause && cause.code === "ERR_STRING_TOO_LONG"))
)
return new AnalysisResourceConstraintError(
operation,
"memory",
"An individual JSON string or number token exceeds the runtime string limit",
{
input_path: path,
input_file_bytes: size,
max_string_code_units: constants.MAX_STRING_LENGTH,
},
{
cause,
remediationAction: JSON_INPUT_RESOURCE_REMEDIATION,
},
);
return undefined;
};
const packJsonScalars = (
operation: string,
inputFileBytes: number,
@@ -304,6 +495,7 @@ async function* decodedChunks(
admittedBytes: number,
path: string,
signal: AbortSignal | undefined,
observe?: (bytes: Uint8Array) => void,
): AsyncGenerator<string> {
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
let leading = true;
@@ -322,15 +514,15 @@ async function* decodedChunks(
bytes,
0,
Math.min(bytes.length, admittedBytes - observedBytes + 1),
null,
observedBytes,
);
signal?.throwIfAborted();
if (read.bytesRead === 0) break;
observedBytes += read.bytesRead;
if (observedBytes > admittedBytes) throw new RegularFileChangedError(path);
yield checked(
decoder.decode(bytes.subarray(0, read.bytesRead), { stream: true }),
);
const chunk = bytes.subarray(0, read.bytesRead);
observe?.(chunk);
yield checked(decoder.decode(chunk, { stream: true }));
}
if (observedBytes !== admittedBytes) throw new RegularFileChangedError(path);
yield checked(decoder.decode());
@@ -362,6 +554,28 @@ const readPrefix = async (
return { bytes, complete: false };
};
const readNativeJson = async (
handle: FileHandle,
size: number,
path: string,
operation: string,
signal?: AbortSignal,
): Promise<Result<unknown, JsonInputFileFailure> | undefined> => {
if (!nativeParseFits(size)) return undefined;
const prefix = await readPrefix(handle, size + 1, signal);
if (!prefix.complete || prefix.bytes.length !== size)
throw new RegularFileChangedError(path);
const parsed = parseUtf8Json(
prefix.bytes,
operation,
path,
"json-file-input",
);
return parsed.ok
? ok(parsed.value)
: err(invalidJson(operation, parsed.error, parsed.cause));
};
const invalidJson = (
operation: string,
message: string,
@@ -18,6 +18,7 @@ import {
snapshotMatchesTarget,
snapshotTarget,
parseAnalysisSnapshot,
immutableAnalysisSnapshot,
type AnalysisSnapshot,
type AnalysisSnapshotEntry,
type AnalysisSnapshotWorkflowEntry,
@@ -107,6 +108,7 @@ export class AnalysisSnapshotCache {
/** Merge already-validated entries and return the new-entry count. */
stage(snapshot: AnalysisSnapshot): number {
snapshot = immutableAnalysisSnapshot(snapshot);
if (
(this.#target !== undefined &&
JSON.stringify(this.#target) !== JSON.stringify(snapshot.target)) ||
@@ -123,12 +125,12 @@ export class AnalysisSnapshotCache {
for (const entry of snapshot.entries) {
if (!this.#entries.has(entry.query_id) && !this.#hasCapacity()) continue;
if (!this.#entries.has(entry.query_id)) imported += 1;
this.#entries.set(entry.query_id, structuredClone(entry));
this.#entries.set(entry.query_id, entry);
}
for (const entry of snapshot.workflow_entries) {
if (!this.#workflowEntries.has(entry.query_id) && !this.#hasCapacity())
continue;
this.#workflowEntries.set(entry.query_id, structuredClone(entry));
this.#workflowEntries.set(entry.query_id, entry);
}
return imported;
}
@@ -186,7 +188,7 @@ export class AnalysisSnapshotCache {
): Result<number, AnalysisError> {
let validated: AnalysisSnapshot;
try {
validated = parseAnalysisSnapshot(snapshot);
validated = immutableAnalysisSnapshot(snapshot);
} catch (cause: unknown) {
return err(
new EvidenceIntegrityError("Analysis snapshot validation failed", {
@@ -1,5 +1,9 @@
import { setImmediate } from "node:timers/promises";
import {
parseAnalysisSnapshot,
prepareSnapshotEntry,
parsePreparedAnalysisSnapshot,
type PreparedSnapshotEntry,
serializeAnalysisSnapshot,
type AnalysisSnapshot,
} from "../../domain/analysisSnapshot.js";
@@ -9,27 +13,106 @@ import {
} from "../../domain/evidenceErrors.js";
import { err, ok, type Result } from "../../domain/result.js";
import type { AnalysisResourceConstraintError } from "../../domain/analysisErrorCore.js";
import { readJsonFile, writeTextFile } from "../JsonFiles.js";
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
import { readJsonObjectMembersFile, writeTextFile } from "../JsonFiles.js";
import type { JsonObjectMemberReader } from "../JsonInputFile.js";
type SnapshotFailure = EvidenceFileError | EvidenceIntegrityError;
/** Read and validate an analysis snapshot from the caller's path. */
export const readAnalysisSnapshot = async (
path: string,
signal?: AbortSignal,
): Promise<
Result<AnalysisSnapshot, SnapshotFailure | AnalysisResourceConstraintError>
Result<
AnalysisSnapshot,
SnapshotFailure | AnalysisResourceConstraintError | AnalysisCancelledError
>
> => {
const loaded = await readJsonFile(path);
if (!loaded.ok) return loaded;
const entries = snapshotEntryReader("provider");
const workflows = snapshotEntryReader("workflow");
const header: Record<string, unknown> = {};
const metadata = new Map<string, JsonObjectMemberReader>(
["target", "binding", "evidence_bundle"].map((name) => [
name,
{
mode: "value",
begin: () => {
delete header[name];
},
accept: (value) => {
header[name] = value;
},
reject: (cause) => {
throw cause;
},
},
]),
);
try {
return ok(parseAnalysisSnapshot(loaded.value));
const loaded = await readJsonObjectMembersFile(
path,
[
new Map([
["entries", entries.reader],
["workflow_entries", workflows.reader],
]),
metadata,
],
signal,
);
if (!loaded.ok) return loaded;
signal?.throwIfAborted();
const snapshot = parsePreparedAnalysisSnapshot(
header,
entries.finish(),
workflows.finish(),
);
// Deliver queued cancellation before handing a complete cache to the session.
await setImmediate();
signal?.throwIfAborted();
return ok(snapshot);
} catch (cause: unknown) {
if (signal?.aborted === true)
return err(new AnalysisCancelledError("read_evidence_file", { cause }));
return err(
new EvidenceIntegrityError(snapshotValidationMessage(cause), { cause }),
);
}
};
const snapshotEntryReader = (kind: "provider" | "workflow") => {
let seen = false;
let entries: PreparedSnapshotEntry[] = [];
let failure: { readonly cause: unknown } | undefined;
const reader: JsonObjectMemberReader = {
mode: "array",
begin: () => {
seen = true;
entries = [];
failure = undefined;
},
accept: (value) => {
if (failure === undefined)
entries.push(prepareSnapshotEntry(value, kind));
},
reject: (cause) => {
failure ??= { cause };
},
};
return {
reader,
finish: (): PreparedSnapshotEntry[] => {
if (!seen)
throw new TypeError(
`Analysis snapshot ${kind === "provider" ? "entries" : "workflow_entries"} is missing`,
);
if (failure !== undefined) throw failure.cause;
return entries;
},
};
};
/** Atomically write a deterministic analysis snapshot to the caller's path. */
export const writeAnalysisSnapshot = async (
snapshot: AnalysisSnapshot,
+340 -199
View File
@@ -3,7 +3,10 @@ import { createHash } from "node:crypto";
import canonicalize from "canonicalize";
import { z } from "zod";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
import {
compareUnicodeCodePoints,
compositeKey,
} from "./unicodeCodePointOrder.js";
import {
analysisProfileSchema,
@@ -19,6 +22,7 @@ import {
evidenceBundleForTarget,
evidenceBundleSchema,
parseEvidenceBundle,
parseImmutableEvidenceBundle,
} from "./evidenceBundle.js";
import {
evidenceLocationSchema,
@@ -33,6 +37,8 @@ import {
} from "./jsonValue.js";
import { digestSchema } from "./../domain/digests.js";
import { prefixedDigestSchema } from "./../domain/digests.js";
import { canonicalJsonDigestSteps } from "./canonicalJsonDigestSteps.js";
import { freezeJsonSnapshot } from "./immutableJson.js";
const architectureSchema = z.enum(BINARY_ARCHITECTURES);
const formatSchema = z.enum([
@@ -136,6 +142,146 @@ export type AnalysisSnapshotWorkflowEntry = z.infer<typeof workflowEntrySchema>;
export type AnalysisSnapshotTarget = z.infer<typeof targetSchema>;
export type AnalysisSnapshotBinding = z.infer<typeof bindingSchema>;
// These handles only represent entries parsed in this realm. File readers can
// release their payloads while retaining an authenticated comparison operand.
const preparedEntryBrand = Symbol("prepared-snapshot-entry");
export interface PreparedSnapshotEntry {
readonly [preparedEntryBrand]: true;
}
type EntryMetadata = Omit<AnalysisSnapshotEntry, "execution"> & {
readonly execution: Omit<
AnalysisSnapshotEntry["execution"],
"result" | "raw_result"
>;
};
type WorkflowMetadata = Omit<AnalysisSnapshotWorkflowEntry, "execution"> & {
readonly execution: Omit<
AnalysisSnapshotWorkflowEntry["execution"],
"result" | "raw_result"
>;
};
type PreparedEntry =
| {
readonly kind: "provider";
readonly entry: EntryMetadata;
readonly digest: string;
}
| {
readonly kind: "workflow";
readonly entry: WorkflowMetadata;
readonly digest: string;
};
const preparedEntries = new WeakMap<PreparedSnapshotEntry, PreparedEntry>();
/** Capture a cache entry without retaining a second copy of its full payload. */
export const prepareSnapshotEntry = (
input: unknown,
kind: "provider" | "workflow",
): PreparedSnapshotEntry => {
const handle: PreparedSnapshotEntry = Object.freeze({
[preparedEntryBrand]: true as const,
});
if (kind === "provider") {
const entry = entrySchema.parse(input);
const { result, raw_result, ...execution } = entry.execution;
preparedEntries.set(handle, {
kind,
entry: { ...entry, execution },
digest: snapshotContentDigest(result, raw_result, execution),
});
} else {
const entry = workflowEntrySchema.parse(input);
const { result, raw_result, ...execution } = entry.execution;
preparedEntries.set(handle, {
kind,
entry: { ...entry, execution },
digest: snapshotContentDigest(result, raw_result, execution),
});
}
return handle;
};
const snapshotFileHeaderSchema = z.object({
target: targetSchema,
binding: bindingSchema,
evidence_bundle: z.unknown(),
});
/** Reconstruct file entries only from fully authenticated matching Evidence. */
export const parsePreparedAnalysisSnapshot = (
input: unknown,
entries: readonly PreparedSnapshotEntry[],
workflowEntries: readonly PreparedSnapshotEntry[],
): AnalysisSnapshot => {
const header = snapshotFileHeaderSchema.parse(input);
const bundle = parseImmutableEvidenceBundle(header.evidence_bundle);
const providerRows = entries.map((handle) => {
const row = preparedEntries.get(handle);
if (row?.kind !== "provider")
throw new TypeError(
"Analysis snapshot entry was not parsed in this realm",
);
return row;
});
const workflowRows = workflowEntries.map((handle) => {
const row = preparedEntries.get(handle);
if (row?.kind !== "workflow")
throw new TypeError(
"Analysis snapshot workflow entry was not parsed in this realm",
);
return row;
});
const restored = sealSnapshot(
restoreSnapshot(header, bundle, providerRows, workflowRows),
);
for (const handle of [...entries, ...workflowEntries])
preparedEntries.delete(handle);
return restored;
};
const contentDigest = (value: JsonValue): string => {
const steps = canonicalJsonDigestSteps(value);
let step = steps.next();
while (!step.done) step = steps.next();
return step.value;
};
const snapshotContentDigest = (
result: JsonValue,
rawResult: JsonValue | null,
execution: Pick<
AnalysisSnapshotEntry["execution"],
"limitations" | "locations" | "subject"
>,
): string => {
const metadata = jsonObjectSchema.parse({
limitations: execution.limitations,
locations: execution.locations,
subject:
execution.subject === null
? null
: {
local_path: execution.subject.path,
sha256: execution.subject.sha256,
format: execution.subject.format,
architecture: execution.subject.architecture,
},
});
return contentDigest({
...metadata,
normalized_result: result,
raw_result: rawResult,
});
};
const evidenceContentDigest = (evidence: Evidence): string => {
return contentDigest({
...jsonObjectSchema.parse(evidenceContentMetadata(evidence)),
normalized_result: evidence.normalized_result,
raw_result: evidence.raw_result,
});
};
interface SnapshotExecution {
readonly result: JsonValue;
readonly rawResult: JsonValue | null;
@@ -305,22 +451,10 @@ const evidenceMatchesEntry = (
snapshot: Pick<AnalysisSnapshot, "target" | "binding">,
): boolean =>
isCorrespondingEvidence(evidence, snapshot) &&
canonicalJson({
query: evidenceQueryKey(evidence),
normalized_result: evidence.normalized_result,
raw_result: evidence.raw_result,
limitations: evidence.limitations,
locations: evidence.locations,
subject:
evidence.subject === null
? null
: {
local_path: evidence.subject.local_path,
sha256: evidence.subject.digest.sha256,
format: evidence.subject.format,
architecture: evidence.subject.architecture,
},
}) === entryEvidenceKey(entry, snapshot.binding);
compositeKey([
evidenceQueryKey(evidence),
evidenceContentDigest(evidence),
]) === entryEvidenceKey(entry, snapshot.binding);
/** Compute the stable lookup key for one provider/profile-specific query. */
export const analysisQueryId = (
@@ -373,94 +507,189 @@ export const createAnalysisSnapshotEntry = (input: {
};
};
const validatedSnapshots = new WeakMap<object, AnalysisSnapshot>();
/** Parse a provider/profile-exact snapshot and verify its entries and evidence. */
export const parseAnalysisSnapshot = (input: unknown): AnalysisSnapshot => {
if (typeof input === "object" && input !== null) {
const owned = validatedSnapshots.get(input);
if (owned !== undefined) return owned;
}
const parsed = analysisSnapshotSchema.parse(input);
parseEvidenceBundle(parsed.evidence_bundle);
const scopedEvidence = evidenceBundleForTarget(
parsed.evidence_bundle,
parsed.target.sha256,
restoreSnapshot(
parsed,
parseEvidenceBundle(parsed.evidence_bundle),
parsed.entries.map((entry) => ({
kind: "provider",
entry,
digest: snapshotContentDigest(
entry.execution.result,
entry.execution.raw_result,
entry.execution,
),
})),
parsed.workflow_entries.map((entry) => ({
kind: "workflow",
entry,
digest: snapshotContentDigest(
entry.execution.result,
entry.execution.raw_result,
entry.execution,
),
})),
);
// The validated canonical bundle is filtered, never extended. Equal counts
// establish that no record or unknown was removed without serializing results.
return parsed;
};
/** Own an authenticated immutable cache while preserving mutable caller exports. */
export const immutableAnalysisSnapshot = (input: unknown): AnalysisSnapshot =>
sealSnapshot(parseAnalysisSnapshot(input));
const sealSnapshot = (snapshot: AnalysisSnapshot): AnalysisSnapshot => {
for (const entry of [...snapshot.entries, ...snapshot.workflow_entries]) {
freezeJsonSnapshot(entry.execution.result);
freezeJsonSnapshot(entry.execution.raw_result);
}
freezeJsonSnapshot(snapshot);
validatedSnapshots.set(snapshot, snapshot);
return snapshot;
};
const restoreSnapshot = (
header: Pick<AnalysisSnapshot, "target" | "binding">,
bundle: AnalysisSnapshot["evidence_bundle"],
providerRows: readonly Extract<PreparedEntry, { kind: "provider" }>[],
workflowRows: readonly Extract<PreparedEntry, { kind: "workflow" }>[],
): AnalysisSnapshot => {
const scopedEvidence = evidenceBundleForTarget(bundle, header.target.sha256);
if (
scopedEvidence.records.length !== parsed.evidence_bundle.records.length ||
scopedEvidence.unknowns.length !== parsed.evidence_bundle.unknowns.length
scopedEvidence.records.length !== bundle.records.length ||
scopedEvidence.unknowns.length !== bundle.unknowns.length
)
throw new TypeError(
"Analysis snapshot evidence contains records for another target",
);
if (!hasCanonicalQueryOrder(parsed.entries))
if (!hasCanonicalQueryOrder(providerRows.map(({ entry }) => entry)))
throw new TypeError("Analysis snapshot entries are not canonical");
if (!hasCanonicalQueryOrder(parsed.workflow_entries))
if (!hasCanonicalQueryOrder(workflowRows.map(({ entry }) => entry)))
throw new TypeError("Analysis snapshot workflow entries are not canonical");
const ids = new Set<string>();
const index = createEvidenceIndex(parsed);
const workflowIndex = createWorkflowEvidenceIndex(parsed);
for (const entry of parsed.entries) {
const corresponding = new Set<string>();
const matching = new Map<string, Evidence>();
for (const evidence of bundle.records) {
if (
entry.execution.provider.id !== parsed.binding.provider.id ||
entry.execution.provider.name !== parsed.binding.provider.name ||
entry.execution.provider.version !== parsed.binding.provider.version
evidence.predicate_type !== "rea.analysis" ||
evidence.authority !== "shipped-artifact" ||
evidence.subject?.digest.sha256 !== header.target.sha256 ||
evidence.analysis_profile === null ||
(evidence.confidence !== "observed" && evidence.confidence !== "derived")
)
throw new TypeError("Analysis snapshot entry provider does not match");
continue;
if (
evidence.confidence === "observed" &&
!isCorrespondingEvidence(evidence, header)
)
continue;
const query = compositeKey([
evidence.confidence,
evidenceQueryKey(evidence),
]);
corresponding.add(query);
matching.set(
compositeKey([query, evidenceContentDigest(evidence)]),
evidence,
);
}
const find = (
query: string,
digest: string,
label: string,
operation: string,
): Evidence => {
const evidence = matching.get(compositeKey([query, digest]));
if (evidence !== undefined) return evidence;
throw new TypeError(
corresponding.has(query)
? `Analysis snapshot ${label} ${operation} differs from its Evidence record`
: `Analysis snapshot ${label} ${operation} has no profile-bound Evidence record`,
);
};
const checkId = (
entry: EntryMetadata | WorkflowMetadata,
workflow: boolean,
): void => {
const expected = analysisQueryId(
parsed.target,
parsed.binding,
header.target,
header.binding,
entry.operation,
entry.parameters,
);
if (entry.query_id !== expected)
throw new TypeError("Analysis snapshot query identifier does not match");
throw new TypeError(
workflow
? "Analysis snapshot workflow query identifier does not match"
: "Analysis snapshot query identifier does not match",
);
if (ids.has(entry.query_id))
throw new TypeError("Analysis snapshot contains duplicate queries");
ids.add(entry.query_id);
if (!entryHasMatchingEvidence(entry, parsed, index)) {
if (entryHasCorrespondingEvidence(entry, parsed, index))
throw new TypeError(
`Analysis snapshot entry ${entry.operation} differs from its Evidence record`,
);
throw new TypeError(
`Analysis snapshot entry ${entry.operation} has no profile-bound Evidence record`,
);
}
}
for (const entry of parsed.workflow_entries) {
};
const entries = providerRows.map(({ entry, digest }) => {
if (
entry.execution.provider.id !==
entry.execution.analysis_profile.provider.id ||
entry.execution.provider.name !==
entry.execution.analysis_profile.provider.name ||
entry.execution.provider.version !==
entry.execution.analysis_profile.provider.version
entry.execution.provider.id !== header.binding.provider.id ||
entry.execution.provider.name !== header.binding.provider.name ||
entry.execution.provider.version !== header.binding.provider.version
)
throw new TypeError("Analysis snapshot entry provider does not match");
checkId(entry, false);
const evidence = find(
compositeKey(["observed", entryQueryKey(entry, header.binding)]),
digest,
"entry",
entry.operation,
);
return {
...entry,
execution: {
...entry.execution,
result: evidence.normalized_result,
raw_result: evidence.raw_result,
},
};
});
const workflow_entries = workflowRows.map(({ entry, digest }) => {
const provider = entry.execution.analysis_profile.provider;
if (
entry.execution.provider.id !== provider.id ||
entry.execution.provider.name !== provider.name ||
entry.execution.provider.version !== provider.version
)
throw new TypeError(
"Analysis snapshot workflow entry provider does not match its profile",
);
const expected = analysisQueryId(
parsed.target,
parsed.binding,
checkId(entry, true);
const evidence = find(
compositeKey(["derived", workflowEntryQueryKey(entry)]),
digest,
"workflow entry",
entry.operation,
entry.parameters,
);
if (entry.query_id !== expected)
throw new TypeError(
"Analysis snapshot workflow query identifier does not match",
);
if (ids.has(entry.query_id))
throw new TypeError("Analysis snapshot contains duplicate queries");
ids.add(entry.query_id);
if (!workflowEntryHasMatchingEvidence(entry, workflowIndex)) {
if (workflowEntryHasCorrespondingEvidence(entry, workflowIndex))
throw new TypeError(
`Analysis snapshot workflow entry ${entry.operation} differs from its Evidence record`,
);
throw new TypeError(
`Analysis snapshot workflow entry ${entry.operation} has no profile-bound Evidence record`,
);
}
}
return parsed;
return {
...entry,
execution: {
...entry.execution,
result: evidence.normalized_result,
raw_result: evidence.raw_result,
},
};
});
return {
target: header.target,
binding: header.binding,
entries,
workflow_entries,
evidence_bundle: bundle,
};
};
const hasCanonicalQueryOrder = (
@@ -478,56 +707,6 @@ const hasCanonicalQueryOrder = (
return true;
};
/** Check that a cached provider execution is represented by bundled Evidence. */
interface EvidenceIndex {
readonly matching: ReadonlySet<string>;
readonly corresponding: ReadonlySet<string>;
}
const createEvidenceIndex = (
snapshot: Pick<AnalysisSnapshot, "target" | "binding" | "evidence_bundle">,
): EvidenceIndex => {
const matching = new Set<string>();
const corresponding = new Set<string>();
for (const evidence of snapshot.evidence_bundle.records) {
if (!isCorrespondingEvidence(evidence, snapshot)) continue;
const query = evidenceQueryKey(evidence);
corresponding.add(query);
matching.add(
canonicalJson({
query,
normalized_result: evidence.normalized_result,
raw_result: evidence.raw_result,
limitations: evidence.limitations,
locations: evidence.locations,
subject:
evidence.subject === null
? null
: {
local_path: evidence.subject.local_path,
sha256: evidence.subject.digest.sha256,
format: evidence.subject.format,
architecture: evidence.subject.architecture,
},
}),
);
}
const result = { matching, corresponding };
return result;
};
const entryHasMatchingEvidence = (
entry: AnalysisSnapshotEntry,
snapshot: Pick<AnalysisSnapshot, "target" | "binding" | "evidence_bundle">,
index: EvidenceIndex,
): boolean => index.matching.has(entryEvidenceKey(entry, snapshot.binding));
const entryHasCorrespondingEvidence = (
entry: AnalysisSnapshotEntry,
snapshot: Pick<AnalysisSnapshot, "target" | "binding" | "evidence_bundle">,
index: EvidenceIndex,
): boolean => index.corresponding.has(entryQueryKey(entry, snapshot.binding));
const isCorrespondingEvidence = (
evidence: Evidence,
snapshot: Pick<AnalysisSnapshot, "target" | "binding">,
@@ -543,7 +722,7 @@ const isCorrespondingEvidence = (
);
const evidenceQueryKey = (evidence: Evidence): string =>
canonicalJson({
contentDigest({
operation: evidence.operation,
parameters: evidence.parameters,
provider: evidence.provider,
@@ -551,10 +730,12 @@ const evidenceQueryKey = (evidence: Evidence): string =>
});
const entryQueryKey = (
entry: AnalysisSnapshotEntry,
entry: Pick<AnalysisSnapshotEntry, "operation" | "parameters"> & {
readonly execution: Pick<AnalysisSnapshotEntry["execution"], "provider">;
},
binding: AnalysisSnapshotBinding,
): string =>
canonicalJson({
contentDigest({
operation: entry.operation,
parameters: entry.parameters,
provider: entry.execution.provider,
@@ -565,25 +746,24 @@ const entryEvidenceKey = (
entry: AnalysisSnapshotEntry,
binding: AnalysisSnapshotBinding,
): string =>
canonicalJson({
query: entryQueryKey(entry, binding),
normalized_result: entry.execution.result,
raw_result: entry.execution.raw_result,
limitations: entry.execution.limitations,
locations: entry.execution.locations,
subject:
entry.execution.subject === null
? null
: {
local_path: entry.execution.subject.path,
sha256: entry.execution.subject.sha256,
format: entry.execution.subject.format,
architecture: entry.execution.subject.architecture,
},
});
compositeKey([
entryQueryKey(entry, binding),
snapshotContentDigest(
entry.execution.result,
entry.execution.raw_result,
entry.execution,
),
]);
const workflowEntryQueryKey = (entry: AnalysisSnapshotWorkflowEntry): string =>
canonicalJson({
const workflowEntryQueryKey = (
entry: Pick<AnalysisSnapshotWorkflowEntry, "operation" | "parameters"> & {
readonly execution: Pick<
AnalysisSnapshotWorkflowEntry["execution"],
"provider" | "analysis_profile"
>;
},
): string =>
contentDigest({
operation: entry.operation,
parameters: entry.parameters,
provider: entry.execution.provider,
@@ -593,22 +773,14 @@ const workflowEntryQueryKey = (entry: AnalysisSnapshotWorkflowEntry): string =>
const workflowEntryEvidenceKey = (
entry: AnalysisSnapshotWorkflowEntry,
): string =>
canonicalJson({
query: workflowEntryQueryKey(entry),
normalized_result: entry.execution.result,
raw_result: entry.execution.raw_result,
limitations: entry.execution.limitations,
locations: entry.execution.locations,
subject:
entry.execution.subject === null
? null
: {
local_path: entry.execution.subject.path,
sha256: entry.execution.subject.sha256,
format: entry.execution.subject.format,
architecture: entry.execution.subject.architecture,
},
});
compositeKey([
workflowEntryQueryKey(entry),
snapshotContentDigest(
entry.execution.result,
entry.execution.raw_result,
entry.execution,
),
]);
const isWorkflowEvidence = (
evidence: Evidence,
@@ -628,49 +800,18 @@ const isWorkflowEvidence = (
evidence.provider.name === entry.execution.provider.name &&
evidence.provider.version === entry.execution.provider.version;
const createWorkflowEvidenceIndex = (
snapshot: Pick<AnalysisSnapshot, "target" | "evidence_bundle">,
): EvidenceIndex => {
const matching = new Set<string>();
const corresponding = new Set<string>();
for (const evidence of snapshot.evidence_bundle.records) {
if (
evidence.predicate_type !== "rea.analysis" ||
evidence.confidence !== "derived" ||
evidence.authority !== "shipped-artifact" ||
evidence.subject?.digest.sha256 !== snapshot.target.sha256 ||
evidence.analysis_profile === null
)
continue;
corresponding.add(evidenceQueryKey(evidence));
matching.add(canonicalJson(evidenceContentProjection(evidence)));
}
return { matching, corresponding };
};
const workflowEntryHasMatchingEvidence = (
entry: AnalysisSnapshotWorkflowEntry,
index: EvidenceIndex,
): boolean => index.matching.has(workflowEntryEvidenceKey(entry));
const workflowEntryHasCorrespondingEvidence = (
entry: AnalysisSnapshotWorkflowEntry,
index: EvidenceIndex,
): boolean => index.corresponding.has(workflowEntryQueryKey(entry));
const evidenceMatchesWorkflowEntry = (
evidence: Evidence,
entry: AnalysisSnapshotWorkflowEntry,
snapshot: Pick<AnalysisSnapshot, "target">,
): boolean =>
isWorkflowEvidence(evidence, snapshot, entry) &&
canonicalJson(evidenceContentProjection(evidence)) ===
workflowEntryEvidenceKey(entry);
compositeKey([
evidenceQueryKey(evidence),
evidenceContentDigest(evidence),
]) === workflowEntryEvidenceKey(entry);
const evidenceContentProjection = (evidence: Evidence) => ({
query: evidenceQueryKey(evidence),
normalized_result: evidence.normalized_result,
raw_result: evidence.raw_result,
const evidenceContentMetadata = (evidence: Evidence) => ({
limitations: evidence.limitations,
locations: evidence.locations,
subject:
+27 -2
View File
@@ -234,16 +234,32 @@ export const describeValidationFailure = (cause: unknown): string => {
/** Parse records, verify semantic IDs, and reject inconsistent manifests. */
export const parseEvidenceBundle = (input: unknown): EvidenceBundle => {
return parseBundle(input, false);
};
/** Authenticate and seal file-owned payloads once for downstream zero-copy reuse. */
export const parseImmutableEvidenceBundle = (
input: unknown,
): EvidenceBundle => {
return parseBundle(input, true);
};
const parseBundle = (input: unknown, seal: boolean): EvidenceBundle => {
if (typeof input === "object" && input !== null) {
const owned = immutableBundles.get(input);
if (owned !== undefined) return owned;
}
return authenticateParsedEvidenceBundle(evidenceBundleSchema.parse(input));
return authenticateBundle(evidenceBundleSchema.parse(input), seal);
};
/** Authenticate evidence and bundle invariants after the bundle shape is parsed. */
export const authenticateParsedEvidenceBundle = (
parsed: EvidenceBundle,
): EvidenceBundle => authenticateBundle(parsed, false);
const authenticateBundle = (
parsed: EvidenceBundle,
seal: boolean,
): EvidenceBundle => {
const recordIds = parsed.records.map(({ evidence_id: id }) => id);
if (new Set(recordIds).size !== recordIds.length)
@@ -252,7 +268,12 @@ export const authenticateParsedEvidenceBundle = (
const canonical = createEvidenceBundle(
parsed.records.map((record, index) => {
try {
return parseEvidence(record);
if (!seal) return parseEvidence(record);
// Schema parsing gave this boundary exclusive ownership. Register only
// validated payloads before authenticating the complete record envelope.
record.normalized_result = freezeJsonSnapshot(record.normalized_result);
record.raw_result = freezeJsonSnapshot(record.raw_result);
return immutableEvidence(record);
} catch (cause: unknown) {
if (cause instanceof z.ZodError)
throw new z.ZodError(
@@ -268,6 +289,10 @@ export const authenticateParsedEvidenceBundle = (
);
if (JSON.stringify(parsed) !== JSON.stringify(canonical))
throw new TypeError("Evidence bundle manifests are not canonical");
if (seal) {
freezeJsonSnapshot(canonical);
immutableBundles.set(canonical, canonical);
}
return canonical;
};
+4 -1
View File
@@ -191,7 +191,10 @@ const registerOpenLifecycleTool = ({
async (input, context) => {
let snapshot: AnalysisSnapshot | undefined;
if (input.snapshot_path !== undefined) {
const loaded = await readAnalysisSnapshot(input.snapshot_path);
const loaded = await readAnalysisSnapshot(
input.snapshot_path,
context.mcpReq.signal,
);
if (!loaded.ok)
return server.delivery.toCallToolResult(loaded, openContract);
snapshot = loaded.value;
@@ -13,6 +13,8 @@ import {
import {
createAnalysisSnapshotEntry,
parseAnalysisSnapshot,
parsePreparedAnalysisSnapshot,
prepareSnapshotEntry,
snapshotBinding,
snapshotTarget,
type AnalysisSnapshot,
@@ -24,21 +26,23 @@ import {
} from "../../../src/domain/analysisSnapshot.fixture.js";
import { createEvidence } from "../../../src/domain/evidence.js";
import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
import { readJsonObjectFile } from "../../../src/application/JsonInputFile.js";
import { RegularFileChangedError } from "../../../src/filesystem/RegularFile.js";
describe("analysis snapshots: persistence", () => {
it("writes canonical private v2 JSON and rejects changed query identity", async () => {
const directory = await createTestTempDirectory("rea-snapshot-");
const path = join(directory, "analysis.json");
const target = snapshotTarget(TARGET);
const binding = snapshotBinding(PROFILE);
const entry = createAnalysisSnapshotEntry({
target,
binding,
operation: "address_name",
parameters: { address: "0x1000" },
execution: createAnalysisExecution("main", PROVIDER, {
const snapshotFixture = (): AnalysisSnapshot => {
const target = snapshotTarget(TARGET);
const binding = snapshotBinding(PROFILE);
const entry = createAnalysisSnapshotEntry({
target,
binding,
operation: "address_name",
parameters: { address: "0x1000" },
execution: createAnalysisExecution(
{ name: "main", "😀": { "\uE000": "unicode" } },
PROVIDER,
{
analysisProfile: PROFILE,
rawResult: "main",
rawResult: { name: "main", "😀": { "\uE000": "unicode" } },
subject: {
path: TARGET.path,
sha256: TARGET.sha256,
@@ -47,22 +51,31 @@ describe("analysis snapshots: persistence", () => {
? {}
: { architecture: TARGET.architecture }),
},
}),
});
const evidence = createEvidence(TARGET, PROVIDER, {
operation: "address_name",
parameters: { address: "0x1000" },
result: "main",
rawResult: "main",
analysisProfile: PROFILE,
});
const snapshot: AnalysisSnapshot = {
target,
binding,
entries: [entry],
workflow_entries: [],
evidence_bundle: createEvidenceBundle([evidence]),
};
},
),
});
const evidence = createEvidence(TARGET, PROVIDER, {
operation: "address_name",
parameters: { address: "0x1000" },
result: { name: "main", "😀": { "\uE000": "unicode" } },
rawResult: { name: "main", "😀": { "\uE000": "unicode" } },
analysisProfile: PROFILE,
});
const snapshot: AnalysisSnapshot = {
target,
binding,
entries: [entry],
workflow_entries: [],
evidence_bundle: createEvidenceBundle([evidence]),
};
return snapshot;
};
describe("analysis snapshots: persistence", () => {
it("writes canonical private v2 JSON and rejects changed query identity", async () => {
const directory = await createTestTempDirectory("rea-snapshot-");
const path = join(directory, "analysis.json");
const snapshot = snapshotFixture();
const written = await writeAnalysisSnapshot(snapshot, path, false);
expect(written.ok).toBe(true);
expect((await readFile(path, "utf8")).endsWith("\n")).toBe(true);
@@ -94,3 +107,178 @@ describe("analysis snapshots: persistence", () => {
expect(() => parseAnalysisSnapshot(altered)).toThrow(/identifier/u);
});
});
describe("analysis snapshot member decoding", () => {
it("restores reordered legacy members with last-key semantics and authenticated shared payloads", async () => {
const directory = await createTestTempDirectory("rea-snapshot-members-");
const path = join(directory, "snapshot.json");
const snapshot = snapshotFixture();
const entry = snapshot.entries[0];
if (entry === undefined) throw new Error("Missing fixture entry");
const parts = [
`"evidence_bundle":${JSON.stringify(snapshot.evidence_bundle)}`,
'"entries":[{"bad":true}]',
'"workflow_entries":null',
`"binding":${JSON.stringify(snapshot.binding)}`,
`"entries":${JSON.stringify(snapshot.entries)}`,
`"target":${JSON.stringify(snapshot.target)}`,
'"workflow_entries":[]',
'"ignored":{"entries":["nested"]}',
];
await writeFile(path, `${" ".repeat(9 * 1024 * 1024)}{${parts.join(",")}}`);
const loaded = await readAnalysisSnapshot(path);
if (!loaded.ok) throw loaded.error;
expect(loaded.value).toEqual(snapshot);
const restored = loaded.value.entries[0];
const evidence = loaded.value.evidence_bundle.records[0];
if (restored === undefined || evidence === undefined)
throw new Error("Missing restored content");
expect(restored.execution.result).toBe(evidence.normalized_result);
expect(restored.execution.raw_result).toBe(evidence.raw_result);
expect(Object.isFrozen(restored.execution)).toBe(true);
expect(parseAnalysisSnapshot(loaded.value)).toBe(loaded.value);
await writeFile(
path,
`${" ".repeat(9 * 1024 * 1024)}{${parts.join(",")},"entries":null}`,
);
const rejected = await readAnalysisSnapshot(path);
expect(rejected).toMatchObject({
ok: false,
error: { _tag: "EvidenceIntegrityError" },
});
});
it.each(["result", "raw_result", "limitations", "provider", "query_id"])(
"rejects cache %s changes even when bundled Evidence is intact",
async (field) => {
const directory = await createTestTempDirectory("rea-snapshot-tamper-");
const path = join(directory, "snapshot.json");
const snapshot = snapshotFixture();
const entry = snapshot.entries[0];
if (entry === undefined) throw new Error("Missing fixture entry");
if (field === "query_id") entry.query_id = `query_${"f".repeat(64)}`;
else
Reflect.set(
entry.execution,
field,
field === "limitations"
? ["changed"]
: field === "provider"
? { ...PROVIDER, version: "changed" }
: "changed",
);
await writeFile(path, JSON.stringify(snapshot));
const loaded = await readAnalysisSnapshot(path);
expect(loaded).toMatchObject({
ok: false,
error: { _tag: "EvidenceIntegrityError" },
});
if (!loaded.ok)
expect(loaded.error.message).toMatch(
field === "query_id"
? /identifier/u
: field === "provider"
? /provider/u
: /differs from/u,
);
},
);
it("validates skipped JSON syntax and UTF-8 and does not turn missing arrays into empty caches", async () => {
const directory = await createTestTempDirectory("rea-snapshot-invalid-");
const path = join(directory, "snapshot.json");
const snapshot = snapshotFixture();
const encoded = JSON.stringify(snapshot);
for (const bytes of [
Buffer.from(`${encoded.slice(0, -1)},"ignored":[1,]}`),
Buffer.concat([
Buffer.from(`${encoded.slice(0, -1)},"ignored":"`),
Buffer.from([0xff]),
Buffer.from('"}'),
]),
Buffer.from(`\uFEFF${encoded}`),
]) {
await writeFile(path, bytes);
expect(await readAnalysisSnapshot(path)).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "invalid-json" },
});
}
const { workflow_entries: _workflows, ...missing } = snapshot;
await writeFile(path, JSON.stringify(missing));
expect(await readAnalysisSnapshot(path)).toMatchObject({
ok: false,
error: { _tag: "EvidenceIntegrityError" },
});
});
it("returns cancellation and permits the next complete read", async () => {
const directory = await createTestTempDirectory("rea-snapshot-cancel-");
const path = join(directory, "snapshot.json");
const snapshot = snapshotFixture();
await writeFile(
path,
`${" ".repeat(9 * 1024 * 1024)}${JSON.stringify(snapshot)}`,
);
const controller = new AbortController();
const pending = readAnalysisSnapshot(path, controller.signal);
const timer = setTimeout(() => controller.abort(), 1);
try {
expect(await pending).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
} finally {
clearTimeout(timer);
}
const loaded = await readAnalysisSnapshot(path);
if (!loaded.ok) throw loaded.error;
expect(loaded.value).toEqual(snapshot);
});
it("rejects changed bytes between scans on the same descriptor", async () => {
const directory = await createTestTempDirectory("rea-snapshot-changed-");
const path = join(directory, "input.json");
await writeFile(path, `${" ".repeat(9 * 1024 * 1024)}{"value":1}`);
const reader = new Map([
[
"value",
{
mode: "value" as const,
begin: () => undefined,
accept: async () => {
await writeFile(path, `${" ".repeat(9 * 1024 * 1024)}{"value":2}`);
},
reject: (cause: unknown) => {
throw cause;
},
},
],
]);
await expect(
readJsonObjectFile(path, "test-input", [reader, new Map()]),
).rejects.toBeInstanceOf(RegularFileChangedError);
expect((await readFile(path, "utf8")).trim()).toBe('{"value":2}');
});
});
it("rejects copied preparation handles and prevents mutable caller values from changing authenticated cache content", () => {
const snapshot = snapshotFixture();
const entry = snapshot.entries[0];
if (entry === undefined) throw new Error("Missing fixture entry");
const prepared = prepareSnapshotEntry(entry, "provider");
const forged = { ...prepared };
expect(() => parsePreparedAnalysisSnapshot(snapshot, [forged], [])).toThrow(
/not parsed in this realm/u,
);
const restored = parsePreparedAnalysisSnapshot(snapshot, [prepared], []);
Reflect.set(entry.execution, "result", "changed after preparation");
expect(restored.entries[0]?.execution.result).toEqual({
name: "main",
"😀": { "\uE000": "unicode" },
});
expect(() => parsePreparedAnalysisSnapshot(snapshot, [prepared], [])).toThrow(
/not parsed in this realm/u,
);
});