mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
292 lines
8.4 KiB
TypeScript
292 lines
8.4 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { readFile, realpath, rm, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "./fixtures/temporaryDirectory.js";
|
|
|
|
import {
|
|
executeNodeCharacterization,
|
|
prepareNodeCharacterization,
|
|
} from "../src/application/NodeRuntimeCharacterizationService.js";
|
|
import type {
|
|
JavaScriptReplayHost,
|
|
JavaScriptReplayPolicy,
|
|
JavaScriptReplayRunner,
|
|
} from "../src/application/JavaScriptReplayPlanning.js";
|
|
import { PermissionAuthority } from "../src/application/PermissionAuthority.js";
|
|
import { createPermissionPolicy } from "../src/domain/permissionPolicy.js";
|
|
import {
|
|
nodeCharacterizationPreparationInputSchema,
|
|
nodeCharacterizationPreparationOutputSchema,
|
|
} from "../src/domain/nodeRuntimeCharacterization.js";
|
|
|
|
const roots: string[] = [];
|
|
const sha256 = (value: Uint8Array): string =>
|
|
createHash("sha256").update(value).digest("hex");
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
roots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
describe("Node runtime characterization", () => {
|
|
it("prepares without execution, then executes only the approved exact plan", async () => {
|
|
const fixture = await createFixture();
|
|
let executions = 0;
|
|
const dependencies = dependenciesFor(fixture.root, () => {
|
|
executions += 1;
|
|
});
|
|
const input = preparationInput(fixture);
|
|
|
|
const prepared = await prepareNodeCharacterization(dependencies, input);
|
|
expect(prepared.ok).toBe(true);
|
|
expect(executions).toBe(0);
|
|
if (!prepared.ok) throw prepared.error;
|
|
const output = nodeCharacterizationPreparationOutputSchema.parse(
|
|
prepared.value,
|
|
);
|
|
expect(output).toMatchObject({
|
|
phase: "preparation",
|
|
plan: {
|
|
preparation_sha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
|
|
runtime: { family: "ecmascript", provider_id: "node-javascript" },
|
|
authority: {
|
|
preparation_approved: true,
|
|
execution_approved: false,
|
|
},
|
|
},
|
|
transformation: {
|
|
original_sha256: fixture.sha256,
|
|
output_format: "commonjs-factory",
|
|
},
|
|
transformation_evidence: {
|
|
predicate_type: "rea.javascript-export-transformation/v1",
|
|
authority: "shipped-artifact",
|
|
},
|
|
replay: { phase: "plan" },
|
|
});
|
|
|
|
const stale = await executeNodeCharacterization(dependencies, {
|
|
execution_approved: true,
|
|
approved_plan_sha256: "0".repeat(64),
|
|
preparation: input,
|
|
});
|
|
expect(stale.ok).toBe(false);
|
|
expect(!stale.ok && stale.error._tag).toBe("ReplayPlanStaleError");
|
|
expect(executions).toBe(0);
|
|
|
|
const executed = await executeNodeCharacterization(dependencies, {
|
|
execution_approved: true,
|
|
approved_plan_sha256: output.plan.plan_sha256,
|
|
preparation: input,
|
|
});
|
|
expect(executed.ok && executed.value).toMatchObject({
|
|
phase: "execution",
|
|
replay: {
|
|
phase: "execute",
|
|
evidence: { authority: "controlled-replay" },
|
|
},
|
|
evidence: {
|
|
predicate_type: "rea.runtime-characterization/v1",
|
|
authority: "controlled-replay",
|
|
},
|
|
});
|
|
expect(executions).toBe(1);
|
|
expect(await readFile(fixture.path)).toEqual(Buffer.from(fixture.bytes));
|
|
});
|
|
|
|
it("rejects aliases that cannot fit the provider-neutral plan identity", async () => {
|
|
const input = preparationInput(await createFixture());
|
|
expect(() =>
|
|
nodeCharacterizationPreparationInputSchema.parse({
|
|
...input,
|
|
selected_alias: "a".repeat(201),
|
|
}),
|
|
).toThrow();
|
|
});
|
|
|
|
it("maps replay-only alias syntax to a provider-neutral module identity", async () => {
|
|
const fixture = await createFixture();
|
|
const input = preparationInput(fixture);
|
|
const alias = "@scope/pkg";
|
|
const prepared = await prepareNodeCharacterization(
|
|
dependenciesFor(fixture.root, () => undefined),
|
|
{
|
|
...input,
|
|
selected_alias: alias,
|
|
replay: {
|
|
...input.replay,
|
|
left: {
|
|
...input.replay.left,
|
|
entry_alias: alias,
|
|
modules: input.replay.left.modules.map((module) => ({
|
|
...module,
|
|
alias,
|
|
})),
|
|
},
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(prepared.ok && prepared.value).toMatchObject({
|
|
plan: {
|
|
callable: {
|
|
module_id: expect.stringMatching(/^module\/[a-f0-9]{64}$/u),
|
|
},
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
const createFixture = async () => {
|
|
const root = await createTestTempDirectory("rea-characterization-");
|
|
roots.push(root);
|
|
const path = join(root, "bundle.js");
|
|
const source = "const hidden = (value) => value.trim();";
|
|
const selected = "(value) => value.trim()";
|
|
const bytes = new TextEncoder().encode(source);
|
|
await writeFile(path, bytes);
|
|
const byteStart = source.indexOf(selected);
|
|
return {
|
|
root,
|
|
path,
|
|
bytes,
|
|
sha256: sha256(bytes),
|
|
byteStart,
|
|
selectedBytes: new TextEncoder().encode(selected),
|
|
};
|
|
};
|
|
|
|
const preparationInput = (
|
|
fixture: Awaited<ReturnType<typeof createFixture>>,
|
|
) => ({
|
|
preparation_approved: true,
|
|
selected_alias: "bundle",
|
|
expected_effect: "pure" as const,
|
|
instrumentation: {
|
|
artifact_path: fixture.path,
|
|
artifact_sha256: fixture.sha256,
|
|
selection: {
|
|
byte_start: fixture.byteStart,
|
|
byte_end: fixture.byteStart + fixture.selectedBytes.byteLength,
|
|
selected_sha256: sha256(fixture.selectedBytes),
|
|
export_name: "selected",
|
|
},
|
|
},
|
|
replay: {
|
|
mode: "plan" as const,
|
|
left: {
|
|
modules: [
|
|
{
|
|
alias: "bundle",
|
|
path: fixture.path,
|
|
format: "commonjs-factory" as const,
|
|
role: "module" as const,
|
|
dependencies: {},
|
|
},
|
|
],
|
|
entry_alias: "bundle",
|
|
entry_export: "selected",
|
|
},
|
|
cases: [{ case_id: "trim", arguments: [" value "] }],
|
|
approved: false,
|
|
},
|
|
});
|
|
|
|
const dependenciesFor = (root: string, onExecute: () => void) => {
|
|
const policy: JavaScriptReplayPolicy = {
|
|
enabled: true,
|
|
roots: [root],
|
|
nodePath: process.execPath,
|
|
bubblewrapPath: process.execPath,
|
|
systemdRunPath: process.execPath,
|
|
systemctlPath: process.execPath,
|
|
shellPath: process.execPath,
|
|
};
|
|
const host: JavaScriptReplayHost = {
|
|
readSource: async (path, maximumBytes) => {
|
|
const canonicalPath = await realpath(path);
|
|
const bytes = await readFile(canonicalPath);
|
|
if (bytes.byteLength > maximumBytes)
|
|
throw new RangeError("fixture limit");
|
|
return { canonicalPath, bytes };
|
|
},
|
|
identifyExecutable: async (path) => ({
|
|
path,
|
|
version: "fixture-node-24",
|
|
sha256: "1".repeat(64),
|
|
}),
|
|
identifyWorker: async () => ({
|
|
path: "/fixture/worker.js",
|
|
version: "fixture-worker",
|
|
sha256: "2".repeat(64),
|
|
}),
|
|
identifyRuntimeClosure: async (path) => [
|
|
{
|
|
sourcePath: path,
|
|
destinationPath: "/runtime/node",
|
|
sha256: "1".repeat(64),
|
|
},
|
|
],
|
|
seccompDigest: () => "3".repeat(64),
|
|
probe: async () => undefined,
|
|
};
|
|
const runner: JavaScriptReplayRunner = {
|
|
execute: async (prepared) => {
|
|
onExecute();
|
|
return {
|
|
schema_version: 1,
|
|
plan_digest: prepared.publicPlan.plan_digest,
|
|
outcomes: [
|
|
{
|
|
case_id: "trim",
|
|
outcome: "return",
|
|
value: "value",
|
|
input_sha256:
|
|
prepared.publicPlan.cases[0]?.sha256 ?? "0".repeat(64),
|
|
output_sha256: "4".repeat(64),
|
|
truncated: false,
|
|
},
|
|
],
|
|
stderr: "",
|
|
termination: "completed",
|
|
cleanup: { state: "complete", residual_resources: [] },
|
|
limitations: ["fixture runner"],
|
|
reproducer: null,
|
|
};
|
|
},
|
|
};
|
|
const ceiling = {
|
|
capability: "javascript_replay" as const,
|
|
roots: [root],
|
|
executables: [
|
|
policy.nodePath,
|
|
policy.bubblewrapPath,
|
|
policy.systemdRunPath,
|
|
policy.systemctlPath,
|
|
policy.shellPath,
|
|
],
|
|
environment_names: [],
|
|
network: "none" as const,
|
|
mount: true,
|
|
};
|
|
const authority = new PermissionAuthority(
|
|
createPermissionPolicy(
|
|
[ceiling],
|
|
[
|
|
{
|
|
...ceiling,
|
|
grant_id: "administrator:javascript_replay",
|
|
lifetime: "administrator",
|
|
operation_identity: null,
|
|
expires_at: null,
|
|
},
|
|
],
|
|
),
|
|
);
|
|
return { policy, host, runner, authority };
|
|
};
|