@mooloo-ai/eutaxy 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +16 -2
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +17 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/cli-DTuDOgGM.mjs +2 -0
- package/dist/cli.d.mts +8 -0
- package/dist/cli.d.mts.map +1 -0
- package/dist/cli.mjs +2 -0
- package/dist/cloudflare.d.mts +125 -0
- package/dist/cloudflare.d.mts.map +1 -0
- package/dist/cloudflare.mjs +815 -0
- package/dist/cloudflare.mjs.map +1 -0
- package/dist/explore-BB77_V0x.d.mts +302 -0
- package/dist/explore-BB77_V0x.d.mts.map +1 -0
- package/dist/index.d.mts +23 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/src-Bef00JwK.mjs +202 -0
- package/dist/src-Bef00JwK.mjs.map +1 -0
- package/dist/src-Dn3Lm5Us.mjs +1091 -0
- package/dist/src-Dn3Lm5Us.mjs.map +1 -0
- package/dist/src-dP-0Apun.mjs +310 -0
- package/dist/src-dP-0Apun.mjs.map +1 -0
- package/package.json +59 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src-Dn3Lm5Us.mjs","names":[],"sources":["../../core/src/prng.ts","../../core/src/faults.ts","../../core/src/invariants.ts","../../core/src/scenario.ts","../../core/src/shrink.ts","../../core/src/verdict.ts","../../engine/src/clock.ts","../../engine/src/run.ts","../../engine/src/shrink.ts","../../engine/src/explore.ts"],"sourcesContent":["/**\n * A pure, seedable pseudo-random generator (xoshiro128**).\n *\n * State is an immutable tuple of four 32-bit words. Every operation returns a\n * value and the next state; nothing is mutated, so a schedule can be replayed\n * from any state and two explorations can never interfere with each other.\n * Not cryptographic.\n */\n\nexport type PrngState = readonly [number, number, number, number];\n\nfunction rotl(x: number, k: number): number {\n return ((x << k) | (x >>> (32 - k))) >>> 0;\n}\n\n/** splitmix32: expands a 32-bit seed into well-mixed words for seeding. */\nfunction splitmix32(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a = (a + 0x9e3779b9) >>> 0;\n let t = a;\n t = Math.imul(t ^ (t >>> 16), 0x21f0aaad);\n t = Math.imul(t ^ (t >>> 15), 0x735a2d97);\n return (t ^ (t >>> 15)) >>> 0;\n };\n}\n\n/** FNV-1a over UTF-16 code units, so string seeds are stable across runtimes. */\nfunction hashString(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n/** Derive an initial state from a number (taken modulo 2^32) or a string. */\nexport function seedPrng(seed: number | string): PrngState {\n const word = typeof seed === \"string\" ? hashString(seed) : seed >>> 0;\n const next = splitmix32(word);\n const s: [number, number, number, number] = [next(), next(), next(), next()];\n if (s[0] === 0 && s[1] === 0 && s[2] === 0 && s[3] === 0) {\n s[0] = 1; // xoshiro must never hold the all-zero state\n }\n return s;\n}\n\n/** Next 32-bit unsigned integer and the state that follows it. */\nexport function nextU32(state: PrngState): readonly [number, PrngState] {\n const [s0, s1, s2, s3] = state;\n const result = Math.imul(rotl(Math.imul(s1, 5) >>> 0, 7), 9) >>> 0;\n const t = (s1 << 9) >>> 0;\n let n2 = (s2 ^ s0) >>> 0;\n let n3 = (s3 ^ s1) >>> 0;\n const n1 = (s1 ^ n2) >>> 0;\n const n0 = (s0 ^ n3) >>> 0;\n n2 = (n2 ^ t) >>> 0;\n n3 = rotl(n3, 11);\n return [result, [n0, n1, n2, n3]];\n}\n\n/** Next float in [0, 1) with 24 bits of precision. */\nexport function nextFloat(state: PrngState): readonly [number, PrngState] {\n const [u, next] = nextU32(state);\n return [(u >>> 8) * 2 ** -24, next];\n}\n\n/**\n * Next integer in [0, bound). Uses rejection sampling, so every value is\n * equally likely; a bound of 1 always yields 0 without consuming state.\n */\nexport function nextInt(state: PrngState, bound: number): readonly [number, PrngState] {\n if (!Number.isInteger(bound) || bound < 1 || bound > 0x1_0000_0000) {\n throw new RangeError(`nextInt bound must be an integer in [1, 2^32], got ${bound}`);\n }\n if (bound === 1) return [0, state];\n const limit = 0x1_0000_0000 - (0x1_0000_0000 % bound);\n let s = state;\n for (;;) {\n const [u, next] = nextU32(s);\n s = next;\n if (u < limit) return [u % bound, s];\n }\n}\n\n/**\n * Split one generator into two independent streams: the first element is the\n * continuation of the parent, the second a freshly seeded child.\n */\nexport function fork(state: PrngState): readonly [PrngState, PrngState] {\n const [a, s1] = nextU32(state);\n const [b, s2] = nextU32(s1);\n const next = splitmix32((a ^ Math.imul(b, 0x9e3779b1)) >>> 0);\n const child: [number, number, number, number] = [next(), next(), next(), next()];\n if (child[0] === 0 && child[1] === 0 && child[2] === 0 && child[3] === 0) child[0] = 1;\n return [s2, child];\n}\n","/**\n * The fault model and the schedule. A schedule is the complete, replayable\n * record of which fault was injected at which effect point in one run.\n */\n\nimport { nextFloat, nextInt, type PrngState } from \"./prng.ts\";\n\n/**\n * `cut_off` is a crash that happens after a response or acknowledgement was\n * already sent, so only background work (ctx.waitUntil) is lost; platforms\n * offer it at the point where that work would start.\n */\nexport type FaultKind = \"crash_before\" | \"crash_after\" | \"ambiguous\" | \"error\" | \"cut_off\";\n\nexport const allFaults: readonly FaultKind[] = [\n \"crash_before\",\n \"crash_after\",\n \"ambiguous\",\n \"error\",\n \"cut_off\",\n];\n\nexport interface FaultDecision {\n /** The invocation the effect belongs to, e.g. \"msg-1#2\". */\n readonly invocation: string;\n /** Position of the effect within that invocation, from 0. Stable when other invocations are removed. */\n readonly ordinal: number;\n readonly fault: FaultKind;\n /** What the effect was, for people reading the schedule. */\n readonly target: string;\n readonly op: string;\n}\n\nexport interface Schedule {\n /** The per-run seed the decisions were drawn from. */\n readonly seed: number;\n readonly faults: readonly FaultDecision[];\n}\n\nexport interface FaultPolicy {\n readonly maxFaultsPerRun: number;\n /** Probability of injecting at an eligible effect point, in [0, 1]. */\n readonly faultProbability: number;\n}\n\n/**\n * Decide whether to inject a fault at an effect point during exploration.\n * Pure: the same state and inputs always give the same decision. The PRNG is\n * only consumed when a fault is possible, so adding an ineligible point does\n * not perturb later decisions.\n */\nexport function decideFault(\n state: PrngState,\n allowed: readonly FaultKind[],\n faultsSoFar: number,\n policy: FaultPolicy,\n): readonly [FaultKind | null, PrngState] {\n if (allowed.length === 0 || faultsSoFar >= policy.maxFaultsPerRun) return [null, state];\n const [p, s1] = nextFloat(state);\n if (p >= policy.faultProbability) return [null, s1];\n const [i, s2] = nextInt(s1, allowed.length);\n return [allowed[i] ?? null, s2];\n}\n","/**\n * Invariants are plain data describing a property of the effect log; checkers\n * are pure functions from (log, invariant) to a violation or null. The\n * library starts with what the first planted bug needs and grows from there.\n */\n\nimport { canonicalJson, type JsonValue } from \"./canonical-json.ts\";\nimport type { EffectKind, EffectLog, EffectRecord } from \"./effect-log.ts\";\n\n/** Picks effect records by target, op, kind and payload fields. */\nexport interface EffectSelector {\n readonly target: string;\n readonly op?: string;\n readonly kind?: EffectKind;\n /** Dot paths into the payload that must equal the given values. */\n readonly where?: { readonly [path: string]: JsonValue };\n /** Dot paths into the payload whose numeric value must be at least the given bound. */\n readonly atLeast?: { readonly [path: string]: number };\n}\n\n/** No two selected effects share the same key. Catches duplicate writes. */\nexport interface AtMostOncePerKey {\n readonly kind: \"atMostOncePerKey\";\n readonly name: string;\n readonly effect: EffectSelector;\n /** Dot path into the payload that identifies the key. */\n readonly key: string;\n}\n\n/** Every expected key appears in at least one selected effect. Catches lost work. */\nexport interface AtLeastOnce {\n readonly kind: \"atLeastOnce\";\n readonly name: string;\n readonly effect: EffectSelector;\n readonly key: string;\n readonly expected: readonly JsonValue[];\n /** Set when `expected` was derived from the drive's message bodies, so it can be re-derived when messages are removed. */\n readonly perMessage?: { readonly bodyKey: string };\n}\n\n/** Re-derive `expected` for per-message invariants from the bodies that remain. */\nexport function recomputePerMessageExpected(\n invariants: readonly Invariant[],\n bodies: readonly JsonValue[],\n): readonly Invariant[] {\n return invariants.map((inv) => {\n if (inv.kind !== \"atLeastOnce\" || !inv.perMessage) return inv;\n const bodyKey = inv.perMessage.bodyKey;\n const expected: JsonValue[] = [];\n for (const b of bodies) {\n const v = getPath(b, bodyKey);\n if (v !== undefined) expected.push(v);\n }\n return { ...inv, expected };\n });\n}\n\n/** A condition on one column of a row. */\nexport type RowCondition =\n | { readonly column: string; readonly op: \"eq\" | \"neq\"; readonly value: JsonValue }\n | { readonly column: string; readonly op: \"gt\" | \"gte\" | \"lt\" | \"lte\"; readonly value: number }\n | { readonly column: string; readonly op: \"in\"; readonly value: readonly JsonValue[] }\n | { readonly column: string; readonly op: \"notNull\" };\n\n/** Rows of a table in the last snapshot of a target, optionally filtered. */\nexport interface StateQuery {\n /** The snapshot target, e.g. \"D1:DB\". */\n readonly target: string;\n readonly table: string;\n readonly where?: readonly RowCondition[];\n}\n\n/** Every row the query returns satisfies all of `holds`. */\nexport interface EveryRow {\n readonly kind: \"everyRow\";\n readonly name: string;\n readonly query: StateQuery;\n readonly holds: readonly RowCondition[];\n}\n\n/**\n * For every selected effect, a row exists whose `column` equals the effect's\n * key. Ties what the shell did to what the state says, e.g. \"every acked\n * message has its payment row\".\n */\nexport interface Implies {\n readonly kind: \"implies\";\n readonly name: string;\n readonly when: EffectSelector;\n /** Dot path into the effect payload that yields the key. */\n readonly key: string;\n readonly state: StateQuery;\n readonly column: string;\n}\n\nexport type Invariant = AtMostOncePerKey | AtLeastOnce | EveryRow | Implies;\n\nexport type Row = { readonly [column: string]: JsonValue };\n\n/** The tables in the last snapshot recorded for a target, or undefined when none was. */\nexport function lastSnapshot(\n log: EffectLog,\n target: string,\n): { readonly [table: string]: readonly Row[] } | undefined {\n for (let i = log.entries.length - 1; i >= 0; i--) {\n const e = log.entries[i] as EffectRecord;\n if (e.kind === \"snapshot\" && e.target === target) {\n const tables = getPath(e.payload, \"tables\");\n if (tables && typeof tables === \"object\" && !Array.isArray(tables)) {\n return tables as { readonly [table: string]: readonly Row[] };\n }\n return {};\n }\n }\n return undefined;\n}\n\nexport function rowMatches(row: Row, condition: RowCondition): boolean {\n const v = row[condition.column];\n switch (condition.op) {\n case \"eq\":\n return v !== undefined && jsonEqual(v, condition.value);\n case \"neq\":\n return v === undefined || !jsonEqual(v, condition.value);\n case \"gt\":\n return typeof v === \"number\" && v > condition.value;\n case \"gte\":\n return typeof v === \"number\" && v >= condition.value;\n case \"lt\":\n return typeof v === \"number\" && v < condition.value;\n case \"lte\":\n return typeof v === \"number\" && v <= condition.value;\n case \"in\":\n return v !== undefined && condition.value.some((c) => jsonEqual(v, c));\n case \"notNull\":\n return v !== undefined && v !== null;\n }\n}\n\n/** Rows a state query selects from the log's last snapshot; undefined when there is no snapshot. */\nexport function queryRows(log: EffectLog, query: StateQuery): readonly Row[] | undefined {\n const tables = lastSnapshot(log, query.target);\n if (!tables) return undefined;\n const rows = tables[query.table] ?? [];\n const where = query.where ?? [];\n return rows.filter((r) => where.every((c) => rowMatches(r, c)));\n}\n\nfunction snapshotSeq(log: EffectLog, target: string): number[] {\n let last: number | undefined;\n for (const e of log.entries) if (e.kind === \"snapshot\" && e.target === target) last = e.seq;\n return last === undefined ? [] : [last];\n}\n\nexport interface Violation {\n readonly invariant: string;\n readonly message: string;\n /** The keys involved, in canonical JSON, so two violations can be compared across runs. */\n readonly keys: readonly string[];\n /** Sequence numbers of the effects that witness the violation. */\n readonly evidence: readonly number[];\n}\n\n/** True when two violations are of the same invariant for the same keys, whatever the traces. */\nexport function sameViolation(a: Violation, b: Violation): boolean {\n return (\n a.invariant === b.invariant &&\n a.keys.length === b.keys.length &&\n a.keys.every((k, i) => k === b.keys[i])\n );\n}\n\n/** Read a dot path such as \"args.params.0\" out of JSON data; undefined when absent. */\nexport function getPath(value: JsonValue, path: string): JsonValue | undefined {\n let cur: JsonValue | undefined = value;\n if (path === \"\") return cur;\n for (const part of path.split(\".\")) {\n if (cur === null || typeof cur !== \"object\") return undefined;\n if (Array.isArray(cur)) {\n const i = Number(part);\n if (!Number.isInteger(i) || i < 0) return undefined;\n cur = cur[i];\n } else {\n cur = (cur as { readonly [k: string]: JsonValue })[part];\n }\n if (cur === undefined) return undefined;\n }\n return cur;\n}\n\nfunction jsonEqual(a: JsonValue | undefined, b: JsonValue): boolean {\n if (a === undefined) return false;\n if (a === b) return true;\n if (a === null || b === null || typeof a !== \"object\" || typeof b !== \"object\") return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((x, i) => jsonEqual(x, b[i] as JsonValue));\n }\n const ao = a as { readonly [k: string]: JsonValue };\n const bo = b as { readonly [k: string]: JsonValue };\n const ak = Object.keys(ao).sort();\n const bk = Object.keys(bo).sort();\n return (\n ak.length === bk.length &&\n ak.every((k, i) => k === bk[i] && jsonEqual(ao[k], bo[k] as JsonValue))\n );\n}\n\nexport function matches(record: EffectRecord, sel: EffectSelector): boolean {\n if (record.target !== sel.target) return false;\n if (sel.op !== undefined && record.op !== sel.op) return false;\n if (sel.kind !== undefined && record.kind !== sel.kind) return false;\n if (sel.where) {\n for (const [path, want] of Object.entries(sel.where)) {\n if (!jsonEqual(getPath(record.payload, path), want)) return false;\n }\n }\n if (sel.atLeast) {\n for (const [path, min] of Object.entries(sel.atLeast)) {\n const v = getPath(record.payload, path);\n if (typeof v !== \"number\" || v < min) return false;\n }\n }\n return true;\n}\n\nexport function selectEffects(log: EffectLog, sel: EffectSelector): readonly EffectRecord[] {\n return log.entries.filter((e) => matches(e, sel));\n}\n\n/** Canonical form of a key, so equal JSON values compare equal whatever their key order. */\nfunction keyOf(record: EffectRecord, path: string): string | undefined {\n const v = getPath(record.payload, path);\n return v === undefined ? undefined : canonicalJson(v);\n}\n\nexport function checkInvariant(log: EffectLog, inv: Invariant): Violation | null {\n switch (inv.kind) {\n case \"everyRow\":\n return checkEveryRow(log, inv);\n case \"implies\":\n return checkImplies(log, inv);\n case \"atMostOncePerKey\": {\n const seen = new Map<string, number>();\n for (const e of selectEffects(log, inv.effect)) {\n const k = keyOf(e, inv.key);\n if (k === undefined) continue;\n const first = seen.get(k);\n if (first !== undefined) {\n return {\n invariant: inv.name,\n message: `${inv.effect.target} ${inv.effect.op ?? \"*\"} happened twice for key ${k} (seq ${first} and ${e.seq})`,\n keys: [k],\n evidence: [first, e.seq],\n };\n }\n seen.set(k, e.seq);\n }\n return null;\n }\n case \"atLeastOnce\": {\n const present = new Set<string>();\n const selected = selectEffects(log, inv.effect);\n for (const e of selected) {\n const k = keyOf(e, inv.key);\n if (k !== undefined) present.add(k);\n }\n const missing = inv.expected.map((v) => canonicalJson(v)).filter((k) => !present.has(k));\n if (missing.length === 0) return null;\n return {\n invariant: inv.name,\n message: `${inv.effect.target} ${inv.effect.op ?? \"*\"} never happened for key(s) ${missing.join(\", \")}`,\n keys: missing,\n evidence: selected.map((e) => e.seq),\n };\n }\n }\n}\n\nfunction checkEveryRow(log: EffectLog, inv: EveryRow): Violation | null {\n const rows = queryRows(log, inv.query);\n if (rows === undefined) {\n return {\n invariant: inv.name,\n message: `no snapshot of ${inv.query.target} was recorded`,\n keys: [],\n evidence: [],\n };\n }\n const failing = rows.filter((r) => !inv.holds.every((c) => rowMatches(r, c)));\n if (failing.length === 0) return null;\n const keys = failing.map((r) => canonicalJson(r));\n const shown = keys.slice(0, 3).join(\", \") + (failing.length > 3 ? \", …\" : \"\");\n return {\n invariant: inv.name,\n message: `${failing.length} row(s) of ${inv.query.table} in ${inv.query.target} violate the condition: ${shown}`,\n keys,\n evidence: snapshotSeq(log, inv.query.target),\n };\n}\n\nfunction checkImplies(log: EffectLog, inv: Implies): Violation | null {\n const rows = queryRows(log, inv.state);\n if (rows === undefined) {\n return {\n invariant: inv.name,\n message: `no snapshot of ${inv.state.target} was recorded`,\n keys: [],\n evidence: [],\n };\n }\n const present = new Set<string>();\n for (const r of rows) {\n const v = r[inv.column];\n if (v !== undefined) present.add(canonicalJson(v));\n }\n const missing: string[] = [];\n const evidence: number[] = [];\n for (const e of selectEffects(log, inv.when)) {\n const k = keyOf(e, inv.key);\n if (k === undefined || present.has(k)) continue;\n if (!missing.includes(k)) missing.push(k);\n evidence.push(e.seq);\n }\n if (missing.length === 0) return null;\n return {\n invariant: inv.name,\n message: `${inv.when.target} ${inv.when.op ?? \"*\"} happened for key(s) ${missing.join(\", \")} but ${inv.state.table}.${inv.column} in ${inv.state.target} has no such row`,\n keys: missing,\n evidence,\n };\n}\n\nexport function checkInvariants(\n log: EffectLog,\n invariants: readonly Invariant[],\n): readonly Violation[] {\n const out: Violation[] = [];\n for (const inv of invariants) {\n const v = checkInvariant(log, inv);\n if (v) out.push(v);\n }\n return out;\n}\n","/**\n * The scenario spec: everything Eutaxy needs to run one scenario, as plain\n * data. The platform-neutral parts are typed here; what a platform needs to\n * drive the Worker and set up its state travels as `PlatformData`, which only\n * that platform's package knows how to build and interpret.\n */\n\nimport type { JsonValue } from \"./canonical-json.ts\";\nimport type { Invariant } from \"./invariants.ts\";\n\n/** Opaque, JSON-shaped data owned by a platform package. `kind` names its shape. */\nexport interface PlatformData {\n readonly kind: string;\n readonly [key: string]: JsonValue;\n}\n\n/** Where the Worker under test lives, relative to the scenario file. */\nexport interface WorkerRef {\n readonly entry: string;\n readonly config: string;\n}\n\n/** An external service the Worker talks to, and what it promises. */\nexport interface ExternalDecl {\n readonly name: string;\n /** Host pattern for outbound requests, e.g. \"api.stripe.com\". */\n readonly host: string;\n readonly guarantees: {\n /** Header whose value makes repeated requests idempotent, if any. */\n readonly dedupeBy?: string;\n };\n}\n\n/** Exploration bounds. Every verdict reports the bounds actually reached. */\nexport interface Bounds {\n readonly maxFaultsPerRun: number;\n readonly maxRedeliveries: number;\n readonly maxConcurrentInvocations: number;\n readonly budgetMs: number;\n}\n\nexport interface ScenarioSpec {\n readonly version: 1;\n readonly name: string;\n readonly worker: WorkerRef;\n /** How the scenario drives the Worker (which events, with what payloads). */\n readonly drive: PlatformData;\n /** State to establish before each run, in order. */\n readonly setup: readonly PlatformData[];\n readonly externals: readonly ExternalDecl[];\n readonly invariants: readonly Invariant[];\n readonly bounds: Bounds;\n}\n\nexport const defaultBounds: Bounds = {\n maxFaultsPerRun: 1,\n maxRedeliveries: 2,\n maxConcurrentInvocations: 1,\n budgetMs: 2000,\n};\n\nfunction isPositiveInt(n: unknown): n is number {\n return typeof n === \"number\" && Number.isInteger(n) && n > 0;\n}\n\n/** Every reason a spec is not usable; empty when it is. */\nexport function validateScenario(spec: ScenarioSpec): readonly string[] {\n const errors: string[] = [];\n if (spec.version !== 1) errors.push(`version must be 1, got ${String(spec.version)}`);\n if (!spec.name.trim()) errors.push(\"name must not be empty\");\n if (!/^[a-z0-9][a-z0-9-]*$/.test(spec.name))\n errors.push(`name must be lowercase letters, digits and dashes: ${JSON.stringify(spec.name)}`);\n if (!spec.worker.entry) errors.push(\"worker.entry must not be empty\");\n if (!spec.worker.config) errors.push(\"worker.config must not be empty\");\n if (!spec.drive.kind) errors.push(\"drive.kind must not be empty\");\n spec.setup.forEach((s, i) => {\n if (!s.kind) errors.push(`setup[${i}].kind must not be empty`);\n });\n if (spec.invariants.length === 0) errors.push(\"at least one invariant is required\");\n const names = new Set<string>();\n for (const inv of spec.invariants) {\n if (!inv.name) errors.push(\"every invariant needs a name\");\n else if (names.has(inv.name))\n errors.push(`duplicate invariant name ${JSON.stringify(inv.name)}`);\n names.add(inv.name);\n }\n const ext = new Set<string>();\n for (const e of spec.externals) {\n if (ext.has(e.name)) errors.push(`duplicate external ${JSON.stringify(e.name)}`);\n ext.add(e.name);\n }\n for (const k of [\n \"maxFaultsPerRun\",\n \"maxRedeliveries\",\n \"maxConcurrentInvocations\",\n \"budgetMs\",\n ] as const) {\n if (k === \"maxFaultsPerRun\" || k === \"maxRedeliveries\") {\n const v = spec.bounds[k];\n if (!(Number.isInteger(v) && v >= 0))\n errors.push(`bounds.${k} must be a non-negative integer`);\n } else if (!isPositiveInt(spec.bounds[k]))\n errors.push(`bounds.${k} must be a positive integer`);\n }\n return errors;\n}\n","/**\n * Shrinking, the pure part: given a violating schedule and the ids of the\n * events that drove it, the candidates to try next, smallest change first.\n * The engine replays each candidate and keeps it when the same invariant is\n * still violated for the same keys.\n */\n\nimport type { Schedule } from \"./faults.ts\";\n\nexport interface ShrinkState {\n readonly schedule: Schedule;\n readonly keepIds: readonly string[];\n}\n\n/** One-step candidates: each single fault removed, then each single event removed. */\nexport function shrinkCandidates(state: ShrinkState): readonly ShrinkState[] {\n const out: ShrinkState[] = [];\n state.schedule.faults.forEach((_, i) => {\n out.push({\n schedule: {\n seed: state.schedule.seed,\n faults: state.schedule.faults.filter((_, j) => j !== i),\n },\n keepIds: state.keepIds,\n });\n });\n if (state.keepIds.length > 1) {\n state.keepIds.forEach((id) => {\n out.push({\n schedule: {\n seed: state.schedule.seed,\n // Faults that belonged to the removed event's invocations can no longer fire.\n faults: state.schedule.faults.filter((f) => !f.invocation.startsWith(`${id}#`)),\n },\n keepIds: state.keepIds.filter((k) => k !== id),\n });\n });\n }\n return out;\n}\n\n/** Fewer events, then fewer faults: the order that keeps shrinking monotone. */\nexport function shrinkSize(state: ShrinkState): number {\n return state.keepIds.length * 1000 + state.schedule.faults.length;\n}\n","/** Simulation verdicts, with what each one rests on. */\n\nimport type { EffectLog } from \"./effect-log.ts\";\nimport type { Schedule } from \"./faults.ts\";\nimport type { Violation } from \"./invariants.ts\";\nimport type { Bounds } from \"./scenario.ts\";\n\nexport interface BoundsReached {\n readonly runs: number;\n readonly effectPoints: number;\n readonly faultsInjected: number;\n readonly elapsedMs: number;\n}\n\ninterface VerdictBase {\n readonly scenario: string;\n readonly seed: number | string;\n readonly reached: BoundsReached;\n readonly assumptions: readonly string[];\n}\n\nexport interface Shrunk {\n readonly faultsRemoved: number;\n readonly eventsRemoved: number;\n readonly runs: number;\n}\n\nexport interface Violated extends VerdictBase {\n readonly kind: \"VIOLATED\";\n readonly violation: Violation;\n /** The replayed, shrunk trace that breaks the invariant. */\n readonly trace: EffectLog;\n readonly schedule: Schedule;\n /** Ids of the drive's events that remain after shrinking. */\n readonly keptEvents: readonly string[];\n readonly shrunk: Shrunk;\n}\n\nexport interface Bounded extends VerdictBase {\n readonly kind: \"BOUNDED\";\n readonly bounds: Bounds;\n}\n\nexport type UnsupportedCause =\n | \"unsupported_api\"\n | \"not_replayable\"\n | \"module_state_leak\"\n | \"unsupported_bounds\";\n\nexport interface Unsupported extends VerdictBase {\n readonly kind: \"UNSUPPORTED\";\n readonly cause: UnsupportedCause;\n readonly reason: string;\n}\n\nexport type ScenarioVerdict = Violated | Bounded | Unsupported;\n\n/** One line per verdict, for error messages; full rendering lives in `report`. */\nexport function describeVerdict(v: ScenarioVerdict): string {\n switch (v.kind) {\n case \"VIOLATED\":\n return `VIOLATED ${v.scenario}: ${v.violation.message} (seed ${v.schedule.seed}, ${v.reached.runs} run(s))`;\n case \"BOUNDED\":\n return `BOUNDED ${v.scenario}: no violation in ${v.reached.runs} run(s), ${v.reached.effectPoints} effect point(s), ${v.reached.faultsInjected} fault(s)`;\n case \"UNSUPPORTED\":\n return `UNSUPPORTED ${v.scenario}: ${v.cause}: ${v.reason}`;\n }\n}\n","/**\n * The virtual clock and the globals patch. Within a run, Date, Math.random,\n * crypto.randomUUID, crypto.getRandomValues, performance.now and the timer\n * functions are replaced by deterministic versions driven by the run's seed\n * and by a clock that starts at a fixed epoch and advances only when the\n * engine says so: one millisecond per effect, and to each timer's due time\n * when the timer fires. Timers fire only while the invocation is idle (no\n * effect in flight), in due order, so they never race real I/O.\n */\n\nimport { nextFloat, nextU32, type PrngState, seedPrng } from \"@eutaxy/core\";\n\n/** 2026-01-01T00:00:00.000Z. Every run starts here. */\nexport const virtualEpoch = Date.UTC(2026, 0, 1);\n\n/** Virtual time advances this much at each effect completion. */\nexport const effectLatencyMs = 1;\n\n// Captured before any patching, so the engine can yield to the real event loop.\nconst realSetTimeout = globalThis.setTimeout.bind(globalThis);\n\ninterface Timer {\n readonly id: number;\n due: number;\n readonly callback: (...args: unknown[]) => void;\n readonly args: readonly unknown[];\n readonly interval: number | null;\n /** The invocation that scheduled it; cancelled with that invocation when it crashes. */\n readonly owner: string | null;\n}\n\nexport class VirtualClock {\n private time = virtualEpoch;\n private timers: Timer[] = [];\n private nextId = 1;\n /** The invocation whose code is running; timers scheduled now belong to it. */\n owner: string | null = null;\n\n now(): number {\n return this.time;\n }\n\n advance(ms: number): void {\n if (ms > 0) this.time += ms;\n }\n\n private schedule(\n callback: (...args: unknown[]) => void,\n delay: number,\n args: readonly unknown[],\n interval: number | null,\n ): number {\n const ms = Number.isFinite(delay) && delay > 0 ? Math.floor(delay) : 0;\n const timer: Timer = {\n id: this.nextId++,\n due: this.time + ms,\n callback,\n args,\n interval,\n owner: this.owner,\n };\n this.timers.push(timer);\n return timer.id;\n }\n\n setTimeout(callback: (...args: unknown[]) => void, delay = 0, ...args: unknown[]): number {\n return this.schedule(callback, delay, args, null);\n }\n\n setInterval(callback: (...args: unknown[]) => void, delay = 0, ...args: unknown[]): number {\n return this.schedule(callback, delay, args, Math.max(1, Math.floor(delay)));\n }\n\n clear(id: number): void {\n this.timers = this.timers.filter((t) => t.id !== id);\n }\n\n pendingTimers(): number {\n return this.timers.length;\n }\n\n /** Drop every timer an invocation scheduled: a crashed invocation's code must never run again. */\n cancelOwner(owner: string): void {\n this.timers = this.timers.filter((t) => t.owner !== owner);\n }\n\n /** Advance to the earliest due timer and run it. False when none is pending. */\n fireNext(): boolean {\n if (this.timers.length === 0) return false;\n let earliest = this.timers[0] as Timer;\n for (const t of this.timers)\n if (t.due < earliest.due || (t.due === earliest.due && t.id < earliest.id)) earliest = t;\n if (earliest.due > this.time) this.time = earliest.due;\n if (earliest.interval === null) this.timers = this.timers.filter((t) => t !== earliest);\n else earliest.due = this.time + earliest.interval;\n try {\n earliest.callback(...earliest.args);\n } catch {\n // An exception in a timer callback is uncaught in the real runtime too; it does not stop the run.\n }\n return true;\n }\n}\n\n/** Deterministic randomness for a run: Math.random, UUIDs and random bytes from one seeded stream. */\nexport class VirtualRandom {\n private state: PrngState;\n\n constructor(seed: number | string) {\n this.state = seedPrng(`random:${seed}`);\n }\n\n float(): number {\n const [f, next] = nextFloat(this.state);\n this.state = next;\n return f;\n }\n\n u32(): number {\n const [u, next] = nextU32(this.state);\n this.state = next;\n return u;\n }\n\n fill<T extends ArrayBufferView>(view: T): T {\n const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);\n for (let i = 0; i < bytes.length; i += 4) {\n const u = this.u32();\n for (let j = 0; j < 4 && i + j < bytes.length; j++) bytes[i + j] = (u >>> (8 * j)) & 0xff;\n }\n return view;\n }\n\n /** A version-4 UUID from the stream. */\n uuid(): string {\n const b = this.fill(new Uint8Array(16));\n b[6] = ((b[6] as number) & 0x0f) | 0x40;\n b[8] = ((b[8] as number) & 0x3f) | 0x80;\n const hex = [...b].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n }\n}\n\nexport interface Patched {\n readonly clock: VirtualClock;\n readonly random: VirtualRandom;\n /** Put every global back. Safe to call twice. */\n restore(): void;\n}\n\ntype Global = Record<string, unknown>;\n\n/** Replace the nondeterministic globals for one run. Returns the handle that restores them. */\nexport function patchGlobals(seed: number | string): Patched {\n const clock = new VirtualClock();\n const random = new VirtualRandom(seed);\n const g = globalThis as unknown as Global;\n const saved = new Map<string, PropertyDescriptor | undefined>();\n const define = (target: object, key: string, value: unknown): void => {\n const owner = target === g ? g : target;\n saved.set(key, Object.getOwnPropertyDescriptor(owner, key));\n Object.defineProperty(owner, key, {\n value,\n configurable: true,\n writable: true,\n enumerable: false,\n });\n };\n\n const RealDate = Date;\n const VirtualDate = function (this: unknown, ...args: unknown[]): unknown {\n if (new.target === undefined) return new RealDate(clock.now()).toString();\n return Reflect.construct(RealDate, args.length === 0 ? [clock.now()] : args, new.target);\n } as unknown as DateConstructor;\n Object.setPrototypeOf(VirtualDate, RealDate);\n Object.defineProperty(VirtualDate, \"prototype\", { value: RealDate.prototype, writable: false });\n Object.defineProperty(VirtualDate, \"now\", {\n value: () => clock.now(),\n configurable: true,\n writable: true,\n });\n Object.defineProperty(VirtualDate, \"name\", { value: \"Date\" });\n\n define(g, \"Date\", VirtualDate);\n define(Math, \"random\", () => random.float());\n define(g, \"setTimeout\", (cb: (...a: unknown[]) => void, ms?: number, ...args: unknown[]) =>\n clock.setTimeout(cb, ms, ...args),\n );\n define(g, \"setInterval\", (cb: (...a: unknown[]) => void, ms?: number, ...args: unknown[]) =>\n clock.setInterval(cb, ms, ...args),\n );\n define(g, \"clearTimeout\", (id?: number) => id !== undefined && clock.clear(id));\n define(g, \"clearInterval\", (id?: number) => id !== undefined && clock.clear(id));\n const perf = g[\"performance\"];\n if (perf && typeof perf === \"object\") define(perf, \"now\", () => clock.now() - virtualEpoch);\n const cryptoObj = g[\"crypto\"];\n if (cryptoObj && typeof cryptoObj === \"object\") {\n define(cryptoObj, \"randomUUID\", () => random.uuid());\n define(cryptoObj, \"getRandomValues\", <T extends ArrayBufferView>(view: T) => random.fill(view));\n }\n\n let restored = false;\n const targets: Record<string, object> = {\n Date: g,\n random: Math,\n setTimeout: g,\n setInterval: g,\n clearTimeout: g,\n clearInterval: g,\n };\n if (perf && typeof perf === \"object\") targets[\"now\"] = perf;\n if (cryptoObj && typeof cryptoObj === \"object\") {\n targets[\"randomUUID\"] = cryptoObj;\n targets[\"getRandomValues\"] = cryptoObj;\n }\n return {\n clock,\n random,\n restore() {\n if (restored) return;\n restored = true;\n for (const [key, descriptor] of saved) {\n const target = targets[key] as object;\n if (descriptor) Object.defineProperty(target, key, descriptor);\n else delete (target as Global)[key];\n }\n },\n };\n}\n\n/** Yield to the real event loop so microtasks and pending I/O can make progress. */\nexport function realTick(): Promise<void> {\n return new Promise((resolve) => realSetTimeout(() => resolve(), 0));\n}\n\nexport interface SettleContext {\n readonly clock: VirtualClock;\n /** Effects currently performing real I/O. */\n readonly inFlight: () => number;\n /** True once the invocation has been crashed; the promise is then abandoned. */\n readonly crashed: () => boolean;\n /** Called when nothing can make progress: no I/O, no timers, not done. */\n readonly stuck: () => void;\n}\n\n/**\n * Wait for a promise under the virtual clock: while it is pending, let real\n * I/O complete, and when the invocation is idle fire the next due timer.\n */\nexport async function settleOn<T>(promise: Promise<T>, ctx: SettleContext): Promise<T> {\n let done = false;\n let failed = false;\n let value: T | undefined;\n let error: unknown;\n const tracked = promise.then(\n (v) => {\n done = true;\n value = v;\n },\n (e: unknown) => {\n done = true;\n failed = true;\n error = e;\n },\n );\n for (;;) {\n await Promise.race([tracked, realTick()]);\n if (done) {\n if (failed) throw error;\n return value as T;\n }\n if (ctx.crashed()) return new Promise<T>(() => {});\n if (ctx.inFlight() > 0) continue;\n if (ctx.clock.fireNext()) continue;\n ctx.stuck();\n return new Promise<T>(() => {});\n }\n}\n","/**\n * One run of a scenario: the effect log being built, the fault decisions\n * (drawn during exploration, looked up during replay), and the invocations\n * in flight. Proxies in the platform package call `effect`/`effectSync`\n * around every real effect; everything else is engine-internal.\n */\n\nimport {\n appendEffect,\n type Bounds,\n decideFault,\n type EffectLog,\n type EffectPoint,\n emptyLog,\n type FaultDecision,\n type FaultKind,\n type Invocation,\n type JsonValue,\n type PrngState,\n type RunHooks,\n type Schedule,\n seedPrng,\n} from \"@eutaxy/core\";\nimport { effectLatencyMs, type Patched, patchGlobals, settleOn } from \"./clock.ts\";\n\n/** Thrown into user code by a synchronous effect that crashes the invocation. */\nexport class CrashSignal extends Error {\n override readonly name = \"CrashSignal\";\n readonly invocation: string;\n constructor(invocation: string) {\n super(`invocation ${invocation} crashed by an injected fault`);\n this.invocation = invocation;\n }\n}\n\n/** The error user code sees for an injected `error` or `ambiguous` fault. */\nexport class InjectedEffectError extends Error {\n override readonly name = \"InjectedEffectError\";\n readonly point: EffectPoint;\n readonly fault: FaultKind;\n constructor(point: EffectPoint, fault: FaultKind) {\n super(`${point.target} ${point.op} failed (injected ${fault})`);\n this.point = point;\n this.fault = fault;\n }\n}\n\nexport interface RunOptions {\n readonly mode: \"explore\" | \"replay\";\n readonly schedule: Schedule;\n readonly bounds: Bounds;\n readonly faultProbability: number;\n}\n\nclass InvocationState implements Invocation {\n readonly crashed: Promise<void>;\n private readonly onCrash: () => void;\n /** Effect points seen so far in this invocation; the next effect's ordinal. */\n ordinal = 0;\n private crashedFlag = false;\n private resolveCrashed: () => void = () => {};\n readonly id: string;\n\n constructor(id: string, onCrash: () => void) {\n this.id = id;\n this.onCrash = onCrash;\n this.crashed = new Promise<void>((resolve) => {\n this.resolveCrashed = resolve;\n });\n }\n\n isCrashed(): boolean {\n return this.crashedFlag;\n }\n\n crash(): void {\n if (this.crashedFlag) return;\n this.crashedFlag = true;\n this.onCrash();\n this.resolveCrashed();\n }\n}\n\n/** A promise that never settles: how an abandoned invocation experiences a crash. */\nfunction hang<T>(): Promise<T> {\n return new Promise<T>(() => {});\n}\n\nexport class Run implements RunHooks {\n private logState: EffectLog = emptyLog;\n private readonly faults: FaultDecision[] = [];\n private effectIndex = 0;\n private prng: PrngState;\n private readonly invocations = new Map<string, InvocationState>();\n private readonly reasons: string[] = [];\n private inFlight = 0;\n private patched: Patched | null = null;\n\n readonly options: RunOptions;\n\n constructor(options: RunOptions) {\n this.options = options;\n this.prng = seedPrng(options.schedule.seed);\n }\n\n get log(): EffectLog {\n return this.logState;\n }\n\n /** The schedule as actually executed: input decisions during replay, drawn ones during exploration. */\n get schedule(): Schedule {\n return { seed: this.options.schedule.seed, faults: [...this.faults] };\n }\n\n get effectPoints(): number {\n return this.effectIndex;\n }\n\n get faultsInjected(): number {\n return this.faults.length;\n }\n\n get unsupportedReasons(): readonly string[] {\n return this.reasons;\n }\n\n /** Replace the nondeterministic globals for this run. Call `uninstall` in a finally. */\n install(): void {\n if (!this.patched) this.patched = patchGlobals(this.options.schedule.seed);\n }\n\n uninstall(): void {\n this.patched?.restore();\n this.patched = null;\n }\n\n /** Virtual milliseconds since the epoch, or the real clock when the globals are not installed. */\n now(): number {\n return this.patched ? this.patched.clock.now() : Date.now();\n }\n\n /** Timers scheduled and not yet fired or cancelled (0 when the globals are not installed). */\n pendingTimers(): number {\n return this.patched?.clock.pendingTimers() ?? 0;\n }\n\n startInvocation(id: string): Invocation {\n if (this.invocations.has(id)) throw new Error(`invocation ${id} already started`);\n const inv = new InvocationState(id, () => this.patched?.clock.cancelOwner(id));\n this.invocations.set(id, inv);\n if (this.patched) this.patched.clock.owner = id;\n return inv;\n }\n\n private invocation(id: string): InvocationState {\n const inv = this.invocations.get(id);\n if (!inv) throw new Error(`effect from unknown invocation ${id}`);\n return inv;\n }\n\n private decide(point: EffectPoint, ordinal: number): FaultKind | null {\n if (this.options.mode === \"replay\") {\n const d = this.options.schedule.faults.find(\n (f) => f.invocation === point.invocation && f.ordinal === ordinal,\n );\n return d && point.allowed.includes(d.fault) ? d.fault : null;\n }\n const [fault, next] = decideFault(this.prng, point.allowed, this.faults.length, {\n maxFaultsPerRun: this.options.bounds.maxFaultsPerRun,\n faultProbability: this.options.faultProbability,\n });\n this.prng = next;\n return fault;\n }\n\n private record(point: EffectPoint, kind: \"call\" | \"result\" | \"fault\", payload: JsonValue): void {\n this.logState = appendEffect(this.logState, {\n kind,\n invocation: point.invocation,\n target: point.target,\n op: point.op,\n payload,\n });\n }\n\n /** Open an effect point: record the call and decide the fault. */\n private open(point: EffectPoint, inv: InvocationState): FaultKind | null {\n this.effectIndex++;\n const ordinal = inv.ordinal++;\n this.record(point, \"call\", { args: point.args });\n const fault = this.decide(point, ordinal);\n if (fault) {\n this.faults.push({\n invocation: point.invocation,\n ordinal,\n fault,\n target: point.target,\n op: point.op,\n });\n }\n return fault;\n }\n\n async effect<T>(\n point: EffectPoint,\n perform: () => Promise<T>,\n summarize: (value: T) => JsonValue,\n ): Promise<T> {\n const inv = this.invocation(point.invocation);\n if (inv.isCrashed()) return hang();\n const fault = this.open(point, inv);\n if (fault === \"crash_before\" || fault === \"cut_off\") {\n this.record(point, \"fault\", { args: point.args, fault });\n inv.crash();\n return hang();\n }\n if (fault === \"error\") {\n this.record(point, \"fault\", { args: point.args, fault });\n throw new InjectedEffectError(point, fault);\n }\n let value: T;\n this.inFlight++;\n try {\n value = await perform();\n } catch (err) {\n this.record(point, \"result\", { args: point.args, result: null, error: String(err) });\n throw err;\n } finally {\n this.inFlight--;\n this.patched?.clock.advance(effectLatencyMs);\n }\n this.record(point, \"result\", { args: point.args, result: summarize(value) });\n // The effect committed while the invocation was dying: nothing after it runs.\n if (inv.isCrashed()) return hang();\n if (fault === \"crash_after\") {\n this.record(point, \"fault\", { args: point.args, fault });\n inv.crash();\n return hang();\n }\n if (fault === \"ambiguous\") {\n this.record(point, \"fault\", { args: point.args, fault });\n throw new InjectedEffectError(point, fault);\n }\n return value;\n }\n\n effectSync<T>(point: EffectPoint, perform: () => T, summarize: (value: T) => JsonValue): T {\n const inv = this.invocation(point.invocation);\n if (inv.isCrashed()) throw new CrashSignal(inv.id);\n const fault = this.open(point, inv);\n if (fault === \"crash_before\" || fault === \"cut_off\") {\n this.record(point, \"fault\", { args: point.args, fault });\n inv.crash();\n throw new CrashSignal(inv.id);\n }\n if (fault === \"error\") {\n this.record(point, \"fault\", { args: point.args, fault });\n throw new InjectedEffectError(point, fault);\n }\n let value: T;\n try {\n value = perform();\n } catch (err) {\n this.record(point, \"result\", { args: point.args, result: null, error: String(err) });\n throw err;\n }\n this.record(point, \"result\", { args: point.args, result: summarize(value) });\n if (fault === \"crash_after\") {\n this.record(point, \"fault\", { args: point.args, fault });\n inv.crash();\n throw new CrashSignal(inv.id);\n }\n if (fault === \"ambiguous\") {\n this.record(point, \"fault\", { args: point.args, fault });\n throw new InjectedEffectError(point, fault);\n }\n return value;\n }\n\n unsupported(reason: string): void {\n this.reasons.push(reason);\n }\n\n settle<T>(promise: Promise<T>, invocation: string): Promise<T> {\n const inv = this.invocation(invocation);\n const patched = this.patched;\n if (patched) patched.clock.owner = invocation;\n if (!patched) {\n // Globals not installed (direct use outside runOnce): just wait, abandoning on crash.\n return Promise.race([promise, inv.crashed.then(() => new Promise<T>(() => {}))]);\n }\n return settleOn(promise, {\n clock: patched.clock,\n inFlight: () => this.inFlight,\n crashed: () => inv.isCrashed(),\n stuck: () => {\n // Nothing can make progress: report it and abandon the invocation so the run can end.\n this.unsupported(\n `invocation ${invocation} is waiting on something Eutaxy cannot see (no effect in flight, no timer due)`,\n );\n inv.crash();\n },\n });\n }\n\n snapshot(invocation: string, target: string, payload: JsonValue): void {\n this.logState = appendEffect(this.logState, {\n kind: \"snapshot\",\n invocation,\n target,\n op: \"snapshot\",\n payload,\n });\n }\n}\n","/**\n * Shrinking, the loop: try the pure candidates in order, replay each, keep\n * the first that still violates the same invariant for the same keys, and\n * repeat from there until nothing smaller works or the budget is spent.\n */\n\nimport {\n checkInvariants,\n type EffectLog,\n type Platform,\n recomputePerMessageExpected,\n type ScenarioSpec,\n type Schedule,\n type ShrinkState,\n sameViolation,\n shrinkCandidates,\n type Violation,\n} from \"@eutaxy/core\";\nimport { replayRun } from \"./explore.ts\";\n\nexport interface ShrinkOptions {\n readonly maxRuns: number;\n readonly deadline: number;\n readonly now: () => number;\n}\n\nexport interface ShrinkResult {\n readonly spec: ScenarioSpec;\n readonly schedule: Schedule;\n readonly keptEvents: readonly string[];\n readonly trace: EffectLog;\n readonly violation: Violation;\n readonly runs: number;\n readonly faultsRemoved: number;\n readonly eventsRemoved: number;\n}\n\n/** The scenario restricted to the kept events, with per-message invariants re-derived. */\nexport function restrictSpec(\n platform: Platform,\n spec: ScenarioSpec,\n keepIds: readonly string[],\n): ScenarioSpec {\n const restricted = platform.restrictEvents(spec, keepIds);\n const bodies = platform.initialEvents(restricted).map((e) => e.data);\n return { ...restricted, invariants: recomputePerMessageExpected(restricted.invariants, bodies) };\n}\n\nexport async function shrink(\n spec: ScenarioSpec,\n platform: Platform,\n schedule: Schedule,\n violation: Violation,\n trace: EffectLog,\n options: ShrinkOptions,\n): Promise<ShrinkResult> {\n const allIds = platform.initialEvents(spec).map((e) => e.id);\n let state: ShrinkState = { schedule, keepIds: allIds };\n let best = { trace, violation };\n let runs = 0;\n\n let improved = true;\n while (improved && runs < options.maxRuns && options.now() < options.deadline) {\n improved = false;\n for (const candidate of shrinkCandidates(state)) {\n if (runs >= options.maxRuns || options.now() >= options.deadline) break;\n const candidateSpec = restrictSpec(platform, spec, candidate.keepIds);\n const run = await replayRun(candidateSpec, platform, candidate.schedule);\n const log = run.log;\n runs++;\n // A smaller run that touches something Eutaxy cannot control is not a valid counterexample.\n if (run.unsupportedReasons.length > 0) continue;\n const found = checkInvariants(log, candidateSpec.invariants).find((v) =>\n sameViolation(v, violation),\n );\n if (found) {\n state = candidate;\n best = { trace: log, violation: found };\n improved = true;\n break;\n }\n }\n }\n return {\n spec: restrictSpec(platform, spec, state.keepIds),\n schedule: state.schedule,\n keptEvents: state.keepIds,\n trace: best.trace,\n violation: best.violation,\n runs,\n faultsRemoved: schedule.faults.length - state.schedule.faults.length,\n eventsRemoved: allIds.length - state.keepIds.length,\n };\n}\n","/**\n * The exploration loop: run the scenario under seeded fault injection until\n * an invariant breaks or the budget is spent; replay any violation before\n * reporting it.\n */\n\nimport {\n type BoundsReached,\n checkInvariants,\n digestLog,\n type EffectLog,\n type EventRef,\n nextU32,\n type Platform,\n type PrngState,\n type ScenarioSpec,\n type ScenarioVerdict,\n type Schedule,\n seedPrng,\n} from \"@eutaxy/core\";\nimport { Run } from \"./run.ts\";\nimport { shrink } from \"./shrink.ts\";\n\nexport interface ExploreOptions {\n readonly seed: number | string;\n /** Hard cap on runs, in addition to the scenario's time budget. */\n readonly maxRuns?: number;\n /** Probability of injecting at an eligible effect point. */\n readonly faultProbability?: number;\n /** Clock for the time budget; injectable for tests. */\n readonly now?: () => number;\n /** Hard cap on replay runs spent shrinking a violation. */\n readonly maxShrinkRuns?: number;\n /**\n * How many of the first exploration runs to replay in reverse order\n * before reporting BOUNDED, to detect module-level state leaking between\n * runs. 0 disables the pass.\n */\n readonly leakCheckRuns?: number;\n}\n\nconst engineAssumptions: readonly string[] = [\n \"schedule points are effect boundaries: proxied bindings, patched timers and fetch; other awaits run in program order\",\n \"faults: crash_before, crash_after, ambiguous, error at any effect point; redelivery per the platform's queue model\",\n \"one invocation at a time (maxConcurrentInvocations = 1)\",\n \"Date, Math.random, crypto.randomUUID, crypto.getRandomValues, performance.now and timers are virtualised per run from the seed; time starts at 2026-01-01T00:00:00Z and advances 1 ms per effect and to each timer's due time\",\n \"timers fire only while the invocation is idle (no effect in flight), in due order; an invocation waiting on something outside Eutaxy makes the run UNSUPPORTED\",\n \"module-level state cannot be reset between runs; before BOUNDED the first runs are replayed in reverse order and any difference makes the run UNSUPPORTED (module_state_leak)\",\n];\n\n/** Run the scenario once under the given run's fault decisions and return its log. */\nexport async function runOnce(\n spec: ScenarioSpec,\n platform: Platform,\n run: Run,\n): Promise<EffectLog> {\n run.install();\n try {\n await platform.reset(spec);\n const pending: EventRef[] = [...platform.initialEvents(spec)];\n while (pending.length > 0) {\n const event = pending.shift() as EventRef;\n const result = await platform.deliver(event, run, spec);\n pending.push(...result.followUps);\n }\n await platform.finish?.(spec, run);\n } finally {\n run.uninstall();\n }\n return run.log;\n}\n\n/** Re-run a recorded schedule exactly and return the whole run (log and support status). */\nexport async function replayRun(\n spec: ScenarioSpec,\n platform: Platform,\n schedule: Schedule,\n): Promise<Run> {\n const run = new Run({ mode: \"replay\", schedule, bounds: spec.bounds, faultProbability: 0 });\n await runOnce(spec, platform, run);\n return run;\n}\n\n/** Re-run a recorded schedule exactly. */\nexport async function replay(\n spec: ScenarioSpec,\n platform: Platform,\n schedule: Schedule,\n): Promise<EffectLog> {\n return (await replayRun(spec, platform, schedule)).log;\n}\n\nexport async function explore(\n spec: ScenarioSpec,\n platform: Platform,\n options: ExploreOptions,\n): Promise<ScenarioVerdict> {\n const now = options.now ?? (() => Date.now());\n const maxRuns = options.maxRuns ?? 100;\n const faultProbability = options.faultProbability ?? 0.5;\n const assumptions = [...engineAssumptions, ...platform.assumptions];\n const start = now();\n const reached = { runs: 0, effectPoints: 0, faultsInjected: 0 };\n const done = (): BoundsReached => ({ ...reached, elapsedMs: now() - start });\n const base = { scenario: spec.name, seed: options.seed, assumptions };\n\n if (spec.bounds.maxConcurrentInvocations !== 1) {\n return {\n ...base,\n kind: \"UNSUPPORTED\",\n cause: \"unsupported_bounds\",\n reason: \"maxConcurrentInvocations above 1 is not supported yet\",\n reached: done(),\n };\n }\n\n const leakCheckRuns = options.leakCheckRuns ?? 2;\n /** The first runs' schedules and digests, replayed in reverse order before BOUNDED. */\n const witnesses: { schedule: Schedule; digest: string }[] = [];\n\n let master: PrngState = seedPrng(options.seed);\n while (reached.runs < maxRuns && now() - start < spec.bounds.budgetMs) {\n const [runSeed, next] = nextU32(master);\n master = next;\n const run = new Run({\n mode: \"explore\",\n schedule: { seed: runSeed, faults: [] },\n bounds: spec.bounds,\n faultProbability,\n });\n const log = await runOnce(spec, platform, run);\n reached.runs++;\n reached.effectPoints += run.effectPoints;\n reached.faultsInjected += run.faultsInjected;\n if (witnesses.length < leakCheckRuns)\n witnesses.push({ schedule: run.schedule, digest: digestLog(log) });\n\n if (run.unsupportedReasons.length > 0) {\n return {\n ...base,\n kind: \"UNSUPPORTED\",\n cause: \"unsupported_api\",\n reason: run.unsupportedReasons.join(\"; \"),\n reached: done(),\n };\n }\n\n const violations = checkInvariants(log, spec.invariants);\n const violation = violations[0];\n if (violation) {\n const schedule = run.schedule;\n const replayedRun = await replayRun(spec, platform, schedule);\n const replayed = replayedRun.log;\n reached.runs++;\n if (replayedRun.unsupportedReasons.length > 0 || digestLog(replayed) !== digestLog(log)) {\n return {\n ...base,\n kind: \"UNSUPPORTED\",\n cause: \"not_replayable\",\n reason: `the violating run of seed ${schedule.seed} did not reproduce on replay`,\n reached: done(),\n };\n }\n // Shrink within the scenario's budget (a second budget's worth at most), then report.\n const shrunk = await shrink(spec, platform, schedule, violation, replayed, {\n maxRuns: options.maxShrinkRuns ?? 200,\n deadline: now() + spec.bounds.budgetMs,\n now,\n });\n reached.runs += shrunk.runs;\n return {\n ...base,\n kind: \"VIOLATED\",\n violation: shrunk.violation,\n trace: shrunk.trace,\n schedule: shrunk.schedule,\n keptEvents: shrunk.keptEvents,\n shrunk: {\n faultsRemoved: shrunk.faultsRemoved,\n eventsRemoved: shrunk.eventsRemoved,\n runs: shrunk.runs,\n },\n reached: done(),\n };\n }\n }\n // Module-state leak detection: the same schedules, in the opposite order, must give the same logs.\n for (const witness of [...witnesses].reverse()) {\n const again = await replayRun(spec, platform, witness.schedule);\n reached.runs++;\n if (again.unsupportedReasons.length > 0 || digestLog(again.log) !== witness.digest) {\n return {\n ...base,\n kind: \"UNSUPPORTED\",\n cause: \"module_state_leak\",\n reason: `run seed ${witness.schedule.seed} produced a different log when replayed after other runs: state is leaking between runs (module-level variables, or a binding Eutaxy does not reset)`,\n reached: done(),\n };\n }\n }\n return { ...base, kind: \"BOUNDED\", bounds: spec.bounds, reached: done() };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { c as canonicalJson, i as emitRegressionTest, n as renderText } from "./src-Bef00JwK.mjs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
6
|
+
// packages/cli/src/args.ts
|
|
7
|
+
function parseArgs(argv, booleans = []) {
|
|
8
|
+
const positionals = [];
|
|
9
|
+
const flags = {};
|
|
10
|
+
for (let i = 0; i < argv.length; i++) {
|
|
11
|
+
const a = argv[i];
|
|
12
|
+
if (!a.startsWith("--")) {
|
|
13
|
+
positionals.push(a);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const eq = a.indexOf("=");
|
|
17
|
+
if (eq !== -1) {
|
|
18
|
+
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const name = a.slice(2);
|
|
22
|
+
const next = argv[i + 1];
|
|
23
|
+
if (booleans.includes(name) || next === void 0 || next.startsWith("--")) flags[name] = true;
|
|
24
|
+
else {
|
|
25
|
+
flags[name] = next;
|
|
26
|
+
i++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
positionals,
|
|
31
|
+
flags
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// packages/cli/src/generate.ts
|
|
35
|
+
/**
|
|
36
|
+
* The files `eutaxy check` writes into `<scenario dir>/.eutaxy/` before
|
|
37
|
+
* running Vitest: a test that performs the check inside workerd and prints
|
|
38
|
+
* the verdict on one marked line, and a Vitest config with the Cloudflare
|
|
39
|
+
* plugin pointed at the scenario's wrangler config.
|
|
40
|
+
*/
|
|
41
|
+
/** Key under which the generated test stores its result in the Vitest task meta, read back from the JSON reporter. */
|
|
42
|
+
const metaKey = "eutaxy";
|
|
43
|
+
function checkTestSource(g) {
|
|
44
|
+
const maxRuns = g.maxRuns === void 0 ? "" : `, maxRuns: ${g.maxRuns}`;
|
|
45
|
+
return `// Generated by eutaxy check; deleted after the run.
|
|
46
|
+
import { renderJson } from "@mooloo-ai/eutaxy";
|
|
47
|
+
import { check, createCloudflarePlatform, type QueueHandler } from "@mooloo-ai/eutaxy/cloudflare";
|
|
48
|
+
import { env } from "cloudflare:workers";
|
|
49
|
+
import { test } from "vitest";
|
|
50
|
+
import scenario from ${JSON.stringify(g.scenarioImport)};
|
|
51
|
+
|
|
52
|
+
const sqlFiles = import.meta.glob("../**/*.sql", { query: "?raw", import: "default", eager: true }) as Record<string, string>;
|
|
53
|
+
const modules = import.meta.glob(["../**/*.ts", "!../**/*.test.ts", "!../**/*.sim.ts", "!../**/node_modules/**", "!../**/.eutaxy/**"]) as Record<string, () => Promise<{ default: QueueHandler }>>;
|
|
54
|
+
|
|
55
|
+
function key(path: string): string {
|
|
56
|
+
return \`../\${path.replace(/^\\.\\//, "")}\`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test("eutaxy check", async ({ task }) => {
|
|
60
|
+
const schemas: Record<string, string> = {};
|
|
61
|
+
for (const step of scenario.setup) {
|
|
62
|
+
if (step.kind !== "d1") continue;
|
|
63
|
+
const sql = sqlFiles[key(String(step.schemaFile))];
|
|
64
|
+
if (sql === undefined) throw new Error(\`schema file \${String(step.schemaFile)} not found next to the scenario\`);
|
|
65
|
+
schemas[String(step.binding)] = sql;
|
|
66
|
+
}
|
|
67
|
+
const load = modules[key(scenario.worker.entry)];
|
|
68
|
+
if (!load) throw new Error(\`worker entry \${scenario.worker.entry} not found next to the scenario\`);
|
|
69
|
+
const handler = (await load()).default;
|
|
70
|
+
const platform = createCloudflarePlatform({ env, handler, schemas });
|
|
71
|
+
const verdict = await check(scenario, platform, { seed: ${JSON.stringify(g.seed)}${maxRuns} });
|
|
72
|
+
task.meta[${JSON.stringify(metaKey)}] = { report: JSON.parse(renderJson(verdict)), worker: scenario.worker, setup: scenario.setup };
|
|
73
|
+
});
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
function vitestConfigSource(wranglerConfigPath, testPath, outputFile) {
|
|
77
|
+
return `// Generated by eutaxy check; deleted after the run.
|
|
78
|
+
import { cloudflareTest } from "@cloudflare/vitest-plugin";
|
|
79
|
+
import { defineConfig } from "vitest/config";
|
|
80
|
+
|
|
81
|
+
export default defineConfig({
|
|
82
|
+
plugins: [cloudflareTest({ wrangler: { configPath: ${JSON.stringify(wranglerConfigPath)} } })],
|
|
83
|
+
test: {
|
|
84
|
+
include: [${JSON.stringify(testPath)}],
|
|
85
|
+
testTimeout: 600_000,
|
|
86
|
+
reporters: ["json"],
|
|
87
|
+
outputFile: ${JSON.stringify(outputFile)},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
// packages/cli/src/regression.ts
|
|
93
|
+
/** A Vitest regression test for a Cloudflare scenario, to run under the Cloudflare Vitest plugin. */
|
|
94
|
+
function cloudflareRegressionTest(verdict, options) {
|
|
95
|
+
const bindings = Object.keys(options.schemaPaths).sort();
|
|
96
|
+
const imports = [
|
|
97
|
+
"import { createCloudflarePlatform } from \"@mooloo-ai/eutaxy/cloudflare\";",
|
|
98
|
+
"import { env } from \"cloudflare:workers\";",
|
|
99
|
+
`import scenario from ${JSON.stringify(options.scenarioPath)};`,
|
|
100
|
+
...bindings.map((b) => `import schema_${b} from ${JSON.stringify(`${options.schemaPaths[b]}?raw`)};`),
|
|
101
|
+
`import handler from ${JSON.stringify(options.workerPath)};`
|
|
102
|
+
];
|
|
103
|
+
const schemas = `{ ${bindings.map((b) => `${b}: schema_${b}`).join(", ")} }`;
|
|
104
|
+
const retries = options.maxRetries === void 0 ? "" : `, maxRetries: ${options.maxRetries}`;
|
|
105
|
+
return emitRegressionTest(verdict, {
|
|
106
|
+
imports,
|
|
107
|
+
platform: `createCloudflarePlatform({ env, handler, schemas: ${schemas}${retries} })`,
|
|
108
|
+
scenario: "scenario",
|
|
109
|
+
...options.expect ? { expect: options.expect } : {},
|
|
110
|
+
...options.engineModule ? { engineModule: options.engineModule } : {}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// packages/cli/src/check.ts
|
|
114
|
+
/**
|
|
115
|
+
* `eutaxy check <scenario.sim.ts>`: run a scenario inside workerd through
|
|
116
|
+
* the user's Vitest and the Cloudflare plugin, print the verdict as text or
|
|
117
|
+
* JSON, and on a violation write a regression test next to the scenario.
|
|
118
|
+
*/
|
|
119
|
+
const exitCodes = {
|
|
120
|
+
BOUNDED: 0,
|
|
121
|
+
VIOLATED: 1,
|
|
122
|
+
UNSUPPORTED: 2,
|
|
123
|
+
error: 3
|
|
124
|
+
};
|
|
125
|
+
function findWrangler(dir, explicit, cwd) {
|
|
126
|
+
if (explicit) return resolve(cwd, explicit);
|
|
127
|
+
for (const name of [
|
|
128
|
+
"wrangler.jsonc",
|
|
129
|
+
"wrangler.json",
|
|
130
|
+
"wrangler.toml"
|
|
131
|
+
]) {
|
|
132
|
+
const p = join(dir, name);
|
|
133
|
+
if (existsSync(p)) return p;
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
function vitestBin(from) {
|
|
138
|
+
const req = createRequire(join(from, "package.json"));
|
|
139
|
+
return join(dirname(req.resolve("vitest/package.json")), "vitest.mjs");
|
|
140
|
+
}
|
|
141
|
+
function runVitest(bin, configPath, cwd) {
|
|
142
|
+
return new Promise((resolveRun) => {
|
|
143
|
+
const child = spawn(process.execPath, [
|
|
144
|
+
bin,
|
|
145
|
+
"run",
|
|
146
|
+
"--config",
|
|
147
|
+
configPath
|
|
148
|
+
], {
|
|
149
|
+
cwd,
|
|
150
|
+
env: {
|
|
151
|
+
...process.env,
|
|
152
|
+
CI: "1",
|
|
153
|
+
FORCE_COLOR: "0"
|
|
154
|
+
},
|
|
155
|
+
stdio: [
|
|
156
|
+
"ignore",
|
|
157
|
+
"pipe",
|
|
158
|
+
"pipe"
|
|
159
|
+
]
|
|
160
|
+
});
|
|
161
|
+
let output = "";
|
|
162
|
+
child.stdout.on("data", (d) => {
|
|
163
|
+
output += d.toString();
|
|
164
|
+
});
|
|
165
|
+
child.stderr.on("data", (d) => {
|
|
166
|
+
output += d.toString();
|
|
167
|
+
});
|
|
168
|
+
child.on("close", (code) => resolveRun({
|
|
169
|
+
code: code ?? 1,
|
|
170
|
+
output
|
|
171
|
+
}));
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** The transport the generated test left in its task meta, via Vitest's JSON reporter. */
|
|
175
|
+
function readTransport(reportPath) {
|
|
176
|
+
if (!existsSync(reportPath)) return null;
|
|
177
|
+
const report = JSON.parse(readFileSync(reportPath, "utf8"));
|
|
178
|
+
for (const file of report.testResults ?? []) for (const t of file.assertionResults ?? []) {
|
|
179
|
+
const m = t.meta?.[metaKey];
|
|
180
|
+
if (m) return m;
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
function toPosix(p) {
|
|
185
|
+
const s = p.split("\\").join("/");
|
|
186
|
+
return s.startsWith(".") ? s : `./${s}`;
|
|
187
|
+
}
|
|
188
|
+
async function runCheck(o) {
|
|
189
|
+
const scenarioPath = resolve(o.cwd, o.scenario);
|
|
190
|
+
if (!existsSync(scenarioPath)) {
|
|
191
|
+
o.stderr(`scenario not found: ${scenarioPath}\n`);
|
|
192
|
+
return exitCodes.error;
|
|
193
|
+
}
|
|
194
|
+
const dir = dirname(scenarioPath);
|
|
195
|
+
const name = basename(scenarioPath).replace(/\.sim\.ts$/, "").replace(/\.ts$/, "");
|
|
196
|
+
const wrangler = findWrangler(dir, o.wrangler, o.cwd);
|
|
197
|
+
if (!wrangler || !existsSync(wrangler)) {
|
|
198
|
+
o.stderr(`no wrangler config found next to the scenario; pass --wrangler <path>\n`);
|
|
199
|
+
return exitCodes.error;
|
|
200
|
+
}
|
|
201
|
+
const workDir = join(dir, ".eutaxy");
|
|
202
|
+
const testPath = join(workDir, `${name}.check.test.ts`);
|
|
203
|
+
const configPath = join(workDir, "vitest.config.mts");
|
|
204
|
+
mkdirSync(workDir, { recursive: true });
|
|
205
|
+
try {
|
|
206
|
+
writeFileSync(testPath, checkTestSource({
|
|
207
|
+
seed: o.seed ?? name,
|
|
208
|
+
...o.maxRuns === void 0 ? {} : { maxRuns: o.maxRuns },
|
|
209
|
+
scenarioImport: `../${basename(scenarioPath)}`
|
|
210
|
+
}));
|
|
211
|
+
const reportPath = join(workDir, "vitest-report.json");
|
|
212
|
+
rmSync(reportPath, { force: true });
|
|
213
|
+
writeFileSync(configPath, vitestConfigSource(wrangler, testPath, reportPath));
|
|
214
|
+
const { code, output } = await runVitest(vitestBin(dir), configPath, dir);
|
|
215
|
+
const transport = code === 0 ? readTransport(reportPath) : null;
|
|
216
|
+
if (!transport) {
|
|
217
|
+
o.stderr(`eutaxy check could not read a verdict from Vitest (exit ${code}):\n${output}\n`);
|
|
218
|
+
return exitCodes.error;
|
|
219
|
+
}
|
|
220
|
+
const verdict = transport.report.verdict;
|
|
221
|
+
if (o.json) o.stdout(`${canonicalJson(transport.report)}\n`);
|
|
222
|
+
else o.stdout(`${renderText(verdict)}\n`);
|
|
223
|
+
if (verdict.kind === "VIOLATED" && !o.noRegression) {
|
|
224
|
+
const outPath = o.out ? resolve(o.cwd, o.out) : join(dir, `${name}.regression.test.ts`);
|
|
225
|
+
const outDir = dirname(outPath);
|
|
226
|
+
const schemaPaths = {};
|
|
227
|
+
for (const step of transport.setup) if (step.kind === "d1") schemaPaths[String(step["binding"])] = toPosix(relative(outDir, resolve(dir, String(step["schemaFile"]))));
|
|
228
|
+
const source = cloudflareRegressionTest(verdict, {
|
|
229
|
+
scenarioPath: toPosix(relative(outDir, scenarioPath)),
|
|
230
|
+
workerPath: toPosix(relative(outDir, resolve(dir, transport.worker.entry))),
|
|
231
|
+
schemaPaths,
|
|
232
|
+
...o.expectViolation ? { expect: "violates" } : {}
|
|
233
|
+
});
|
|
234
|
+
mkdirSync(outDir, { recursive: true });
|
|
235
|
+
writeFileSync(outPath, source);
|
|
236
|
+
if (!o.json) o.stdout(`\nRegression test written to ${relative(o.cwd, outPath) || outPath}\n`);
|
|
237
|
+
}
|
|
238
|
+
return exitCodes[verdict.kind];
|
|
239
|
+
} finally {
|
|
240
|
+
if (!o.keep) rmSync(workDir, {
|
|
241
|
+
recursive: true,
|
|
242
|
+
force: true
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// packages/cli/src/main.ts
|
|
247
|
+
const usage = `eutaxy — deterministic simulation checks for Cloudflare Workers
|
|
248
|
+
|
|
249
|
+
Usage:
|
|
250
|
+
eutaxy check <scenario.sim.ts> [options]
|
|
251
|
+
|
|
252
|
+
Options:
|
|
253
|
+
--wrangler <path> wrangler config (default: wrangler.jsonc/json/toml beside the scenario)
|
|
254
|
+
--seed <seed> exploration seed (default: the scenario file name)
|
|
255
|
+
--max-runs <n> cap on exploration runs in addition to the scenario's time budget
|
|
256
|
+
--json print the versioned JSON report instead of text
|
|
257
|
+
--out <path> where to write the regression test on a violation
|
|
258
|
+
--no-regression do not write a regression test
|
|
259
|
+
--expect-violation write a regression test that asserts the violation (for corpora)
|
|
260
|
+
--keep keep the generated .eutaxy/ files
|
|
261
|
+
|
|
262
|
+
Exit codes: 0 BOUNDED, 1 VIOLATED, 2 UNSUPPORTED, 3 error.
|
|
263
|
+
`;
|
|
264
|
+
async function main(argv, io) {
|
|
265
|
+
const { positionals, flags } = parseArgs(argv, [
|
|
266
|
+
"json",
|
|
267
|
+
"no-regression",
|
|
268
|
+
"expect-violation",
|
|
269
|
+
"keep",
|
|
270
|
+
"help"
|
|
271
|
+
]);
|
|
272
|
+
const command = positionals[0];
|
|
273
|
+
if (!command || command === "help" || flags["help"] === true) {
|
|
274
|
+
io.stdout(usage);
|
|
275
|
+
return command ? exitCodes.BOUNDED : exitCodes.error;
|
|
276
|
+
}
|
|
277
|
+
if (command !== "check") {
|
|
278
|
+
io.stderr(`unknown command ${command}\n\n${usage}`);
|
|
279
|
+
return exitCodes.error;
|
|
280
|
+
}
|
|
281
|
+
const scenario = positionals[1];
|
|
282
|
+
if (!scenario) {
|
|
283
|
+
io.stderr(`check needs a scenario file\n\n${usage}`);
|
|
284
|
+
return exitCodes.error;
|
|
285
|
+
}
|
|
286
|
+
const str = (k) => typeof flags[k] === "string" ? flags[k] : void 0;
|
|
287
|
+
const maxRunsText = str("max-runs");
|
|
288
|
+
const maxRuns = maxRunsText === void 0 ? void 0 : Number(maxRunsText);
|
|
289
|
+
if (maxRuns !== void 0 && !(Number.isInteger(maxRuns) && maxRuns > 0)) {
|
|
290
|
+
io.stderr(`--max-runs must be a positive integer, got ${JSON.stringify(maxRunsText)}\n`);
|
|
291
|
+
return exitCodes.error;
|
|
292
|
+
}
|
|
293
|
+
return runCheck({
|
|
294
|
+
scenario,
|
|
295
|
+
cwd: io.cwd,
|
|
296
|
+
...str("wrangler") ? { wrangler: str("wrangler") } : {},
|
|
297
|
+
...str("seed") ? { seed: str("seed") } : {},
|
|
298
|
+
...maxRuns === void 0 ? {} : { maxRuns },
|
|
299
|
+
json: flags["json"] === true,
|
|
300
|
+
...str("out") ? { out: str("out") } : {},
|
|
301
|
+
noRegression: flags["no-regression"] === true,
|
|
302
|
+
expectViolation: flags["expect-violation"] === true,
|
|
303
|
+
keep: flags["keep"] === true,
|
|
304
|
+
stdout: io.stdout,
|
|
305
|
+
stderr: io.stderr
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
export { cloudflareRegressionTest as n, main as t };
|
|
309
|
+
|
|
310
|
+
//# sourceMappingURL=src-dP-0Apun.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src-dP-0Apun.mjs","names":[],"sources":["../../cli/src/args.ts","../../cli/src/generate.ts","../../cli/src/regression.ts","../../cli/src/check.ts","../../cli/src/main.ts"],"sourcesContent":["/** A small argument parser: positionals, `--flag value`, `--flag=value` and boolean `--flag`. */\n\nexport interface ParsedArgs {\n readonly positionals: readonly string[];\n readonly flags: Readonly<Record<string, string | true>>;\n}\n\nexport function parseArgs(argv: readonly string[], booleans: readonly string[] = []): ParsedArgs {\n const positionals: string[] = [];\n const flags: Record<string, string | true> = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i] as string;\n if (!a.startsWith(\"--\")) {\n positionals.push(a);\n continue;\n }\n const eq = a.indexOf(\"=\");\n if (eq !== -1) {\n flags[a.slice(2, eq)] = a.slice(eq + 1);\n continue;\n }\n const name = a.slice(2);\n const next = argv[i + 1];\n if (booleans.includes(name) || next === undefined || next.startsWith(\"--\")) {\n flags[name] = true;\n } else {\n flags[name] = next;\n i++;\n }\n }\n return { positionals, flags };\n}\n","/**\n * The files `eutaxy check` writes into `<scenario dir>/.eutaxy/` before\n * running Vitest: a test that performs the check inside workerd and prints\n * the verdict on one marked line, and a Vitest config with the Cloudflare\n * plugin pointed at the scenario's wrangler config.\n */\n\n/** Key under which the generated test stores its result in the Vitest task meta, read back from the JSON reporter. */\nexport const metaKey = \"eutaxy\";\n\nexport interface GeneratedCheck {\n readonly seed: string;\n readonly maxRuns?: number;\n /** Import specifier of the scenario from the generated test (e.g. \"../scenario.sim.ts\"). */\n readonly scenarioImport: string;\n}\n\n/** What the generated test stores in its task meta. */\nexport interface CheckTransport {\n readonly report: unknown;\n readonly worker: { readonly entry: string; readonly config: string };\n readonly setup: readonly { readonly kind: string; readonly [key: string]: unknown }[];\n}\n\nexport function checkTestSource(g: GeneratedCheck): string {\n const maxRuns = g.maxRuns === undefined ? \"\" : `, maxRuns: ${g.maxRuns}`;\n return `// Generated by eutaxy check; deleted after the run.\nimport { renderJson } from \"@mooloo-ai/eutaxy\";\nimport { check, createCloudflarePlatform, type QueueHandler } from \"@mooloo-ai/eutaxy/cloudflare\";\nimport { env } from \"cloudflare:workers\";\nimport { test } from \"vitest\";\nimport scenario from ${JSON.stringify(g.scenarioImport)};\n\nconst sqlFiles = import.meta.glob(\"../**/*.sql\", { query: \"?raw\", import: \"default\", eager: true }) as Record<string, string>;\nconst modules = import.meta.glob([\"../**/*.ts\", \"!../**/*.test.ts\", \"!../**/*.sim.ts\", \"!../**/node_modules/**\", \"!../**/.eutaxy/**\"]) as Record<string, () => Promise<{ default: QueueHandler }>>;\n\nfunction key(path: string): string {\n return \\`../\\${path.replace(/^\\\\.\\\\//, \"\")}\\`;\n}\n\ntest(\"eutaxy check\", async ({ task }) => {\n const schemas: Record<string, string> = {};\n for (const step of scenario.setup) {\n if (step.kind !== \"d1\") continue;\n const sql = sqlFiles[key(String(step.schemaFile))];\n if (sql === undefined) throw new Error(\\`schema file \\${String(step.schemaFile)} not found next to the scenario\\`);\n schemas[String(step.binding)] = sql;\n }\n const load = modules[key(scenario.worker.entry)];\n if (!load) throw new Error(\\`worker entry \\${scenario.worker.entry} not found next to the scenario\\`);\n const handler = (await load()).default;\n const platform = createCloudflarePlatform({ env, handler, schemas });\n const verdict = await check(scenario, platform, { seed: ${JSON.stringify(g.seed)}${maxRuns} });\n task.meta[${JSON.stringify(metaKey)}] = { report: JSON.parse(renderJson(verdict)), worker: scenario.worker, setup: scenario.setup };\n});\n`;\n}\n\nexport function vitestConfigSource(\n wranglerConfigPath: string,\n testPath: string,\n outputFile: string,\n): string {\n return `// Generated by eutaxy check; deleted after the run.\nimport { cloudflareTest } from \"@cloudflare/vitest-plugin\";\nimport { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n plugins: [cloudflareTest({ wrangler: { configPath: ${JSON.stringify(wranglerConfigPath)} } })],\n test: {\n include: [${JSON.stringify(testPath)}],\n testTimeout: 600_000,\n reporters: [\"json\"],\n outputFile: ${JSON.stringify(outputFile)},\n },\n});\n`;\n}\n","import type { Violated } from \"@eutaxy/core\";\nimport { emitRegressionTest } from \"@eutaxy/report\";\n\nexport interface CloudflareRegressionOptions {\n /** Import path of the scenario module, relative to the emitted file. */\n readonly scenarioPath: string;\n /** Import path of the Worker module. */\n readonly workerPath: string;\n /** D1 binding name to schema file path, imported with `?raw`. */\n readonly schemaPaths: Readonly<Record<string, string>>;\n /** The platform's retry limit, when the scenario was checked with a non-default one. */\n readonly maxRetries?: number;\n readonly expect?: \"holds\" | \"violates\";\n readonly engineModule?: string;\n}\n\n/** A Vitest regression test for a Cloudflare scenario, to run under the Cloudflare Vitest plugin. */\nexport function cloudflareRegressionTest(\n verdict: Violated,\n options: CloudflareRegressionOptions,\n): string {\n const bindings = Object.keys(options.schemaPaths).sort();\n const imports = [\n 'import { createCloudflarePlatform } from \"@mooloo-ai/eutaxy/cloudflare\";',\n 'import { env } from \"cloudflare:workers\";',\n `import scenario from ${JSON.stringify(options.scenarioPath)};`,\n ...bindings.map(\n (b) => `import schema_${b} from ${JSON.stringify(`${options.schemaPaths[b]}?raw`)};`,\n ),\n `import handler from ${JSON.stringify(options.workerPath)};`,\n ];\n const schemas = `{ ${bindings.map((b) => `${b}: schema_${b}`).join(\", \")} }`;\n const retries = options.maxRetries === undefined ? \"\" : `, maxRetries: ${options.maxRetries}`;\n return emitRegressionTest(verdict, {\n imports,\n platform: `createCloudflarePlatform({ env, handler, schemas: ${schemas}${retries} })`,\n scenario: \"scenario\",\n ...(options.expect ? { expect: options.expect } : {}),\n ...(options.engineModule ? { engineModule: options.engineModule } : {}),\n });\n}\n","/**\n * `eutaxy check <scenario.sim.ts>`: run a scenario inside workerd through\n * the user's Vitest and the Cloudflare plugin, print the verdict as text or\n * JSON, and on a violation write a regression test next to the scenario.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { basename, dirname, join, relative, resolve } from \"node:path\";\nimport { canonicalJson, type JsonValue, type ScenarioVerdict, type Violated } from \"@eutaxy/core\";\nimport { renderText } from \"@eutaxy/report\";\nimport { type CheckTransport, checkTestSource, metaKey, vitestConfigSource } from \"./generate.ts\";\nimport { cloudflareRegressionTest } from \"./regression.ts\";\n\nexport interface CheckOptions {\n readonly scenario: string;\n readonly cwd: string;\n readonly wrangler?: string;\n readonly seed?: string;\n readonly maxRuns?: number;\n readonly json: boolean;\n /** Where to write the regression test on a violation; default `<name>.regression.test.ts` beside the scenario. */\n readonly out?: string;\n readonly noRegression: boolean;\n /** Emit a regression test that asserts the violation (for corpora) rather than one that expects it fixed. */\n readonly expectViolation: boolean;\n /** Keep the generated `.eutaxy/` files for inspection. */\n readonly keep: boolean;\n readonly stdout: (text: string) => void;\n readonly stderr: (text: string) => void;\n}\n\nexport const exitCodes = { BOUNDED: 0, VIOLATED: 1, UNSUPPORTED: 2, error: 3 } as const;\n\nfunction findWrangler(dir: string, explicit: string | undefined, cwd: string): string | null {\n if (explicit) return resolve(cwd, explicit);\n for (const name of [\"wrangler.jsonc\", \"wrangler.json\", \"wrangler.toml\"]) {\n const p = join(dir, name);\n if (existsSync(p)) return p;\n }\n return null;\n}\n\nfunction vitestBin(from: string): string {\n const req = createRequire(join(from, \"package.json\"));\n return join(dirname(req.resolve(\"vitest/package.json\")), \"vitest.mjs\");\n}\n\nfunction runVitest(\n bin: string,\n configPath: string,\n cwd: string,\n): Promise<{ code: number; output: string }> {\n return new Promise((resolveRun) => {\n const child = spawn(process.execPath, [bin, \"run\", \"--config\", configPath], {\n cwd,\n env: { ...process.env, CI: \"1\", FORCE_COLOR: \"0\" },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let output = \"\";\n child.stdout.on(\"data\", (d: Buffer) => {\n output += d.toString();\n });\n child.stderr.on(\"data\", (d: Buffer) => {\n output += d.toString();\n });\n child.on(\"close\", (code) => resolveRun({ code: code ?? 1, output }));\n });\n}\n\n/** The transport the generated test left in its task meta, via Vitest's JSON reporter. */\nfunction readTransport(reportPath: string): CheckTransport | null {\n if (!existsSync(reportPath)) return null;\n const report = JSON.parse(readFileSync(reportPath, \"utf8\")) as {\n testResults?: { assertionResults?: { meta?: Record<string, unknown> }[] }[];\n };\n for (const file of report.testResults ?? []) {\n for (const t of file.assertionResults ?? []) {\n const m = t.meta?.[metaKey];\n if (m) return m as CheckTransport;\n }\n }\n return null;\n}\n\nfunction toPosix(p: string): string {\n const s = p.split(\"\\\\\").join(\"/\");\n return s.startsWith(\".\") ? s : `./${s}`;\n}\n\nexport async function runCheck(o: CheckOptions): Promise<number> {\n const scenarioPath = resolve(o.cwd, o.scenario);\n if (!existsSync(scenarioPath)) {\n o.stderr(`scenario not found: ${scenarioPath}\\n`);\n return exitCodes.error;\n }\n const dir = dirname(scenarioPath);\n const name = basename(scenarioPath)\n .replace(/\\.sim\\.ts$/, \"\")\n .replace(/\\.ts$/, \"\");\n const wrangler = findWrangler(dir, o.wrangler, o.cwd);\n if (!wrangler || !existsSync(wrangler)) {\n o.stderr(`no wrangler config found next to the scenario; pass --wrangler <path>\\n`);\n return exitCodes.error;\n }\n const workDir = join(dir, \".eutaxy\");\n const testPath = join(workDir, `${name}.check.test.ts`);\n const configPath = join(workDir, \"vitest.config.mts\");\n mkdirSync(workDir, { recursive: true });\n try {\n writeFileSync(\n testPath,\n checkTestSource({\n seed: o.seed ?? name,\n ...(o.maxRuns === undefined ? {} : { maxRuns: o.maxRuns }),\n scenarioImport: `../${basename(scenarioPath)}`,\n }),\n );\n const reportPath = join(workDir, \"vitest-report.json\");\n rmSync(reportPath, { force: true }); // never read a report left by an earlier --keep run\n writeFileSync(configPath, vitestConfigSource(wrangler, testPath, reportPath));\n const { code, output } = await runVitest(vitestBin(dir), configPath, dir);\n const transport = code === 0 ? readTransport(reportPath) : null;\n if (!transport) {\n o.stderr(`eutaxy check could not read a verdict from Vitest (exit ${code}):\\n${output}\\n`);\n return exitCodes.error;\n }\n const report = transport.report as { verdict: ScenarioVerdict };\n const verdict = report.verdict;\n if (o.json) o.stdout(`${canonicalJson(transport.report as JsonValue)}\\n`);\n else o.stdout(`${renderText(verdict)}\\n`);\n\n if (verdict.kind === \"VIOLATED\" && !o.noRegression) {\n const outPath = o.out ? resolve(o.cwd, o.out) : join(dir, `${name}.regression.test.ts`);\n const outDir = dirname(outPath);\n const schemaPaths: Record<string, string> = {};\n for (const step of transport.setup) {\n if (step.kind === \"d1\") {\n schemaPaths[String(step[\"binding\"])] = toPosix(\n relative(outDir, resolve(dir, String(step[\"schemaFile\"]))),\n );\n }\n }\n const source = cloudflareRegressionTest(verdict as Violated, {\n scenarioPath: toPosix(relative(outDir, scenarioPath)),\n workerPath: toPosix(relative(outDir, resolve(dir, transport.worker.entry))),\n schemaPaths,\n ...(o.expectViolation ? { expect: \"violates\" as const } : {}),\n });\n mkdirSync(outDir, { recursive: true });\n writeFileSync(outPath, source);\n if (!o.json)\n o.stdout(`\\nRegression test written to ${relative(o.cwd, outPath) || outPath}\\n`);\n }\n return exitCodes[verdict.kind];\n } finally {\n if (!o.keep) rmSync(workDir, { recursive: true, force: true });\n }\n}\n","import { parseArgs } from \"./args.ts\";\nimport { exitCodes, runCheck } from \"./check.ts\";\n\nconst usage = `eutaxy — deterministic simulation checks for Cloudflare Workers\n\nUsage:\n eutaxy check <scenario.sim.ts> [options]\n\nOptions:\n --wrangler <path> wrangler config (default: wrangler.jsonc/json/toml beside the scenario)\n --seed <seed> exploration seed (default: the scenario file name)\n --max-runs <n> cap on exploration runs in addition to the scenario's time budget\n --json print the versioned JSON report instead of text\n --out <path> where to write the regression test on a violation\n --no-regression do not write a regression test\n --expect-violation write a regression test that asserts the violation (for corpora)\n --keep keep the generated .eutaxy/ files\n\nExit codes: 0 BOUNDED, 1 VIOLATED, 2 UNSUPPORTED, 3 error.\n`;\n\nexport interface Io {\n readonly cwd: string;\n readonly stdout: (text: string) => void;\n readonly stderr: (text: string) => void;\n}\n\nexport async function main(argv: readonly string[], io: Io): Promise<number> {\n const { positionals, flags } = parseArgs(argv, [\n \"json\",\n \"no-regression\",\n \"expect-violation\",\n \"keep\",\n \"help\",\n ]);\n const command = positionals[0];\n if (!command || command === \"help\" || flags[\"help\"] === true) {\n io.stdout(usage);\n return command ? exitCodes.BOUNDED : exitCodes.error;\n }\n if (command !== \"check\") {\n io.stderr(`unknown command ${command}\\n\\n${usage}`);\n return exitCodes.error;\n }\n const scenario = positionals[1];\n if (!scenario) {\n io.stderr(`check needs a scenario file\\n\\n${usage}`);\n return exitCodes.error;\n }\n const str = (k: string): string | undefined =>\n typeof flags[k] === \"string\" ? (flags[k] as string) : undefined;\n const maxRunsText = str(\"max-runs\");\n const maxRuns = maxRunsText === undefined ? undefined : Number(maxRunsText);\n if (maxRuns !== undefined && !(Number.isInteger(maxRuns) && maxRuns > 0)) {\n io.stderr(`--max-runs must be a positive integer, got ${JSON.stringify(maxRunsText)}\\n`);\n return exitCodes.error;\n }\n return runCheck({\n scenario,\n cwd: io.cwd,\n ...(str(\"wrangler\") ? { wrangler: str(\"wrangler\") as string } : {}),\n ...(str(\"seed\") ? { seed: str(\"seed\") as string } : {}),\n ...(maxRuns === undefined ? {} : { maxRuns }),\n json: flags[\"json\"] === true,\n ...(str(\"out\") ? { out: str(\"out\") as string } : {}),\n noRegression: flags[\"no-regression\"] === true,\n expectViolation: flags[\"expect-violation\"] === true,\n keep: flags[\"keep\"] === true,\n stdout: io.stdout,\n stderr: io.stderr,\n });\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mooloo-ai/eutaxy",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deterministic simulation and formal proof for TypeScript on Cloudflare Workers",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Mooloo-AI/eutaxy.git",
|
|
9
|
+
"directory": "packages/eutaxy"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=24"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.mts",
|
|
18
|
+
"default": "./dist/index.mjs"
|
|
19
|
+
},
|
|
20
|
+
"./cloudflare": {
|
|
21
|
+
"types": "./dist/cloudflare.d.mts",
|
|
22
|
+
"default": "./dist/cloudflare.mjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"eutaxy": "./dist/bin.mjs"
|
|
27
|
+
},
|
|
28
|
+
"types": "./dist/index.d.mts",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@cloudflare/vitest-plugin": ">=1.4.0",
|
|
38
|
+
"@cloudflare/workers-types": ">=4.0.0",
|
|
39
|
+
"vitest": ">=4.1.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@cloudflare/workers-types": {
|
|
43
|
+
"optional": true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@eutaxy/spec": "0.0.0",
|
|
48
|
+
"@eutaxy/core": "0.0.0",
|
|
49
|
+
"@eutaxy/engine": "0.0.0",
|
|
50
|
+
"@eutaxy/cloudflare": "0.0.0",
|
|
51
|
+
"@eutaxy/prover": "0.0.0",
|
|
52
|
+
"@eutaxy/prover-lean": "0.0.0",
|
|
53
|
+
"@eutaxy/prover-thales": "0.0.0",
|
|
54
|
+
"@eutaxy/prover-lemmascript": "0.0.0",
|
|
55
|
+
"@eutaxy/report": "0.0.0",
|
|
56
|
+
"@eutaxy/cli": "0.0.0",
|
|
57
|
+
"@types/node": "24.19.2"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "pnpm -w run build"
|
|
61
|
+
}
|
|
6
62
|
}
|