import { execFile } from "node:child_process"; import { copyFile, mkdir, rm, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { promisify } from "node:util"; import { afterEach, describe, expect, it } from "vitest"; import { createTestTempDirectory } from "./fixtures/temporaryDirectory.js"; import { JAVASCRIPT_APPLICATION_VERSION_COMPARISON_EXAMPLE, JAVASCRIPT_FEATURE_TRACE_FULL_EVIDENCE_EXAMPLE, JAVASCRIPT_FEATURE_TRACE_EXAMPLE, JAVASCRIPT_VERSION_COMPARISON_FULL_EVIDENCE_EXAMPLE, } from "../src/contracts/javascriptApplicationWorkflowExamples.js"; import { createEvidenceBundle, serializeEvidenceBundle, } from "../src/domain/evidenceBundle.js"; import { analyzeJavaScriptApplication } from "../src/application/JavaScriptApplicationService.js"; import { permissionAuthorityForRoot } from "./fixtures/permissionAuthority.js"; import { REPLAY_MACHINE_RUN_EXAMPLE } from "../src/contracts/replayMachineExample.js"; import { javascriptApplicationAnalysisResultSchema } from "../src/domain/javascriptApplicationAnalysis.js"; const execute = promisify(execFile); describe("application workflow CLI parity", () => { const temporary: string[] = []; afterEach(async () => { await Promise.all( temporary .splice(0) .map(async (path) => rm(path, { recursive: true, force: true })), ); }); it("accepts inline trace JSON and file-backed comparison JSON", async () => { const traced = await runCli([ "trace-application-feature", JSON.stringify(JAVASCRIPT_FEATURE_TRACE_FULL_EVIDENCE_EXAMPLE), "--json", ]); expect(traced).toMatchObject({ operation: "trace_application_feature", normalized_result: { schema_version: 1 }, }); const root = await createTestTempDirectory("rea-application-cli-"); temporary.push(root); const comparisonPath = join(root, "comparison.json"); await writeFile( comparisonPath, JSON.stringify(JAVASCRIPT_VERSION_COMPARISON_FULL_EVIDENCE_EXAMPLE), ); const compared = await runCli([ "compare-application-versions", comparisonPath, "--json", ]); expect(compared).toMatchObject({ operation: "compare_application_versions", normalized_result: { schema_version: 1, summary: { unknown: expect.any(Number) }, }, }); }, 20_000); it("traces the same authenticated semantic graph through the CLI", async () => { const root = await createTestTempDirectory("rea-semantic-cli-"); temporary.push(root); await writeFile( join(root, "app.js"), "function add(value) { return value + 1; } add(2);", ); const authority = await permissionAuthorityForRoot( root, ["investigation_input"], ["investigation_input"], ); const analyzed = await analyzeJavaScriptApplication(authority, { input_path: root, approved: true, }); if (!analyzed.ok) throw analyzed.error; const result = javascriptApplicationAnalysisResultSchema.parse( analyzed.value.normalized_result, ); if (result.schema_version !== 2) throw new TypeError("Expected semantic application Evidence"); const seed = result.semantic_graph.relations[0]?.source_node_id; if (seed === undefined) throw new TypeError("Expected at least one semantic relation"); const traced = await runCli([ "trace-javascript-semantics", JSON.stringify({ application: analyzed.value, query: { seed: { kind: "semantic-node", node_id: seed }, direction: "forward-influence", include_ambiguous_dynamic_edges: true, }, }), "--json", ]); expect(traced).toMatchObject({ operation: "trace_javascript_semantics", predicate_type: "rea.javascript-semantic-trace/v1", normalized_result: { source_evidence_id: analyzed.value.evidence_id, source_graph_id: result.semantic_graph.graph_id, }, }); }, 20_000); it("resolves Evidence IDs from an explicitly authorized bundle", async () => { const root = await createTestTempDirectory("rea-application-bundle-cli-"); temporary.push(root); const bundlePath = join(root, "evidence.json"); await writeFile( bundlePath, serializeEvidenceBundle( createEvidenceBundle([ JAVASCRIPT_VERSION_COMPARISON_FULL_EVIDENCE_EXAMPLE.left, JAVASCRIPT_VERSION_COMPARISON_FULL_EVIDENCE_EXAMPLE.right, ]), ), ); const environment = { REA_EVIDENCE_ROOTS_JSON: JSON.stringify([root]), }; const traced = await runCli( [ "trace-application-feature", JSON.stringify({ ...JAVASCRIPT_FEATURE_TRACE_EXAMPLE, application_evidence_id: JAVASCRIPT_FEATURE_TRACE_FULL_EVIDENCE_EXAMPLE.application .evidence_id, }), "--evidence-bundle", bundlePath, "--json", ], environment, ); expect(traced).toMatchObject({ operation: "trace_application_feature", normalized_result: { schema_version: 1 }, }); const compared = await runCli( [ "compare-application-versions", JSON.stringify({ ...JAVASCRIPT_APPLICATION_VERSION_COMPARISON_EXAMPLE, left_evidence_id: JAVASCRIPT_VERSION_COMPARISON_FULL_EVIDENCE_EXAMPLE.left .evidence_id, right_evidence_id: JAVASCRIPT_VERSION_COMPARISON_FULL_EVIDENCE_EXAMPLE.right .evidence_id, }), "--evidence-bundle", bundlePath, "--json", ], environment, ); expect(compared).toMatchObject({ operation: "compare_application_versions", normalized_result: { schema_version: 1 }, }); const missingRecord = await runCli( [ "trace-application-feature", JSON.stringify(JAVASCRIPT_FEATURE_TRACE_EXAMPLE), "--evidence-bundle", bundlePath, "--json", ], environment, ); expect(missingRecord).toMatchObject({ code: "evidence_integrity_mismatch", details: { reason: "missing" }, }); const outsideRoot = await createTestTempDirectory( "rea-outside-bundle-cli-", ); temporary.push(outsideRoot); const outsideBundlePath = join(outsideRoot, "evidence.json"); await writeFile( outsideBundlePath, serializeEvidenceBundle(createEvidenceBundle([])), ); const denied = await runCli( [ "trace-application-feature", JSON.stringify(JAVASCRIPT_FEATURE_TRACE_EXAMPLE), "--evidence-bundle", outsideBundlePath, "--json", ], environment, ); expect(denied).toMatchObject({ code: "permission_required" }); }, 20_000); it("compares exact export shapes from an authorized Evidence bundle", async () => { const root = await createTestTempDirectory("rea-export-shape-cli-"); temporary.push(root); const leftRoot = join(root, "left"); const rightRoot = join(root, "right"); await Promise.all([mkdir(leftRoot), mkdir(rightRoot)]); await Promise.all([ copyFile( join(process.cwd(), "tests/fixtures/replay/parser.mjs"), join(leftRoot, "parser.mjs"), ), copyFile( join(process.cwd(), "tests/fixtures/replay/parser-v2.mjs"), join(rightRoot, "parser.mjs"), ), ]); const authority = await permissionAuthorityForRoot( root, ["investigation_input"], ["investigation_input"], ); const [left, right] = await Promise.all([ analyzeJavaScriptApplication(authority, { input_path: leftRoot, approved: true, }), analyzeJavaScriptApplication(authority, { input_path: rightRoot, approved: true, }), ]); if (!left.ok) throw left.error; if (!right.ok) throw right.error; const bundlePath = join(root, "evidence.json"); await writeFile( bundlePath, serializeEvidenceBundle(createEvidenceBundle([left.value, right.value])), ); const compared = await runCli( [ "compare-javascript-export-shapes", JSON.stringify({ left_evidence_id: left.value.evidence_id, right_evidence_id: right.value.evidence_id, left_module_path: "parser.mjs", left_export_name: "default", right_module_path: "parser.mjs", right_export_name: "default", }), "--evidence-bundle", bundlePath, "--json", ], { REA_EVIDENCE_ROOTS_JSON: JSON.stringify([root]) }, ); expect(compared).toMatchObject({ operation: "compare_javascript_export_shapes", predicate_type: "rea.javascript-export-shape-comparison/v1", normalized_result: { summary: { added: 1, removed: 0, changed: 0, unknown: 0 }, changes: [ { status: "added", path: "/depth", right: { availability: "literal", value: 1 }, }, ], }, }); }, 20_000); it("returns safe actionable JSON validation details", async () => { const malformed = await runCli([ "trace-application-feature", "{not-json", "--json", ]); expect(malformed).toMatchObject({ code: "invalid_request", retryable: true, details: { issues: [{ path: [], reason: "invalid_format", expected: "JSON" }], }, }); expect(JSON.stringify(malformed)).not.toContain("not-json"); const missing = await runCli(["trace-application-feature", "{}", "--json"]); expect(missing).toMatchObject({ code: "invalid_request", details: { issues: expect.arrayContaining([ expect.objectContaining({ path: ["seed"], reason: "missing_argument", }), ]), }, }); }, 20_000); it("runs a replay machine from inline and file-backed JSON", async () => { const input = REPLAY_MACHINE_RUN_EXAMPLE; const inline = await runCli([ "run-replay-machine", JSON.stringify(input), "--json", ]); expect(inline).toMatchObject({ schema_version: 1, final_state: "complete", decisions: [{ outcome: "matched" }], transition_journal: [ { captured_aliases: [{ name: "token", sensitive: true }] }, ], }); expect(JSON.stringify(inline)).not.toContain("opaque"); const root = await createTestTempDirectory("rea-replay-machine-cli-"); temporary.push(root); const inputPath = join(root, "run.json"); await writeFile(inputPath, JSON.stringify(input)); const fileBacked = await runCli([ "run-replay-machine", inputPath, "--json", ]); expect(fileBacked).toEqual(inline); const invalid = await runCli([ "run-replay-machine", JSON.stringify({ machine: input.machine, events: [ { protocol: "http", connection: "not_applicable", at_ms: 0, method: null, path: "/callback", headers: {}, body: "private-cli-body", }, ], }), "--json", ]); expect(invalid).toMatchObject({ code: "invalid_request", details: { issues: expect.arrayContaining([ expect.objectContaining({ path: ["events", 0, "method"], }), ]), }, }); expect(JSON.stringify(invalid)).not.toContain("private-cli-body"); }, 20_000); }); const runCli = async ( arguments_: readonly string[], environment: Readonly> = {}, ): Promise => { try { const { stdout } = await execute( process.execPath, ["scripts/rea.mjs", ...arguments_], { cwd: process.cwd(), env: { ...process.env, ...environment }, maxBuffer: 16 * 1_024 * 1_024, }, ); return JSON.parse(stdout); } catch (cause: unknown) { if ( typeof cause === "object" && cause !== null && "stdout" in cause && typeof cause.stdout === "string" ) return JSON.parse(cause.stdout); throw cause; } };