aifsmjs 0.5.6 → 0.5.9
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/README.md +44 -517
- package/README_ZHTW.md +44 -507
- package/dist/{chunk-VZCSHTOI.js → chunk-LG2AH5X6.js} +15 -7
- package/dist/chunk-LG2AH5X6.js.map +1 -0
- package/dist/{chunk-2MME5V4F.cjs → chunk-TPCDOVU4.cjs} +15 -7
- package/dist/chunk-TPCDOVU4.cjs.map +1 -0
- package/dist/index.cjs +10 -10
- package/dist/index.js +1 -1
- package/dist/pbt/index.cjs +16 -11
- package/dist/pbt/index.cjs.map +1 -1
- package/dist/pbt/index.d.cts +18 -1
- package/dist/pbt/index.d.ts +18 -1
- package/dist/pbt/index.js +9 -4
- package/dist/pbt/index.js.map +1 -1
- package/llms-full.txt +114 -1072
- package/llms.txt +8 -35
- package/package.json +1 -1
- package/dist/chunk-2MME5V4F.cjs.map +0 -1
- package/dist/chunk-VZCSHTOI.js.map +0 -1
package/dist/pbt/index.d.ts
CHANGED
|
@@ -39,6 +39,22 @@ type AssertOpts = Readonly<{
|
|
|
39
39
|
seed?: number;
|
|
40
40
|
verbose?: boolean;
|
|
41
41
|
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Structural deep-equality for two context values (C3). Backed by `node:util`
|
|
44
|
+
* `isDeepStrictEqual`, replacing the previous `JSON.stringify(a) === JSON.stringify(b)`
|
|
45
|
+
* oracle which was unsound:
|
|
46
|
+
*
|
|
47
|
+
* - key-order-sensitive → false-FAIL on `{a:1,b:2}` vs `{b:2,a:1}`;
|
|
48
|
+
* - drops undefined keys → false-PASS on `{v:undefined,w:1}` vs `{w:1}`;
|
|
49
|
+
* - lossy for `Map`/`Set`/`Date` (all serialise to `{}` or an ISO string);
|
|
50
|
+
* - throws on `BigInt`.
|
|
51
|
+
*
|
|
52
|
+
* `isDeepStrictEqual` distinguishes present-but-undefined from absent keys,
|
|
53
|
+
* compares `Map`/`Set`/`Date` by contents, and tolerates `BigInt` — exactly
|
|
54
|
+
* the verdicts a context-equality oracle for PBT requires. No new dependency
|
|
55
|
+
* (Node built-in; the package already targets Node >=18).
|
|
56
|
+
*/
|
|
57
|
+
declare function contextEquals(a: unknown, b: unknown): boolean;
|
|
42
58
|
/**
|
|
43
59
|
* #1 snapshotAlwaysFrozen — after any event sequence the live snapshot remains
|
|
44
60
|
* frozen at the top level.
|
|
@@ -103,13 +119,14 @@ declare function assertAll<Ctx, Evt extends {
|
|
|
103
119
|
type propertiesModule_AssertOpts = AssertOpts;
|
|
104
120
|
declare const propertiesModule_assertAll: typeof assertAll;
|
|
105
121
|
declare const propertiesModule_assignDoesNotMutate: typeof assignDoesNotMutate;
|
|
122
|
+
declare const propertiesModule_contextEquals: typeof contextEquals;
|
|
106
123
|
declare const propertiesModule_guardsFalseNoTransition: typeof guardsFalseNoTransition;
|
|
107
124
|
declare const propertiesModule_reachableStatesSubsetDeclared: typeof reachableStatesSubsetDeclared;
|
|
108
125
|
declare const propertiesModule_replayEqualsFold: typeof replayEqualsFold;
|
|
109
126
|
declare const propertiesModule_snapshotAlwaysFrozen: typeof snapshotAlwaysFrozen;
|
|
110
127
|
declare const propertiesModule_unknownEventNoOp: typeof unknownEventNoOp;
|
|
111
128
|
declare namespace propertiesModule {
|
|
112
|
-
export { type propertiesModule_AssertOpts as AssertOpts, propertiesModule_assertAll as assertAll, propertiesModule_assignDoesNotMutate as assignDoesNotMutate, propertiesModule_guardsFalseNoTransition as guardsFalseNoTransition, propertiesModule_reachableStatesSubsetDeclared as reachableStatesSubsetDeclared, propertiesModule_replayEqualsFold as replayEqualsFold, propertiesModule_snapshotAlwaysFrozen as snapshotAlwaysFrozen, propertiesModule_unknownEventNoOp as unknownEventNoOp };
|
|
129
|
+
export { type propertiesModule_AssertOpts as AssertOpts, propertiesModule_assertAll as assertAll, propertiesModule_assignDoesNotMutate as assignDoesNotMutate, propertiesModule_contextEquals as contextEquals, propertiesModule_guardsFalseNoTransition as guardsFalseNoTransition, propertiesModule_reachableStatesSubsetDeclared as reachableStatesSubsetDeclared, propertiesModule_replayEqualsFold as replayEqualsFold, propertiesModule_snapshotAlwaysFrozen as snapshotAlwaysFrozen, propertiesModule_unknownEventNoOp as unknownEventNoOp };
|
|
113
130
|
}
|
|
114
131
|
|
|
115
132
|
declare const properties: typeof propertiesModule;
|
package/dist/pbt/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { createRuntime, initialSnapshot } from '../chunk-
|
|
1
|
+
import { createRuntime, initialSnapshot } from '../chunk-LG2AH5X6.js';
|
|
2
2
|
import { replay } from '../chunk-NEJYZAKR.js';
|
|
3
3
|
import { step, mergeContext, normalizeTransitions } from '../chunk-ZLQ7HZCE.js';
|
|
4
4
|
import '../chunk-JKZAOPQC.js';
|
|
5
5
|
import '../chunk-A7U7QQL5.js';
|
|
6
6
|
import { __export } from '../chunk-PZ5AY32C.js';
|
|
7
7
|
import * as fc2 from 'fast-check';
|
|
8
|
+
import { isDeepStrictEqual } from 'util';
|
|
8
9
|
|
|
9
10
|
function initialModel(def) {
|
|
10
11
|
return {
|
|
@@ -58,6 +59,7 @@ var properties_exports = {};
|
|
|
58
59
|
__export(properties_exports, {
|
|
59
60
|
assertAll: () => assertAll,
|
|
60
61
|
assignDoesNotMutate: () => assignDoesNotMutate,
|
|
62
|
+
contextEquals: () => contextEquals,
|
|
61
63
|
guardsFalseNoTransition: () => guardsFalseNoTransition,
|
|
62
64
|
reachableStatesSubsetDeclared: () => reachableStatesSubsetDeclared,
|
|
63
65
|
replayEqualsFold: () => replayEqualsFold,
|
|
@@ -71,6 +73,9 @@ function buildAssertOpts(opts) {
|
|
|
71
73
|
if (opts?.verbose) out.verbose = true;
|
|
72
74
|
return out;
|
|
73
75
|
}
|
|
76
|
+
function contextEquals(a, b) {
|
|
77
|
+
return isDeepStrictEqual(a, b);
|
|
78
|
+
}
|
|
74
79
|
function snapshotAlwaysFrozen(def, impl, eventArbitraries, opts) {
|
|
75
80
|
fc2.assert(
|
|
76
81
|
fc2.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {
|
|
@@ -115,7 +120,7 @@ function replayEqualsFold(def, impl, eventArbitraries, opts) {
|
|
|
115
120
|
for (const e of events) real.send(e);
|
|
116
121
|
const live = real.getSnapshot();
|
|
117
122
|
const replayed = replay(initialSnapshot(def), events, def, impl).snapshot;
|
|
118
|
-
return live.value === replayed.value &&
|
|
123
|
+
return live.value === replayed.value && contextEquals(live.context, replayed.context);
|
|
119
124
|
}),
|
|
120
125
|
buildAssertOpts(opts)
|
|
121
126
|
);
|
|
@@ -162,9 +167,9 @@ function assignDoesNotMutate(def, impl, eventArbitraries, opts) {
|
|
|
162
167
|
(events) => {
|
|
163
168
|
let snap = initialSnapshot(def);
|
|
164
169
|
for (const e of events) {
|
|
165
|
-
const
|
|
170
|
+
const beforeCtx = structuredClone(snap.context);
|
|
166
171
|
step(def, snap, e, impl);
|
|
167
|
-
if (
|
|
172
|
+
if (!contextEquals(snap.context, beforeCtx)) return false;
|
|
168
173
|
snap = step(def, snap, e, impl).snapshot;
|
|
169
174
|
}
|
|
170
175
|
return true;
|
package/dist/pbt/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pbt/commands.ts","../../src/pbt/properties.ts","../../src/pbt/index.ts"],"names":["fc"],"mappings":"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","file":"index.js","sourcesContent":["import * as fc from \"fast-check\";\nimport { step } from \"../fsm/lifecycle.js\";\nimport type { Implementations, MachineDef, Runtime, Snapshot } from \"../fsm/types.js\";\n\n/**\n * Pure-model representation of an FSM run, used by `fc.commands`.\n * `reached` tracks every state visited so generic properties can check\n * containment without re-running.\n */\nexport type FsmModel<Ctx, States extends string> = {\n value: States;\n context: Ctx;\n status: \"active\" | \"final\";\n reached: Set<States>;\n};\n\nexport type EventArbitraries<Evt extends { type: string }> = Readonly<\n Record<string, fc.Arbitrary<Evt>>\n>;\n\nexport type FsmCommand<Ctx, Evt extends { type: string }, States extends string> = fc.Command<\n FsmModel<Ctx, States>,\n Runtime<Ctx, Evt, States>\n>;\n\nexport function initialModel<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n): FsmModel<Ctx, States> {\n return {\n value: def.initial,\n context: def.context,\n status: def.states[def.initial]?.final ? \"final\" : \"active\",\n reached: new Set([def.initial]),\n };\n}\n\nclass SendCommand<Ctx, Evt extends { type: string }, States extends string>\n implements FsmCommand<Ctx, Evt, States>\n{\n constructor(\n private readonly event: Evt,\n private readonly def: MachineDef<Ctx, Evt, States>,\n private readonly impl: Implementations<Ctx, Evt>,\n ) {}\n\n check(_m: Readonly<FsmModel<Ctx, States>>): boolean {\n // Every event is always applicable; invariants are asserted in run().\n return true;\n }\n\n run(m: FsmModel<Ctx, States>, r: Runtime<Ctx, Evt, States>): void {\n const before: Snapshot<Ctx, States> = r.getSnapshot();\n r.send(this.event);\n const after = r.getSnapshot();\n\n const predicted = step(this.def, before, this.event, this.impl);\n\n /* v8 ignore start — invariant guard: fires only when the live runtime\n diverges from the pure step() prediction, i.e. an internal bug. */\n if (predicted.snapshot.value !== after.value) {\n throw new Error(\n `aifsmjs/pbt: determinism violation — predicted \"${String(predicted.snapshot.value)}\" but runtime returned \"${String(after.value)}\" after ${this.toString()}`,\n );\n }\n /* v8 ignore stop */\n\n m.value = after.value;\n m.context = after.context as Ctx;\n m.status = after.status;\n m.reached.add(after.value);\n }\n\n toString(): string {\n return `send(${JSON.stringify(this.event)})`;\n }\n}\n\n/**\n * Build an `fc.Arbitrary` of FSM command sequences. Each command pulls one\n * event from the user-supplied arbitrary map and, when run, asserts that the\n * pure `step()` prediction matches the runtime's observable outcome.\n *\n * Pair this arbitrary with `fc.property(...)` inside a `fc.assert(...)` call,\n * or use the helpers in `aifsmjs/pbt` properties to get the six generic\n * invariants for free.\n */\nexport function commandsFromMachine<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n): fc.Arbitrary<Iterable<FsmCommand<Ctx, Evt, States>>> {\n const arbs: fc.Arbitrary<FsmCommand<Ctx, Evt, States>>[] = [];\n for (const arb of Object.values(eventArbitraries)) {\n arbs.push(arb.map((event) => new SendCommand(event, def, impl)));\n }\n return fc.commands(arbs, { size: \"+1\" });\n}\n","import * as fc from \"fast-check\";\nimport { initialSnapshot } from \"../fsm/definition.js\";\nimport { step } from \"../fsm/lifecycle.js\";\nimport { normalizeTransitions } from \"../fsm/resolver.js\";\nimport { createRuntime } from \"../fsm/runtime.js\";\nimport type { Guard, Implementations, MachineDef } from \"../fsm/types.js\";\nimport { mergeContext } from \"../fsm/updater.js\";\nimport { replay } from \"../replay/index.js\";\nimport {\n type EventArbitraries,\n type FsmModel,\n commandsFromMachine,\n initialModel,\n} from \"./commands.js\";\n\nexport type AssertOpts = Readonly<{\n numRuns?: number;\n seed?: number;\n verbose?: boolean;\n}>;\n\nfunction buildAssertOpts(opts: AssertOpts | undefined): fc.Parameters<unknown> {\n const out: fc.Parameters<unknown> = {};\n if (opts?.numRuns !== undefined) out.numRuns = opts.numRuns;\n if (opts?.seed !== undefined) out.seed = opts.seed;\n if (opts?.verbose) out.verbose = true;\n return out;\n}\n\n/**\n * #1 snapshotAlwaysFrozen — after any event sequence the live snapshot remains\n * frozen at the top level.\n */\nexport function snapshotAlwaysFrozen<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n fc.assert(\n fc.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {\n const real = createRuntime(def, impl);\n const model: FsmModel<Ctx, States> = initialModel(def);\n fc.modelRun(() => ({ model, real }), cmds);\n return Object.isFrozen(real.getSnapshot());\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #2 unknownEventNoOp — sending an event whose `type` is not declared in any\n * state's `on` map never changes the snapshot.\n */\nexport function unknownEventNoOp<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n unknownType: string,\n opts?: AssertOpts,\n): void {\n fc.assert(\n fc.property(fc.constant(unknownType), (t) => {\n const initial = initialSnapshot(def);\n const result = step(def, initial, { type: t } as unknown as Evt, impl);\n return result.changed === false && result.snapshot === initial && result.effects.length === 0;\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #3 reachableStatesSubsetDeclared — every state visited during a run belongs\n * to `def.states`.\n */\nexport function reachableStatesSubsetDeclared<\n Ctx,\n Evt extends { type: string },\n States extends string,\n>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const declared = new Set<string>(Object.keys(def.states));\n fc.assert(\n fc.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {\n const real = createRuntime(def, impl);\n const model: FsmModel<Ctx, States> = initialModel(def);\n fc.modelRun(() => ({ model, real }), cmds);\n for (const s of model.reached as Set<string>) {\n /* v8 ignore next — property failure branch; an unreachable state would indicate a bug. */\n if (!declared.has(s)) return false;\n }\n return declared.has(real.getSnapshot().value);\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #4 replayEqualsFold — `replay(initial, log)` produces the same final state\n * as a live runtime fed the same events. Effects dispatched by the runtime are\n * ignored; the comparison is on `{ value, context }`.\n */\nexport function replayEqualsFold<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const eventArb = fc.oneof(...Object.values(eventArbitraries));\n fc.assert(\n fc.property(fc.array(eventArb, { maxLength: 32 }), (events) => {\n const real = createRuntime(def, impl, { dispatchEffects: false });\n for (const e of events) real.send(e);\n const live = real.getSnapshot();\n const replayed = replay(initialSnapshot(def), events, def, impl).snapshot;\n return (\n live.value === replayed.value &&\n JSON.stringify(live.context) === JSON.stringify(replayed.context)\n );\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #5 guardsFalseNoTransition — when every candidate transition for the current\n * (state, event) pair carries a guard and every guard returns `false`, the\n * snapshot is unchanged (`changed === false`).\n *\n * Implementation: synthesise an impl that forces every guard to `false`, then\n * for each step whose candidate list is fully guarded, assert the step did not\n * change state. Steps with an unguarded fallback candidate (which fires even\n * when all guards are false) are skipped — the README claim is specifically\n * about the all-guards-false case.\n */\nexport function guardsFalseNoTransition<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const blockedGuards = new Proxy(\n {},\n {\n get: () => () => false,\n },\n ) as Readonly<Record<string, Guard<Ctx, Evt>>>;\n const blockedImpl: Implementations<Ctx, Evt> = {\n ...impl,\n guards: blockedGuards,\n };\n // True when every candidate transition for (value, eventType) carries a\n // guard — i.e. blocking all guards leaves no unconditional fallback, so a\n // correct step() must report changed === false.\n const isFullyGuarded = (value: States, eventType: string): boolean => {\n const candidates = normalizeTransitions(def.states[value]?.on?.[eventType]);\n return candidates.length > 0 && candidates.every((t) => t.guard !== undefined);\n };\n fc.assert(\n fc.property(\n fc.array(fc.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),\n (events) => {\n let snap = initialSnapshot(def);\n for (const e of events) {\n const fullyGuarded = isFullyGuarded(snap.value, e.type);\n const r = step(def, snap, e, blockedImpl);\n // The named invariant: all guards false + no unconditional fallback\n // ⇒ no transition. Without this assertion the property was vacuous\n // (it only failed if step() threw).\n if (fullyGuarded && r.changed !== false) return false;\n snap = r.snapshot;\n }\n return true;\n },\n ),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #6 assignDoesNotMutate — running an `assign`-style action never mutates the\n * previous context object. Verified by deep-equality check on a snapshot taken\n * before each event.\n */\nexport function assignDoesNotMutate<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n // Quick sanity guard: mergeContext is the only context mutator used by step.\n const dummy = { a: 1, b: 2 };\n const merged = mergeContext(dummy, { b: 3 });\n /* v8 ignore next — invariant guard; mergeContext returning the same ref would mean unit tests have already broken. */\n if (merged === dummy) throw new Error(\"aifsmjs/pbt: mergeContext returned the same reference\");\n\n fc.assert(\n fc.property(\n fc.array(fc.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),\n (events) => {\n let snap = initialSnapshot(def);\n for (const e of events) {\n const beforeCtxSerialised = JSON.stringify(snap.context);\n step(def, snap, e, impl);\n /* v8 ignore next — property failure branch; step() mutating snap.context would indicate a bug. */\n if (JSON.stringify(snap.context) !== beforeCtxSerialised) return false;\n // Continue with the actual result for subsequent events\n snap = step(def, snap, e, impl).snapshot;\n }\n return true;\n },\n ),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * Assert every generic property in one call. Use this when you don't need\n * fine-grained control over per-property options.\n */\nexport function assertAll<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts & { unknownEventType?: string },\n): void {\n snapshotAlwaysFrozen(def, impl, eventArbitraries, opts);\n unknownEventNoOp(def, impl, opts?.unknownEventType ?? \"__AIFSMJS_UNKNOWN__\", opts);\n reachableStatesSubsetDeclared(def, impl, eventArbitraries, opts);\n replayEqualsFold(def, impl, eventArbitraries, opts);\n guardsFalseNoTransition(def, impl, eventArbitraries, opts);\n assignDoesNotMutate(def, impl, eventArbitraries, opts);\n}\n","export {\n commandsFromMachine,\n initialModel,\n type EventArbitraries,\n type FsmCommand,\n type FsmModel,\n} from \"./commands.js\";\n\nexport {\n assertAll,\n assignDoesNotMutate,\n guardsFalseNoTransition,\n reachableStatesSubsetDeclared,\n replayEqualsFold,\n snapshotAlwaysFrozen,\n unknownEventNoOp,\n type AssertOpts,\n} from \"./properties.js\";\n\n// Convenience namespace mirroring the README:\n// import { properties } from \"aifsmjs/pbt\"\n// properties.snapshotAlwaysFrozen(...)\nimport * as propertiesModule from \"./properties.js\";\nexport const properties = propertiesModule;\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/pbt/commands.ts","../../src/pbt/properties.ts","../../src/pbt/index.ts"],"names":["fc"],"mappings":"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","file":"index.js","sourcesContent":["import * as fc from \"fast-check\";\nimport { step } from \"../fsm/lifecycle.js\";\nimport type { Implementations, MachineDef, Runtime, Snapshot } from \"../fsm/types.js\";\n\n/**\n * Pure-model representation of an FSM run, used by `fc.commands`.\n * `reached` tracks every state visited so generic properties can check\n * containment without re-running.\n */\nexport type FsmModel<Ctx, States extends string> = {\n value: States;\n context: Ctx;\n status: \"active\" | \"final\";\n reached: Set<States>;\n};\n\nexport type EventArbitraries<Evt extends { type: string }> = Readonly<\n Record<string, fc.Arbitrary<Evt>>\n>;\n\nexport type FsmCommand<Ctx, Evt extends { type: string }, States extends string> = fc.Command<\n FsmModel<Ctx, States>,\n Runtime<Ctx, Evt, States>\n>;\n\nexport function initialModel<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n): FsmModel<Ctx, States> {\n return {\n value: def.initial,\n context: def.context,\n status: def.states[def.initial]?.final ? \"final\" : \"active\",\n reached: new Set([def.initial]),\n };\n}\n\nclass SendCommand<Ctx, Evt extends { type: string }, States extends string>\n implements FsmCommand<Ctx, Evt, States>\n{\n constructor(\n private readonly event: Evt,\n private readonly def: MachineDef<Ctx, Evt, States>,\n private readonly impl: Implementations<Ctx, Evt>,\n ) {}\n\n check(_m: Readonly<FsmModel<Ctx, States>>): boolean {\n // Every event is always applicable; invariants are asserted in run().\n return true;\n }\n\n run(m: FsmModel<Ctx, States>, r: Runtime<Ctx, Evt, States>): void {\n const before: Snapshot<Ctx, States> = r.getSnapshot();\n r.send(this.event);\n const after = r.getSnapshot();\n\n const predicted = step(this.def, before, this.event, this.impl);\n\n /* v8 ignore start — invariant guard: fires only when the live runtime\n diverges from the pure step() prediction, i.e. an internal bug. */\n if (predicted.snapshot.value !== after.value) {\n throw new Error(\n `aifsmjs/pbt: determinism violation — predicted \"${String(predicted.snapshot.value)}\" but runtime returned \"${String(after.value)}\" after ${this.toString()}`,\n );\n }\n /* v8 ignore stop */\n\n m.value = after.value;\n m.context = after.context as Ctx;\n m.status = after.status;\n m.reached.add(after.value);\n }\n\n toString(): string {\n return `send(${JSON.stringify(this.event)})`;\n }\n}\n\n/**\n * Build an `fc.Arbitrary` of FSM command sequences. Each command pulls one\n * event from the user-supplied arbitrary map and, when run, asserts that the\n * pure `step()` prediction matches the runtime's observable outcome.\n *\n * Pair this arbitrary with `fc.property(...)` inside a `fc.assert(...)` call,\n * or use the helpers in `aifsmjs/pbt` properties to get the six generic\n * invariants for free.\n */\nexport function commandsFromMachine<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n): fc.Arbitrary<Iterable<FsmCommand<Ctx, Evt, States>>> {\n const arbs: fc.Arbitrary<FsmCommand<Ctx, Evt, States>>[] = [];\n for (const arb of Object.values(eventArbitraries)) {\n arbs.push(arb.map((event) => new SendCommand(event, def, impl)));\n }\n return fc.commands(arbs, { size: \"+1\" });\n}\n","import { isDeepStrictEqual } from \"node:util\";\nimport * as fc from \"fast-check\";\nimport { initialSnapshot } from \"../fsm/definition.js\";\nimport { step } from \"../fsm/lifecycle.js\";\nimport { normalizeTransitions } from \"../fsm/resolver.js\";\nimport { createRuntime } from \"../fsm/runtime.js\";\nimport type { Guard, Implementations, MachineDef } from \"../fsm/types.js\";\nimport { mergeContext } from \"../fsm/updater.js\";\nimport { replay } from \"../replay/index.js\";\nimport {\n type EventArbitraries,\n type FsmModel,\n commandsFromMachine,\n initialModel,\n} from \"./commands.js\";\n\nexport type AssertOpts = Readonly<{\n numRuns?: number;\n seed?: number;\n verbose?: boolean;\n}>;\n\nfunction buildAssertOpts(opts: AssertOpts | undefined): fc.Parameters<unknown> {\n const out: fc.Parameters<unknown> = {};\n if (opts?.numRuns !== undefined) out.numRuns = opts.numRuns;\n if (opts?.seed !== undefined) out.seed = opts.seed;\n if (opts?.verbose) out.verbose = true;\n return out;\n}\n\n/**\n * Structural deep-equality for two context values (C3). Backed by `node:util`\n * `isDeepStrictEqual`, replacing the previous `JSON.stringify(a) === JSON.stringify(b)`\n * oracle which was unsound:\n *\n * - key-order-sensitive → false-FAIL on `{a:1,b:2}` vs `{b:2,a:1}`;\n * - drops undefined keys → false-PASS on `{v:undefined,w:1}` vs `{w:1}`;\n * - lossy for `Map`/`Set`/`Date` (all serialise to `{}` or an ISO string);\n * - throws on `BigInt`.\n *\n * `isDeepStrictEqual` distinguishes present-but-undefined from absent keys,\n * compares `Map`/`Set`/`Date` by contents, and tolerates `BigInt` — exactly\n * the verdicts a context-equality oracle for PBT requires. No new dependency\n * (Node built-in; the package already targets Node >=18).\n */\nexport function contextEquals(a: unknown, b: unknown): boolean {\n return isDeepStrictEqual(a, b);\n}\n\n/**\n * #1 snapshotAlwaysFrozen — after any event sequence the live snapshot remains\n * frozen at the top level.\n */\nexport function snapshotAlwaysFrozen<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n fc.assert(\n fc.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {\n const real = createRuntime(def, impl);\n const model: FsmModel<Ctx, States> = initialModel(def);\n fc.modelRun(() => ({ model, real }), cmds);\n return Object.isFrozen(real.getSnapshot());\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #2 unknownEventNoOp — sending an event whose `type` is not declared in any\n * state's `on` map never changes the snapshot.\n */\nexport function unknownEventNoOp<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n unknownType: string,\n opts?: AssertOpts,\n): void {\n fc.assert(\n fc.property(fc.constant(unknownType), (t) => {\n const initial = initialSnapshot(def);\n const result = step(def, initial, { type: t } as unknown as Evt, impl);\n return result.changed === false && result.snapshot === initial && result.effects.length === 0;\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #3 reachableStatesSubsetDeclared — every state visited during a run belongs\n * to `def.states`.\n */\nexport function reachableStatesSubsetDeclared<\n Ctx,\n Evt extends { type: string },\n States extends string,\n>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const declared = new Set<string>(Object.keys(def.states));\n fc.assert(\n fc.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {\n const real = createRuntime(def, impl);\n const model: FsmModel<Ctx, States> = initialModel(def);\n fc.modelRun(() => ({ model, real }), cmds);\n for (const s of model.reached as Set<string>) {\n /* v8 ignore next — property failure branch; an unreachable state would indicate a bug. */\n if (!declared.has(s)) return false;\n }\n return declared.has(real.getSnapshot().value);\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #4 replayEqualsFold — `replay(initial, log)` produces the same final state\n * as a live runtime fed the same events. Effects dispatched by the runtime are\n * ignored; the comparison is on `{ value, context }`.\n */\nexport function replayEqualsFold<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const eventArb = fc.oneof(...Object.values(eventArbitraries));\n fc.assert(\n fc.property(fc.array(eventArb, { maxLength: 32 }), (events) => {\n const real = createRuntime(def, impl, { dispatchEffects: false });\n for (const e of events) real.send(e);\n const live = real.getSnapshot();\n const replayed = replay(initialSnapshot(def), events, def, impl).snapshot;\n return live.value === replayed.value && contextEquals(live.context, replayed.context);\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #5 guardsFalseNoTransition — when every candidate transition for the current\n * (state, event) pair carries a guard and every guard returns `false`, the\n * snapshot is unchanged (`changed === false`).\n *\n * Implementation: synthesise an impl that forces every guard to `false`, then\n * for each step whose candidate list is fully guarded, assert the step did not\n * change state. Steps with an unguarded fallback candidate (which fires even\n * when all guards are false) are skipped — the README claim is specifically\n * about the all-guards-false case.\n */\nexport function guardsFalseNoTransition<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const blockedGuards = new Proxy(\n {},\n {\n get: () => () => false,\n },\n ) as Readonly<Record<string, Guard<Ctx, Evt>>>;\n const blockedImpl: Implementations<Ctx, Evt> = {\n ...impl,\n guards: blockedGuards,\n };\n // True when every candidate transition for (value, eventType) carries a\n // guard — i.e. blocking all guards leaves no unconditional fallback, so a\n // correct step() must report changed === false.\n const isFullyGuarded = (value: States, eventType: string): boolean => {\n const candidates = normalizeTransitions(def.states[value]?.on?.[eventType]);\n return candidates.length > 0 && candidates.every((t) => t.guard !== undefined);\n };\n fc.assert(\n fc.property(\n fc.array(fc.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),\n (events) => {\n let snap = initialSnapshot(def);\n for (const e of events) {\n const fullyGuarded = isFullyGuarded(snap.value, e.type);\n const r = step(def, snap, e, blockedImpl);\n // The named invariant: all guards false + no unconditional fallback\n // ⇒ no transition. Without this assertion the property was vacuous\n // (it only failed if step() threw).\n if (fullyGuarded && r.changed !== false) return false;\n snap = r.snapshot;\n }\n return true;\n },\n ),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #6 assignDoesNotMutate — running an `assign`-style action never mutates the\n * previous context object. Verified by deep-equality check on a snapshot taken\n * before each event.\n */\nexport function assignDoesNotMutate<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n // Quick sanity guard: mergeContext is the only context mutator used by step.\n const dummy = { a: 1, b: 2 };\n const merged = mergeContext(dummy, { b: 3 });\n /* v8 ignore next — invariant guard; mergeContext returning the same ref would mean unit tests have already broken. */\n if (merged === dummy) throw new Error(\"aifsmjs/pbt: mergeContext returned the same reference\");\n\n fc.assert(\n fc.property(\n fc.array(fc.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),\n (events) => {\n let snap = initialSnapshot(def);\n for (const e of events) {\n // Structural snapshot of the pre-step context (C3). structuredClone +\n // contextEquals replaces the old JSON.stringify round-trip, which was\n // lossy for Map/Set/Date and threw on BigInt. structuredClone produces\n // an independent copy so a subsequent in-place mutation by step() is\n // detectable by deep comparison.\n const beforeCtx = structuredClone(snap.context);\n step(def, snap, e, impl);\n /* v8 ignore next — property failure branch; step() mutating snap.context would indicate a bug. */\n if (!contextEquals(snap.context, beforeCtx)) return false;\n // Continue with the actual result for subsequent events\n snap = step(def, snap, e, impl).snapshot;\n }\n return true;\n },\n ),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * Assert every generic property in one call. Use this when you don't need\n * fine-grained control over per-property options.\n */\nexport function assertAll<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts & { unknownEventType?: string },\n): void {\n snapshotAlwaysFrozen(def, impl, eventArbitraries, opts);\n unknownEventNoOp(def, impl, opts?.unknownEventType ?? \"__AIFSMJS_UNKNOWN__\", opts);\n reachableStatesSubsetDeclared(def, impl, eventArbitraries, opts);\n replayEqualsFold(def, impl, eventArbitraries, opts);\n guardsFalseNoTransition(def, impl, eventArbitraries, opts);\n assignDoesNotMutate(def, impl, eventArbitraries, opts);\n}\n","export {\n commandsFromMachine,\n initialModel,\n type EventArbitraries,\n type FsmCommand,\n type FsmModel,\n} from \"./commands.js\";\n\nexport {\n assertAll,\n assignDoesNotMutate,\n guardsFalseNoTransition,\n reachableStatesSubsetDeclared,\n replayEqualsFold,\n snapshotAlwaysFrozen,\n unknownEventNoOp,\n type AssertOpts,\n} from \"./properties.js\";\n\n// Convenience namespace mirroring the README:\n// import { properties } from \"aifsmjs/pbt\"\n// properties.snapshotAlwaysFrozen(...)\nimport * as propertiesModule from \"./properties.js\";\nexport const properties = propertiesModule;\n"]}
|