@typeonce/effect-machine 0.2.0 → 0.4.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.
Files changed (150) hide show
  1. package/README.md +345 -42
  2. package/dist/Machine.d.ts +1273 -347
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +161 -426
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/activities.d.ts +57 -0
  7. package/dist/internal/machine/activities.d.ts.map +1 -0
  8. package/dist/internal/machine/activities.js +49 -0
  9. package/dist/internal/machine/activities.js.map +1 -0
  10. package/dist/internal/machine/atom.d.ts +69 -0
  11. package/dist/internal/machine/atom.d.ts.map +1 -0
  12. package/dist/{AtomMachine.js → internal/machine/atom.js} +19 -94
  13. package/dist/internal/machine/atom.js.map +1 -0
  14. package/dist/internal/machine/cluster.d.ts +63 -0
  15. package/dist/internal/machine/cluster.d.ts.map +1 -0
  16. package/dist/{ClusterMachine.js → internal/machine/cluster.js} +15 -119
  17. package/dist/internal/machine/cluster.js.map +1 -0
  18. package/dist/internal/machine/command.d.ts +16 -0
  19. package/dist/internal/machine/command.d.ts.map +1 -0
  20. package/dist/internal/machine/command.js +45 -0
  21. package/dist/internal/machine/command.js.map +1 -0
  22. package/dist/internal/machine/commandRuntime.d.ts +13 -0
  23. package/dist/internal/machine/commandRuntime.d.ts.map +1 -0
  24. package/dist/internal/machine/commandRuntime.js +16 -0
  25. package/dist/internal/machine/commandRuntime.js.map +1 -0
  26. package/dist/internal/{machineModel.d.ts → machine/configuration.d.ts} +32 -38
  27. package/dist/internal/machine/configuration.d.ts.map +1 -0
  28. package/dist/internal/machine/configuration.js +965 -0
  29. package/dist/internal/machine/configuration.js.map +1 -0
  30. package/dist/internal/{machineErrors.d.ts → machine/errors.d.ts} +3 -3
  31. package/dist/internal/machine/errors.d.ts.map +1 -0
  32. package/dist/internal/{machineErrors.js → machine/errors.js} +1 -1
  33. package/dist/internal/machine/errors.js.map +1 -0
  34. package/dist/internal/machine/executionPlan.d.ts +49 -0
  35. package/dist/internal/machine/executionPlan.d.ts.map +1 -0
  36. package/dist/internal/machine/executionPlan.js +667 -0
  37. package/dist/internal/machine/executionPlan.js.map +1 -0
  38. package/dist/internal/machine/invocation.d.ts +23 -0
  39. package/dist/internal/machine/invocation.d.ts.map +1 -0
  40. package/dist/internal/machine/invocation.js +77 -0
  41. package/dist/internal/machine/invocation.js.map +1 -0
  42. package/dist/internal/machine/machine.d.ts +183 -0
  43. package/dist/internal/machine/machine.d.ts.map +1 -0
  44. package/dist/internal/machine/machine.js +552 -0
  45. package/dist/internal/machine/machine.js.map +1 -0
  46. package/dist/internal/machine/planner.d.ts +182 -0
  47. package/dist/internal/machine/planner.d.ts.map +1 -0
  48. package/dist/internal/machine/planner.js +1082 -0
  49. package/dist/internal/machine/planner.js.map +1 -0
  50. package/dist/internal/{machineProcess.d.ts → machine/process.d.ts} +10 -4
  51. package/dist/internal/machine/process.d.ts.map +1 -0
  52. package/dist/internal/machine/process.js +446 -0
  53. package/dist/internal/machine/process.js.map +1 -0
  54. package/dist/internal/machine/protocol.d.ts +34 -0
  55. package/dist/internal/machine/protocol.d.ts.map +1 -0
  56. package/dist/internal/machine/protocol.js +182 -0
  57. package/dist/internal/machine/protocol.js.map +1 -0
  58. package/dist/internal/machine/readiness.d.ts +4 -0
  59. package/dist/internal/machine/readiness.d.ts.map +1 -0
  60. package/dist/internal/machine/readiness.js +2 -0
  61. package/dist/internal/machine/readiness.js.map +1 -0
  62. package/dist/internal/{machineRuntime.d.ts → machine/runtime.d.ts} +104 -2
  63. package/dist/internal/machine/runtime.d.ts.map +1 -0
  64. package/dist/internal/machine/runtime.js +1381 -0
  65. package/dist/internal/machine/runtime.js.map +1 -0
  66. package/dist/internal/machine/serialization.d.ts +14 -0
  67. package/dist/internal/machine/serialization.d.ts.map +1 -0
  68. package/dist/internal/machine/serialization.js +338 -0
  69. package/dist/internal/machine/serialization.js.map +1 -0
  70. package/dist/internal/machine/stateDefinition.d.ts +15 -0
  71. package/dist/internal/machine/stateDefinition.d.ts.map +1 -0
  72. package/dist/internal/machine/stateDefinition.js +210 -0
  73. package/dist/internal/machine/stateDefinition.js.map +1 -0
  74. package/dist/internal/machine/symbols.d.ts +3 -0
  75. package/dist/internal/machine/symbols.d.ts.map +1 -0
  76. package/dist/internal/machine/symbols.js +3 -0
  77. package/dist/internal/machine/symbols.js.map +1 -0
  78. package/dist/internal/machine/topology.d.ts +98 -0
  79. package/dist/internal/machine/topology.d.ts.map +1 -0
  80. package/dist/internal/machine/topology.js +347 -0
  81. package/dist/internal/machine/topology.js.map +1 -0
  82. package/dist/internal/testing/machine/arbitrary.d.ts +36 -0
  83. package/dist/internal/testing/machine/arbitrary.d.ts.map +1 -0
  84. package/dist/internal/testing/machine/arbitrary.js +68 -0
  85. package/dist/internal/testing/machine/arbitrary.js.map +1 -0
  86. package/dist/internal/testing/machine/finiteModel.d.ts +322 -0
  87. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -0
  88. package/dist/internal/testing/machine/finiteModel.js +976 -0
  89. package/dist/internal/testing/machine/finiteModel.js.map +1 -0
  90. package/dist/internal/testing/machine/referenceModel.d.ts +203 -0
  91. package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -0
  92. package/dist/internal/testing/machine/referenceModel.js +1012 -0
  93. package/dist/internal/testing/machine/referenceModel.js.map +1 -0
  94. package/dist/internal/testing/machine/runtime.d.ts +328 -0
  95. package/dist/internal/testing/machine/runtime.d.ts.map +1 -0
  96. package/dist/internal/testing/machine/runtime.js +437 -0
  97. package/dist/internal/testing/machine/runtime.js.map +1 -0
  98. package/dist/internal/testing/machine/verification.d.ts +33 -0
  99. package/dist/internal/testing/machine/verification.d.ts.map +1 -0
  100. package/dist/internal/testing/machine/verification.js +1446 -0
  101. package/dist/internal/testing/machine/verification.js.map +1 -0
  102. package/dist/testing/MachineTest.d.ts +606 -0
  103. package/dist/testing/MachineTest.d.ts.map +1 -0
  104. package/dist/testing/MachineTest.js +102 -0
  105. package/dist/testing/MachineTest.js.map +1 -0
  106. package/dist/testing/index.d.ts +7 -0
  107. package/dist/testing/index.d.ts.map +1 -0
  108. package/dist/testing/index.js +7 -0
  109. package/dist/testing/index.js.map +1 -0
  110. package/dist/{ClusterMachine.d.ts → unstable/cluster/ClusterMachine.d.ts} +22 -30
  111. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -0
  112. package/dist/unstable/cluster/ClusterMachine.js +126 -0
  113. package/dist/unstable/cluster/ClusterMachine.js.map +1 -0
  114. package/dist/{cluster.js → unstable/cluster/index.d.ts} +1 -1
  115. package/dist/unstable/cluster/index.d.ts.map +1 -0
  116. package/dist/{cluster.d.ts → unstable/cluster/index.js} +1 -1
  117. package/dist/unstable/cluster/index.js.map +1 -0
  118. package/dist/{AtomMachine.d.ts → unstable/reactivity/AtomMachine.d.ts} +40 -23
  119. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -0
  120. package/dist/unstable/reactivity/AtomMachine.js +113 -0
  121. package/dist/unstable/reactivity/AtomMachine.js.map +1 -0
  122. package/dist/{reactivity.js → unstable/reactivity/index.d.ts} +1 -1
  123. package/dist/unstable/reactivity/index.d.ts.map +1 -0
  124. package/dist/{reactivity.d.ts → unstable/reactivity/index.js} +1 -1
  125. package/dist/unstable/reactivity/index.js.map +1 -0
  126. package/docs/agent-guide.md +271 -38
  127. package/package.json +23 -13
  128. package/dist/AtomMachine.d.ts.map +0 -1
  129. package/dist/AtomMachine.js.map +0 -1
  130. package/dist/ClusterMachine.d.ts.map +0 -1
  131. package/dist/ClusterMachine.js.map +0 -1
  132. package/dist/cluster.d.ts.map +0 -1
  133. package/dist/cluster.js.map +0 -1
  134. package/dist/internal/machineErrors.d.ts.map +0 -1
  135. package/dist/internal/machineErrors.js.map +0 -1
  136. package/dist/internal/machineModel.d.ts.map +0 -1
  137. package/dist/internal/machineModel.js +0 -766
  138. package/dist/internal/machineModel.js.map +0 -1
  139. package/dist/internal/machinePlanner.d.ts +0 -74
  140. package/dist/internal/machinePlanner.d.ts.map +0 -1
  141. package/dist/internal/machinePlanner.js +0 -621
  142. package/dist/internal/machinePlanner.js.map +0 -1
  143. package/dist/internal/machineProcess.d.ts.map +0 -1
  144. package/dist/internal/machineProcess.js +0 -181
  145. package/dist/internal/machineProcess.js.map +0 -1
  146. package/dist/internal/machineRuntime.d.ts.map +0 -1
  147. package/dist/internal/machineRuntime.js +0 -365
  148. package/dist/internal/machineRuntime.js.map +0 -1
  149. package/dist/reactivity.d.ts.map +0 -1
  150. package/dist/reactivity.js.map +0 -1
