Files
reverse-engineering-anything/src/application/analysisView/AnalysisViewService.test.ts
T
Alex GorevskiandCopilot 4e28166626 fix(cancellation): preserve caller abort reason in cancellation errors (#6144)
* fix(cancellation): preserve caller abort reason in cancellation errors

Several application, provider, and MCP boundaries constructed
AnalysisCancelledError, ArtifactReaderFailure, or
DetachedPgpSignatureError for an already-observed or newly-observed abort
without passing the caller's signal.reason as the error cause. The typed
result then reported cancellation with an undefined reason even though the
exact object supplied to AbortController.abort(reason) was still available.

Pass signal.reason as the cause at each producing cancellation site:
JavaScript artifact reconstruction and file abort checks, derived MCP
operation checkpoints, detached PGP verification, ASAR listing admission,
Dart AOT inspection and its provider guard, evidence-file analysis views,
binary layout and recorded crash admission, WASM inspection admission,
and the Kuna shared assembly-index wait.

Cancellation classification and cleanup behavior are unchanged.

Fixes morluto/rea#5935
Fixes morluto/rea#5944
Fixes morluto/rea#6100
Fixes morluto/rea#6101
Fixes morluto/rea#6110
Fixes morluto/rea#6113
Fixes morluto/rea#6121
Fixes morluto/rea#6131
Fixes morluto/rea#6132

* test(cancellation): assert the exact caller reason identity

Copied reasons satisfied toMatchObject, so the tests would pass even if the
caller's abort reason were replaced by a structurally equal copy. Capture the
rejection and assert the cause is the same object.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 35b247e9-c793-4b8f-979a-807c27112cf5

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 35b247e9-c793-4b8f-979a-807c27112cf5
2026-10-22 04:11:15 +08:00

1117 lines
36 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import {
analysisViewCaptureFixture,
analysisViewCaptureEvidence,
analysisViewObjectDenseCapture,
environmentManifestCaptureFixture,
environmentStateCaptureFixture,
} from "../../../tests/fixtures/analysisView.js";
import { describe, expect, it } from "vitest";
import { JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE } from "../../contracts/javascript/javascriptRuntimeReconciliationExample.js";
import { createEvidence, parseEvidence } from "../../domain/evidence.js";
import { inspectAnalysisViewInputSchema } from "../../domain/analysisView/analysisView.js";
import { functionDossierSchema } from "../../domain/hopperValues.js";
import { ghidraFunctionDossier } from "../../domain/ghidraValues.fixture.js";
import {
analysisViewJavaScriptAnalysisWithSource,
analysisViewJavaScriptEvidence,
analysisViewLayoutEvidence,
} from "../../../tests/fixtures/analysisView.js";
import {
inspectAnalysisView,
inspectAnalysisViewValidated,
inspectAnalysisViewWithFiles,
summarizeRetainedAnalysis,
} from "./AnalysisViewService.js";
import type { Evidence } from "../../domain/evidence.js";
import { EvidenceIntegrityError } from "../../domain/evidenceErrors.js";
import { err, ok } from "../../domain/result.js";
import { EvidenceLedger } from "../investigation/EvidenceLedger.js";
import { WEB_NETWORK_CAPTURE_LIMITS } from "../../domain/webNetworkCapture.js";
import { isImmutableJsonSnapshot } from "../../domain/immutableJson.js";
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
import {
SCENARIO_EXCHANGES,
scenarioCaptureEvidence,
scenarioCaptureFixture,
} from "../../../tests/fixtures/browserScenarioCapture.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
it.each(["layout", "javascript"])(
"carries the retained %s source snapshot into repeatable shared views",
(kind) => {
const parent =
kind === "layout"
? analysisViewLayoutEvidence()
: analysisViewJavaScriptEvidence();
const ledger = new EvidenceLedger();
const recorded = ledger.record(parent);
if (!recorded.ok) throw recorded.error;
const borrowed = ledger.forAnalysis(parent.evidence_id)!;
const original = JSON.stringify(borrowed);
const input = {
source: { kind: "retained-evidence", evidence_id: parent.evidence_id },
view: { kind: "summary" },
} as const;
const first = inspectAnalysisView(input, (id) => ledger.forAnalysis(id));
if (!first.ok) throw first.error;
const repeated = inspectAnalysisView(input, (id) => ledger.forAnalysis(id));
if (!repeated.ok) throw repeated.error;
expect(repeated.value.normalized_result).toEqual(
first.value.normalized_result,
);
expect(isImmutableJsonSnapshot(borrowed.normalized_result)).toBe(true);
expect(JSON.stringify(borrowed)).toBe(original);
expect(ledger.forAnalysis(parent.evidence_id)).toBe(borrowed);
},
);
it.each(["/fixtures/unknown.exe", ""])(
"preserves recorded empty native identity fields: path=%j",
(path) => {
const original = functionDossierSchema.parse(ghidraFunctionDossier());
const parent = createEvidence(
{ path, format: "pe", sha256: "c".repeat(64) },
{ id: "ghidra", name: "Ghidra", version: "12.1.4" },
{
operation: "analyze_function",
parameters: {},
result: {
...original,
procedure: {
...original.procedure,
address: "",
body: { available: false, reason: "Not recorded" },
},
},
},
);
const result = inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: {
kind: "native",
facet: "procedure",
offset: 0,
limit: 64,
},
});
if (!result.ok) throw result.error;
expect(result.value.normalized_result).toMatchObject({
procedure_address: "",
artifact: { path },
item: { address: "" },
});
expect(result.value.locations).toEqual(
path.length > 0 ? [{ kind: "artifact-path", path }] : [],
);
},
);
it("reports inline Evidence identity failures at the complete source path", () => {
const parent = analysisViewLayoutEvidence();
const input = {
source: {
kind: "inline",
evidence: { ...parent, evidence_id: `ev_${"0".repeat(64)}` },
},
view: { kind: "summary" },
} as const;
for (const result of [
inspectAnalysisView(input),
inspectAnalysisViewValidated(input),
])
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisInputError",
issues: [
{
path: ["source", "evidence", "evidence_id"],
reason: "invalid_value",
message: "Evidence semantic identifier does not match its record",
},
],
},
});
});
it("projects a small native view from an authenticated >10 MiB retained dossier", () => {
const original = functionDossierSchema.parse(ghidraFunctionDossier());
const parent = createEvidence(
{ path: "/fixtures/large.exe", format: "pe", sha256: "c".repeat(64) },
{ id: "ghidra", name: "Ghidra", version: "12.1.4" },
{
operation: "analyze_function",
parameters: { address: "0x401000" },
result: { ...original, pseudocode: "X".repeat(11 * 1024 * 1024) },
limitations: ["Synthetic fixture"],
},
);
const input = {
source: {
kind: "retained-evidence" as const,
evidence_id: parent.evidence_id,
},
view: {
kind: "native" as const,
facet: "pseudocode" as const,
offset: 0,
limit: 128,
},
};
const view = inspectAnalysisView(input, (id) =>
id === parent.evidence_id ? parent : undefined,
);
if (!view.ok) throw view.error;
expect(view.value.evidence_links).toEqual([parent.evidence_id]);
expect(view.value.normalized_result).toMatchObject({
kind: "native",
parent_operation: "analyze_function",
parent_evidence_id: parent.evidence_id,
procedure_address: "0x401000",
artifact: { path: "/fixtures/large.exe", sha256: "c".repeat(64) },
coverage: { total: 11 * 1024 * 1024, examined: 128, next_offset: 128 },
});
expect(view.value.locations).toEqual([
{ kind: "artifact-path", path: "/fixtures/large.exe" },
{ kind: "address", address: "0x401000" },
]);
expect(JSON.stringify(view.value).length).toBeLessThan(12000);
expect(JSON.stringify(parent).length).toBeGreaterThan(10 * 1024 * 1024);
expect(inspectAnalysisView(input, () => undefined)).toMatchObject({
ok: false,
error: { _tag: "EvidenceIntegrityError" },
});
});
it("projects inline layout Evidence and retains a derived view record", () => {
const parent = analysisViewLayoutEvidence();
const result = inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: {
kind: "item",
collection: "sections",
selector: { index: 2 },
},
});
if (!result.ok) throw result.error;
const evidence = parseEvidence(result.value);
expect(evidence.operation).toBe("inspect_analysis_view");
expect(evidence.predicate_type).toBe("rea.analysis-view");
expect(evidence.confidence).toBe("derived");
expect(evidence.evidence_links).toEqual([parent.evidence_id]);
expect(evidence.normalized_result).toMatchObject({
kind: "item",
parent_evidence_id: parent.evidence_id,
item: { name: { display: ".data" } },
});
});
it("resolves same-session retained JavaScript Evidence without re-running analysis", () => {
const parent = analysisViewJavaScriptEvidence(
analysisViewJavaScriptAnalysisWithSource(),
);
const lookup = (evidenceId: string) =>
evidenceId === parent.evidence_id ? parent : undefined;
const summary = inspectAnalysisView(
{
source: {
kind: "retained-evidence",
evidence_id: parent.evidence_id,
},
view: { kind: "summary" },
},
lookup,
);
if (!summary.ok) throw summary.error;
expect(summary.value.normalized_result).toMatchObject({
kind: "summary",
parent_evidence_id: parent.evidence_id,
parent_operation: "analyze_javascript_application",
});
expect(summary.value.normalized_result).not.toHaveProperty(
"summary.semantic_graph",
);
const item = inspectAnalysisView(
{
source: {
kind: "retained-evidence",
evidence_id: parent.evidence_id,
},
view: {
kind: "item",
collection: "modules",
selector: { path: "renderer.js" },
},
},
lookup,
);
if (!item.ok) throw item.error;
expect(JSON.stringify(item.value.normalized_result)).toContain(
"export const secret = 1;",
);
});
it("rejects stale retained IDs and unsupported parent operations", () => {
const missing = inspectAnalysisView(
{
source: {
kind: "retained-evidence",
evidence_id: JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE.evidence_id,
},
view: { kind: "summary" },
},
() => undefined,
);
expect(missing).toMatchObject({
ok: false,
error: {
_tag: "EvidenceIntegrityError",
evidenceId: JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE.evidence_id,
},
});
const unsupported = createEvidence(
undefined,
{ id: "fixture", name: "Fixture", version: "1" },
{
operation: "inspect_recorded_crash",
parameters: {},
result: { path: "/artifacts/crash.core" },
},
);
const rejected = inspectAnalysisView({
source: { kind: "inline", evidence: unsupported },
view: { kind: "summary" },
});
expect(rejected).toMatchObject({
ok: false,
error: { _tag: "AnalysisUnsupportedTargetError" },
});
expect(
inspectAnalysisView({
source: { kind: "inline", evidence: analysisViewLayoutEvidence() },
view: { kind: "summary", approval: true },
}),
).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
});
it("preserves an absent parent subject instead of guessing its format", () => {
const analysis = analysisViewJavaScriptAnalysisWithSource();
const original = analysisViewJavaScriptEvidence(analysis);
const parent = createEvidence(undefined, original.provider, {
operation: original.operation,
parameters: {},
result: original.normalized_result,
limitations: original.limitations,
});
const result = inspectAnalysisView(
{
source: { kind: "retained-evidence", evidence_id: parent.evidence_id },
view: { kind: "summary" },
},
() => parent,
);
if (!result.ok) throw result.error;
expect(result.value.subject).toBeNull();
expect(result.value.locations).toEqual([
{ kind: "artifact-path", path: analysis.input_path },
]);
});
it("rejects contradictory subject and analysis artifact digests", () => {
const analysis = analysisViewJavaScriptAnalysisWithSource();
const original = analysisViewJavaScriptEvidence(analysis);
const parent = createEvidence(
{
path: analysis.input_path,
format: analysis.format,
sha256: "d".repeat(64),
},
original.provider,
{
operation: original.operation,
parameters: {},
result: original.normalized_result,
},
);
const result = inspectAnalysisView(
{
source: { kind: "retained-evidence", evidence_id: parent.evidence_id },
view: { kind: "summary" },
},
() => parent,
);
expect(result).toMatchObject({
ok: false,
error: { _tag: "EvidenceIntegrityError" },
});
});
describe("summarizeRetainedAnalysis", () => {
const parent = analysisViewJavaScriptEvidence(
analysisViewJavaScriptAnalysisWithSource(),
);
it("retains the complete Evidence before projecting its summary", () => {
const retained = new Map<string, Evidence>();
const summary = summarizeRetainedAnalysis(parent, {
recordEvidence: (evidence) => {
retained.set(evidence.evidence_id, evidence);
return ok("added");
},
evidenceById: (evidenceId) => retained.get(evidenceId),
});
if (!summary.ok) throw summary.error;
expect(retained.get(parent.evidence_id)).toBe(parent);
expect(summary.value.evidence_links).toEqual([parent.evidence_id]);
expect(summary.value.normalized_result).toMatchObject({
kind: "summary",
parent_evidence_id: parent.evidence_id,
parent_operation: "analyze_javascript_application",
});
});
it("refuses a summary whose complete Evidence cannot be retained", () => {
const unavailable = summarizeRetainedAnalysis(parent, {
recordEvidence: undefined,
evidenceById: undefined,
});
expect(unavailable.ok).toBe(false);
if (unavailable.ok) return;
expect(unavailable.error._tag).toBe("AnalysisCapabilityUnavailableError");
expect(unavailable.error.userMessage).toContain("detail complete");
const conflict = new EvidenceIntegrityError("ledger conflict");
expect(
summarizeRetainedAnalysis(parent, {
recordEvidence: () => err(conflict),
evidenceById: () => parent,
}),
).toEqual(err(conflict));
});
});
it("seals inferred and literal HAR pages, including a null artifact digest", () => {
const capture = analysisViewCaptureFixture();
capture.artifact.sha256 = null;
const parent = analysisViewCaptureEvidence(capture);
// Any templated group in the full grouping makes every page inferred.
for (const [offset, confidence] of [
[0, "inferred"],
[11, "inferred"],
] as const) {
const result = inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind: "api-surface", offset, limit: 1 },
});
if (!result.ok) throw result.error;
expect(result.value.confidence).toBe(confidence);
expect(result.value.subject).toBeNull();
expect(parseEvidence(result.value)).toEqual(result.value);
}
expect(
inspectAnalysisView({
source: { kind: "inline", evidence: analysisViewLayoutEvidence() },
view: { kind: "api-surface" },
}),
).toMatchObject({
ok: false,
error: { _tag: "AnalysisUnsupportedTargetError" },
});
});
it("rejects a null projected digest under a present parent subject", () => {
const capture = analysisViewCaptureFixture();
capture.artifact.sha256 = null;
const parent = createEvidence(
{ path: capture.artifact.path, sha256: "a".repeat(64), format: "file" },
{ id: "fixture", name: "fixture", version: "1" },
{
operation: "inspect_web_network_capture",
parameters: { sensitive_value_count: 0 },
result: capture,
confidence: "observed",
},
);
expect(
inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind: "api-surface" },
}),
).toMatchObject({ ok: false, error: { _tag: "EvidenceIntegrityError" } });
});
it("refuses an object-dense borrowed capture before making a full schema clone", () => {
const capture = analysisViewObjectDenseCapture();
expect(Buffer.byteLength(JSON.stringify(capture))).toBeLessThan(
WEB_NETWORK_CAPTURE_LIMITS.inputBytes,
);
const parent = analysisViewCaptureEvidence(capture);
const ledger = new EvidenceLedger();
const recorded = ledger.record(parent);
if (!recorded.ok) throw recorded.error;
const borrowed = ledger.forAnalysis(parent.evidence_id)!;
expect(Object.isFrozen(borrowed.normalized_result)).toBe(true);
for (const view of [
{ kind: "api-surface", limit: 1 },
{ kind: "environment-manifest" },
{ kind: "environment-state" },
] as const) {
const result = inspectAnalysisView(
{
source: { kind: "retained-evidence", evidence_id: parent.evidence_id },
view,
},
(id) => ledger.forAnalysis(id),
);
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisResourceConstraintError",
resource: "memory",
},
});
}
expect(ledger.forAnalysis(parent.evidence_id)).toBe(borrowed);
});
it("seals an inferred environment manifest and a derived manifest without templated groups", () => {
const parent = analysisViewCaptureEvidence(
environmentManifestCaptureFixture(),
);
const inferred = inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind: "environment-manifest", template_rules: ["opaque-token"] },
});
if (!inferred.ok) throw inferred.error;
expect(inferred.value.confidence).toBe("inferred");
expect(inferred.value.authority).toBe(parent.authority);
expect(inferred.value.evidence_links).toEqual([parent.evidence_id]);
expect(inferred.value.parameters).toMatchObject({
view: {
kind: "environment-manifest",
template_rules: ["opaque-token"],
include_query_parameter_names: true,
},
});
expect(inferred.value.normalized_result).toMatchObject({
kind: "environment-manifest",
name: "grafana-example",
parent_evidence_id: parent.evidence_id,
});
expect(parseEvidence(inferred.value)).toEqual(inferred.value);
const literal = environmentManifestCaptureFixture();
literal.artifact.sha256 = null;
literal.records = [3, 4, 8].map((ordinal) => literal.records[ordinal]!);
const derived = inspectAnalysisView({
source: { kind: "inline", evidence: analysisViewCaptureEvidence(literal) },
view: { kind: "environment-manifest" },
});
if (!derived.ok) throw derived.error;
expect(derived.value.confidence).toBe("derived");
expect(derived.value.subject).toBeNull();
expect(derived.value.normalized_result).toMatchObject({
templated_groups: 0,
tools: [{ name: "get_org" }, { name: "get_search" }, { name: "get_user" }],
limitations: expect.arrayContaining([
"Parent contains an ordinal subset; the manifest covers only retained records.",
]),
});
expect(
inspectAnalysisView({
source: { kind: "inline", evidence: analysisViewLayoutEvidence() },
view: { kind: "environment-manifest" },
}),
).toMatchObject({
ok: false,
error: { _tag: "AnalysisUnsupportedTargetError" },
});
});
it("seals an inferred environment state whose values are the recorded ones", () => {
const parent = analysisViewCaptureEvidence(environmentStateCaptureFixture());
const sealed = inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind: "environment-state", template_rules: ["opaque-token"] },
});
if (!sealed.ok) throw sealed.error;
expect(sealed.value.confidence).toBe("inferred");
expect(sealed.value.authority).toBe(parent.authority);
expect(sealed.value.evidence_links).toEqual([parent.evidence_id]);
expect(sealed.value.parameters).toMatchObject({
view: { kind: "environment-state", template_rules: ["opaque-token"] },
});
expect(sealed.value.normalized_result).toMatchObject({
kind: "environment-state",
name: "grafana-example",
parent_evidence_id: parent.evidence_id,
timeline: "recorded-start-time",
});
expect(JSON.stringify(sealed.value.normalized_result)).toContain(
"probe folder",
);
expect(parseEvidence(sealed.value)).toEqual(sealed.value);
});
it.each([
"summary",
"capture",
"api-surface",
"environment-manifest",
"environment-state",
] as const)("rejects inline accessors before parsing a %s view", (kind) => {
let accessed = false;
const evidence = {
get normalized_result() {
accessed = true;
throw new Error("Evidence must be admitted before parsing");
},
};
const input = {
source: { kind: "inline", evidence },
view: { kind },
};
const parsed = inspectAnalysisViewInputSchema.parse(input);
if (parsed.source.kind !== "inline") throw new Error("Expected inline input");
expect(parsed.source.evidence).toBe(evidence);
expect(inspectAnalysisView(input)).toMatchObject({
ok: false,
error: {
_tag: "AnalysisInputError",
issues: [{ path: ["source", "evidence", "normalized_result"] }],
},
});
expect(accessed).toBe(false);
expect(
inspectAnalysisView({
source: { kind: "inline", evidence: analysisViewCaptureEvidence() },
view: { kind },
}).ok,
).toBe(true);
});
it.each([
"summary",
"capture",
"api-surface",
"environment-manifest",
"environment-state",
] as const)(
"rejects nested, hidden and inherited inline accessors for %s",
(kind) => {
const parent = analysisViewCaptureEvidence();
let accessed = false;
const getter = () => {
accessed = true;
throw new Error("Inline accessors must remain inert");
};
const nested = {
get value() {
return getter();
},
};
const hidden = Object.defineProperty({}, "value", { get: getter });
const array = Object.defineProperty([null], 0, { get: getter });
const inherited: unknown = Object.create({
get value() {
return getter();
},
});
const cyclic: Record<string, unknown> = {};
cyclic["self"] = cyclic;
const cases: { data: unknown; path: (string | number)[] }[] = [
{ data: nested, path: ["value"] },
{ data: hidden, path: ["value"] },
{ data: array, path: [0] },
{ data: inherited, path: [] },
{ data: cyclic, path: ["self"] },
];
for (const { data, path } of cases) {
// A complete envelope reaches payload parsing and Evidence authentication.
const input = {
source: {
kind: "inline" as const,
evidence: { ...parent, raw_result: data },
},
view: { kind },
};
for (const result of [
inspectAnalysisView(input),
inspectAnalysisViewValidated(
inspectAnalysisViewInputSchema.parse(input),
),
])
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisInputError",
issues: [{ path: ["source", "evidence", "raw_result", ...path] }],
},
});
}
expect(accessed).toBe(false);
},
);
it.each([
["own map", "map", false],
["own iterator", Symbol.iterator, false],
["own constructor", "constructor", false],
["inherited map", "map", true],
["inherited iterator", Symbol.iterator, true],
] as const)(
"rejects array %s accessors without invoking them",
(_name, key, inherited) => {
const parent = analysisViewCaptureEvidence();
let accessed = false;
const array: unknown[] = [];
const owner: object = inherited ? Object.create(Array.prototype) : array;
Object.defineProperty(owner, key, {
get() {
accessed = true;
throw new Error("Array behavior must remain inert");
},
});
if (inherited) Object.setPrototypeOf(array, owner);
for (const kind of [
"summary",
"capture",
"api-surface",
"environment-manifest",
"environment-state",
] as const) {
const result = inspectAnalysisView({
source: { kind: "inline", evidence: { ...parent, raw_result: array } },
view: { kind },
});
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
}
expect(accessed).toBe(false);
},
);
it("rejects callable array overrides and subclass species before parsing", () => {
const parent = analysisViewCaptureEvidence();
let invoked = false;
const callback = () => {
invoked = true;
throw new Error("Array callbacks must remain inert");
};
class CustomArray extends Array<unknown> {
static override get [Symbol.species](): ArrayConstructor {
return callback();
}
}
for (const array of [
Object.defineProperty([], "map", { value: callback }),
Object.defineProperty([], Symbol.iterator, { value: callback }),
new CustomArray(),
])
for (const kind of [
"summary",
"capture",
"api-surface",
"environment-manifest",
"environment-state",
] as const) {
const result = inspectAnalysisView({
source: { kind: "inline", evidence: { ...parent, raw_result: array } },
view: { kind },
});
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
}
expect(invoked).toBe(false);
});
it.each([
"summary",
"capture",
"api-surface",
"environment-manifest",
"environment-state",
] as const)("preserves ordinary inline Evidence for %s", (kind) => {
const parent = analysisViewCaptureEvidence();
const before = JSON.stringify(parent);
const result = inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind },
});
if (!result.ok) throw result.error;
expect(parseEvidence(result.value)).toEqual(result.value);
expect(result.value.evidence_links).toEqual([parent.evidence_id]);
expect(result.value.normalized_result).toMatchObject({
kind,
parent_evidence_id: parent.evidence_id,
});
expect(JSON.stringify(parent)).toBe(before);
expect(Object.isFrozen(parent)).toBe(false);
});
it("rejects executable constructor values before schema plain-object checks", () => {
const parent = analysisViewCaptureEvidence();
let invoked = false;
const constructor = () => undefined;
Object.defineProperty(constructor, "prototype", {
get() {
invoked = true;
throw new Error("Constructor metadata must remain inert");
},
});
for (const kind of [
"summary",
"capture",
"api-surface",
"environment-manifest",
"environment-state",
] as const) {
expect(
inspectAnalysisView({
source: {
kind: "inline",
evidence: { ...parent, raw_result: { constructor } },
},
view: { kind },
}),
).toMatchObject({ ok: false, error: { _tag: "AnalysisInputError" } });
}
expect(invoked).toBe(false);
});
it("rejects inline proxy values before reflection or parsing can run traps", () => {
const parent = analysisViewCaptureEvidence();
let invoked = false;
const trap = () => {
invoked = true;
throw new Error("Proxy behavior must remain inert");
};
const revoked = Proxy.revocable([], {});
revoked.revoke();
for (const value of [
new Proxy({}, { getPrototypeOf: trap, ownKeys: trap, get: trap }),
new Proxy(() => undefined, { get: trap }),
revoked.proxy,
])
for (const kind of [
"summary",
"capture",
"api-surface",
"environment-manifest",
"environment-state",
] as const) {
const result = inspectAnalysisView({
source: { kind: "inline", evidence: { ...parent, raw_result: value } },
view: { kind },
});
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
}
expect(invoked).toBe(false);
});
it("keeps ignored array metadata inert through parsing and projection", () => {
const capture = analysisViewCaptureFixture();
const parent = createEvidence(
{
path: capture.artifact.path,
sha256: capture.artifact.sha256!,
format: "file",
},
{ id: "fixture", name: "fixture", version: "1" },
{
operation: "inspect_web_network_capture",
parameters: { sensitive_value_count: 0 },
result: capture,
rawResult: [],
},
);
if (!Array.isArray(parent.raw_result)) throw new Error("Expected raw array");
const array = parent.raw_result;
let invoked = false;
const getter = () => {
invoked = true;
throw new Error("Ignored array metadata must remain inert");
};
for (let index = 0; index < 10_000; index += 1)
Object.defineProperty(array, `metadata-${index}`, { get: getter });
Object.defineProperty(array, Symbol("metadata"), { get: getter });
for (const kind of [
"summary",
"capture",
"api-surface",
"environment-manifest",
"environment-state",
] as const) {
const result = inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind },
});
if (!result.ok) throw result.error;
expect(result.value.evidence_links).toEqual([parent.evidence_id]);
}
expect(invoked).toBe(false);
expect(parseEvidence(parent).raw_result).toEqual([]);
});
it("keeps inline-envelope admission live alongside capture validation and grouping", () => {
// Each clone alone fits; both graphs coexist while the view is projected.
const capture = analysisViewObjectDenseCapture(40_000);
const parent = analysisViewCaptureEvidence(capture);
for (const kind of ["summary", "capture"] as const) {
const projected = inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind },
});
if (!projected.ok) throw projected.error;
expect(projected.value.evidence_links).toEqual([parent.evidence_id]);
}
const retained = inspectAnalysisView(
{
source: { kind: "retained-evidence", evidence_id: parent.evidence_id },
view: { kind: "api-surface" },
},
() => parent,
);
if (!retained.ok) throw retained.error;
expect(
inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind: "api-surface" },
}),
).toMatchObject({
ok: false,
error: { _tag: "AnalysisResourceConstraintError", resource: "memory" },
});
});
it("admits raw_result and parameters before authenticating a portable capture", () => {
const dense = analysisViewObjectDenseCapture();
const parent = analysisViewCaptureEvidence();
for (const field of ["raw_result", "parameters"] as const)
for (const kind of [
"api-surface",
"environment-manifest",
"environment-state",
] as const) {
const result = inspectAnalysisView({
source: { kind: "inline", evidence: { ...parent, [field]: dense } },
view: { kind },
});
// Refuse expansion before hashing can report the deliberately stale ID.
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisResourceConstraintError", resource: "memory" },
});
}
});
const manifestView = { kind: "environment-manifest" } as const;
const writeEvidenceFile = async (value: unknown): Promise<string> => {
const root = await createTestTempDirectory("rea-analysis-view-file-");
const path = join(root, "capture.json");
await writeFile(path, JSON.stringify(value));
return path;
};
describe("inspectAnalysisViewWithFiles", () => {
const view = manifestView;
it("projects an Evidence file exactly as the same Evidence inline", async () => {
const parent = analysisViewCaptureEvidence(
environmentManifestCaptureFixture(),
);
const fromFile = await inspectAnalysisViewWithFiles({
source: { kind: "evidence-file", path: await writeEvidenceFile(parent) },
view,
});
const inline = inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view,
});
if (!fromFile.ok) throw fromFile.error;
if (!inline.ok) throw inline.error;
expect(fromFile.value.normalized_result).toEqual(
inline.value.normalized_result,
);
expect(fromFile.value.parameters).toEqual(inline.value.parameters);
});
it("accepts from a file a capture whose response text exceeds the inline budget", async () => {
const capture = environmentManifestCaptureFixture();
const reported = capture.records[0]!.reported as {
response: { content: { text: string } };
};
// Inline admission charges about 14 bytes per character; this body alone overruns it.
reported.response.content.text = "a".repeat(
Math.ceil(WEB_NETWORK_CAPTURE_LIMITS.outputBytes / 14) + 1024,
);
const parent = analysisViewCaptureEvidence(capture);
expect(
inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view,
}),
).toMatchObject({
ok: false,
error: { _tag: "AnalysisResourceConstraintError", resource: "memory" },
});
const fromFile = await inspectAnalysisViewWithFiles({
source: { kind: "evidence-file", path: await writeEvidenceFile(parent) },
view,
});
if (!fromFile.ok) throw fromFile.error;
expect(fromFile.value.parameters).toMatchObject({
parent_evidence_id: parent.evidence_id,
});
});
it("refuses a relative path, a missing file and JSON that is not Evidence", async () => {
expect(
await inspectAnalysisViewWithFiles({
source: { kind: "evidence-file", path: "capture.json" },
view,
}),
).toMatchObject({ ok: false, error: { _tag: "AnalysisInputError" } });
const missing = join(
await createTestTempDirectory("rea-analysis-view-missing-"),
"absent.json",
);
expect(
await inspectAnalysisViewWithFiles({
source: { kind: "evidence-file", path: missing },
view,
}),
).toMatchObject({ ok: false, error: { _tag: "EvidenceFileError" } });
const notEvidence = await inspectAnalysisViewWithFiles({
source: {
kind: "evidence-file",
path: await writeEvidenceFile({ hello: 1 }),
},
view,
});
expect(notEvidence).toMatchObject({
ok: false,
error: { _tag: "EvidenceFileError", reason: "wrong-kind" },
});
if (notEvidence.ok) throw new Error("Expected invalid Evidence file");
expect(projectAnalysisError(notEvidence.error)).toMatchObject({
details: {
content_issues: expect.arrayContaining([
{
path: ["evidence_id"],
reason: "missing_argument",
expected: "string",
},
]),
},
});
});
it("returns typed cancellation for a cancelled file request", async () => {
const controller = new AbortController();
const reason = Object.freeze({ requested: "view-stop" });
controller.abort(reason);
const parent = analysisViewCaptureEvidence(
environmentManifestCaptureFixture(),
);
const result = await inspectAnalysisViewWithFiles(
{
source: {
kind: "evidence-file",
path: await writeEvidenceFile(parent),
},
view,
},
undefined,
{ signal: controller.signal },
);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
expect(result.ok ? undefined : result.error.cause).toBe(reason);
});
it("returns typed cancellation when a running file request is aborted", async () => {
const controller = new AbortController();
const reason = Object.freeze({ requested: "running-view-stop" });
const parent = analysisViewCaptureEvidence(
environmentManifestCaptureFixture(),
);
const path = await writeEvidenceFile(parent);
const running = inspectAnalysisViewWithFiles(
{ source: { kind: "evidence-file", path }, view },
undefined,
{ signal: controller.signal },
);
// Queued before the request yields after its synchronous work, so it is seen even if the read already finished.
setImmediate(() => controller.abort(reason));
const result = await running;
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
expect(result.ok ? undefined : result.error.cause).toBe(reason);
});
it("leaves file sources to the file-aware entry point", () => {
expect(
inspectAnalysisView({
source: { kind: "evidence-file", path: "/tmp/capture.json" },
view,
}),
).toMatchObject({
ok: false,
error: { _tag: "AnalysisCapabilityUnavailableError" },
});
});
});
describe("inspectAnalysisViewWithFiles scenario parents", () => {
const view = manifestView;
it("accepts from a file a scenario capture whose response bodies exceed the inline budget", async () => {
const blob = "a".repeat(
Math.ceil(WEB_NETWORK_CAPTURE_LIMITS.outputBytes / 14) + 1024,
);
const parent = scenarioCaptureEvidence(
scenarioCaptureFixture(
SCENARIO_EXCHANGES.map((exchange, index) =>
index === 1 ? { ...exchange, body: `{"blob":"${blob}"}` } : exchange,
),
),
);
expect(
inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view,
}),
).toMatchObject({
ok: false,
error: { _tag: "AnalysisResourceConstraintError", resource: "memory" },
});
const fromFile = await inspectAnalysisViewWithFiles({
source: { kind: "evidence-file", path: await writeEvidenceFile(parent) },
view,
});
if (!fromFile.ok) throw fromFile.error;
expect(fromFile.value.parameters).toMatchObject({
parent_evidence_id: parent.evidence_id,
});
});
});