Files
reverse-engineering-anything/src/application/analysisView/AnalysisViewService.test.ts
T
f93a0335a8 feat(analysis-view): project rich captures from Evidence files and template path pairs (#5293)
Project large local Evidence captures through the authenticated file-source boundary and add guarded opt-in sibling-pair path inference. Preserve request cancellation, resource ownership and file-relative validation diagnostics.

Validated relevant source and compiled CLI/MCP/environment boundaries, static/build/docs/catalog gates, and a real isolated Gitea 28.2.0 capture through CLI, default stdio MCP and HTTP replay. Current-head automated review passed; unrelated long-running platform jobs may still be pending.

Co-authored-by: Edgar Arakelyan <edgar.arakelyan@scale.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: gensokyo-dev <gensokyo-dev@users.noreply.github.com>
2026-10-20 10:33:52 +08:00

1095 lines
35 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import {
analysisViewCaptureFixture,
analysisViewCaptureEvidence,
analysisViewObjectDenseCapture,
environmentManifestCaptureFixture,
} from "../../../tests/fixtures/analysisView.js";
import { describe, expect, it, vi } 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 { readEvidenceFileSource } from "./evidenceFileSource.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
vi.mock("./evidenceFileSource.js", { spy: true });
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" },
] 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.each(["summary", "capture", "api-surface", "environment-manifest"] 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"] 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",
] 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",
] 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"] 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",
] 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",
] 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",
] 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"] 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();
controller.abort();
const parent = analysisViewCaptureEvidence(
environmentManifestCaptureFixture(),
);
expect(
await inspectAnalysisViewWithFiles(
{
source: {
kind: "evidence-file",
path: await writeEvidenceFile(parent),
},
view,
},
undefined,
{ signal: controller.signal },
),
).toMatchObject({ ok: false, error: { _tag: "AnalysisCancelledError" } });
});
it("returns typed cancellation when a running file request is aborted", async () => {
const controller = new AbortController();
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());
expect(await running).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
});
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 and cancellation", () => {
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,
});
});
it("reports cancellation rather than the authentication failure of a file aborted while it is authenticated", async () => {
const controller = new AbortController();
const parent = analysisViewCaptureEvidence(
environmentManifestCaptureFixture(),
);
const path = await writeEvidenceFile({
...parent,
evidence_id: `ev_${"0".repeat(64)}`,
});
const { readEvidenceFileSource: read } = await vi.importActual<
typeof import("./evidenceFileSource.js")
>("./evidenceFileSource.js");
// The abort arrives once the read has finished, while the stale ID is being authenticated.
vi.mocked(readEvidenceFileSource).mockImplementationOnce(
async (...args) => {
const loaded = await read(...args);
controller.abort();
return loaded;
},
);
expect(
await inspectAnalysisViewWithFiles(
{ source: { kind: "evidence-file", path }, view },
undefined,
{ signal: controller.signal },
),
).toMatchObject({ ok: false, error: { _tag: "AnalysisCancelledError" } });
});
});