@@ -0,0 +1,1012 @@
1
+ /**
2
+ * Independent hierarchical, parallel, history, choice, and automatic-
3
+ * transition statechart semantics for finite test models.
4
+ *
5
+ * This module deliberately knows nothing about `Machine`, its snapshots, the
6
+ * finite-model compiler, target builders, or planner internals. The actual
7
+ * planner trace is treated as opaque data and projected structurally only at
8
+ * the comparison boundary.
9
+ *
10
+ * @internal
11
+ */
12
+ import * as Data from "effect/Data";
13
+ import * as Effect from "effect/Effect";
14
+ /**
15
+ * All semantic disagreements found for one finite-model trace.
16
+ *
17
+ * @category errors
18
+ * @since 4.0.0
19
+ */
20
+ export class ModelVerificationError extends Data.TaggedError("MachineTestModelVerificationError") {
21
+ }
22
+ const triggerKey = (trigger) => trigger.type === "event" ? `event:${trigger.event}` : trigger.type;
23
+ const reenters = (transition) => "reenter" in transition && transition.reenter;
24
+ const ControlOrder = Symbol("MachineTestReferenceControlOrder");
25
+ const withControlOrder = (state, order) => {
26
+ Object.defineProperty(state, ControlOrder, { value: order.slice(), enumerable: false });
27
+ return state;
28
+ };
29
+ const controlOrder = (state) => ControlOrder in state ? state[ControlOrder] : state.activePaths;
30
+ const stateTag = (path) => `State_${path.replaceAll(".", "_")}`;
31
+ /**
32
+ * Builds the oracle's own state index. This traversal intentionally duplicates
33
+ * the structural work performed by the compiler instead of importing it.
34
+ */
35
+ const indexModel = (model) => {
36
+ const ordered = [];
37
+ const visit = (states, parent, root, depth) => {
38
+ for (const node of states) {
39
+ const path = parent === undefined ? node.key : `${parent}.${node.key}`;
40
+ const nodeRoot = root ?? path;
41
+ ordered.push({
42
+ node,
43
+ path,
44
+ parent,
45
+ root: nodeRoot,
46
+ depth,
47
+ order: ordered.length,
48
+ children: node._tag === "Compound" || node._tag === "Parallel"
49
+ ? node.states.filter((child) => child._tag !== "History" && child._tag !== "Choice").map((child) => `${path}.${child.key}`)
50
+ : []
51
+ });
52
+ if (node._tag === "Compound" || node._tag === "Parallel") {
53
+ visit(node.states, path, nodeRoot, depth + 1);
54
+ }
55
+ }
56
+ };
57
+ visit(model.roots, undefined, undefined, 1);
58
+ const byPath = new Map(ordered.map((state) => [state.path, state]));
59
+ const transitions = new Map(model.transitions.map((transition) => [`${transition.source}\u0000${triggerKey(transition.trigger)}`, transition]));
60
+ const histories = ordered.filter((state) => state.node._tag === "History");
61
+ return { ordered, byPath, transitions, histories };
62
+ };
63
+ const getState = (index, path) => {
64
+ const state = index.byPath.get(path);
65
+ if (state === undefined) {
66
+ throw new Error(`MachineTest.interpretModel received unknown state path "${path}"`);
67
+ }
68
+ return state;
69
+ };
70
+ const activeValue = (state, value) => {
71
+ if (state.node._tag === "History" || state.node._tag === "Choice") {
72
+ throw new Error(`MachineTest.interpretModel cannot activate pseudo-state "${state.path}"`);
73
+ }
74
+ return { _tag: stateTag(state.path), value: value ?? state.node.value };
75
+ };
76
+ const applyTargetValue = (index, state, transition) => {
77
+ if (transition.targetValue === undefined || transition.target === undefined)
78
+ return state;
79
+ const target = getState(index, transition.target);
80
+ if (target.node._tag === "History" || !state.activePaths.includes(target.path))
81
+ return state;
82
+ return withControlOrder({
83
+ ...state,
84
+ values: { ...state.values, [target.path]: activeValue(target, transition.targetValue) }
85
+ }, controlOrder(state));
86
+ };
87
+ const pathsToRoot = (index, path) => {
88
+ const paths = [];
89
+ let current = getState(index, path);
90
+ while (current !== undefined) {
91
+ paths.unshift(current.path);
92
+ current = current.parent === undefined ? undefined : getState(index, current.parent);
93
+ }
94
+ return paths;
95
+ };
96
+ const initialChildPath = (state) => {
97
+ const node = state.node;
98
+ const child = node.states.find((candidate) => candidate.key === node.initial);
99
+ if (child === undefined) {
100
+ throw new Error(`MachineTest.interpretModel received unknown initial child "${node.initial}" for "${state.path}"`);
101
+ }
102
+ return `${state.path}.${child.key}`;
103
+ };
104
+ const resolveChoicePath = (index, path) => {
105
+ let current = getState(index, path);
106
+ const seen = new Set();
107
+ while (current.node._tag === "Choice") {
108
+ if (seen.has(current.path)) {
109
+ throw new Error(`MachineTest.interpretModel received infinite choice loop at "${current.path}"`);
110
+ }
111
+ seen.add(current.path);
112
+ current = getState(index, current.node.selected);
113
+ }
114
+ return current.path;
115
+ };
116
+ const expandInitial = (index, path) => {
117
+ const current = getState(index, path);
118
+ if (current.node._tag === "Choice")
119
+ return expandInitial(index, resolveChoicePath(index, current.path));
120
+ if (current.node._tag === "Compound") {
121
+ return [current.path, ...expandInitial(index, initialChildPath(current))];
122
+ }
123
+ if (current.node._tag === "Parallel") {
124
+ return [
125
+ current.path,
126
+ ...current.children.flatMap((child) => expandInitial(index, child))
127
+ ];
128
+ }
129
+ return [current.path];
130
+ };
131
+ const makeUnsettledState = (index, target) => {
132
+ const activePaths = [...pathsToRoot(index, target).slice(0, -1), ...expandInitial(index, target)];
133
+ const values = {};
134
+ for (const path of activePaths) {
135
+ values[path] = activeValue(getState(index, path));
136
+ }
137
+ return withControlOrder({
138
+ activePaths,
139
+ values,
140
+ completions: [],
141
+ history: {},
142
+ status: "active",
143
+ output: undefined
144
+ }, activePaths);
145
+ };
146
+ const directActiveChild = (index, state, parent) => {
147
+ const parentState = getState(index, parent);
148
+ return parentState.children
149
+ .map((path) => getState(index, path))
150
+ .find((child) => state.activePaths.includes(child.path));
151
+ };
152
+ const settleCompletions = (index, state) => {
153
+ const completions = [...state.completions];
154
+ const outputs = new Map(completions.map((completion) => [completion.path, completion.output]));
155
+ const complete = (path) => {
156
+ if (outputs.has(path))
157
+ return outputs.get(path);
158
+ const current = getState(index, path);
159
+ let output;
160
+ if (current.node._tag === "Final") {
161
+ output = current.node.output;
162
+ }
163
+ else if (current.node._tag === "Compound") {
164
+ const child = directActiveChild(index, state, current.path);
165
+ // A compound completes only when its direct active child is final. A
166
+ // completed compound child instead enables that child's `done`
167
+ // transition; completion propagates when it transitions to a final
168
+ // sibling of the parent.
169
+ if (child?.node._tag !== "Final")
170
+ return undefined;
171
+ output = complete(child.path);
172
+ }
173
+ else if (current.node._tag === "Parallel") {
174
+ for (const childPath of current.children) {
175
+ if (!state.activePaths.includes(childPath) || complete(childPath) === undefined) {
176
+ return undefined;
177
+ }
178
+ }
179
+ output = current.node.output;
180
+ }
181
+ if (output !== undefined) {
182
+ outputs.set(path, output);
183
+ completions.push({ path, output });
184
+ }
185
+ return output;
186
+ };
187
+ const deepestFirst = state.activePaths.slice().sort((left, right) => {
188
+ const leftState = getState(index, left);
189
+ const rightState = getState(index, right);
190
+ return rightState.depth - leftState.depth || leftState.order - rightState.order;
191
+ });
192
+ for (const path of deepestFirst) {
193
+ complete(path);
194
+ }
195
+ const root = state.activePaths.find((path) => getState(index, path).parent === undefined);
196
+ const output = root === undefined ? undefined : outputs.get(root);
197
+ return withControlOrder({
198
+ ...state,
199
+ completions,
200
+ status: output === undefined ? "active" : "done",
201
+ output
202
+ }, controlOrder(state));
203
+ };
204
+ const samePaths = (left, right) => left.length === right.length && left.every((path, index) => path === right[index]);
205
+ const leastCommonAncestor = (index, left, right) => {
206
+ const leftPaths = pathsToRoot(index, left);
207
+ const rightPaths = pathsToRoot(index, right);
208
+ let result;
209
+ for (let position = 0; position < Math.min(leftPaths.length, rightPaths.length); position++) {
210
+ if (leftPaths[position] !== rightPaths[position])
211
+ break;
212
+ result = leftPaths[position];
213
+ }
214
+ return result;
215
+ };
216
+ const isDescendant = (index, path, ancestor) => {
217
+ let parent = getState(index, path).parent;
218
+ while (parent !== undefined) {
219
+ if (parent === ancestor)
220
+ return true;
221
+ parent = getState(index, parent).parent;
222
+ }
223
+ return false;
224
+ };
225
+ const broadenBoundary = (index, natural, reentry) => {
226
+ if (natural === undefined || reentry === undefined)
227
+ return undefined;
228
+ return natural === reentry || isDescendant(index, natural, reentry) ? reentry : natural;
229
+ };
230
+ const lifecyclePaths = (index, activePaths, boundary, direction) => activePaths
231
+ .filter((path) => boundary === undefined || isDescendant(index, path, boundary))
232
+ .sort((left, right) => {
233
+ const leftState = getState(index, left);
234
+ const rightState = getState(index, right);
235
+ const depth = direction === "entry"
236
+ ? leftState.depth - rightState.depth
237
+ : rightState.depth - leftState.depth;
238
+ if (depth !== 0)
239
+ return depth;
240
+ return direction === "entry"
241
+ ? leftState.order - rightState.order
242
+ : rightState.order - leftState.order;
243
+ });
244
+ const activeLeaves = (index, state) => state.activePaths.filter((path) => {
245
+ const current = getState(index, path);
246
+ return current.children.every((child) => !state.activePaths.includes(child));
247
+ });
248
+ const selectTransitions = (index, state, trigger) => {
249
+ const selections = [];
250
+ const selectedSources = new Set();
251
+ for (const leaf of activeLeaves(index, state)) {
252
+ const candidates = pathsToRoot(index, leaf).slice().reverse();
253
+ for (const source of candidates) {
254
+ const transition = index.transitions.get(`${source}\u0000${triggerKey(trigger)}`);
255
+ if (transition === undefined)
256
+ continue;
257
+ if (!selectedSources.has(source)) {
258
+ selectedSources.add(source);
259
+ selections.push({ transition, leaf });
260
+ }
261
+ break;
262
+ }
263
+ }
264
+ return selections.filter(({ transition }) => !selections.some(({ transition: other }) => other.source !== transition.source && isDescendant(index, other.source, transition.source)));
265
+ };
266
+ const runtimeTargetPath = (index, transition) => {
267
+ if (transition.target === undefined)
268
+ return undefined;
269
+ const source = getState(index, transition.source);
270
+ const target = getState(index, transition.target);
271
+ if (target.node._tag === "History")
272
+ return target.parent;
273
+ if (target.node._tag === "Choice") {
274
+ return runtimeTargetPath(index, { ...transition, target: resolveChoicePath(index, target.path) });
275
+ }
276
+ // A same-root branch builder identifies the concrete initialized leaf. A
277
+ // full builder replaces the root with a complete snapshot and identifies
278
+ // that snapshot's root even when it contains initialized descendants.
279
+ if (source.root !== target.root)
280
+ return target.path;
281
+ const initial = (path) => {
282
+ const current = getState(index, path);
283
+ if (current.node._tag === "Parallel") {
284
+ if (transition.source !== current.path && !transition.source.startsWith(`${current.path}.`)) {
285
+ return current.path;
286
+ }
287
+ const child = transition.source === current.path
288
+ ? current.children[0]
289
+ : current.children.find((candidate) => isPathInSubtree(transition.source, candidate));
290
+ return initial(child);
291
+ }
292
+ return current.node._tag === "Compound" ? initial(initialChildPath(current)) : current.path;
293
+ };
294
+ const inspect = (path) => {
295
+ const current = getState(index, path);
296
+ if (current.node._tag === "Parallel" && transition.source !== current.path &&
297
+ !transition.source.startsWith(`${current.path}.`)) {
298
+ return current.path;
299
+ }
300
+ if (current.path === target.path)
301
+ return initial(current.path);
302
+ const next = target.path.slice(current.path.length + 1).split(".")[0];
303
+ return inspect(`${current.path}.${next}`);
304
+ };
305
+ return inspect(source.root);
306
+ };
307
+ const entryChoicePath = (index, path) => {
308
+ const state = getState(index, path);
309
+ if (state.node._tag === "Choice")
310
+ return state.path;
311
+ if (state.node._tag === "Compound") {
312
+ return entryChoicePath(index, `${state.path}.${state.node.initial}`);
313
+ }
314
+ if (state.node._tag === "Parallel") {
315
+ for (const child of state.children) {
316
+ const choice = entryChoicePath(index, child);
317
+ if (choice !== undefined)
318
+ return choice;
319
+ }
320
+ }
321
+ return undefined;
322
+ };
323
+ const choiceResolvedTargetPath = (index, path) => {
324
+ const choice = getState(index, path);
325
+ if (choice.node._tag !== "Choice")
326
+ return choice.path;
327
+ const selected = getState(index, choice.node.selected);
328
+ if (selected.node._tag === "Choice")
329
+ return choiceResolvedTargetPath(index, selected.path);
330
+ return runtimeTargetPath(index, {
331
+ source: choice.path,
332
+ trigger: { type: "always" },
333
+ target: selected.path
334
+ });
335
+ };
336
+ const choiceChainResolvedTargetPath = (index, path) => {
337
+ let current = getState(index, path);
338
+ const seen = new Set();
339
+ while (current.node._tag === "Choice") {
340
+ if (seen.has(current.path))
341
+ return choiceResolvedTargetPath(index, current.path);
342
+ seen.add(current.path);
343
+ const selected = getState(index, current.node.selected);
344
+ const nestedChoice = entryChoicePath(index, selected.path);
345
+ if (nestedChoice === undefined)
346
+ return choiceResolvedTargetPath(index, current.path);
347
+ current = getState(index, nestedChoice);
348
+ }
349
+ return current.path;
350
+ };
351
+ const isPathInSubtree = (path, root) => path === root || path.startsWith(`${root}.`);
352
+ const expandSelection = (index, path, requested) => {
353
+ const current = getState(index, path);
354
+ if (current.node._tag === "History")
355
+ return [];
356
+ if (current.node._tag === "Compound") {
357
+ const selected = current.children.find((child) => isPathInSubtree(requested, child)) ?? initialChildPath(current);
358
+ return [current.path, ...expandSelection(index, selected, requested)];
359
+ }
360
+ if (current.node._tag === "Parallel") {
361
+ return [
362
+ current.path,
363
+ ...current.children.flatMap((child) => expandSelection(index, child, isPathInSubtree(requested, child) ? requested : child))
364
+ ];
365
+ }
366
+ return [current.path];
367
+ };
368
+ const makeHistoryConfiguration = (index, current, owner, activePaths, rememberedValues, history) => {
369
+ const active = new Set(activePaths);
370
+ const ownerAncestors = pathsToRoot(index, owner);
371
+ const ownerAncestry = new Set(ownerAncestors);
372
+ // Parallel ancestors outside the owner retain active sibling regions. When
373
+ // the owner belongs to an inactive root, those regions follow initial entry.
374
+ for (const ancestorPath of ownerAncestors) {
375
+ const ancestor = getState(index, ancestorPath);
376
+ if (ancestor.node._tag !== "Parallel")
377
+ continue;
378
+ const selectedRegion = ancestor.children.find((child) => ownerAncestry.has(child));
379
+ for (const region of ancestor.children) {
380
+ if (region === selectedRegion || active.has(region))
381
+ continue;
382
+ const retained = current.activePaths.filter((path) => isPathInSubtree(path, region));
383
+ for (const path of retained.length === 0 ? expandInitial(index, region) : retained)
384
+ active.add(path);
385
+ }
386
+ }
387
+ const ordered = index.ordered
388
+ .filter(({ node, path }) => node._tag !== "History" && node._tag !== "Choice" && active.has(path))
389
+ .map(({ path }) => path);
390
+ const values = {};
391
+ for (const path of ordered) {
392
+ values[path] = rememberedValues[path] ?? current.values[path] ?? activeValue(getState(index, path));
393
+ }
394
+ const completions = current.completions.filter(({ path }) => active.has(path) && !isPathInSubtree(path, owner));
395
+ return withControlOrder({
396
+ activePaths: ordered,
397
+ values,
398
+ completions,
399
+ history,
400
+ status: "active",
401
+ output: undefined
402
+ }, ordered);
403
+ };
404
+ const captureHistory = (index, current, next, exitPaths) => {
405
+ if (exitPaths.length === 0)
406
+ return next;
407
+ const exited = new Set(exitPaths);
408
+ const history = { ...next.history };
409
+ for (const state of index.histories) {
410
+ const owner = state.parent;
411
+ if (!exited.has(owner))
412
+ continue;
413
+ const active = current.activePaths.filter((path) => pathsToRoot(index, owner).includes(path) || path === owner ||
414
+ (state.node.history === "deep"
415
+ ? isPathInSubtree(path, owner)
416
+ : getState(index, path).parent === owner));
417
+ const values = {};
418
+ for (const path of active)
419
+ values[path] = current.values[path];
420
+ history[state.path] = {
421
+ mode: state.node.history,
422
+ active,
423
+ values
424
+ };
425
+ }
426
+ return withControlOrder({ ...next, history }, controlOrder(next));
427
+ };
428
+ const restoreHistory = (index, current, historyState) => {
429
+ const owner = historyState.parent;
430
+ const record = current.history[historyState.path];
431
+ if (record === undefined) {
432
+ const active = [
433
+ ...pathsToRoot(index, owner).slice(0, -1),
434
+ ...expandSelection(index, owner, historyState.node.fallback)
435
+ ];
436
+ const values = {};
437
+ for (const path of active)
438
+ values[path] = activeValue(getState(index, path));
439
+ return makeHistoryConfiguration(index, current, owner, active, values, current.history);
440
+ }
441
+ const active = new Set(record.active);
442
+ if (record.mode === "shallow") {
443
+ for (const child of getState(index, owner).children) {
444
+ if (!active.has(child))
445
+ continue;
446
+ for (const path of expandInitial(index, child).slice(1))
447
+ active.add(path);
448
+ }
449
+ }
450
+ return makeHistoryConfiguration(index, current, owner, Array.from(active), record.values, current.history);
451
+ };
452
+ const resolveHistoryState = (index, before, transition, leaf) => {
453
+ const historyState = getState(index, transition.target);
454
+ const owner = historyState.parent;
455
+ const reenteredOwner = reenters(transition) && before.activePaths.includes(owner);
456
+ const provisionalBoundary = reenters(transition)
457
+ ? getState(index, transition.source).parent
458
+ : leastCommonAncestor(index, leaf, owner);
459
+ const provisionalExitPaths = reenteredOwner
460
+ ? lifecyclePaths(index, before.activePaths.filter((path) => isPathInSubtree(path, owner)), undefined, "exit")
461
+ : lifecyclePaths(index, before.activePaths, provisionalBoundary, "exit");
462
+ const stateAtResolution = provisionalExitPaths.includes(owner)
463
+ ? captureHistory(index, before, before, provisionalExitPaths)
464
+ : before;
465
+ return restoreHistory(index, stateAtResolution, historyState);
466
+ };
467
+ const targetState = (index, before, transition, leaf = transition.source) => {
468
+ if (transition.target === undefined)
469
+ return before;
470
+ const source = getState(index, transition.source);
471
+ const target = getState(index, transition.target);
472
+ if (target.node._tag === "Choice") {
473
+ return targetState(index, before, { ...transition, target: resolveChoicePath(index, target.path) }, leaf);
474
+ }
475
+ if (target.node._tag === "History")
476
+ return resolveHistoryState(index, before, transition, leaf);
477
+ if (source.root !== target.root) {
478
+ const unsettled = makeUnsettledState(index, target.path);
479
+ return applyTargetValue(index, withControlOrder({ ...unsettled, history: before.history }, controlOrder(unsettled)), transition);
480
+ }
481
+ const actualTarget = runtimeTargetPath(index, transition);
482
+ const actualNode = getState(index, actualTarget);
483
+ // A returned parallel target is an upper bound carrying a complete nested
484
+ // snapshot: retain the more specific declared descendant for that snapshot.
485
+ // Conversely, a branch target may resolve a declared compound/parallel
486
+ // ancestor to the concrete initialized leaf selected inside the source
487
+ // region. In both cases the deeper path describes the control change.
488
+ const configurationTarget = actualNode.depth >= target.depth ? actualTarget : target.path;
489
+ const active = new Set([
490
+ ...pathsToRoot(index, configurationTarget).slice(0, -1),
491
+ ...expandInitial(index, configurationTarget)
492
+ ]);
493
+ const retainedValuePaths = new Set(pathsToRoot(index, configurationTarget)
494
+ .slice(0, -1)
495
+ .filter((path) => before.activePaths.includes(path)));
496
+ const retainedCompletionPaths = [];
497
+ const order = actualNode.node._tag === "Parallel"
498
+ ? [
499
+ ...index.ordered
500
+ .filter(({ path }) => active.has(path) && isPathInSubtree(path, actualTarget))
501
+ .map(({ path }) => path),
502
+ ...pathsToRoot(index, actualTarget).slice(0, -1)
503
+ ]
504
+ : pathsToRoot(index, actualTarget).slice();
505
+ const targetAncestors = pathsToRoot(index, configurationTarget);
506
+ for (const ancestorPath of targetAncestors) {
507
+ const ancestor = getState(index, ancestorPath);
508
+ if (ancestor.node._tag !== "Parallel")
509
+ continue;
510
+ const selectedRegion = ancestor.children.find((child) => isPathInSubtree(configurationTarget, child));
511
+ const sourceInside = transition.source === ancestor.path || transition.source.startsWith(`${ancestor.path}.`);
512
+ for (const region of ancestor.children) {
513
+ if (region === selectedRegion)
514
+ continue;
515
+ if (sourceInside && before.activePaths.includes(region)) {
516
+ for (const path of controlOrder(before)) {
517
+ if (isPathInSubtree(path, region)) {
518
+ active.add(path);
519
+ retainedValuePaths.add(path);
520
+ if (!order.includes(path))
521
+ order.push(path);
522
+ if (before.completions.some((completion) => completion.path === path) &&
523
+ !retainedCompletionPaths.includes(path)) {
524
+ retainedCompletionPaths.push(path);
525
+ }
526
+ }
527
+ }
528
+ }
529
+ else {
530
+ for (const path of expandInitial(index, region)) {
531
+ active.add(path);
532
+ if (!order.includes(path))
533
+ order.push(path);
534
+ }
535
+ }
536
+ }
537
+ }
538
+ if (actualNode.node._tag === "Parallel") {
539
+ const subtreeOrder = index.ordered
540
+ .filter(({ path }) => active.has(path) && isPathInSubtree(path, actualTarget))
541
+ .map(({ path }) => path);
542
+ const outsideOrder = order.filter((path) => !isPathInSubtree(path, actualTarget));
543
+ order.splice(0, order.length, ...subtreeOrder, ...outsideOrder);
544
+ }
545
+ const activePaths = index.ordered.filter(({ path }) => active.has(path)).map(({ path }) => path);
546
+ const values = {};
547
+ for (const path of activePaths) {
548
+ values[path] = retainedValuePaths.has(path) && before.values[path] !== undefined
549
+ ? before.values[path]
550
+ : activeValue(getState(index, path));
551
+ }
552
+ return applyTargetValue(index, withControlOrder({
553
+ activePaths,
554
+ values,
555
+ completions: retainedCompletionPaths.flatMap((path) => {
556
+ const completion = before.completions.find((candidate) => candidate.path === path);
557
+ return completion === undefined ? [] : [completion];
558
+ }),
559
+ history: before.history,
560
+ status: "active",
561
+ output: undefined
562
+ }, order), transition);
563
+ };
564
+ const transitionRecord = (index, transition) => ({
565
+ source: transition.source,
566
+ trigger: transition.trigger,
567
+ reenter: reenters(transition),
568
+ // Retain the target identity returned by the compiler's selected builder;
569
+ // the finite AST keeps the broader declared bound separately.
570
+ target: transition.target !== undefined && getState(index, transition.target).node._tag === "History"
571
+ ? transition.target
572
+ : transition.target === undefined
573
+ ? runtimeTargetPath(index, transition)
574
+ : getState(index, transition.source).root !== getState(index, transition.target).root
575
+ ? transition.target
576
+ : entryChoicePath(index, transition.target) ?? runtimeTargetPath(index, transition),
577
+ resolvedTarget: transition.target === undefined
578
+ ? runtimeTargetPath(index, transition)
579
+ : entryChoicePath(index, transition.target) === undefined
580
+ ? runtimeTargetPath(index, transition)
581
+ : choiceChainResolvedTargetPath(index, entryChoicePath(index, transition.target))
582
+ });
583
+ const choiceTransitionRecords = (index, path) => {
584
+ const records = [];
585
+ let current = getState(index, path);
586
+ const seen = new Set();
587
+ while (current.node._tag === "Choice") {
588
+ if (seen.has(current.path))
589
+ break;
590
+ seen.add(current.path);
591
+ const selected = getState(index, current.node.selected);
592
+ const nestedChoice = entryChoicePath(index, selected.path);
593
+ const selectedTarget = nestedChoice ?? choiceResolvedTargetPath(index, current.path);
594
+ records.push({
595
+ source: current.path,
596
+ trigger: { type: "choice" },
597
+ reenter: false,
598
+ target: selectedTarget,
599
+ resolvedTarget: choiceResolvedTargetPath(index, current.path)
600
+ });
601
+ if (nestedChoice === undefined)
602
+ break;
603
+ current = getState(index, nestedChoice);
604
+ }
605
+ return records;
606
+ };
607
+ const initialChoiceTransitionRecords = (index, path) => {
608
+ const current = getState(index, path);
609
+ if (current.node._tag === "Choice") {
610
+ return choiceTransitionRecords(index, current.path);
611
+ }
612
+ if (current.node._tag === "Compound") {
613
+ return initialChoiceTransitionRecords(index, initialChildPath(current));
614
+ }
615
+ if (current.node._tag === "Parallel") {
616
+ return current.children.flatMap((child) => initialChoiceTransitionRecords(index, child));
617
+ }
618
+ return [];
619
+ };
620
+ const evaluateTransition = (index, before, selection) => {
621
+ const { transition } = selection;
622
+ const targetPath = runtimeTargetPath(index, transition);
623
+ const next = targetState(index, before, transition, selection.leaf);
624
+ const changed = reenters(transition) || !samePaths(before.activePaths, next.activePaths);
625
+ if (!changed) {
626
+ return { ...selection, next, targetPath, changed, exitPaths: [], entryPaths: [] };
627
+ }
628
+ const naturalBoundary = targetPath === undefined
629
+ ? getState(index, transition.source).parent
630
+ : leastCommonAncestor(index, selection.leaf, targetPath);
631
+ const choiceEntry = transition.target === undefined ? undefined : entryChoicePath(index, transition.target);
632
+ const choiceResolvesToActiveAncestor = choiceEntry !== undefined &&
633
+ isPathInSubtree(transition.source, resolveChoicePath(index, choiceEntry));
634
+ const boundary = reenters(transition)
635
+ ? !choiceResolvesToActiveAncestor
636
+ ? broadenBoundary(index, naturalBoundary, getState(index, transition.source).parent)
637
+ : getState(index, transition.source).parent
638
+ : naturalBoundary;
639
+ const historyTarget = transition.target === undefined ? undefined : getState(index, transition.target);
640
+ const reenteredHistoryOwner = historyTarget?.node._tag === "History" && reenters(transition) &&
641
+ historyTarget.parent !== undefined && before.activePaths.includes(historyTarget.parent);
642
+ return {
643
+ ...selection,
644
+ next,
645
+ targetPath,
646
+ changed,
647
+ exitPaths: reenteredHistoryOwner
648
+ ? lifecyclePaths(index, before.activePaths.filter((path) => isPathInSubtree(path, historyTarget.parent)), undefined, "exit")
649
+ : lifecyclePaths(index, before.activePaths, boundary, "exit"),
650
+ entryPaths: reenteredHistoryOwner
651
+ ? lifecyclePaths(index, next.activePaths.filter((path) => isPathInSubtree(path, historyTarget.parent)), undefined, "entry")
652
+ : lifecyclePaths(index, next.activePaths, boundary, "entry")
653
+ };
654
+ };
655
+ const hasPathIntersection = (left, right) => left.some((path) => right.includes(path));
656
+ const sortByDocumentOrder = (index, transitions) => Array.from(transitions).sort((left, right) => getState(index, left.transition.source).order - getState(index, right.transition.source).order);
657
+ const removeConflicts = (index, transitions) => {
658
+ const retained = [];
659
+ for (const transition of sortByDocumentOrder(index, transitions)) {
660
+ let preempted = false;
661
+ const remove = new Set();
662
+ for (const other of retained) {
663
+ if (!hasPathIntersection(transition.exitPaths, other.exitPaths))
664
+ continue;
665
+ if (isDescendant(index, transition.transition.source, other.transition.source)) {
666
+ remove.add(other);
667
+ }
668
+ else {
669
+ preempted = true;
670
+ break;
671
+ }
672
+ }
673
+ if (preempted)
674
+ continue;
675
+ for (const other of remove)
676
+ retained.splice(retained.indexOf(other), 1);
677
+ retained.push(transition);
678
+ }
679
+ return retained;
680
+ };
681
+ const applySelections = (index, before, selected, event) => {
682
+ const retained = removeConflicts(index, selected.map((selection) => evaluateTransition(index, before, selection)));
683
+ const sorted = sortByDocumentOrder(index, retained);
684
+ let next = before;
685
+ const applicationOrder = [
686
+ ...sorted.filter((transition) => !transition.changed),
687
+ ...sorted.filter((transition) => transition.changed)
688
+ ];
689
+ for (const evaluated of applicationOrder) {
690
+ next = targetState(index, next, evaluated.transition, evaluated.leaf);
691
+ }
692
+ const changed = sorted.some((transition) => transition.changed);
693
+ const exitPaths = lifecyclePaths(index, sorted.flatMap((transition) => transition.exitPaths), undefined, "exit");
694
+ const entryPaths = lifecyclePaths(index, sorted.flatMap((transition) => transition.entryPaths), undefined, "entry");
695
+ next = captureHistory(index, before, next, exitPaths);
696
+ const enteredChoice = sorted.some(({ transition }) => transition.target !== undefined && entryChoicePath(index, transition.target) !== undefined);
697
+ if (enteredChoice)
698
+ next = withControlOrder({ ...next, completions: [] }, controlOrder(next));
699
+ return {
700
+ microstep: {
701
+ next,
702
+ event,
703
+ transitions: sorted.flatMap(({ transition }) => {
704
+ const choice = transition.target === undefined ? undefined : entryChoicePath(index, transition.target);
705
+ return [
706
+ transitionRecord(index, transition),
707
+ ...(choice === undefined ? [] : choiceTransitionRecords(index, choice))
708
+ ];
709
+ }),
710
+ exitPaths,
711
+ entryPaths,
712
+ changed
713
+ },
714
+ after: settleCompletions(index, next)
715
+ };
716
+ };
717
+ const automaticTransitions = (index, initial, event, completedBefore, refreshedByExit = []) => {
718
+ const microsteps = [];
719
+ let current = initial;
720
+ let previousCompletions = completedBefore;
721
+ const newlyCompleted = (state, previous, exited) => state.completions.filter((completion) => (!previous.some(({ path }) => path === completion.path) || exited.includes(completion.path)) &&
722
+ index.transitions.has(`${completion.path}\u0000${triggerKey({ type: "done" })}`));
723
+ const pendingDone = [
724
+ ...newlyCompleted(current, previousCompletions, refreshedByExit)
725
+ ];
726
+ let shouldRunAlways = true;
727
+ let iterations = 0;
728
+ while (true) {
729
+ iterations += 1;
730
+ if (iterations > 1000) {
731
+ throw new Error("MachineTest.interpretModel detected an infinite automatic-transition cycle");
732
+ }
733
+ const completion = pendingDone.shift();
734
+ if (completion !== undefined && current.activePaths.includes(completion.path)) {
735
+ const transition = index.transitions.get(`${completion.path}\u0000${triggerKey({ type: "done" })}`);
736
+ const applied = applySelections(index, current, [{ transition, leaf: completion.path }], event);
737
+ microsteps.push(applied.microstep);
738
+ previousCompletions = current.completions;
739
+ current = applied.after;
740
+ pendingDone.push(...newlyCompleted(current, previousCompletions, applied.microstep.exitPaths));
741
+ shouldRunAlways = applied.microstep.changed;
742
+ continue;
743
+ }
744
+ if (current.status === "done")
745
+ break;
746
+ const always = shouldRunAlways ? selectTransitions(index, current, { type: "always" }) : [];
747
+ if (always.length === 0)
748
+ break;
749
+ const applied = applySelections(index, current, always, event);
750
+ microsteps.push(applied.microstep);
751
+ previousCompletions = current.completions;
752
+ current = applied.after;
753
+ pendingDone.push(...newlyCompleted(current, previousCompletions, applied.microstep.exitPaths));
754
+ shouldRunAlways = applied.microstep.changed;
755
+ }
756
+ return { state: current, microsteps };
757
+ };
758
+ const stepModel = (index, before, event, stepIndex) => {
759
+ // Public events delivered after terminal completion are observed but cannot
760
+ // select another transition.
761
+ if (before.status === "done") {
762
+ return {
763
+ index: stepIndex,
764
+ event,
765
+ before,
766
+ microsteps: [],
767
+ after: before,
768
+ done: true,
769
+ output: before.output
770
+ };
771
+ }
772
+ const selected = selectTransitions(index, before, { type: "event", event });
773
+ if (selected.length === 0) {
774
+ return {
775
+ index: stepIndex,
776
+ event,
777
+ before,
778
+ microsteps: [],
779
+ after: before,
780
+ done: false,
781
+ output: undefined
782
+ };
783
+ }
784
+ const applied = applySelections(index, before, selected, event);
785
+ const stabilized = automaticTransitions(index, applied.after, event, before.completions, applied.microstep.exitPaths);
786
+ return {
787
+ index: stepIndex,
788
+ event,
789
+ before,
790
+ microsteps: [applied.microstep, ...stabilized.microsteps],
791
+ after: stabilized.state,
792
+ done: stabilized.state.status === "done",
793
+ output: stabilized.state.output
794
+ };
795
+ };
796
+ /**
797
+ * Purely interprets a hierarchical finite model without compiling or
798
+ * executing a `Machine`.
799
+ *
800
+ * @category verification
801
+ * @since 4.0.0
802
+ */
803
+ export const interpretModel = (model, events) => {
804
+ const index = indexModel(model);
805
+ const initialRoot = getState(index, model.initial);
806
+ if (initialRoot.parent !== undefined) {
807
+ throw new Error(`MachineTest.interpretModel expected initial state "${model.initial}" to be a root`);
808
+ }
809
+ const startingState = makeUnsettledState(index, model.initial);
810
+ const initialState = settleCompletions(index, startingState);
811
+ const initialChoiceTransitions = initialChoiceTransitionRecords(index, model.initial);
812
+ const stabilizedInitial = automaticTransitions(index, initialState, "InitialEvent", []);
813
+ const initial = {
814
+ startingState,
815
+ initialEntryPaths: startingState.activePaths,
816
+ state: stabilizedInitial.state,
817
+ microsteps: [
818
+ ...(initialChoiceTransitions.length === 0 ? [] : [{
819
+ next: startingState,
820
+ event: "InitialEvent",
821
+ transitions: initialChoiceTransitions,
822
+ exitPaths: [],
823
+ entryPaths: [],
824
+ changed: false
825
+ }]),
826
+ ...stabilizedInitial.microsteps
827
+ ],
828
+ done: stabilizedInitial.state.status === "done",
829
+ output: stabilizedInitial.state.output
830
+ };
831
+ const steps = [];
832
+ let current = stabilizedInitial.state;
833
+ for (let eventIndex = 0; eventIndex < events.length; eventIndex++) {
834
+ const step = stepModel(index, current, events[eventIndex], eventIndex);
835
+ steps.push(step);
836
+ current = step.after;
837
+ }
838
+ return {
839
+ events: events.slice(),
840
+ initial,
841
+ steps,
842
+ final: current
843
+ };
844
+ };
845
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
846
+ const projectState = (snapshot) => {
847
+ const activePaths = [];
848
+ const values = {};
849
+ const visit = (current) => {
850
+ if (!isRecord(current))
851
+ return;
852
+ if (typeof current.path === "string") {
853
+ activePaths.push(current.path);
854
+ values[current.path] = current.value;
855
+ }
856
+ if (current.state !== undefined)
857
+ visit(current.state);
858
+ if (isRecord(current.states)) {
859
+ for (const child of Object.values(current.states))
860
+ visit(child);
861
+ }
862
+ };
863
+ visit(snapshot);
864
+ return {
865
+ activePaths,
866
+ values,
867
+ completions: isRecord(snapshot) && Array.isArray(snapshot.completed) ? snapshot.completed : [],
868
+ history: isRecord(snapshot) && isRecord(snapshot.history) ? snapshot.history : {}
869
+ };
870
+ };
871
+ const canonicalize = (value) => {
872
+ if (value === undefined)
873
+ return { $undefined: true };
874
+ if (Array.isArray(value))
875
+ return value.map(canonicalize);
876
+ if (!isRecord(value))
877
+ return value;
878
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]));
879
+ };
880
+ const equal = (left, right) => JSON.stringify(canonicalize(left)) === JSON.stringify(canonicalize(right));
881
+ const get = (value, key) => isRecord(value) ? value[key] : undefined;
882
+ const array = (value) => Array.isArray(value) ? value : [];
883
+ const eventTag = (value) => {
884
+ if (typeof value === "string")
885
+ return value;
886
+ const tag = get(value, "_tag");
887
+ return typeof tag === "string" ? tag : undefined;
888
+ };
889
+ const completionOrderIndependent = (values) => values.slice().sort((left, right) => {
890
+ const leftPath = get(left, "path");
891
+ const rightPath = get(right, "path");
892
+ return String(leftPath).localeCompare(String(rightPath));
893
+ });
894
+ const historyOrderIndependent = (value) => {
895
+ if (!isRecord(value))
896
+ return value;
897
+ return Object.fromEntries(Object.keys(value).sort().map((path) => {
898
+ const record = value[path];
899
+ if (!isRecord(record))
900
+ return [path, record];
901
+ const active = array(record.active).slice().sort((left, right) => String(left).localeCompare(String(right)));
902
+ return [path, { mode: record.mode, active, values: record.values }];
903
+ }));
904
+ };
905
+ const projectTransition = (value) => {
906
+ if (!isRecord(value))
907
+ return value;
908
+ const trigger = get(value, "trigger");
909
+ return {
910
+ source: value.source,
911
+ trigger: isRecord(trigger)
912
+ ? { type: trigger.type, ...(trigger.type === "event" ? { event: trigger.event } : {}) }
913
+ : trigger,
914
+ reenter: value.reenter,
915
+ target: value.target,
916
+ resolvedTarget: value.resolvedTarget
917
+ };
918
+ };
919
+ /**
920
+ * Compares an opaque executable planner trace with the independent finite
921
+ * reference interpreter. The function accumulates every mismatch so shrunk
922
+ * counterexamples preserve the full semantic difference.
923
+ *
924
+ * @internal The public wrapper narrows `actualTrace` to `MachineTest.Trace`.
925
+ */
926
+ export const verifyModelTrace = (model, actualTrace) => {
927
+ const mismatches = [];
928
+ const add = (location, field, expected, actual) => {
929
+ if (equal(expected, actual))
930
+ return;
931
+ mismatches.push({
932
+ location,
933
+ field,
934
+ expected,
935
+ actual,
936
+ message: `${field} differs from the independent finite-model interpretation`
937
+ });
938
+ };
939
+ const scenario = get(actualTrace, "scenario");
940
+ const actualEvents = array(get(scenario, "events"));
941
+ const tags = actualEvents.map(eventTag);
942
+ for (let index = 0; index < tags.length; index++) {
943
+ if (tags[index] === undefined) {
944
+ add({ phase: "event", eventIndex: index }, "event.tag", "a string _tag", tags[index]);
945
+ }
946
+ }
947
+ const reference = interpretModel(model, tags.map((tag) => tag ?? "<invalid-event>"));
948
+ const actualInitial = get(actualTrace, "initial");
949
+ const actualInitialPlan = get(actualInitial, "plan");
950
+ const initialLocation = { phase: "initial" };
951
+ const compareState = (location, field, expected, actual) => {
952
+ const projected = projectState(actual);
953
+ add(location, `${field}.activePaths`, expected.activePaths, projected.activePaths);
954
+ add(location, `${field}.values`, expected.values, projected.values);
955
+ // Completion records are a logical cache. Their array insertion order can
956
+ // change when an unaffected parallel region is copied through a target,
957
+ // but path/output membership is the observable semantic contract.
958
+ add(location, `${field}.completions`, completionOrderIndependent(expected.completions), completionOrderIndependent(projected.completions));
959
+ add(location, `${field}.history`, historyOrderIndependent(expected.history), historyOrderIndependent(projected.history));
960
+ };
961
+ compareState(initialLocation, "initial.startingState", reference.initial.startingState, get(actualInitial, "startingState"));
962
+ compareState(initialLocation, "initial.plan.startingState", reference.initial.startingState, get(actualInitialPlan, "startingState"));
963
+ add(initialLocation, "initial.startingConfiguration", reference.initial.startingState.activePaths, get(actualInitial, "startingConfiguration"));
964
+ add(initialLocation, "initial.initialEntryPaths", reference.initial.initialEntryPaths, get(actualInitial, "initialEntryPaths"));
965
+ add(initialLocation, "initial.plan.initialEntryPaths", reference.initial.initialEntryPaths, get(actualInitialPlan, "initialEntryPaths"));
966
+ compareState(initialLocation, "initial.plan.state", reference.initial.state, get(actualInitialPlan, "state"));
967
+ add(initialLocation, "initial.configuration", reference.initial.state.activePaths, get(actualInitial, "configuration"));
968
+ add(initialLocation, "initial.plan.microsteps", reference.initial.microsteps.length, array(get(actualInitialPlan, "microsteps")).length);
969
+ add(initialLocation, "initial.plan.done", reference.initial.done, get(actualInitialPlan, "done"));
970
+ add(initialLocation, "initial.plan.output", reference.initial.output, get(actualInitialPlan, "output"));
971
+ const actualSteps = array(get(actualTrace, "steps"));
972
+ add({ phase: "final" }, "trace.steps.length", reference.steps.length, actualSteps.length);
973
+ for (let stepIndex = 0; stepIndex < reference.steps.length; stepIndex++) {
974
+ const expected = reference.steps[stepIndex];
975
+ const actual = actualSteps[stepIndex];
976
+ const location = { phase: "event", eventIndex: stepIndex };
977
+ const actualPlan = get(actual, "plan");
978
+ add(location, "step.index", expected.index, get(actual, "index"));
979
+ add(location, "step.event", expected.event, eventTag(get(actual, "event")));
980
+ compareState(location, "step.before", expected.before, get(actual, "before"));
981
+ add(location, "step.beforeConfiguration", expected.before.activePaths, get(actual, "beforeConfiguration"));
982
+ const actualMicrosteps = array(get(actualPlan, "microsteps"));
983
+ add(location, "step.plan.microsteps.length", expected.microsteps.length, actualMicrosteps.length);
984
+ for (let microstepIndex = 0; microstepIndex < expected.microsteps.length; microstepIndex++) {
985
+ const expectedMicrostep = expected.microsteps[microstepIndex];
986
+ const actualMicrostep = actualMicrosteps[microstepIndex];
987
+ const microstepLocation = {
988
+ phase: "event",
989
+ eventIndex: stepIndex,
990
+ microstepIndex
991
+ };
992
+ compareState(microstepLocation, "microstep.next", expectedMicrostep.next, get(actualMicrostep, "next"));
993
+ add(microstepLocation, "microstep.event", expectedMicrostep.event, eventTag(get(actualMicrostep, "event")));
994
+ add(microstepLocation, "microstep.transitions", expectedMicrostep.transitions, array(get(actualMicrostep, "transitions")).map(projectTransition));
995
+ add(microstepLocation, "microstep.exitPaths", expectedMicrostep.exitPaths, get(actualMicrostep, "exitPaths"));
996
+ add(microstepLocation, "microstep.entryPaths", expectedMicrostep.entryPaths, get(actualMicrostep, "entryPaths"));
997
+ add(microstepLocation, "microstep.changed", expectedMicrostep.changed, get(actualMicrostep, "changed"));
998
+ }
999
+ compareState(location, "step.plan.next", expected.after, get(actualPlan, "next"));
1000
+ compareState(location, "step.after", expected.after, get(actual, "after"));
1001
+ add(location, "step.afterConfiguration", expected.after.activePaths, get(actual, "afterConfiguration"));
1002
+ add(location, "step.plan.done", expected.done, get(actualPlan, "done"));
1003
+ add(location, "step.plan.output", expected.output, get(actualPlan, "output"));
1004
+ }
1005
+ const finalLocation = { phase: "final" };
1006
+ compareState(finalLocation, "trace.final", reference.final, get(actualTrace, "final"));
1007
+ add(finalLocation, "trace.finalConfiguration", reference.final.activePaths, get(actualTrace, "finalConfiguration"));
1008
+ return mismatches.length === 0
1009
+ ? Effect.void
1010
+ : Effect.fail(new ModelVerificationError({ mismatches }));
1011
+ };
1012
+ //# sourceMappingURL=referenceModel.js.map