refactor(process): remove the redundant scenario parsing alias

Use the authoritative scenario schema directly in all 25 test consumers. Preserve unknown-input annotations and every lifecycle, permission, normalization, identity and cleanup assertion; retain the separate branded capture validation boundary.
This commit is contained in:
morluto
2026-10-24 09:57:15 +08:00
parent 254f9d9ea2
commit 584120e802
26 changed files with 136 additions and 135 deletions
@@ -19,7 +19,7 @@ import { PROCESS_PROVIDER } from "../domain/process/processEvidenceProvider.js";
import { parseProcessCapture } from "../domain/process/processCaptureParsing.js";
import {
digestProcessCommitment,
parseProcessScenario,
processScenarioSchema,
processComparisonContract,
processScenarioCommitment,
} from "../domain/process/processScenario.js";
@@ -169,7 +169,7 @@ describe("reconstruction obligation candidates", () => {
});
it("does not bind an unknown process image to a packaged artifact obligation", () => {
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "/path/whose-launch-identity-is-unknown",
});
const base = emptyUnverifiedProcessCapture();
@@ -1,6 +1,6 @@
import { expect, it } from "vitest";
import {
parseProcessScenario,
processScenarioSchema,
processScenarioCommitment,
processComparisonContract,
digestProcessCommitment,
@@ -332,7 +332,7 @@ it.each([
it("refuses mixed terminal observation versions even with PID and port rules disabled", () => {
const base = emptyCapture();
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "/usr/bin/true",
normalization: { pids: false, ports: false },
});
+15 -10
View File
@@ -5,7 +5,6 @@ import { Ajv2020 } from "ajv/dist/2020.js";
import { SESSION_TOOL_CONTRACTS } from "../../contracts/sessionToolContracts.js";
import {
digestProcessCommitment,
parseProcessScenario,
PROCESS_TERMINAL_CELL_BUDGET,
processComparisonContract,
processScenarioCommitment,
@@ -32,7 +31,7 @@ it("advertises the same unsupported PTY environment name as runtime admission",
});
it("defaults the finalization interval to zero without changing committed identity", () => {
const scenario = parseProcessScenario(baseScenario);
const scenario = processScenarioSchema.parse(baseScenario);
expect(scenario, "parsed scenario carries the default").toMatchObject({
finalization_ms: 0,
@@ -49,13 +48,13 @@ it("defaults the finalization interval to zero without changing committed identi
it("accepts an explicit zero finalization interval", () => {
expect(
parseProcessScenario({ ...baseScenario, finalization_ms: 0 }),
processScenarioSchema.parse({ ...baseScenario, finalization_ms: 0 }),
"zero is the immediate-kill default, not an invalid budget",
).toMatchObject({ finalization_ms: 0 });
});
it("commits a positive finalization interval in both identity projections", () => {
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
...baseScenario,
finalization_ms: 500,
});
@@ -74,14 +73,14 @@ it.each([-1, 1.5, Number.MAX_SAFE_INTEGER + 1])(
"rejects the finalization interval %j",
(finalization_ms) => {
expect(() =>
parseProcessScenario({ ...baseScenario, finalization_ms }),
processScenarioSchema.parse({ ...baseScenario, finalization_ms }),
).toThrow();
},
);
it("accepts a combined lifecycle budget up to the safe-integer limit", () => {
const limit = Number.MAX_SAFE_INTEGER;
const accepted = parseProcessScenario({
const accepted = processScenarioSchema.parse({
...baseScenario,
timeout_ms: limit - 100,
idle_timeout_ms: 1,
@@ -94,7 +93,7 @@ it("accepts a combined lifecycle budget up to the safe-integer limit", () => {
"a total exactly at the representable limit is accepted",
).toBe(limit);
expect(
parseProcessScenario(baseScenario),
processScenarioSchema.parse(baseScenario),
"the default scenario stays far below the limit",
).toMatchObject({ timeout_ms: 30_000, settle_ms: 100, finalization_ms: 0 });
});
@@ -119,7 +118,11 @@ it.each([
],
])("rejects %s with a representability message", (_label, budget) => {
const parse = () =>
parseProcessScenario({ ...baseScenario, idle_timeout_ms: 1, ...budget });
processScenarioSchema.parse({
...baseScenario,
idle_timeout_ms: 1,
...budget,
});
expect(parse, "the combined budget is rejected").toThrow(
"timeout_ms + finalization_ms + settle_ms",
@@ -130,7 +133,7 @@ it.each([
});
it("keeps scenario identity without finalization while versioning corrected port semantics", () => {
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "/usr/bin/true",
arguments: ["-x"],
working_directory: "/tmp",
@@ -172,7 +175,9 @@ it("keeps scenario identity without finalization while versioning corrected port
});
it("admits the default and the exact combined terminal-cell budget", () => {
expect(parseProcessScenario({ executable: "/bin/true" }).terminal).toEqual({
expect(
processScenarioSchema.parse({ executable: "/bin/true" }).terminal,
).toEqual({
columns: 80,
rows: 24,
scrollback: 1_000,
-4
View File
@@ -347,7 +347,3 @@ const eventsOutOfOrder = (
/** Parsed instructions and resource bounds for one process experiment. */
export type ProcessScenario = z.infer<typeof processScenarioSchema>;
/** Parse untrusted process scenario input and apply safe default budgets. */
export const parseProcessScenario = (input: unknown): ProcessScenario =>
processScenarioSchema.parse(input);
+19 -19
View File
@@ -10,7 +10,7 @@ import {
} from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import { parseProcessScenario } from "../../domain/process/processScenario.js";
import { processScenarioSchema } from "../../domain/process/processScenario.js";
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
import { snapshotRoots } from "./FilesystemSnapshot.js";
import { classifyFilesystemEffects } from "./ProcessFilesystemEffects.js";
@@ -27,7 +27,7 @@ it.each(["after", "before"] as const)(
await writeFile(join(root, "a1.txt"), "first");
await writeFile(join(root, "a2.txt"), "second");
await writeFile(join(root, "z.txt"), "target");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -68,7 +68,7 @@ it("preserves proven deletion in an exhausted root while another root is partial
await writeFile(join(complete, "removed.txt"), "removed");
await writeFile(join(partial, "first.txt"), "first");
await writeFile(join(partial, "second.txt"), "second");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [complete, partial],
@@ -88,7 +88,7 @@ it("preserves proven deletion in an exhausted root while another root is partial
it("distinguishes unavailable content hashes from completed path enumeration", async () => {
const root = await createTestTempDirectory("rea-fs-hash-coverage-");
await writeFile(join(root, "removed.txt"), "larger than the byte budget");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -115,7 +115,7 @@ it.skipIf(process.platform === "win32")(
await writeFile(visible, "visible");
await mkdir(blocked);
await chmod(blocked, 0);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -155,7 +155,7 @@ it.skipIf(process.platform === "win32")(
await writeFile(unreadable, "permission denied");
await writeFile(readable, "still observed");
await chmod(unreadable, 0);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -219,7 +219,7 @@ it.each([
const file = join(root, "b.txt");
await writeFile(file, "before");
if (beforeOmitted) await writeFile(budgetFile, "budget");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -255,7 +255,7 @@ it("preserves observed content and metadata changes without treating missing has
const root = await createTestTempDirectory("rea-fs-observed-changes-");
for (const name of ["changed", "stable", "unhashed", "retyped"])
await writeFile(join(root, name), "before");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -276,7 +276,7 @@ it("preserves observed content and metadata changes without treating missing has
expect(effects.find(({ path }) => path === "root_0:retyped")?.status).toBe(
"modified",
);
const noHashes = parseProcessScenario({
const noHashes = processScenarioSchema.parse({
...scenario,
limits: { file_bytes: 1 },
});
@@ -300,7 +300,7 @@ it.skipIf(process.platform === "win32")(
const root = await createTestTempDirectory("rea-fs-mode-change-");
const file = join(root, "file");
await writeFile(file, "before", { mode: 0o600 });
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [file],
@@ -324,7 +324,7 @@ it.skipIf(process.platform === "win32")(
await writeFile(join(root, "b.txt"), "unused");
await symlink("a.txt", join(root, "stable-link"));
await symlink("a.txt", join(root, "moved-link"));
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -355,7 +355,7 @@ it.skipIf(process.platform === "win32")(
// Equal length, so only the target spelling distinguishes the links;
// "bbb" does not exist, so the replacement link is broken.
await symlink("aaa/../target", join(root, "link"));
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -381,7 +381,7 @@ it.skipIf(process.platform === "win32")(
it("preserves creation when the selected root was observed missing before capture", async () => {
const root = await createTestTempDirectory("rea-fs-missing-root-");
const file = join(root, "created.txt");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [file],
@@ -410,7 +410,7 @@ it.skipIf(process.platform === "win32")(
await mkdir(join(root, "denied"));
await chmod(join(root, "denied"), 0);
const scenarioFor = (path: string) =>
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [path],
@@ -480,7 +480,7 @@ it.skipIf(process.platform === "win32")(
await writeFile(join(listed, "child.txt"), "child");
// Read without search permission: names enumerate, lookups are denied.
await chmod(listed, 0o444);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -509,7 +509,7 @@ it.skipIf(process.platform === "win32")(
"keeps an admitted root behind a symlink loop unobserved",
async () => {
const root = await createTestTempDirectory("rea-fs-root-loop-");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [join(root, "loop", "child")],
@@ -532,7 +532,7 @@ it.skipIf(process.platform === "win32")(
const link = join(root, "link");
await mkdir(target);
await symlink(target, link);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
filesystem_observation_paths: [link],
});
@@ -561,7 +561,7 @@ it.skipIf(process.platform === "win32")(
it("allocates the observer directory before baseline and excludes it before entry accounting", async () => {
const root = await createTestTempDirectory("rea-observer-filesystem-");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
filesystem_observation_paths: [root],
limits: { files: 1 },
@@ -611,7 +611,7 @@ it.skipIf(process.platform === "win32")(
"target",
);
await symlink(physicalParent, alias);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
filesystem_observation_paths: [join(alias, "root")],
});
@@ -8,7 +8,7 @@ import {
} from "../../domain/analysisErrorBase.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
import { emptyProcessCapture } from "../../domain/process/processCapture.fixture.js";
import { parseProcessScenario } from "../../domain/process/processScenario.js";
import { processScenarioSchema } from "../../domain/process/processScenario.js";
import { ProcessCaptureSnapshotFailure } from "./FilesystemSnapshot.js";
import { prepareProcessCapture } from "./ProcessCaptureLifecycle.js";
@@ -54,7 +54,7 @@ it.each(["EACCES", "EPERM"] as const)(
snapshot,
cleanupLeases: [lease],
});
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
filesystem_observation_paths: ["/fixture/absent", selected],
});
@@ -1,7 +1,7 @@
import { expect, it, vi } from "vitest";
import type { IPty } from "@lydell/node-pty";
import { parseProcessScenario } from "../../domain/process/processScenario.js";
import { processScenarioSchema } from "../../domain/process/processScenario.js";
import type { ProcessCaptureFinalization } from "../../domain/process/processCapture.js";
import { awaitTerminalExit } from "./ProcessCaptureLifecycle.js";
import type { ProcessTimer } from "./ProcessTimer.js";
@@ -75,7 +75,7 @@ const start = (
) =>
awaitTerminalExit({
terminal: fake.terminal,
scenario: parseProcessScenario({
scenario: processScenarioSchema.parse({
executable: "unused",
idle_timeout_ms: 10_000,
...scenario,
@@ -8,7 +8,7 @@ import { buildCaptureResult } from "./ProcessCaptureLifecycle.js";
import { isInitializedPtyRoot } from "./ProcessSampling.js";
import { readLinuxChildren } from "./LinuxProcessSampling.js";
import { TerminalRenderer } from "./TerminalRenderer.js";
import { parseProcessScenario } from "../../domain/process/processScenario.js";
import { processScenarioSchema } from "../../domain/process/processScenario.js";
import type { ProcessCapture } from "../../domain/process/processCaptureParsing.js";
import type {
FilesystemCoverage,
@@ -143,7 +143,7 @@ it("preserves rendered observation order instead of timestamp sorting", () => {
coverage: emptyFilesystemCoverage(),
},
truncationDetails: truncationDetails([], renderedFrames, []),
scenario: parseProcessScenario(base),
scenario: processScenarioSchema.parse(base),
rootPid: 1,
samplingPartial: false,
renderedFrames,
@@ -179,7 +179,7 @@ it("marks redacted scripted input as an interaction unknown", () => {
coverage: emptyFilesystemCoverage(),
},
truncationDetails: truncationDetails([], [], []),
scenario: parseProcessScenario({
scenario: processScenarioSchema.parse({
...base,
events: [{ type: "input", at_ms: 0, data: "secret", sensitive: true }],
}),
@@ -278,7 +278,7 @@ it("cancels filesystem snapshots before traversing declared roots", async () =>
const controller = new AbortController();
controller.abort();
const failure: unknown = await snapshotRoots(
parseProcessScenario({
processScenarioSchema.parse({
...base,
filesystem_observation_paths: ["/tmp"],
}),
@@ -330,7 +330,7 @@ it.each(["symlink_not_followed", "observer_owned"] as const)(
filesystem_before: coverage,
filesystem_after: coverage,
},
scenario: parseProcessScenario({
scenario: processScenarioSchema.parse({
...base,
filesystem_observation_paths: ["/selected"],
}),
@@ -1,7 +1,7 @@
import { expect, it } from "vitest";
import type { IPty } from "@lydell/node-pty";
import { parseProcessScenario } from "../../domain/process/processScenario.js";
import { processScenarioSchema } from "../../domain/process/processScenario.js";
import { AnalysisResourceConstraintError } from "../../domain/analysisErrorCore.js";
import { createProcessIdentifierLookup } from "./ProcessNormalization.js";
import type {
@@ -19,7 +19,7 @@ it("omits an expanding chunk whole and retains a later smaller normalized chunk"
return { dispose: () => undefined };
},
} as unknown as IPty;
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "unused",
limits: { output_bytes: 1_000 },
normalization: {
@@ -78,7 +78,7 @@ it.each([false, true])(
return { dispose: () => undefined };
},
} as unknown as IPty;
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "unused",
limits: { output_bytes: overBudget ? 4 : 1000 },
normalization: {
@@ -147,7 +147,7 @@ it("retains PTY chunk boundaries when two output messages arrive fragmented", ()
return { dispose: () => undefined };
},
} as unknown as IPty;
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "unused",
normalization: { paths: false, pids: false, ports: false },
});
@@ -7,7 +7,7 @@ import {
normalizeProcessSamples,
normalizeProcessText,
} from "./ProcessNormalization.js";
import { parseProcessScenario } from "../../domain/process/processScenario.js";
import { processScenarioSchema } from "../../domain/process/processScenario.js";
const rootPid = 41_010;
const samples = Object.freeze(
@@ -67,7 +67,7 @@ const scenario = (normalization: {
readonly replacement: string;
}[];
}) =>
parseProcessScenario({
processScenarioSchema.parse({
executable: "/usr/bin/node",
working_directory: "/workspace",
normalization: {
@@ -294,7 +294,7 @@ it("reuses matchers while observing the terminal PID after launch", () => {
});
it("keeps equal-path priority and replacement aliases local to each capture", () => {
const declared = parseProcessScenario({
const declared = processScenarioSchema.parse({
executable: "/selected/tool",
working_directory: "/selected",
filesystem_observation_paths: ["/selected", "<working-directory>"],
@@ -485,7 +485,7 @@ it("normalizes representative process identities and preserves null, zero, and u
});
it("normalizes nested paths once using the most specific selected identity", () => {
const declared = parseProcessScenario({
const declared = processScenarioSchema.parse({
executable: "/workspace/project/bin/program",
working_directory: "/workspace/project",
filesystem_observation_paths: [
@@ -507,7 +507,7 @@ it("normalizes nested paths once using the most specific selected identity", ()
});
it("treats path metacharacters literally while preferring nested observed roots", () => {
const declared = parseProcessScenario({
const declared = processScenarioSchema.parse({
executable: "/selected/[tool]+",
working_directory: "/selected",
filesystem_observation_paths: [
@@ -529,7 +529,7 @@ it("treats path metacharacters literally while preferring nested observed roots"
});
it("does not give a selected executable identity to a sibling filename", () => {
const declared = parseProcessScenario({
const declared = processScenarioSchema.parse({
executable: "/tmp/process-paths/original/tool",
working_directory: "/tmp/process-paths",
normalization: { paths: true, pids: false, ports: false, patterns: [] },
@@ -19,7 +19,7 @@ import {
resolveExecutable,
resolveProcessScenarioRuntimePaths,
} from "./ProcessScenarioRuntimeValidation.js";
import { parseProcessScenario } from "../../domain/process/processScenario.js";
import { processScenarioSchema } from "../../domain/process/processScenario.js";
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
it.skipIf(process.platform === "win32")(
@@ -33,7 +33,7 @@ it.skipIf(process.platform === "win32")(
await symlink(process.execPath, selected);
await expect(resolveExecutable(command, root, bin)).resolves.toBe(selected);
const resolved = await resolveProcessScenarioRuntimePaths(
parseProcessScenario({
processScenarioSchema.parse({
executable: command,
working_directory: root,
environment: { PATH: bin },
@@ -64,7 +64,7 @@ it("resolves a Windows executable through the canonical PATH spelling", async ()
await mkdir(ambient);
await writeFile(join(selected, "tool.exe"), "");
await chmod(join(selected, "tool.exe"), 0o644);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "tool.exe",
working_directory: root,
environment: { Path: selected },
@@ -200,12 +200,10 @@ const deniedFixture = async () => {
return { root, denied, tool };
};
const runtimePathOutcome = async (
scenario: Parameters<typeof parseProcessScenario>[0],
): Promise<unknown> => {
const runtimePathOutcome = async (scenario: unknown): Promise<unknown> => {
try {
return await resolveProcessScenarioRuntimePaths(
parseProcessScenario(scenario),
processScenarioSchema.parse(scenario),
);
} catch (cause: unknown) {
return cause;
@@ -291,11 +289,13 @@ it.skipIf(SKIP_PERMISSIONS)(
);
const runtimePathFailure = async (
scenario: Parameters<typeof parseProcessScenario>[0],
scenario: unknown,
): Promise<AnalysisInputError> => {
let failure: unknown;
try {
await resolveProcessScenarioRuntimePaths(parseProcessScenario(scenario));
await resolveProcessScenarioRuntimePaths(
processScenarioSchema.parse(scenario),
);
} catch (cause: unknown) {
failure = cause;
}
@@ -17,7 +17,7 @@ import {
} from "./ProcessCaptureError.js";
import { AnalysisResourceConstraintError } from "../../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
import { parseProcessScenario } from "../../domain/process/processScenario.js";
import { processScenarioSchema } from "../../domain/process/processScenario.js";
import {
createProcessCaptureObservationBuffer,
resolveProcessResult,
@@ -27,7 +27,7 @@ import {
import { createProcessTerminalFinalizer } from "./ProcessTerminalObservation.js";
const createTerminalCleanupFixture = () => {
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "unused",
terminal: { columns: 80, rows: 4 },
normalization: { paths: false, ports: false },
@@ -157,7 +157,7 @@ it("retains completed terminal facts and the typed capacity failure through fina
state: "available",
value: rendered,
});
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "unused",
limits: { output_bytes: 4 },
});
+5 -5
View File
@@ -7,7 +7,7 @@ import { expect, it, vi } from "vitest";
import { TerminalRenderer } from "./TerminalRenderer.js";
import { ProcessCaptureResourceScope } from "./ProcessCaptureLifecycle.js";
import { createBoundedProcessTextNormalizer } from "./ProcessNormalization.js";
import { parseProcessScenario } from "../../domain/process/processScenario.js";
import { processScenarioSchema } from "../../domain/process/processScenario.js";
import type { BoundedProcessTextNormalization } from "./ProcessNormalization.js";
const require = createRequire(import.meta.url);
@@ -128,7 +128,7 @@ it("omits rendered frames with unknown expanded size and keeps later bounded sta
scrollback: 0,
maxBytes: 1_000,
normalize: createBoundedProcessTextNormalizer(
parseProcessScenario({
processScenarioSchema.parse({
executable: "unused",
normalization: {
paths: false,
@@ -259,7 +259,7 @@ it.each([1024, 2048])(
scrollback: 0,
maxBytes,
normalize: createBoundedProcessTextNormalizer(
parseProcessScenario({
processScenarioSchema.parse({
executable: "unused",
normalization: { paths: false, pids: false, ports: false },
}),
@@ -285,7 +285,7 @@ it.each([1024, 2048])(
it("renders retained chunks once after late PID knowledge, preserving event times and resize order", async () => {
let identifiers: readonly number[] = [100];
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "unused",
normalization: {
paths: false,
@@ -333,7 +333,7 @@ it("renders retained chunks once after late PID knowledge, preserving event time
});
it("renders a bounded sequence of retained chunks and resizes", async () => {
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "unused",
normalization: { paths: false, ports: false, pids: true, patterns: [] },
});
@@ -3,7 +3,7 @@ import { expect, it } from "vitest";
import { emptyUnverifiedProcessCapture } from "../../../src/domain/process/processCapture.fixture.js";
import { parseProcessCapture } from "../../../src/domain/process/processCaptureParsing.js";
import { parseProcessScenario } from "../../../src/domain/process/processScenario.js";
import { processScenarioSchema } from "../../../src/domain/process/processScenario.js";
import { createProcessCaptureEvidence } from "../../../src/application/process/ProcessEvidence.js";
import { observeSettlement } from "../../../src/process/capture/ProcessCaptureLifecycle.js";
@@ -14,7 +14,7 @@ it.each([
] as const)(
"interprets the executable name using the captured %s host",
(platform, expectedName) => {
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "/fixtures/directory\\program.exe",
working_directory: process.cwd(),
events: [],
@@ -32,7 +32,7 @@ it.each([
);
it("stamps process Evidence from the capture manifest host", () => {
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: process.cwd(),
events: [],
@@ -14,13 +14,13 @@ import { compareProcessCaptures } from "../../../src/domain/process/processCompa
import { parseProcessCapture } from "../../../src/domain/process/processCaptureParsing.js";
import {
digestProcessCommitment,
parseProcessScenario,
processScenarioSchema,
processComparisonContract,
processScenarioCommitment,
} from "../../../src/domain/process/processScenario.js";
const captureEvidenceForSensitiveInput = (data: string) => {
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "/bin/echo",
working_directory: "/tmp",
events: [{ type: "input", at_ms: 0, data, sensitive: true }],
@@ -60,7 +60,7 @@ const captureEvidenceForSensitiveInput = (data: string) => {
};
const captureEvidenceForEnvironmentSecret = async (value: string) => {
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: "/bin/echo",
working_directory: "/tmp",
environment: { API_TOKEN: value },
@@ -1,6 +1,6 @@
import { expect } from "vitest";
import { parseProcessScenario } from "../../../src/domain/process/processScenario.js";
import { processScenarioSchema } from "../../../src/domain/process/processScenario.js";
import { captureProcessScenario } from "../../../src/process/capture/ProcessHarness.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { itWithCaptureCapability } from "./processCaptureCapability.js";
@@ -13,7 +13,7 @@ itWithCaptureCapability(
const reason = { request: "cancel after cleanup completed" };
let cleanupReported = false;
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: ["--eval", "console.log('completed capture')"],
working_directory: root,
@@ -29,7 +29,7 @@ import {
observeLaunchedExecutable,
observeSelectedExecutable,
} from "../../../src/process/capture/ProcessCaptureLifecycle.js";
import { parseProcessScenario } from "../../../src/domain/process/processScenario.js";
import { processScenarioSchema } from "../../../src/domain/process/processScenario.js";
import { emptyProcessCapture } from "../../../src/domain/process/processCapture.fixture.js";
import { processSourceTruncated } from "../../../src/domain/process/processCaptureCoverage.js";
import type { ProcessCapture } from "../../../src/domain/process/processCaptureParsing.js";
@@ -140,7 +140,7 @@ itWithCaptureCapability(
await mkdir(join(root, "reports"));
await writeFile(join(root, "reports", "final.json"), payload);
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [
"-e",
@@ -187,7 +187,7 @@ itWithCaptureCapability(
}, 10_000);
try {
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, "hang"],
working_directory: dirname(processFixture),
@@ -226,7 +226,7 @@ itWithCaptureCapability(
vi.setSystemTime(new Date("2026-01-02T00:00:00.000Z"));
try {
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, "partial"],
working_directory: dirname(processFixture),
@@ -282,7 +282,7 @@ itWithCaptureCapability(
let unownedPid: number | undefined;
try {
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, "detached-cleanup", pidMarker],
working_directory: root,
@@ -374,7 +374,7 @@ itWithCaptureCapability(
rm(path, { recursive: true, force: true }),
};
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, "partial"],
working_directory: dirname(processFixture),
@@ -492,7 +492,7 @@ itWithCaptureCapability(
.digest("hex");
try {
const capturePromise = captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable,
arguments: [processFixture, "ready-hang", readyMarker],
working_directory: root,
@@ -541,7 +541,7 @@ itWithCaptureCapability(
await symlink("/etc/passwd", join(root, "escape"));
try {
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: "/usr/bin/true",
working_directory: root,
filesystem_observation_paths: [root],
@@ -578,7 +578,7 @@ itWithCaptureCapability(
"distinguishes timeout from cancellation and cleans both runs",
async () => {
const timedOut = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, "hang"],
working_directory: dirname(processFixture),
@@ -606,7 +606,7 @@ itWithCaptureCapability(
const controller = new AbortController();
let reachedRunning = false;
const cancelled = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, "hang"],
working_directory: dirname(processFixture),
@@ -665,7 +665,7 @@ const captureFinalizationFixture = async (
): Promise<{ readonly result: CaptureRun; readonly root: string }> => {
const root = await createTestTempDirectory("rea-finalization-");
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [finalizationFixture, mode, root],
working_directory: root,
@@ -958,7 +958,7 @@ itWithCaptureCapability(
};
const root = await createTestTempDirectory("rea-finalization-host-");
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [finalizationFixture, "ignoring", root],
working_directory: root,
@@ -1032,7 +1032,7 @@ itWithCaptureCapability(
});
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [path],
@@ -1066,7 +1066,7 @@ itWithCaptureCapability(
return snapshot;
};
const resultPromise = captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [snapshotCancellationFixture, root],
working_directory: dirname(snapshotCancellationFixture),
@@ -1116,7 +1116,7 @@ itWithCaptureCapability(
};
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [join(obstruction, "child")],
@@ -1139,7 +1139,7 @@ itWithCaptureCapability(
"captures input, Unicode, and resize across PTY startup",
async () => {
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, "interactive"],
working_directory: dirname(processFixture),
@@ -1177,7 +1177,7 @@ itWithCaptureCapability(
"dispatches an external signal without depending on child startup output",
async () => {
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, "hang"],
working_directory: dirname(processFixture),
@@ -1202,7 +1202,7 @@ itWithCaptureCapability(
"buffers newline input until a silent PTY fixture is ready to read it",
async () => {
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, "silent-interactive"],
working_directory: dirname(processFixture),
@@ -1238,7 +1238,7 @@ itWithCaptureCapability(
"samples and cleans a source-owned child and grandchild process tree",
async () => {
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, "tree"],
working_directory: dirname(processFixture),
@@ -8,7 +8,7 @@ import { captureProcessScenario } from "../../../src/process/capture/ProcessHarn
import { createProcessCaptureEvidence } from "../../../src/application/process/ProcessEvidence.js";
import {
digestProcessCommitment,
parseProcessScenario,
processScenarioSchema,
} from "../../../src/domain/process/processScenario.js";
import { parseProcessCapture } from "../../../src/domain/process/processCaptureParsing.js";
import { compareProcessCaptures } from "../../../src/domain/process/processComparison.js";
@@ -37,7 +37,7 @@ itWithCaptureCapability(
const command = "selected\\node";
const selected = join(bin, command);
await symlink(process.execPath, selected);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: command,
working_directory: root,
environment: { PATH: bin },
@@ -69,7 +69,7 @@ itWithCaptureCapability(
score: 20,
});
const original = `${report}\nlisten:8080\n`;
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
arguments: ["-e", `process.stdout.write(${JSON.stringify(original)})`],
@@ -119,7 +119,7 @@ itWithCaptureCapability.each([
"preserves literal replacement $replacement and rejects legacy comparison semantics",
async ({ pattern, replacement, expected }) => {
const root = await createTestTempDirectory("rea-process-literal-");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
arguments: ["-e", 'process.stdout.write("AB")'],
working_directory: root,
@@ -173,7 +173,7 @@ itWithLinuxCaptureCapability.each([false, true])(
async (pids) => {
const root = await createTestTempDirectory("rea-process-identifiers-");
const identityPath = join(root, "identity.json");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
arguments: [processFixture, identityPath],
working_directory: root,
@@ -241,7 +241,7 @@ itWithCaptureCapability(
await symlink(process.execPath, executable);
await mkdir(observed);
const original = `${executable}\n${observed}/result\n${root}/log`;
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable,
working_directory: root,
filesystem_observation_paths: [observed],
@@ -317,7 +317,7 @@ itWithCaptureCapability(
await mkdir(join(root, "original"), { recursive: true });
await symlink(process.execPath, executable);
const sibling = `${executable}-helper`;
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable,
working_directory: root,
arguments: ["-e", `process.stdout.write(${JSON.stringify(sibling)})`],
@@ -339,7 +339,7 @@ itWithCaptureCapability.each([false, true])(
"normalizes a sampled child in raw and rendered terminal projections, pids=%s",
async (pids) => {
const root = await createTestTempDirectory("rea-sampled-terminal-pids-");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
arguments: [
@@ -5,13 +5,13 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { itWithCaptureCapability } from "./processCaptureCapability.js";
import { captureProcessScenario } from "../../../src/process/capture/ProcessHarness.js";
import { parseProcessScenario } from "../../../src/domain/process/processScenario.js";
import { processScenarioSchema } from "../../../src/domain/process/processScenario.js";
itWithCaptureCapability(
"normalizes wide terminal cells without inventing blank cells",
async () => {
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: ["-e", 'process.stdout.write("界")'],
terminal: { columns: 4, rows: 1, scrollback: 0 },
@@ -54,7 +54,7 @@ itWithCaptureCapability(
);
try {
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: [script],
working_directory: root,
@@ -16,7 +16,7 @@ import { analysisCliErrorEnvelopeSchema } from "../../../src/contracts/errorSche
import { parseEvidence } from "../../../src/domain/evidence.js";
import { compareProcessCaptures } from "../../../src/domain/process/processComparison.js";
import { parseProcessCapture } from "../../../src/domain/process/processCaptureParsing.js";
import { parseProcessScenario } from "../../../src/domain/process/processScenario.js";
import { processScenarioSchema } from "../../../src/domain/process/processScenario.js";
import { FINALIZED_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js";
import { createServer } from "../../../src/server/createServer.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
@@ -207,7 +207,7 @@ it("documents a finalization scenario and exit record that match the contract",
throw new Error("Missing finalization examples");
expect(
parseProcessScenario(JSON.parse(scenario)).finalization_ms,
processScenarioSchema.parse(JSON.parse(scenario)).finalization_ms,
"the documented scenario is accepted and carries the interval",
).toBe(1_500);
expect(
@@ -225,7 +225,7 @@ itWithCaptureCapability.each(["cli", "mcp"] as const)(
?.match(/```json\n([\s\S]*?)\n```/u)?.[1];
if (example === undefined)
throw new Error("Missing report capture example");
const scenario = parseProcessScenario(JSON.parse(example));
const scenario = processScenarioSchema.parse(JSON.parse(example));
const root = await createTestTempDirectory("rea-full-report-adapter-");
const evidence = await (adapter === "cli" ? captureViaCli : captureViaMcp)({
...scenario,
@@ -1,7 +1,7 @@
import { ProcessOwnershipEvidenceOwner } from "../../../src/process/ProcessOwnershipEvidence.js";
import { expect, it } from "vitest";
import { parseProcessScenario } from "../../../src/domain/process/processScenario.js";
import { processScenarioSchema } from "../../../src/domain/process/processScenario.js";
import {
createRunManifest,
observeLaunchedExecutable,
@@ -10,7 +10,7 @@ import {
} from "../../../src/process/capture/ProcessCaptureLifecycle.js";
it("uses one selected host identity for manifest and settlement", async () => {
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: process.cwd(),
events: [],
@@ -9,7 +9,7 @@ import {
snapshotRoots,
} from "../../../src/process/capture/FilesystemSnapshot.js";
import { prepareProcessCapture } from "../../../src/process/capture/ProcessCaptureLifecycle.js";
import { parseProcessScenario } from "../../../src/domain/process/processScenario.js";
import { processScenarioSchema } from "../../../src/domain/process/processScenario.js";
import { emptyProcessCapture } from "../../../src/domain/process/processCapture.fixture.js";
const emptyFilesystemCoverage =
@@ -28,7 +28,7 @@ it("prepares ownership inspection before snapshotting or allocating a target run
await expect(
prepareProcessCapture(
parseProcessScenario({
processScenarioSchema.parse({
executable: "/bin/true",
working_directory: "/tmp",
}),
@@ -59,7 +59,7 @@ it("cleans the temporary root when cancellation arrives during identity capture"
await expect(
prepareProcessCapture(
parseProcessScenario({
processScenarioSchema.parse({
executable: "/bin/true",
working_directory: "/tmp",
}),
@@ -98,7 +98,7 @@ it.each([false, true])(
if (failing) throw new Error("controlled temporary-root removal denial");
});
const observed = await prepareProcessCapture(
parseProcessScenario({
processScenarioSchema.parse({
executable: "/bin/true",
filesystem_observation_paths: [path],
}),
@@ -74,7 +74,7 @@ vi.mock("node:fs/promises", async (importOriginal) => {
});
import { AnalysisCleanupLease } from "../../../src/artifacts/AnalysisCleanupLease.js";
import { parseProcessScenario } from "../../../src/domain/process/processScenario.js";
import { processScenarioSchema } from "../../../src/domain/process/processScenario.js";
import {
hashFile,
snapshotRoots,
@@ -97,7 +97,7 @@ type DirectoryReadCallback = (
type DirectoryCloseCallback = (error?: NodeJS.ErrnoException | null) => void;
const scenarioFor = (root: string) =>
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -54,7 +54,7 @@ vi.mock("node:fs/promises", async (importOriginal) => {
};
});
import { parseProcessScenario } from "../../src/domain/process/processScenario.js";
import { processScenarioSchema } from "../../src/domain/process/processScenario.js";
import {
hashFile,
snapshotRoots,
@@ -94,7 +94,7 @@ it.each(["hash", "snapshot"] as const)(
operation === "hash"
? hashFile(path, expected, 1_000, controller.signal)
: snapshotRoots(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
working_directory: directory,
filesystem_observation_paths: [path],
@@ -104,7 +104,7 @@ vi.mock("node:fs/promises", async (importOriginal) => {
});
import {
parseProcessScenario,
processScenarioSchema,
type ProcessScenario,
} from "../../src/domain/process/processScenario.js";
import { classifyFilesystemEffects } from "../../src/process/capture/ProcessFilesystemEffects.js";
@@ -178,7 +178,7 @@ const entryUnavailable = async (
const link = join(root, "z-link");
await writeFile(sibling, "retain this sibling");
await symlink("missing-target", link);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -240,7 +240,7 @@ it.skipIf(process.platform === "win32")(
const link = join(root, "z-link");
await writeFile(sibling, "retain this sibling");
await symlink("original-target", link);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -275,7 +275,7 @@ it("retains siblings and marks child absence unknown when directory revalidation
await writeFile(sibling, "retain this sibling");
await mkdir(directory);
await writeFile(join(directory, "child"), "child observed before race");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -309,7 +309,7 @@ it.skipIf(process.platform === "win32")(
withTemporaryRoot("rea-snapshot-cancel-race-", async (root) => {
const link = join(root, "link");
await symlink("missing-target", link);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -330,7 +330,7 @@ it.skipIf(process.platform === "win32")(
withTemporaryRoot("rea-snapshot-cancel-missing-", async (root) => {
const link = join(root, "link");
await symlink("missing-target", link);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -350,7 +350,7 @@ it("observes cancellation after the selected directory handle closes", async ()
const directory = join(root, "selected");
await mkdir(directory);
await writeFile(join(directory, "child"), "observed child");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [directory],
@@ -374,7 +374,7 @@ it("releases buffered names after a failed directory revalidation before visitin
await writeFile(join(firstRoot, "a-child"), "first");
await writeFile(join(firstRoot, "b-child"), "second");
await writeFile(join(secondRoot, "child"), "retain this root");
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: parent,
filesystem_observation_paths: [firstRoot, secondRoot],
@@ -398,7 +398,7 @@ it.skipIf(process.platform === "win32")(
withTemporaryRoot("rea-snapshot-eio-control-", async (root) => {
const link = join(root, "link");
await symlink("missing-target", link);
const scenario = parseProcessScenario({
const scenario = processScenarioSchema.parse({
executable: process.execPath,
working_directory: root,
filesystem_observation_paths: [root],
@@ -2,7 +2,7 @@ import { rm, stat, type FileHandle } from "node:fs/promises";
import { afterEach, expect, it, vi } from "vitest";
import { captureProcessScenario } from "../../src/process/capture/ProcessHarness.js";
import { parseProcessScenario } from "../../src/domain/process/processScenario.js";
import { processScenarioSchema } from "../../src/domain/process/processScenario.js";
import { emptyProcessCapture } from "../../src/domain/process/processCapture.fixture.js";
const control = vi.hoisted(() => ({
@@ -100,7 +100,7 @@ it.each(["none", "before", "open", "final-stat", "close"] as const)(
if (stage === "before") controller.abort(reason);
const removed: string[] = [];
const result = await captureProcessScenario(
parseProcessScenario({
processScenarioSchema.parse({
executable: process.execPath,
arguments: ["--eval", "process.exit(0)"],
working_directory: process.cwd(),