Files
rea/tests/process-global/analysisViewAuthenticationFailures.test.ts
gensokyo-devandEntrpi 37827af591 feat(artifacts): compare inventories from Evidence sources (#7869)
* feat(artifacts): compare artifacts by Evidence reference and from the CLI

compare_artifacts made agents send back the complete inventory Evidence
that REA already retains, and the CLI had no artifact comparison.

Each side of compare_artifacts now takes exactly one of: complete inline
Evidence (unchanged), {kind: "retained-evidence", evidence_id}, or
{kind: "evidence-file", path, evidence_id?}. The two references are the
sources inspect_analysis_view accepts; both tools now compose one
definition in evidenceReference.ts. A summary still resolves to its exact
retained parent, and every source form gives the same comparison Evidence.
An unresolvable retained reference is refused with a typed missing
reference error.

The application layer owns source resolution and the comparison Evidence,
so MCP and the new `rea compare-artifacts` command share one workflow.
readEvidenceFileSource reports bundle-selection refusals at the caller's
own field path.

* refactor(artifacts): resolve compare_artifacts sides like compare_functions

compare_functions, compare_wasm_interfaces and the JavaScript application
comparisons name each side with evidenceInputSchema and resolve it with
resolvePairedEvidenceRequest. compare_artifacts reached the same result
for inline and retained sides through its own dispatch.

evidenceSourceInputSchema now extends evidenceInputSchema with the
Evidence-file reference, and resolvePairedEvidenceSources sends inline and
retained sides to the same resolveEvidenceInput and file sides to the
shared Evidence file reader. compare_artifacts uses both, so the forms it
shares with compare_functions are parsed and resolved by one owner. The
advertised input schema is unchanged.

* docs(cli): point rea compare to compare-artifacts for inventory contents

rea compare (compare_bundles) compares records by Evidence identity and
explicit pairs and never reads artifact contents, so two build
inventories stay unresolved there. Its help and the CLI guide now name
compare-artifacts for that comparison.

* fix(artifacts): resolve summary parents within Evidence source bundles

* perf(evidence): reuse file-owned immutable authentication proofs

* test(evidence): inject failures at both authentication boundaries

---------

Co-authored-by: Entrpi <[email protected]>
2026-10-29 03:12:03 +08:00

129 lines
4.2 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, onTestFinished, vi } from "vitest";
import { createApplicationMcpHarness } from "../fixtures/applicationMcpHarness.js";
import {
analysisViewCaptureEvidence,
environmentManifestCaptureFixture,
} from "../fixtures/analysisView.js";
import { createTestBinarySession } from "../fixtures/binarySession.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
const unexpectedFailure = (
kind: "TypeError" | "Error",
source: "inline" | "evidence-file",
): Error => {
const message = `injected ${kind} during ${source} Evidence authentication`;
return kind === "TypeError" ? new TypeError(message) : new Error(message);
};
const manifestView = {
kind: "environment-manifest",
template_rules: ["opaque-token", "sibling-pair-variance"],
} as const;
const authenticationEntries = ["parseEvidence", "immutableEvidence"] as const;
const createOwnedHarness = async () => {
const harness = await createApplicationMcpHarness({
session: createTestBinarySession(() => {
throw new Error("stored Evidence views must not launch a provider");
}),
});
onTestFinished(async () => {
vi.restoreAllMocks();
await harness.close();
});
return harness;
};
it.each(
(["inline", "evidence-file"] as const).flatMap((source) =>
(["TypeError", "Error"] as const).map(
(failureKind) => [source, failureKind] as const,
),
),
)(
"preserves an unexpected parser failure from %s input (%s) without retaining a view",
async (sourceKind, failureKind) => {
const harness = await createOwnedHarness();
const parent = analysisViewCaptureEvidence(
environmentManifestCaptureFixture(),
);
expect(harness.session.recordEvidence(parent).ok).toBe(true);
const before = harness.session.exportEvidenceBundle();
let source: Record<string, unknown>;
if (sourceKind === "inline") {
source = { kind: "inline", evidence: parent };
} else {
const root = await createTestTempDirectory("rea-view-auth-failure-");
const path = join(root, "parent.json");
await writeFile(path, JSON.stringify(parent));
source = { kind: "evidence-file", path };
}
const evidenceModule = await import("../../src/domain/evidence.js");
const failure = unexpectedFailure(failureKind, sourceKind);
const authenticators = authenticationEntries.map((name) =>
vi.spyOn(evidenceModule, name).mockImplementation(() => {
throw failure;
}),
);
const response = await harness.client.callTool({
name: "inspect_analysis_view",
arguments: { source, view: manifestView },
});
expect(response).toMatchObject({
isError: true,
content: [{ type: "text", text: failure.message }],
});
expect(response.structuredContent).toBeUndefined();
expect(
authenticators.flatMap((authenticate) => authenticate.mock.calls),
).toContainEqual([
expect.objectContaining({ evidence_id: parent.evidence_id }),
]);
expect(harness.session.exportEvidenceBundle()).toEqual(before);
},
);
it("uses retained Evidence without reparsing its parent", async () => {
const harness = await createOwnedHarness();
const parent = analysisViewCaptureEvidence(
environmentManifestCaptureFixture(),
);
expect(harness.session.recordEvidence(parent).ok).toBe(true);
const evidenceModule = await import("../../src/domain/evidence.js");
for (const name of authenticationEntries) {
const originalParse = evidenceModule[name];
vi.spyOn(evidenceModule, name).mockImplementation((input) => {
if (
typeof input === "object" &&
input !== null &&
"evidence_id" in input &&
input.evidence_id === parent.evidence_id
)
throw new TypeError("retained source parent was reparsed");
return originalParse(input);
});
}
const response = await harness.client.callTool({
name: "inspect_analysis_view",
arguments: {
source: { kind: "retained-evidence", evidence_id: parent.evidence_id },
view: manifestView,
},
});
expect(response.isError).not.toBe(true);
expect(response.structuredContent).toBeDefined();
expect(harness.session.exportEvidenceBundle().records).toHaveLength(2);
});