Files
reverse-engineering-anything/tests/boundary/filesystem/evidenceBundleFiles.test.ts
Jay-Alexander Elliot 57fd3ea5d8 feat(cli): summarize managed members with durable Evidence paging (#5167)
Fixes #5148.

Add opt-in durable managed member summaries and authenticated paging while preserving complete default output, original Evidence, cleanup ownership and queued cancellation.

Verified with bounded real Newtonsoft/Redux/Rust tasks, independent oracles, 556 focused tests and the expanded domain gate; paired token estimates and latency tradeoffs are recorded in experiments/analysis-efficiency/managed-cli.md.
2026-10-22 00:43:30 +00:00

766 lines
27 KiB
TypeScript

import {
mkdir,
readdir,
readFile,
stat,
symlink,
writeFile,
} from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { withArtifactResourceScope } from "../../fixtures/artifactInventory.js";
import {
readEvidenceBundle,
readImmutableEvidenceBundle,
writeEvidenceBundle,
} from "../../../src/application/EvidenceBundleFiles.js";
import {
compareEvidenceBundlesCommand,
deliverAnalysisEvidence,
} from "../../../src/application/EvidenceBundleCommands.js";
import { EvidenceLedger } from "../../../src/application/investigation/EvidenceLedger.js";
import { RegularFileCleanupFailure } from "../../../src/application/RegularFileRead.js";
import { OwnedFileHandle } from "../../../src/filesystem/OwnedFileHandle.js";
import { OwnedStagedOutput } from "../../../src/filesystem/OwnedStagedOutput.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import { writeTextParts } from "../../../src/application/JsonFiles.js";
import { failFileReadAfterAbort } from "../../support/failFileReadAfterAbort.js";
import { holdFirstFileRead } from "../../support/holdFirstFileRead.js";
import { JsonInputPhysicalReadFailure } from "../../../src/application/JsonInputFile.js";
import {
createEvidenceBundle,
parseEvidenceBundle,
serializeEvidenceBundle,
} from "../../../src/domain/evidenceBundle.js";
import { analysisViewLayoutEvidence } from "../../fixtures/analysisView.js";
const bundle = (result = true) =>
createEvidenceBundle([
createEvidence(
undefined,
{ id: "fixture", name: "Fixture", version: "1" },
{ operation: "health", parameters: {}, result },
),
]);
it.each([
["immutable", readImmutableEvidenceBundle],
["mutable", readEvidenceBundle],
] as const)(
"cancels a %s bundle read before opening its selected path",
async (_, read) => {
const root = await createTestTempDirectory("rea-bundle-pre-cancel-");
const controller = new AbortController();
const reason = new Error("cancel before bundle read");
controller.abort(reason);
const result = await read(join(root, "missing.json"), controller.signal);
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisCancelledError",
operation: "import_evidence_bundle",
cause: controller.signal.reason,
},
});
if (result.ok) throw new Error("Expected cancelled bundle read");
expect(result.error.cause).toBe(reason);
expect(await readdir(root)).toEqual([]);
},
);
it.each([
["mutable", readEvidenceBundle],
["immutable", readImmutableEvidenceBundle],
] as const)(
"preserves the caller cancellation reason during %s read",
async (_, read) => {
const root = await createTestTempDirectory("rea-bundle-read-cancel-");
const path = join(root, "bundle.json");
await writeFile(path, JSON.stringify(bundle()));
const reader = await holdFirstFileRead(path);
const controller = new AbortController();
const reason = new Error("stop bundle import");
try {
const pending = read(path, controller.signal);
await reader.firstRead;
controller.abort(reason);
reader.release();
const result = await pending;
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisCancelledError",
operation: "import_evidence_bundle",
},
});
if (result.ok) throw new Error("Expected cancelled bundle read");
expect(result.error.cause).toBe(reason);
} finally {
controller.abort(reason);
reader.release();
vi.restoreAllMocks();
}
},
);
it.each([
["mutable", readEvidenceBundle],
["immutable", readImmutableEvidenceBundle],
] as const)(
"keeps the physical null read cause for %s import after cancellation",
async (_, read) => {
const root = await createTestTempDirectory("rea-bundle-late-io-");
const path = join(root, "bundle.json");
await writeFile(path, JSON.stringify(bundle()));
const controller = new AbortController();
await failFileReadAfterAbort(
path,
controller,
new Error("later abort"),
null,
);
try {
const result = await read(path, controller.signal);
expect(result).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "io", path },
});
if (result.ok) throw new Error("Expected physical JSON read failure");
expect(result.error.cause).toBeNull();
} finally {
vi.restoreAllMocks();
}
},
);
it.each([
["mutable", readEvidenceBundle],
["immutable", readImmutableEvidenceBundle],
] as const)(
"keeps an identical physical and abort reason for %s import",
async (_, read) => {
const root = await createTestTempDirectory("rea-bundle-identical-abort-");
const path = join(root, "bundle.json");
await writeFile(path, JSON.stringify(bundle()));
const controller = new AbortController();
const physical = new JsonInputPhysicalReadFailure(
Object.assign(new Error("physical EIO"), { code: "EIO" }),
);
await failFileReadAfterAbort(path, controller, physical, physical);
try {
const result = await read(path, controller.signal);
expect(result).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "io" },
});
if (result.ok) throw new Error("Expected physical JSON read failure");
expect(result.error.cause).toBe(physical);
} finally {
vi.restoreAllMocks();
}
},
);
it("preserves the failed-close owner when import is also cancelled", async () => {
const root = await createTestTempDirectory("rea-import-cancel-cleanup-");
const path = join(root, "bundle.json");
await writeFile(path, JSON.stringify(bundle()));
const controller = new AbortController();
const cleanupCause = new Error("fixture close failure");
const cancellationReason = new JsonInputPhysicalReadFailure(
new Error("opaque cancellation reason"),
);
const close = vi
.spyOn(OwnedFileHandle.prototype, "close")
.mockImplementationOnce(async () => {
controller.abort(cancellationReason);
throw cleanupCause;
});
let failure: unknown;
try {
try {
await readImmutableEvidenceBundle(path, controller.signal);
} catch (cause: unknown) {
failure = cause;
}
expect(failure).toBeInstanceOf(RegularFileCleanupFailure);
if (!(failure instanceof RegularFileCleanupFailure))
throw new Error("Expected the retained file handle owner");
expect(failure.cleanupCause).toBe(cleanupCause);
} finally {
close.mockRestore();
if (failure instanceof RegularFileCleanupFailure)
await failure.owner.close();
}
expect(await readEvidenceBundle(path)).toMatchObject({ ok: true });
});
describe("evidence bundle export cancellation", () => {
it("retains the completed parent and exact reason when delivery is cancelled before publication", async () => {
const root = await createTestTempDirectory("rea-delivery-cancel-");
const parent = analysisViewLayoutEvidence();
const controller = new AbortController();
const reason = new Error("cancel before bundle publication");
controller.abort(reason);
const result = await deliverAnalysisEvidence(
parent,
join(root, "bundle.json"),
"summary",
controller.signal,
);
if (result.ok) throw new Error("Cancelled delivery reported success");
expect(result.error).toMatchObject({
_tag: "AnalysisCancelledError",
operation: parent.operation,
});
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
details: { operation: parent.operation },
});
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBe(parent);
expect(await readdir(root)).toEqual([]);
});
it("does not inspect the path or advance a source cancelled before writing", async () => {
const root = await createTestTempDirectory("rea-export-pre-cancel-");
const controller = new AbortController();
controller.abort();
let advanced = false;
function* parts() {
advanced = true;
yield "unused";
}
expect(
await writeParts(parts(), join(root, "missing", "bundle.json"), false, {
signal: controller.signal,
operation: "export_evidence_bundle",
}),
).toMatchObject({
ok: false,
error: {
_tag: "AnalysisCancelledError",
operation: "export_evidence_bundle",
cause: controller.signal.reason,
},
});
expect(advanced).toBe(false);
expect(await readdir(root)).toEqual([]);
expect(
await writeBundle(
bundle(),
join(root, "bundle.json"),
false,
controller.signal,
),
).toMatchObject({
ok: false,
error: {
_tag: "AnalysisCancelledError",
cause: controller.signal.reason,
},
});
expect(await readdir(root)).toEqual([]);
});
it.each([false, true])(
"closes a cancelled source and preserves the destination (overwrite: %s)",
async (overwrite) => {
const root = await createTestTempDirectory("rea-export-cancel-");
const path = join(root, "bundle.json");
if (overwrite) await writeFile(path, "original");
const controller = new AbortController();
const cancellationReason = new Error("cancel staged publication");
let closed = false;
let continued = false;
function* parts() {
try {
yield "staged partial data";
controller.abort(cancellationReason);
yield "cancelled data";
continued = true;
yield "unreachable";
} finally {
closed = true;
}
}
expect(
await writeParts(parts(), path, overwrite, {
signal: controller.signal,
operation: "export_evidence_bundle",
}),
).toMatchObject({
ok: false,
error: {
_tag: "AnalysisCancelledError",
operation: "export_evidence_bundle",
cause: cancellationReason,
},
});
expect(closed).toBe(true);
expect(continued).toBe(false);
expect(await readdir(root)).toEqual(overwrite ? ["bundle.json"] : []);
if (overwrite) expect(await readFile(path, "utf8")).toBe("original");
expect(await writeBundle(bundle(), path, overwrite)).toMatchObject({
ok: true,
});
expect(await readEvidenceBundle(path)).toEqual({
ok: true,
value: bundle(),
});
},
);
it("does not publish when cancellation arrives at source exhaustion", async () => {
const root = await createTestTempDirectory("rea-export-final-cancel-");
const path = join(root, "bundle.json");
await writeFile(path, "original");
const controller = new AbortController();
function* parts() {
yield "complete replacement";
controller.abort();
}
expect(
await writeParts(parts(), path, true, {
signal: controller.signal,
operation: "export_evidence_bundle",
}),
).toMatchObject({ ok: false, error: { _tag: "AnalysisCancelledError" } });
expect(await readFile(path, "utf8")).toBe("original");
expect(await readdir(root)).toEqual(["bundle.json"]);
});
});
it.each([1, 2])(
"reports only unresolved cancellation cleanup after %s failed close attempts",
async (failedAttempts) => {
const root = await createTestTempDirectory("rea-delivery-cleanup-retry-");
const parent = analysisViewLayoutEvidence();
const controller = new AbortController();
const reason = new Error("cancel before bundle publication");
const cleanupCause = new Error("fixture staged-output close failure");
const ready = OwnedStagedOutput.prototype.assertReadyForPublication;
const close = OwnedStagedOutput.prototype.close;
let owner: OwnedStagedOutput | undefined;
let attempts = 0;
const publication = vi
.spyOn(OwnedStagedOutput.prototype, "assertReadyForPublication")
.mockImplementationOnce(async function (this: OwnedStagedOutput, staged) {
await ready.call(this, staged);
controller.abort(reason);
});
const cleanup = vi
.spyOn(OwnedStagedOutput.prototype, "close")
.mockImplementation(async function (this: OwnedStagedOutput) {
owner = this;
if (++attempts <= failedAttempts) throw cleanupCause;
await close.call(this);
});
try {
const result = await deliverAnalysisEvidence(
parent,
join(root, "bundle.json"),
"summary",
controller.signal,
);
if (result.ok) throw new Error("Cancelled delivery reported success");
expect(attempts).toBe(2);
expect(result.error._tag).toBe("AnalysisCancelledError");
expect(result.error.partialObservation).toBe(parent);
expect(result.error.cause).toBe(reason);
expect(result.error.cleanupPrimary).toMatchObject({
operation: parent.operation,
});
expect(result.error.cleanupPrimary?.cause).toBe(reason);
expect(result.error.cleanup?.reason).toBe(cleanupCause.message);
const incomplete = failedAttempts === 2;
expect(result.error.cleanupIncomplete).toBe(incomplete);
expect(result.error.cleanupResources).toEqual(
incomplete ? [owner?.directory] : [],
);
expect(projectAnalysisError(result.error)).toMatchObject({
code: incomplete ? "cleanup_incomplete" : "cancelled",
details: { operation: parent.operation },
});
expect((await readdir(root)).map((entry) => join(root, entry))).toEqual(
incomplete ? [owner?.directory] : [],
);
} finally {
publication.mockRestore();
cleanup.mockRestore();
await owner?.close();
}
},
);
const writeBundle = (
value: ReturnType<typeof bundle>,
path: string,
overwrite: boolean,
signal?: AbortSignal,
) =>
withArtifactResourceScope((resources) =>
writeEvidenceBundle(value, path, overwrite, resources, signal),
);
const writeParts = (
parts: Iterable<string>,
path: string,
overwrite: boolean,
cancellation?: {
readonly signal: AbortSignal;
readonly operation: string;
},
) =>
withArtifactResourceScope((resources) =>
writeTextParts(parts, path, overwrite, resources, cancellation),
);
describe("evidence bundle publication", () => {
it("retains the committed bundle receipt when cancellation arrives during final cleanup", async () => {
const root = await createTestTempDirectory(
"rea-delivery-published-cancel-",
);
const path = join(root, "bundle.json");
const parent = analysisViewLayoutEvidence();
const controller = new AbortController();
const reason = new Error("cancel after actual staged-output cleanup");
const close = OwnedStagedOutput.prototype.close;
const seam = vi
.spyOn(OwnedStagedOutput.prototype, "close")
.mockImplementationOnce(async function (this: OwnedStagedOutput) {
await close.call(this);
controller.abort(reason);
});
let result: Awaited<ReturnType<typeof deliverAnalysisEvidence>>;
try {
result = await deliverAnalysisEvidence(
parent,
path,
"summary",
controller.signal,
);
} finally {
seam.mockRestore();
}
if (result.ok) throw new Error("Cancelled delivery reported success");
expect(result.error._tag).toBe("AnalysisCancelledError");
expect(result.error.cause).toBe(reason);
const bytes = await readFile(path);
expect(result.error.diagnostics?.evidence_bundle).toEqual({
path,
bytes: bytes.length,
});
expect(
parseEvidence(result.error.partialObservation).evidence_links,
).toEqual([parent.evidence_id]);
expect(
parseEvidenceBundle(JSON.parse(bytes.toString("utf8"))).records[0]
?.evidence_id,
).toBe(parent.evidence_id);
expect(await readdir(root)).toEqual(["bundle.json"]);
});
it.each([false, true])(
"never publishes a partial streamed file (overwrite: %s)",
async (overwrite) => {
const root = await createTestTempDirectory("rea-stream-publication-");
const path = join(root, "bundle.json");
if (overwrite) await writeFile(path, "original");
function* brokenParts() {
yield "partial";
throw new Error("Source stream failed");
}
expect(await writeParts(brokenParts(), path, overwrite)).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "io" },
});
expect(await readdir(root)).toEqual(overwrite ? ["bundle.json"] : []);
if (overwrite) expect(await readFile(path, "utf8")).toBe("original");
},
);
it("counts complete Unicode bytes and publishes a private file", async () => {
const root = await createTestTempDirectory("rea-stream-bytes-");
const path = join(root, "bundle.json");
const parts = ["雪", "😀", "\n"];
expect(await writeParts(parts, path, false)).toEqual({
ok: true,
value: { path, bytes: Buffer.byteLength(parts.join("")) },
});
expect(await readFile(path, "utf8")).toBe(parts.join(""));
if (process.platform !== "win32")
expect((await stat(path)).mode & 0o777).toBe(0o600);
});
it("allows only one simultaneous export without overwrite approval", async () => {
const root = await createTestTempDirectory("rea-evidence-exclusive-");
const path = join(root, "bundle.json");
const candidates = [bundle(), bundle(false)];
const results = await Promise.all(
candidates.map((value) => writeBundle(value, path, false)),
);
expect(results.filter(({ ok }) => ok)).toHaveLength(1);
expect(results.filter(({ ok }) => !ok)).toMatchObject([
{ ok: false, error: { _tag: "EvidenceFileError", reason: "exists" } },
]);
const winner = candidates[results.findIndex(({ ok }) => ok)];
expect(await readEvidenceBundle(path)).toEqual({
ok: true,
value: winner,
});
expect(await readdir(root)).toEqual(["bundle.json"]);
});
it("round trips caller-selected paths without configured roots", async () => {
const root = await createTestTempDirectory("rea-evidence-dot-child-");
const directory = join(root, "..cache");
await mkdir(directory);
const path = join(directory, "bundle.json");
const value = bundle();
expect(await writeBundle(value, path, false)).toMatchObject({
ok: true,
});
expect(await readEvidenceBundle(path)).toEqual({
ok: true,
value,
});
});
});
it("reuses authenticated file snapshots while isolating mutable public reads", async () => {
const directory = await createTestTempDirectory("rea-evidence-owned-");
const path = join(directory, "bundle.json");
const evidence = createEvidence(
{
path: "/local/observed-target",
sha256: "a".repeat(64),
format: "file",
},
{ id: "fixture", name: "Fixture", version: "1" },
{ operation: "health", parameters: {}, result: { nested: [true] } },
);
const source = serializeEvidenceBundle(createEvidenceBundle([evidence]));
await writeFile(path, source);
const mutable = await readEvidenceBundle(path);
if (!mutable.ok) throw mutable.error;
expect(Object.isFrozen(mutable.value.records[0])).toBe(false);
const loaded = await readImmutableEvidenceBundle(path);
if (!loaded.ok) throw loaded.error;
const record = loaded.value.records[0];
if (record === undefined) throw new Error("Expected an imported record");
expect(record).toEqual(evidence);
expect(Object.isFrozen(loaded.value)).toBe(true);
expect(Object.isFrozen(record.normalized_result)).toBe(true);
expect(Reflect.set(record, "operation", "forged")).toBe(false);
const ledger = new EvidenceLedger();
const imported = ledger.import(loaded.value);
if (!imported.ok) throw imported.error;
expect(imported.value).toMatchObject({ recordsAdded: 1, changed: true });
expect(ledger.forAnalysis(evidence.evidence_id)).toBe(record);
const detached = ledger.get(evidence.evidence_id);
if (detached === undefined) throw new Error("Expected a detached record");
expect(Reflect.set(detached, "normalized_result", false)).toBe(true);
expect(ledger.record(detached).ok).toBe(false);
expect(ledger.export()).toEqual(loaded.value);
expect(await readFile(path, "utf8")).toBe(source);
ledger.clear();
});
describe("evidence bundle filesystem adapter", () => {
it.skipIf(process.platform === "win32").each(["/", "/."])(
"preserves the directory requirement of a missing output ending in %s",
async (suffix) => {
const directory = await createTestTempDirectory(
"rea-evidence-directory-",
);
const selected = `${join(directory, "report.json")}${suffix}`;
expect(await writeBundle(bundle(), selected, false)).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "not-file" },
});
expect(await readdir(directory)).toEqual([]);
},
);
it("round trips canonical bytes and requires explicit overwrite", async () => {
const directory = await createTestTempDirectory("rea-evidence-");
const path = join(directory, "bundle.json");
const evidenceBundle = bundle();
const first = await writeBundle(evidenceBundle, path, false);
expect(first).toMatchObject({ ok: true, value: { path } });
expect(await readFile(path, "utf8")).toBe(
serializeEvidenceBundle(evidenceBundle),
);
expect(await readEvidenceBundle(path)).toEqual({
ok: true,
value: evidenceBundle,
});
expect(
await compareEvidenceBundlesCommand({
leftPath: path,
rightPath: path,
}),
).toMatchObject({
ok: true,
value: {
status: "unchanged",
summary: { records_unchanged: 1 },
},
});
expect(await writeBundle(evidenceBundle, path, false)).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "exists" },
});
expect(await writeBundle(evidenceBundle, path, true)).toMatchObject({
ok: true,
});
});
it("reads the caller-selected symlink target and refuses to replace a symlink", async () => {
const directory = await createTestTempDirectory("rea-evidence-");
const outside = join(directory, "outside");
await mkdir(outside);
const outsidePath = join(outside, "bundle.json");
await writeFile(outsidePath, serializeEvidenceBundle(bundle()));
const link = join(directory, "escaped.json");
await symlink(outsidePath, link);
expect(await readEvidenceBundle(link)).toMatchObject({
ok: true,
value: bundle(),
});
expect(await writeBundle(bundle(), link, true)).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "not-file" },
});
});
it("reports a missing file or output directory with the selected path", async () => {
const directory = await createTestTempDirectory("rea-evidence-");
const absent = join(directory, "absent.json");
const read = await readEvidenceBundle(absent);
expect(read).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "missing", path: absent },
});
if (read.ok) throw new Error("Expected a missing-file failure");
expect(projectAnalysisError(read.error)).toMatchObject({
message: expect.stringContaining("does not exist at the selected path"),
details: { operation: "read", reason: "missing", path: absent },
});
const orphan = join(directory, "absent", "bundle.json");
const written = await writeBundle(bundle(), orphan, false);
expect(written).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "missing", path: orphan },
});
if (written.ok) throw new Error("Expected a missing-directory failure");
expect(projectAnalysisError(written.error).message).toContain(
"output directory does not exist",
);
});
it.each([readEvidenceBundle, readImmutableEvidenceBundle])(
"rejects malformed and tampered input (%s)",
async (readBundle) => {
const directory = await createTestTempDirectory("rea-evidence-");
const malformed = join(directory, "malformed.json");
await writeFile(malformed, "{");
expect(await readBundle(malformed)).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "invalid-json" },
});
const tampered = bundle();
const tamperedPath = join(directory, "tampered.json");
await writeFile(
tamperedPath,
JSON.stringify({
...tampered,
records: [{ ...tampered.records[0], normalized_result: "changed" }],
}),
);
expect(await readBundle(tamperedPath)).toMatchObject({
ok: false,
error: {
_tag: "EvidenceIntegrityError",
userMessage: expect.stringContaining(
"at records.0.evidence_id: Evidence semantic identifier does not match its record",
),
},
});
},
);
it("names schema mismatches that prevent bundle parsing", async () => {
const directory = await createTestTempDirectory("rea-evidence-");
const partial = join(directory, "partial.json");
await writeFile(partial, JSON.stringify({ evidence_id: "typo" }));
const rejected = await readEvidenceBundle(partial);
expect(rejected).toMatchObject({
ok: false,
error: {
userMessage: expect.stringContaining(
"does not match the bundle schema",
),
},
});
expect(rejected).not.toMatchObject({
error: { userMessage: expect.stringContaining("one Evidence record") },
});
const mismatched = join(directory, "mismatched.json");
await writeFile(mismatched, JSON.stringify({ ...bundle(), records: {} }));
expect(await readEvidenceBundle(mismatched)).toMatchObject({
ok: false,
error: {
_tag: "EvidenceIntegrityError",
userMessage: expect.stringContaining(
"does not match the bundle schema at records:",
),
},
});
});
});
describe("standalone Evidence bundle inputs", () => {
it("classifies a record only after authenticating it", async () => {
const directory = await createTestTempDirectory("rea-evidence-");
const record = bundle().records[0];
const single = join(directory, "record.json");
await writeFile(single, JSON.stringify(record));
expect(await readEvidenceBundle(single)).toMatchObject({
ok: false,
error: {
_tag: "EvidenceFileError",
reason: "wrong-kind",
path: single,
userMessage: expect.stringContaining(
`this JSON is one Evidence record (${String(record?.evidence_id)})`,
),
},
});
const tamperedRecord = join(directory, "tampered-record.json");
await writeFile(
tamperedRecord,
JSON.stringify({ ...record, normalized_result: "changed" }),
);
expect(await readEvidenceBundle(tamperedRecord)).toMatchObject({
ok: false,
error: {
_tag: "EvidenceIntegrityError",
userMessage: expect.stringContaining(
"does not match the bundle schema",
),
},
});
});
});