mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 00:07:18 +00:00
* fix: retain address context cleanup failures * fix!: preserve address context input errors BREAKING CHANGE: Strict clients that cached the previous full inspect_address_context output schema must refresh tools/list and accept the typed error field on unavailable facets. * test: use session contract for address context * fix!: preserve address context resource recovery BREAKING CHANGE: Clients validating unavailable context facets against a cached strict full output schema must refresh tools/list to accept resource_constraint errors. * fix: expose authenticated records refused by the evidence ledger (#8426) * fix: preserve completed evidence on retention refusal * fix: preserve context when unretained evidence exceeds delivery limits * fix(mcp): recover oversized context from session evidence * fix(evidence): preserve completed atomic write results * fix: retain completed results when source publication fails * test: cover bounded recovery with unknown retention * fix(evidence): preserve recovery identity and finalization facts * fix(evidence): clarify retention of omitted observations * test(evidence): compare omission guidance with its owner * fix(evidence): preserve completed composed workflows * fix(evidence): clarify unretained recovery export * test(evidence): cover mixed recovery guidance * fix(evidence): preserve caller scope in secondary failures * refactor(evidence): remove superseded cleanup projection * test(evidence): align recovery checks with owners * fix: preserve cleanup on partial recovery limits * test: use owners for diagnostic wording * fix: admit completed analysis before evidence construction * fix: preserve selected paths when reusing evidence * fix(cli): preserve caller path in unretained evidence * fix(evidence): preserve recovery metadata and admission bounds * refactor: consolidate evidence closure imports * style(test): format CLI help default annotations (#9280) * test(mcp): keep recovery checks focused on contract facts * fix(evidence): preserve feature trace replay revision * test: align evidence recovery checks with ownership * test: align partial recovery and cleanup diagnostics * fix(evidence): preserve nested partials during cleanup * fix(evidence): respect outer recovery ownership * fix(capture): discard rejected nested observations * test(frida): assert inline evidence recovery --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
492 lines
17 KiB
TypeScript
492 lines
17 KiB
TypeScript
import { expect, it, vi } from "vitest";
|
|
import { writeFile } from "node:fs/promises";
|
|
import { dirname, basename, join } from "node:path";
|
|
import {
|
|
createBinarySessionTargets,
|
|
createTestProviderRouter,
|
|
} from "../fixtures/binarySession.js";
|
|
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
|
import { BinarySession } from "../../src/application/binary/BinarySession.js";
|
|
import { InvestigationRecords } from "../../src/application/investigation/InvestigationRecords.js";
|
|
import {
|
|
runDirectAnalysis,
|
|
runProviderAnalysis,
|
|
} from "../../src/application/DirectAnalysis.js";
|
|
import type { DirectAnalysisDependencies } from "../../src/application/DirectAnalysisDependencies.js";
|
|
import { createAnalysisExecution } from "../../src/application/AnalysisProvider.js";
|
|
import { createAnalysisExecutionEvidence } from "../../src/domain/analysisObservation.js";
|
|
import { createEvidence } from "../../src/domain/evidence.js";
|
|
import { parseConfig } from "../../src/config/parseConfig.js";
|
|
import { err, ok } from "../../src/domain/result.js";
|
|
import { ProviderCleanupError } from "../../src/domain/providerCleanupError.js";
|
|
import { primaryAnalysisError } from "../../src/domain/analysisErrorBase.js";
|
|
import { AnalysisCancelledError } from "../../src/domain/analysisErrorCore.js";
|
|
|
|
it.each(["inspect_macho", "inspect_managed_members"] as const)(
|
|
"recovers %s Evidence and caller input identity after source admission fails",
|
|
async (tool) => {
|
|
const [target] = await createBinarySessionTargets();
|
|
const path = `${dirname(target)}/./${basename(target)}`;
|
|
const provider = { id: "fixture", name: "Fixture", version: "1" };
|
|
const source = createEvidence(undefined, provider, {
|
|
operation: "health",
|
|
parameters: {},
|
|
result: "large".repeat(20000),
|
|
});
|
|
const calls: Readonly<Record<string, unknown>>[] = [];
|
|
const run = async (maximumRepresentationBytes: number) => {
|
|
const session = new BinarySession(
|
|
createTestProviderRouter(() => ({
|
|
execute: async (operation, parameters) => {
|
|
if (operation !== "health") calls.push(parameters);
|
|
return ok(
|
|
createAnalysisExecution(
|
|
operation === "health" ? null : "completed",
|
|
provider,
|
|
operation === "health" ? {} : { sourceEvidence: [source] },
|
|
),
|
|
);
|
|
},
|
|
close: async () => ok(null),
|
|
})),
|
|
new InvestigationRecords({ maximumRepresentationBytes }),
|
|
);
|
|
const create = () => session;
|
|
const dependencies: DirectAnalysisDependencies = {
|
|
readConfiguration: () => parseConfig({}),
|
|
createBinarySession: create,
|
|
createNativeDispatchBinarySession: create,
|
|
createArtifactBinarySession: create,
|
|
createNativeBinarySession: create,
|
|
createManagedBinarySession: create,
|
|
};
|
|
return runProviderAnalysis(dependencies, path, tool, {});
|
|
};
|
|
const success = await run(500000);
|
|
if (!success.ok) throw new Error(JSON.stringify(success.error));
|
|
const refused = await run(20000);
|
|
if (refused.ok) throw new Error("Expected source admission failure");
|
|
expect(refused.error).toMatchObject({
|
|
details: {
|
|
completed_observation: {
|
|
evidence: {
|
|
evidence_id: success.value.evidence_id,
|
|
parameters: { input_path: path },
|
|
caller_input_path: path,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
expect(calls).toEqual([{}, {}]);
|
|
},
|
|
);
|
|
|
|
it("delivers a preexisting completed record inline after one-shot cleanup", async () => {
|
|
const provider = { id: "fixture", name: "Fixture", version: "1" };
|
|
const source = createEvidence(
|
|
undefined,
|
|
provider,
|
|
{ operation: "health", parameters: {}, result: "large".repeat(20000) },
|
|
{ immutable: true },
|
|
);
|
|
const path = join(
|
|
await createTestTempDirectory("rea-oneshot-source-"),
|
|
"fixture.com",
|
|
);
|
|
await writeFile(path, Buffer.from([0xb8, 0, 0x4c, 0xcd, 0x21]));
|
|
const router = createTestProviderRouter(() => ({
|
|
execute: async (op) =>
|
|
ok(
|
|
createAnalysisExecution(
|
|
op === "health" ? null : "completed",
|
|
provider,
|
|
op === "health" ? {} : { sourceEvidence: [source] },
|
|
),
|
|
),
|
|
close: async () => ok(null),
|
|
}));
|
|
const records = new InvestigationRecords({
|
|
maximumRepresentationBytes: 20000,
|
|
});
|
|
const session = new BinarySession(router, records);
|
|
const opened = await session.open(path, { formatHint: "dos-com" });
|
|
if (!opened.ok) throw opened.error;
|
|
const args = { address: "0x100", length: 4 };
|
|
const primary = createAnalysisExecutionEvidence(
|
|
"read_bytes",
|
|
args,
|
|
{
|
|
...createAnalysisExecution("completed", provider, {
|
|
sourceEvidence: [source],
|
|
}),
|
|
subject: opened.value,
|
|
},
|
|
"observed",
|
|
);
|
|
expect(session.recordEvidence(primary).ok).toBe(true);
|
|
const create = () => session;
|
|
const dependencies: DirectAnalysisDependencies = {
|
|
readConfiguration: () => parseConfig({}),
|
|
createBinarySession: create,
|
|
createNativeDispatchBinarySession: create,
|
|
createArtifactBinarySession: create,
|
|
createNativeBinarySession: create,
|
|
createManagedBinarySession: create,
|
|
};
|
|
const result = await runDirectAnalysis(
|
|
dependencies,
|
|
path,
|
|
"read_bytes",
|
|
args,
|
|
{ formatHint: "dos-com" },
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("Expected source failure");
|
|
expect(result.error).toMatchObject({
|
|
code: "resource_constraint",
|
|
details: {
|
|
completed_observation: {
|
|
kind: "inline-evidence",
|
|
retention: "one-shot-session",
|
|
source_closure: "not-established",
|
|
evidence: {
|
|
evidence_id: primary.evidence_id,
|
|
caller_input_path: path,
|
|
normalized_result: "completed",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it("keeps caller spelling on a raw observation when a source ID is malformed", async () => {
|
|
const provider = { id: "fixture", name: "Fixture", version: "1" };
|
|
const base = createEvidence(undefined, provider, {
|
|
operation: "health",
|
|
parameters: {},
|
|
result: "source",
|
|
});
|
|
const source = { ...base, evidence_id: "invalid" };
|
|
const path = join(
|
|
await createTestTempDirectory("rea-oneshot-invalid-source-"),
|
|
"fixture.com",
|
|
);
|
|
await writeFile(path, Buffer.from([0xb8, 0, 0x4c, 0xcd, 0x21]));
|
|
const router = createTestProviderRouter(() => ({
|
|
execute: async (op) =>
|
|
ok(
|
|
createAnalysisExecution(
|
|
op === "health" ? null : "completed",
|
|
provider,
|
|
op === "health" ? {} : { sourceEvidence: [source] },
|
|
),
|
|
),
|
|
close: async () => ok(null),
|
|
}));
|
|
const session = new BinarySession(router);
|
|
const create = () => session;
|
|
const dependencies: DirectAnalysisDependencies = {
|
|
readConfiguration: () => parseConfig({}),
|
|
createBinarySession: create,
|
|
createNativeDispatchBinarySession: create,
|
|
createArtifactBinarySession: create,
|
|
createNativeBinarySession: create,
|
|
createManagedBinarySession: create,
|
|
};
|
|
const result = await runDirectAnalysis(
|
|
dependencies,
|
|
path,
|
|
"read_bytes",
|
|
{ address: "0x100", length: 4 },
|
|
{ formatHint: "dos-com" },
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("Expected source failure");
|
|
expect(result.error).toMatchObject({
|
|
code: "evidence_integrity_mismatch",
|
|
details: {
|
|
completed_observation: {
|
|
kind: "unretained-observation",
|
|
source_closure: "not-established",
|
|
observation: {
|
|
operation: "read_bytes",
|
|
result: "completed",
|
|
caller_input_path: path,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it.each([false, true])(
|
|
"keeps the source failure and primary when cancellation arrives during final close (close fails: %s)",
|
|
async (closeFails) => {
|
|
const provider = { id: "fixture", name: "Fixture", version: "1" };
|
|
const source = createEvidence(
|
|
undefined,
|
|
provider,
|
|
{ operation: "health", parameters: {}, result: "large".repeat(20000) },
|
|
{ immutable: true },
|
|
);
|
|
const path = join(
|
|
await createTestTempDirectory("rea-source-final-close-"),
|
|
"fixture.com",
|
|
);
|
|
await writeFile(path, Buffer.from([0xb8, 0, 0x4c, 0xcd, 0x21]));
|
|
const session = new BinarySession(
|
|
createTestProviderRouter(() => ({
|
|
execute: async (op) =>
|
|
ok(
|
|
createAnalysisExecution(
|
|
op === "health" ? null : "completed",
|
|
provider,
|
|
op === "health" ? {} : { sourceEvidence: [source] },
|
|
),
|
|
),
|
|
close: async () => ok(null),
|
|
})),
|
|
new InvestigationRecords({ maximumRepresentationBytes: 20000 }),
|
|
);
|
|
const opened = await session.open(path, { formatHint: "dos-com" });
|
|
if (!opened.ok) throw opened.error;
|
|
const args = { address: "0x100", length: 4 };
|
|
const primary = createAnalysisExecutionEvidence(
|
|
"read_bytes",
|
|
args,
|
|
{
|
|
...createAnalysisExecution("completed", provider, {
|
|
sourceEvidence: [source],
|
|
}),
|
|
subject: opened.value,
|
|
},
|
|
"observed",
|
|
);
|
|
expect(session.recordEvidence(primary).ok).toBe(true);
|
|
const create = () => session;
|
|
const dependencies: DirectAnalysisDependencies = {
|
|
readConfiguration: () => parseConfig({}),
|
|
createBinarySession: create,
|
|
createNativeDispatchBinarySession: create,
|
|
createArtifactBinarySession: create,
|
|
createNativeBinarySession: create,
|
|
createManagedBinarySession: create,
|
|
};
|
|
const controller = new AbortController();
|
|
const reason = { during: "final close" };
|
|
const recordEvidence = vi.spyOn(session, "recordEvidence");
|
|
const nativeClose = session.close.bind(session);
|
|
const failure = new ProviderCleanupError("fixture", ["fixture-owner"], {});
|
|
const close = vi
|
|
.spyOn(session, "close")
|
|
.mockImplementationOnce(async () => {
|
|
controller.abort(reason);
|
|
return closeFails ? err(failure) : nativeClose();
|
|
});
|
|
try {
|
|
const result = await runDirectAnalysis(
|
|
dependencies,
|
|
path,
|
|
"read_bytes",
|
|
args,
|
|
{
|
|
formatHint: "dos-com",
|
|
signal: controller.signal,
|
|
},
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("Expected source failure");
|
|
expect(result.error).toMatchObject({
|
|
code: closeFails ? "cleanup_incomplete" : "resource_constraint",
|
|
details: {
|
|
partial_observation: {
|
|
kind: "unretained-evidence",
|
|
evidence: { evidence_id: source.evidence_id },
|
|
},
|
|
completed_observation: {
|
|
kind: "inline-evidence",
|
|
retention: "one-shot-session",
|
|
source_closure: "not-established",
|
|
evidence: {
|
|
evidence_id: primary.evidence_id,
|
|
caller_input_path: path,
|
|
normalized_result: "completed",
|
|
},
|
|
},
|
|
...(closeFails
|
|
? {
|
|
resources: ["fixture-owner"],
|
|
cleanup: "incomplete",
|
|
execution_failure: "resource_constraint",
|
|
}
|
|
: {}),
|
|
},
|
|
});
|
|
const sourceFailure = recordEvidence.mock.results
|
|
.map(({ value }) => value)
|
|
.find((value) => value !== undefined && !value.ok);
|
|
expect(sourceFailure).toBeDefined();
|
|
expect(
|
|
primaryAnalysisError(sourceFailure!.error).secondaryFailures,
|
|
).toContainEqual(expect.objectContaining({ cause: reason }));
|
|
expect(
|
|
primaryAnalysisError(sourceFailure!.error).secondaryFailures.some(
|
|
(failure) =>
|
|
failure instanceof AnalysisCancelledError &&
|
|
failure.cause === reason,
|
|
),
|
|
).toBe(true);
|
|
expect(result.error).toMatchObject({
|
|
details: {
|
|
secondary_failures: [expect.objectContaining({ code: "cancelled" })],
|
|
},
|
|
});
|
|
expect(session.activeTarget() !== undefined).toBe(closeFails);
|
|
} finally {
|
|
close.mockRestore();
|
|
const closed = await nativeClose();
|
|
if (!closed.ok) throw closed.error;
|
|
expect(session.activeTarget()).toBeUndefined();
|
|
}
|
|
},
|
|
);
|
|
|
|
it("reports an omitted completed one-shot result when authentication headroom is exhausted", async () => {
|
|
const provider = { id: "fixture", name: "Fixture", version: "1" };
|
|
const source = createEvidence(
|
|
undefined,
|
|
provider,
|
|
{ operation: "health", parameters: {}, result: "historical source" },
|
|
{ immutable: true },
|
|
);
|
|
const path = join(
|
|
await createTestTempDirectory("rea-oneshot-headroom-"),
|
|
"fixture.com",
|
|
);
|
|
await writeFile(path, Buffer.from([0xb8, 0, 0x4c, 0xcd, 0x21]));
|
|
const router = createTestProviderRouter(() => ({
|
|
execute: async () =>
|
|
ok(
|
|
createAnalysisExecution("completed", provider, {
|
|
sourceEvidence: [source],
|
|
}),
|
|
),
|
|
close: async () => ok(null),
|
|
}));
|
|
const records = new InvestigationRecords({
|
|
maximumRepresentationBytes: 1000000,
|
|
availableAuthenticationBytes: () => 0,
|
|
});
|
|
const create = () => new BinarySession(router, records);
|
|
const dependencies: DirectAnalysisDependencies = {
|
|
readConfiguration: () => parseConfig({}),
|
|
createBinarySession: create,
|
|
createNativeDispatchBinarySession: create,
|
|
createArtifactBinarySession: create,
|
|
createNativeBinarySession: create,
|
|
createManagedBinarySession: create,
|
|
};
|
|
const result = await runDirectAnalysis(
|
|
dependencies,
|
|
path,
|
|
"procedure_pseudo_code",
|
|
{ procedure: "0x100" },
|
|
{ formatHint: "dos-com" },
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("Expected admission failure");
|
|
expect(result.error).toMatchObject({
|
|
code: "resource_constraint",
|
|
details: {
|
|
completed_observation_omitted: {
|
|
reason: "insufficient-authentication-headroom",
|
|
source_closure: "not-established",
|
|
},
|
|
},
|
|
});
|
|
expect(records.statistics().evidenceRecords).toBe(1);
|
|
});
|
|
|
|
it.each([false, true])(
|
|
"preserves admission omission during final-close cancellation (close fails: %s)",
|
|
async (closeFails) => {
|
|
const provider = { id: "fixture", name: "Fixture", version: "1" };
|
|
const source = createEvidence(
|
|
undefined,
|
|
provider,
|
|
{ operation: "health", parameters: {}, result: "history" },
|
|
{ immutable: true },
|
|
);
|
|
const path = join(
|
|
await createTestTempDirectory("rea-admission-final-close-"),
|
|
"fixture.com",
|
|
);
|
|
await writeFile(path, Buffer.from([0xb8, 0, 0x4c, 0xcd, 0x21]));
|
|
const session = new BinarySession(
|
|
createTestProviderRouter(() => ({
|
|
execute: async () =>
|
|
ok(
|
|
createAnalysisExecution("completed", provider, {
|
|
sourceEvidence: [source],
|
|
}),
|
|
),
|
|
close: async () => ok(null),
|
|
})),
|
|
new InvestigationRecords({
|
|
maximumRepresentationBytes: 1000000,
|
|
availableAuthenticationBytes: () => 0,
|
|
}),
|
|
);
|
|
const create = () => session;
|
|
const dependencies: DirectAnalysisDependencies = {
|
|
readConfiguration: () => parseConfig({}),
|
|
createBinarySession: create,
|
|
createNativeDispatchBinarySession: create,
|
|
createArtifactBinarySession: create,
|
|
createNativeBinarySession: create,
|
|
createManagedBinarySession: create,
|
|
};
|
|
const controller = new AbortController();
|
|
const originalClose = session.close.bind(session);
|
|
const failure = new ProviderCleanupError("fixture", ["fixture-owner"], {});
|
|
const close = vi
|
|
.spyOn(session, "close")
|
|
.mockImplementationOnce(async () => {
|
|
controller.abort({ during: "final close" });
|
|
return closeFails ? err(failure) : originalClose();
|
|
});
|
|
try {
|
|
const result = await runDirectAnalysis(
|
|
dependencies,
|
|
path,
|
|
"procedure_pseudo_code",
|
|
{ procedure: "0x100" },
|
|
{ formatHint: "dos-com", signal: controller.signal },
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("Expected admission failure");
|
|
expect(result.error).toMatchObject({
|
|
code: closeFails ? "cleanup_incomplete" : "resource_constraint",
|
|
details: {
|
|
completed_observation_omitted: {
|
|
reason: "insufficient-authentication-headroom",
|
|
source_closure: "not-established",
|
|
},
|
|
secondary_failures: [expect.objectContaining({ code: "cancelled" })],
|
|
...(closeFails
|
|
? {
|
|
resources: ["fixture-owner"],
|
|
cleanup: "incomplete",
|
|
execution_failure: "resource_constraint",
|
|
}
|
|
: {}),
|
|
},
|
|
});
|
|
expect(session.activeTarget() !== undefined).toBe(closeFails);
|
|
} finally {
|
|
close.mockRestore();
|
|
const closed = await originalClose();
|
|
if (!closed.ok) throw closed.error;
|
|
}
|
|
},
|
|
);
|