// Determinism guard for the mock layer (epic #296, S5).
//
// The whole test kit is deterministic under a virtual clock: the drain reaches a fixpoint without
// wall-clock time or randomness. The mock layer (job-worker mocking) — and native call-activity
// execution, which the engine now runs directly — must preserve that. This file guards it two ways:
//
// • Runtime: mocks of every deterministically-terminal outcome (and a native call activity) drive
// an instance to quiescence, and a *second* explicit `drain()` is a no-op (a true fixpoint, not
// a moving target).
// • Static: the mock modules — `worker-mock.ts` and the mock interception added to
// `wasm-engine.ts` — contain NO `setTimeout` / `setInterval` / wall-clock (`Date.now()`, `new Date()`, `performance.now()`) / `Math.random()` usage. This
// is the regression net: a future edit that sneaks real time or randomness into the mock path
// fails CI here even if it happens not to flake in a given run.
//
// Runs on Node and Deno.
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { createWasmEngineClient, type WasmEngineClient } from "./wasm-engine.ts";
// ---------------------------------------------------------------------------------------------
// Runtime fixpoint checks.
// ---------------------------------------------------------------------------------------------
/** Two service tasks then a user task — parks ACTIVE so mocked completions stay observable. */
const TWO_BPMN = `
`;
/** A single service task — completes the whole instance (for the incident/error fixpoints). */
const SVC_BPMN = `
`;
/** Parent with a call activity to `child`, then a user task. */
const PARENT_BPMN = `
`;
const CHILD_BPMN = `
`;
function res(...models: { name: string; xml: string }[]): { name: string; content: string; contentType: string }[] {
return models.map((m) => ({ name: m.name, content: m.xml, contentType: "text/xml" }));
}
async function withEngine(
models: { name: string; xml: string }[],
body: (engine: WasmEngineClient) => Promise,
): Promise {
const engine = await createWasmEngineClient();
try {
await engine.deployResources(res(...models));
await body(engine);
} finally {
await engine.close();
}
}
/** Assert an extra `drain()` after the given `body` changes nothing observable — a true fixpoint.
* Compares the full snapshot before/after the redundant drain. */
async function assertRedundantDrainIsNoop(engine: WasmEngineClient): Promise {
const before = JSON.stringify(engine.snapshot());
await engine.drain();
const after = JSON.stringify(engine.snapshot());
assert.equal(after, before, "a second drain() at the same virtual instant must be a no-op (fixpoint)");
}
test("determinism: worker completions drain to a fixpoint; a redundant drain is a no-op", async () => {
await withEngine([{ name: "two.bpmn", xml: TWO_BPMN }], async (engine) => {
engine.mockWorker("a").completeWith({ a: 1 });
engine.mockWorker("b").completeWith({ b: 2 });
await engine.createInstance({ processDefinitionId: "two" });
await assertRedundantDrainIsNoop(engine);
});
});
test("determinism: a worker incident outcome drains to a fixpoint; a redundant drain is a no-op", async () => {
await withEngine([{ name: "svc.bpmn", xml: SVC_BPMN }], async (engine) => {
engine.mockWorker("work").raiseIncident({ message: "stop" });
await engine.createInstance({ processDefinitionId: "svc" });
await assertRedundantDrainIsNoop(engine);
});
});
test("determinism: a native call activity drains to a fixpoint; a redundant drain is a no-op", async () => {
await withEngine(
[
{ name: "parent.bpmn", xml: PARENT_BPMN },
{ name: "child.bpmn", xml: CHILD_BPMN },
],
async (engine) => {
// The child's service task runs for real (native execution); the parent parks on its user
// task once the child completes. A redundant drain at the same instant must change nothing.
await engine.registerWorker("cwork", async () => ({ done: true }));
await engine.createInstance({ processDefinitionId: "parent" });
await assertRedundantDrainIsNoop(engine);
},
);
});
test("determinism: a native call activity to an un-deployed child drains to a fixpoint; a redundant drain is a no-op", async () => {
// The child is deliberately NOT deployed, so the engine raises a recoverable incident on the call
// activity. That incident state must be a fixpoint — a redundant drain must not re-raise or spin.
await withEngine([{ name: "parent.bpmn", xml: PARENT_BPMN }], async (engine) => {
await engine.createInstance({ processDefinitionId: "parent" });
await assertRedundantDrainIsNoop(engine);
});
});
test("determinism: repeated identical runs produce byte-identical mock-driven variable snapshots", async () => {
// Determinism means reproducibility: the same mocked scenario twice yields the same merged state.
// We compare only the mock-driven variables — the engine stamps a per-instance lineage UUID
// (`_urban`) that legitimately differs run-to-run and is outside the mock layer's contract.
const run = async (): Promise => {
let vars = "{}";
await withEngine([{ name: "two.bpmn", xml: TWO_BPMN }], async (engine) => {
engine.mockWorker("a").when((j) => j.variables.n === 1).completeWith({ a: "one" });
engine.mockWorker("a").completeWith({ a: "default" });
engine.mockWorker("b").completeWith({ b: 2 });
const { processInstanceKey } = await engine.createInstance({ processDefinitionId: "two", variables: { n: 1 } });
const inst = engine.snapshot().instances;
const found = Array.isArray(inst)
? inst.find((i) => i && typeof i === "object" && String(Reflect.get(i, "key")) === processInstanceKey)
: undefined;
const raw = found && typeof found === "object" ? Reflect.get(found, "variables") : {};
const merged: Record = {};
if (raw && typeof raw === "object") {
for (const [k, v] of Object.entries(raw)) {
if (k !== "_urban") merged[k] = v; // drop the per-instance lineage UUID
}
}
vars = JSON.stringify(merged, Object.keys(merged).sort());
});
return vars;
};
const first = await run();
const second = await run();
assert.equal(second, first, "two identical mocked runs produced identical mock-driven variables");
assert.ok(first.includes('"a":"one"'), "the first-match clause fired deterministically");
assert.ok(first.includes('"b":2'), "the unconditional mock fired deterministically");
});
// ---------------------------------------------------------------------------------------------
// Static source guard — no wall-clock / randomness in the mock layer.
// ---------------------------------------------------------------------------------------------
/** Read a sibling source file (works under Node's type-stripping and Deno). */
function readSibling(name: string): string {
return readFileSync(fileURLToPath(new URL(`./${name}`, import.meta.url)), "utf8");
}
/** Extract a named method's body from a class source by brace-matching from its signature, so the
* guard can scope the `wasm-engine.ts` scan to just the mock-interception seam (not unrelated
* engine-client code that may legitimately touch timers/clocks in future). Returns the body text
* including the surrounding braces. Throws if the method (or a balanced body) isn't found. */
function methodBody(source: string, signature: string): string {
const start = source.indexOf(signature);
assert.notEqual(start, -1, `expected to find method \`${signature}\` in the source`);
const open = source.indexOf("{", start);
assert.notEqual(open, -1, `expected an opening brace after \`${signature}\``);
let depth = 0;
for (let i = open; i < source.length; i++) {
const ch = source[i];
if (ch === "{") depth++;
else if (ch === "}") {
depth--;
if (depth === 0) return source.slice(open, i + 1);
}
}
throw new Error(`unbalanced braces scanning \`${signature}\``);
}
/** Forbidden non-deterministic constructs. Each is a real source of wall-clock/randomness that
* would break the virtual-clock fixpoint. `now()` alone is NOT forbidden — the engine exposes a
* virtual `now()` — only JS wall-clock/randomness APIs are. */
const FORBIDDEN: readonly { readonly label: string; readonly pattern: RegExp }[] = [
{ label: "setTimeout", pattern: /\bsetTimeout\s*\(/ },
{ label: "setInterval", pattern: /\bsetInterval\s*\(/ },
{ label: "Date.now()", pattern: /\bDate\s*\.\s*now\s*\(/ },
{ label: "new Date(...) wall-clock", pattern: /\bnew\s+Date\s*\(/ },
{ label: "Date.UTC()", pattern: /\bDate\s*\.\s*UTC\s*\(/ },
{ label: "performance.now()", pattern: /\bperformance\s*\.\s*now\s*\(/ },
{ label: "Math.random()", pattern: /\bMath\s*\.\s*random\s*\(/ },
{ label: "queueMicrotask", pattern: /\bqueueMicrotask\s*\(/ },
{ label: "setImmediate", pattern: /\bsetImmediate\s*\(/ },
];
function assertNoForbidden(where: string, text: string): void {
for (const { label, pattern } of FORBIDDEN) {
assert.equal(
pattern.test(text),
false,
`${where} must not use ${label} — it would break the deterministic virtual-clock drain fixpoint`,
);
}
}
test("determinism (static): worker-mock.ts uses no wall-clock or randomness", () => {
assertNoForbidden("worker-mock.ts", readSibling("worker-mock.ts"));
});
test("determinism (static): the wasm-engine.ts mock-interception seam uses no wall-clock or randomness", () => {
const src = readSibling("wasm-engine.ts");
// Scope to exactly the methods that implement mock dispatch/registration — the seam this epic
// added — rather than the whole engine client, so an unrelated future engine feature that
// legitimately touches timers doesn't false-positive this mock-focused guard.
const seams = [
"async drain(",
"#dispatchableJobTypes(",
"async #runJob(",
"#failFromError(",
"mockWorker(",
"clearWorkerMock(",
"observeJobs(",
];
for (const sig of seams) {
assertNoForbidden(`wasm-engine.ts \`${sig})\``, methodBody(src, sig));
}
});
test("determinism (static): the guard's method extractor actually finds a non-trivial mock seam", () => {
// Guard-the-guard: if a refactor renames/removes a seam, `methodBody` throws (caught above) — but
// a silently-empty body would let the scan pass vacuously. Assert the extracted `#runJob` body is
// substantial and contains its known mock-dispatch call, so the scan above is not a no-op.
const runJob = methodBody(readSibling("wasm-engine.ts"), "async #runJob(");
assert.ok(runJob.length > 200, "the extracted #runJob body should be substantial, not an empty match");
assert.ok(runJob.includes("applyOutcome"), "#runJob should contain the mock-outcome application the guard scans");
});