@mmapp/react-compiler 0.1.0-alpha.16 → 0.1.0-alpha.18

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 (186) hide show
  1. package/dist/{chunk-UBDNXVL2.mjs → chunk-6LAII7OP.mjs} +82 -4
  2. package/dist/{chunk-GVCJJX7E.mjs → chunk-FPAMQXKB.mjs} +14 -18
  3. package/dist/{chunk-NUPJYPFU.mjs → chunk-GMW45YVD.mjs} +68 -1
  4. package/dist/cli/index.js +201 -72
  5. package/dist/cli/index.mjs +55 -8
  6. package/dist/dev-server.js +42 -65
  7. package/dist/dev-server.mjs +1 -1
  8. package/dist/index.js +42 -65
  9. package/dist/index.mjs +1 -1
  10. package/dist/{init-2CRSUGV5.mjs → init-CJCDWI33.mjs} +119 -0
  11. package/package.json +2 -2
  12. package/dist/chunk-26U577GB.mjs +0 -3465
  13. package/dist/chunk-2FBDFAX6.mjs +0 -2362
  14. package/dist/chunk-2L4QSMXG.mjs +0 -175
  15. package/dist/chunk-2REDFOER.mjs +0 -931
  16. package/dist/chunk-3USIFFE4.mjs +0 -2190
  17. package/dist/chunk-45YMGEVT.mjs +0 -186
  18. package/dist/chunk-46YKSHQR.mjs +0 -175
  19. package/dist/chunk-4FN2AISW.mjs +0 -148
  20. package/dist/chunk-4OPI5L7G.mjs +0 -2593
  21. package/dist/chunk-4RYTKOOJ.mjs +0 -186
  22. package/dist/chunk-4XHK6FWL.mjs +0 -2058
  23. package/dist/chunk-52XHYD2V.mjs +0 -214
  24. package/dist/chunk-5FTDWKHH.mjs +0 -244
  25. package/dist/chunk-5GUFFFGL.mjs +0 -148
  26. package/dist/chunk-5RKTOVR5.mjs +0 -244
  27. package/dist/chunk-5VNJ7C6N.mjs +0 -154
  28. package/dist/chunk-5YDMOO4X.mjs +0 -214
  29. package/dist/chunk-64ZWEMLJ.mjs +0 -148
  30. package/dist/chunk-6CQOAAMV.mjs +0 -1803
  31. package/dist/chunk-6SEVAAVT.mjs +0 -3516
  32. package/dist/chunk-6XP4KSWQ.mjs +0 -2190
  33. package/dist/chunk-6YLR5ZDA.mjs +0 -2829
  34. package/dist/chunk-72QWL54I.mjs +0 -175
  35. package/dist/chunk-7B4TRI7C.mjs +0 -4835
  36. package/dist/chunk-7JRAEFRB.mjs +0 -7510
  37. package/dist/chunk-7ZKGHTNB.mjs +0 -4952
  38. package/dist/chunk-AOGY2GK6.mjs +0 -3292
  39. package/dist/chunk-AXXUXRNA.mjs +0 -1434
  40. package/dist/chunk-CHLVKMQW.mjs +0 -175
  41. package/dist/chunk-CKGOZAB7.mjs +0 -939
  42. package/dist/chunk-D34RAZUX.mjs +0 -2223
  43. package/dist/chunk-DE3ZGQAC.mjs +0 -148
  44. package/dist/chunk-DMCY3BBG.mjs +0 -1933
  45. package/dist/chunk-DPIK3PJS.mjs +0 -244
  46. package/dist/chunk-E5IVH4RE.mjs +0 -186
  47. package/dist/chunk-E6FZNUR5.mjs +0 -4953
  48. package/dist/chunk-EJRBDQDP.mjs +0 -2607
  49. package/dist/chunk-ELO4TXJL.mjs +0 -186
  50. package/dist/chunk-EO6SYNCG.mjs +0 -175
  51. package/dist/chunk-EQGA6A6D.mjs +0 -121
  52. package/dist/chunk-EY2CSXYA.mjs +0 -822
  53. package/dist/chunk-FIQ65CDR.mjs +0 -925
  54. package/dist/chunk-FKRO52XH.mjs +0 -3446
  55. package/dist/chunk-FL4YAKU6.mjs +0 -4941
  56. package/dist/chunk-FOZXJFAR.mjs +0 -186
  57. package/dist/chunk-FX6URXWN.mjs +0 -186
  58. package/dist/chunk-FYT47UBU.mjs +0 -5076
  59. package/dist/chunk-G7SMOWOL.mjs +0 -828
  60. package/dist/chunk-GCLGPOJZ.mjs +0 -148
  61. package/dist/chunk-GGB4G5YY.mjs +0 -175
  62. package/dist/chunk-GXB4JOP7.mjs +0 -5072
  63. package/dist/chunk-HFXOUMTD.mjs +0 -175
  64. package/dist/chunk-HLRGCCIL.mjs +0 -4839
  65. package/dist/chunk-HOIUP6IF.mjs +0 -690
  66. package/dist/chunk-HRYR54PT.mjs +0 -175
  67. package/dist/chunk-HWIZ47US.mjs +0 -214
  68. package/dist/chunk-I3AU7GRD.mjs +0 -120
  69. package/dist/chunk-IB7MNPQL.mjs +0 -4953
  70. package/dist/chunk-ICSIHQCG.mjs +0 -148
  71. package/dist/chunk-ILFGMUVD.mjs +0 -1933
  72. package/dist/chunk-IPTX5MJU.mjs +0 -3223
  73. package/dist/chunk-ITGUSH2Z.mjs +0 -2783
  74. package/dist/chunk-IXHBCAMF.mjs +0 -3306
  75. package/dist/chunk-J7JUAHS4.mjs +0 -186
  76. package/dist/chunk-J7TWJ3TM.mjs +0 -2784
  77. package/dist/chunk-JDPLDGVF.mjs +0 -4810
  78. package/dist/chunk-JLA5VNQ3.mjs +0 -186
  79. package/dist/chunk-JQLWFCTM.mjs +0 -214
  80. package/dist/chunk-K53XP2DL.mjs +0 -148
  81. package/dist/chunk-K5HX2SVL.mjs +0 -1902
  82. package/dist/chunk-KFGYOOVS.mjs +0 -214
  83. package/dist/chunk-KFJJCQAL.mjs +0 -148
  84. package/dist/chunk-KFVVOS5N.mjs +0 -925
  85. package/dist/chunk-KJUIIEQE.mjs +0 -186
  86. package/dist/chunk-KNWTHRVQ.mjs +0 -175
  87. package/dist/chunk-KSG4XSZF.mjs +0 -175
  88. package/dist/chunk-L2OZ4CDV.mjs +0 -113
  89. package/dist/chunk-LF5N6DOU.mjs +0 -175
  90. package/dist/chunk-LJQCM2IM.mjs +0 -214
  91. package/dist/chunk-MIZV3TAN.mjs +0 -3293
  92. package/dist/chunk-NKKLQE5V.mjs +0 -148
  93. package/dist/chunk-NOW23XFZ.mjs +0 -186
  94. package/dist/chunk-NRXQKQ74.mjs +0 -148
  95. package/dist/chunk-NTB7OEX2.mjs +0 -2918
  96. package/dist/chunk-NW6555WJ.mjs +0 -186
  97. package/dist/chunk-O4AUS7EU.mjs +0 -148
  98. package/dist/chunk-OMZE6VLQ.mjs +0 -214
  99. package/dist/chunk-OPJKP747.mjs +0 -7506
  100. package/dist/chunk-OWI6XWCD.mjs +0 -3375
  101. package/dist/chunk-P4BR7WVO.mjs +0 -2190
  102. package/dist/chunk-PRUMNNDI.mjs +0 -3192
  103. package/dist/chunk-QQHVYH2X.mjs +0 -244
  104. package/dist/chunk-QTBD5B3F.mjs +0 -148
  105. package/dist/chunk-R57T26RR.mjs +0 -734
  106. package/dist/chunk-S5QLWLLT.mjs +0 -186
  107. package/dist/chunk-SCWGT2FY.mjs +0 -2190
  108. package/dist/chunk-SKSDPPNT.mjs +0 -3788
  109. package/dist/chunk-SMKJUSB3.mjs +0 -2190
  110. package/dist/chunk-SP2YUS33.mjs +0 -186
  111. package/dist/chunk-SU4E6E7B.mjs +0 -3153
  112. package/dist/chunk-SYUUKW5A.mjs +0 -3379
  113. package/dist/chunk-THFYE5ZX.mjs +0 -244
  114. package/dist/chunk-UDDTWG5J.mjs +0 -734
  115. package/dist/chunk-UL2XZEMA.mjs +0 -3128
  116. package/dist/chunk-VCAY2KGM.mjs +0 -175
  117. package/dist/chunk-VLTKQDJ3.mjs +0 -244
  118. package/dist/chunk-WBYMW4NQ.mjs +0 -3450
  119. package/dist/chunk-WECAV6QB.mjs +0 -148
  120. package/dist/chunk-WMKBXUCE.mjs +0 -3228
  121. package/dist/chunk-XAJ5BKKL.mjs +0 -4947
  122. package/dist/chunk-XG2X7AEA.mjs +0 -175
  123. package/dist/chunk-XG7Z23NQ.mjs +0 -148
  124. package/dist/chunk-XMWUHQVV.mjs +0 -939
  125. package/dist/chunk-XWZAOCQ7.mjs +0 -2607
  126. package/dist/chunk-XZNEDRGN.mjs +0 -3876
  127. package/dist/chunk-Y6FXYEAI.mjs +0 -10
  128. package/dist/chunk-Y6MA7ULW.mjs +0 -148
  129. package/dist/chunk-YFS6JMYO.mjs +0 -3342
  130. package/dist/chunk-YMS7Q7LG.mjs +0 -214
  131. package/dist/chunk-Z2G5RZ4H.mjs +0 -186
  132. package/dist/chunk-Z6AIQ4KL.mjs +0 -113
  133. package/dist/chunk-ZA37XTGA.mjs +0 -175
  134. package/dist/chunk-ZE3KCHBM.mjs +0 -2918
  135. package/dist/config-PL24KEWL.mjs +0 -219
  136. package/dist/dev-server-Bs_sz2DG.d.mts +0 -111
  137. package/dist/dev-server-Bs_sz2DG.d.ts +0 -111
  138. package/dist/dev-server-CjoufJ-u.d.mts +0 -109
  139. package/dist/dev-server-CjoufJ-u.d.ts +0 -109
  140. package/dist/dev-server-RmGHIntF.d.mts +0 -113
  141. package/dist/dev-server-RmGHIntF.d.ts +0 -113
  142. package/dist/engine-binary-QQUDACBJ.mjs +0 -455
  143. package/dist/envelope-DD7v0v6E.d.mts +0 -265
  144. package/dist/envelope-DD7v0v6E.d.ts +0 -265
  145. package/dist/envelope-vCVjrHlo.d.mts +0 -265
  146. package/dist/envelope-vCVjrHlo.d.ts +0 -265
  147. package/dist/index-B5gSgvnd.d.mts +0 -44
  148. package/dist/index-B5gSgvnd.d.ts +0 -44
  149. package/dist/index-Bs0MnR54.d.mts +0 -103
  150. package/dist/index-Bs0MnR54.d.ts +0 -103
  151. package/dist/index-DR0nNc_f.d.mts +0 -101
  152. package/dist/index-DR0nNc_f.d.ts +0 -101
  153. package/dist/index-revho_gS.d.mts +0 -104
  154. package/dist/index-revho_gS.d.ts +0 -104
  155. package/dist/init-7FJENUDK.mjs +0 -407
  156. package/dist/init-7JQMAAXS.mjs +0 -363
  157. package/dist/init-DQDX3QK6.mjs +0 -369
  158. package/dist/init-EHO4VQ22.mjs +0 -369
  159. package/dist/init-IXEE2RCF.mjs +0 -340
  160. package/dist/init-UC3FWPIW.mjs +0 -367
  161. package/dist/init-UNSMVKIK.mjs +0 -366
  162. package/dist/init-UNV5XIDE.mjs +0 -367
  163. package/dist/project-compiler-2P4N4DR7.mjs +0 -10
  164. package/dist/project-compiler-D2LCC27O.mjs +0 -10
  165. package/dist/project-compiler-EGJUTAJU.mjs +0 -10
  166. package/dist/project-compiler-EJ3GANJE.mjs +0 -10
  167. package/dist/project-compiler-LOQKVRZJ.mjs +0 -10
  168. package/dist/project-compiler-NNK32MPG.mjs +0 -10
  169. package/dist/project-compiler-OP2VVGJQ.mjs +0 -10
  170. package/dist/project-compiler-RQ6OQKRM.mjs +0 -10
  171. package/dist/project-compiler-VFR6CSDX.mjs +0 -10
  172. package/dist/project-compiler-VWNNCHGO.mjs +0 -10
  173. package/dist/project-compiler-XVAAU4C5.mjs +0 -10
  174. package/dist/project-compiler-YES5FGMD.mjs +0 -10
  175. package/dist/project-compiler-ZKMQDLGU.mjs +0 -10
  176. package/dist/project-decompiler-5GY2KSG4.mjs +0 -7
  177. package/dist/project-decompiler-FLXCEJHS.mjs +0 -7
  178. package/dist/project-decompiler-US7GAVIC.mjs +0 -7
  179. package/dist/project-decompiler-VLPR22QF.mjs +0 -7
  180. package/dist/pull-A2QUHW4K.mjs +0 -109
  181. package/dist/pull-FUS5QYZS.mjs +0 -109
  182. package/dist/pull-JBEQWVPE.mjs +0 -109
  183. package/dist/pull-LD5ENLGY.mjs +0 -109
  184. package/dist/pull-P44LDRWB.mjs +0 -109
  185. package/dist/verify-BYHUKARQ.mjs +0 -1833
  186. package/dist/verify-SEIXUGN4.mjs +0 -1833
@@ -1,1833 +0,0 @@
1
- import {
2
- babelPlugin
3
- } from "./chunk-OPJKP747.mjs";
4
- import "./chunk-CIESM3BP.mjs";
5
-
6
- // src/cli/verify.ts
7
- import { readFileSync, existsSync, statSync } from "fs";
8
- import { resolve, basename, extname } from "path";
9
- import { glob } from "glob";
10
- import { transformSync } from "@babel/core";
11
-
12
- // src/verify/structural.ts
13
- function issue(severity, code, message, location) {
14
- return { severity, code, message, location };
15
- }
16
- function collectSetFieldRefs(actions) {
17
- const refs = [];
18
- for (const action of actions) {
19
- if (action.type === "set_field" && typeof action.config?.field === "string") {
20
- refs.push(action.config.field);
21
- }
22
- const compensate = action.compensate;
23
- if (compensate) {
24
- refs.push(...collectSetFieldRefs([compensate]));
25
- }
26
- }
27
- return refs;
28
- }
29
- function validateStructure(def) {
30
- const errors = [];
31
- const warnings = [];
32
- const stateNames = new Set(def.states.map((s) => s.name));
33
- const fieldNames = new Set(def.fields.map((f) => f.name));
34
- const startStates = def.states.filter((s) => s.type === "START");
35
- if (startStates.length === 0) {
36
- errors.push(
37
- issue("error", "NO_START_STATE", "Workflow has no START state.")
38
- );
39
- } else if (startStates.length > 1) {
40
- errors.push(
41
- issue(
42
- "error",
43
- "MULTIPLE_START_STATES",
44
- `Workflow has ${startStates.length} START states: ${startStates.map((s) => s.name).join(", ")}. Exactly one is required.`
45
- )
46
- );
47
- }
48
- const endStates = def.states.filter((s) => s.type === "END");
49
- if (endStates.length === 0) {
50
- warnings.push(
51
- issue(
52
- "warning",
53
- "NO_END_STATE",
54
- "Workflow has no END state. This may be intentional for infinite-loop workflows."
55
- )
56
- );
57
- }
58
- for (const t of def.transitions) {
59
- for (const from of t.from) {
60
- if (!stateNames.has(from)) {
61
- errors.push(
62
- issue(
63
- "error",
64
- "TRANSITION_FROM_UNKNOWN_STATE",
65
- `Transition "${t.name}" references unknown source state "${from}".`,
66
- `transition:${t.name}`
67
- )
68
- );
69
- }
70
- }
71
- if (!stateNames.has(t.to)) {
72
- errors.push(
73
- issue(
74
- "error",
75
- "TRANSITION_TO_UNKNOWN_STATE",
76
- `Transition "${t.name}" targets unknown state "${t.to}".`,
77
- `transition:${t.name}`
78
- )
79
- );
80
- }
81
- }
82
- checkDuplicateTransitions(def.transitions, warnings);
83
- for (const t of def.transitions) {
84
- const refs = collectSetFieldRefs(t.actions);
85
- for (const ref of refs) {
86
- if (!fieldNames.has(ref)) {
87
- warnings.push(
88
- issue(
89
- "warning",
90
- "SET_FIELD_UNKNOWN",
91
- `Transition "${t.name}" sets unknown field "${ref}".`,
92
- `transition:${t.name}`
93
- )
94
- );
95
- }
96
- }
97
- }
98
- for (const state of def.states) {
99
- const enterRefs = collectSetFieldRefs(state.on_enter);
100
- const exitRefs = collectSetFieldRefs(state.on_exit);
101
- for (const ref of [...enterRefs, ...exitRefs]) {
102
- if (!fieldNames.has(ref)) {
103
- warnings.push(
104
- issue(
105
- "warning",
106
- "SET_FIELD_UNKNOWN",
107
- `State "${state.name}" action sets unknown field "${ref}".`,
108
- `state:${state.name}`
109
- )
110
- );
111
- }
112
- }
113
- }
114
- for (const t of def.transitions) {
115
- if (t.conditions) {
116
- for (const cond of t.conditions) {
117
- if (cond.expression !== void 0 && cond.expression.trim() === "") {
118
- warnings.push(
119
- issue(
120
- "warning",
121
- "EMPTY_CONDITION_EXPRESSION",
122
- `Transition "${t.name}" has an empty condition expression.`,
123
- `transition:${t.name}`
124
- )
125
- );
126
- }
127
- }
128
- }
129
- for (const action of t.actions) {
130
- if (action.condition !== void 0 && action.condition.trim() === "") {
131
- warnings.push(
132
- issue(
133
- "warning",
134
- "EMPTY_ACTION_CONDITION",
135
- `Transition "${t.name}" has an action "${action.id}" with an empty condition.`,
136
- `transition:${t.name}`
137
- )
138
- );
139
- }
140
- }
141
- }
142
- for (const t of def.transitions) {
143
- if (t.required_fields) {
144
- for (const rf of t.required_fields) {
145
- if (!fieldNames.has(rf)) {
146
- warnings.push(
147
- issue(
148
- "warning",
149
- "REQUIRED_FIELD_UNKNOWN",
150
- `Transition "${t.name}" requires unknown field "${rf}".`,
151
- `transition:${t.name}`
152
- )
153
- );
154
- }
155
- }
156
- }
157
- }
158
- return {
159
- valid: errors.length === 0,
160
- errors,
161
- warnings
162
- };
163
- }
164
- function checkDuplicateTransitions(transitions, warnings) {
165
- const bySource = /* @__PURE__ */ new Map();
166
- for (const t of transitions) {
167
- for (const from of t.from) {
168
- let inner = bySource.get(from);
169
- if (!inner) {
170
- inner = /* @__PURE__ */ new Map();
171
- bySource.set(from, inner);
172
- }
173
- inner.set(t.name, (inner.get(t.name) ?? 0) + 1);
174
- }
175
- }
176
- for (const [state, nameMap] of bySource) {
177
- for (const [name, count] of nameMap) {
178
- if (count > 1) {
179
- warnings.push(
180
- issue(
181
- "warning",
182
- "DUPLICATE_TRANSITION_NAME",
183
- `State "${state}" has ${count} transitions named "${name}".`,
184
- `state:${state}`
185
- )
186
- );
187
- }
188
- }
189
- }
190
- }
191
-
192
- // src/verify/reachability.ts
193
- function buildForwardGraph(def) {
194
- const adj = /* @__PURE__ */ new Map();
195
- for (const s of def.states) {
196
- adj.set(s.name, /* @__PURE__ */ new Set());
197
- }
198
- for (const t of def.transitions) {
199
- for (const from of t.from) {
200
- const neighbors = adj.get(from);
201
- if (neighbors) {
202
- neighbors.add(t.to);
203
- }
204
- }
205
- }
206
- return adj;
207
- }
208
- function buildReverseGraph(def) {
209
- const adj = /* @__PURE__ */ new Map();
210
- for (const s of def.states) {
211
- adj.set(s.name, /* @__PURE__ */ new Set());
212
- }
213
- for (const t of def.transitions) {
214
- const neighbors = adj.get(t.to);
215
- if (neighbors) {
216
- for (const from of t.from) {
217
- neighbors.add(from);
218
- }
219
- }
220
- }
221
- return adj;
222
- }
223
- function bfs(adj, starts) {
224
- const visited = /* @__PURE__ */ new Set();
225
- const queue = [...starts];
226
- for (const s of queue) {
227
- if (visited.has(s)) continue;
228
- visited.add(s);
229
- const neighbors = adj.get(s);
230
- if (neighbors) {
231
- for (const n of neighbors) {
232
- if (!visited.has(n)) {
233
- queue.push(n);
234
- }
235
- }
236
- }
237
- }
238
- return visited;
239
- }
240
- function tarjanSCC(adj) {
241
- let index = 0;
242
- const stack = [];
243
- const onStack = /* @__PURE__ */ new Set();
244
- const indices = /* @__PURE__ */ new Map();
245
- const lowlinks = /* @__PURE__ */ new Map();
246
- const sccs = [];
247
- function strongConnect(v) {
248
- indices.set(v, index);
249
- lowlinks.set(v, index);
250
- index++;
251
- stack.push(v);
252
- onStack.add(v);
253
- const neighbors = adj.get(v);
254
- if (neighbors) {
255
- for (const w of neighbors) {
256
- if (!indices.has(w)) {
257
- strongConnect(w);
258
- lowlinks.set(v, Math.min(lowlinks.get(v), lowlinks.get(w)));
259
- } else if (onStack.has(w)) {
260
- lowlinks.set(v, Math.min(lowlinks.get(v), indices.get(w)));
261
- }
262
- }
263
- }
264
- if (lowlinks.get(v) === indices.get(v)) {
265
- const scc = [];
266
- let w;
267
- do {
268
- w = stack.pop();
269
- onStack.delete(w);
270
- scc.push(w);
271
- } while (w !== v);
272
- sccs.push(scc);
273
- }
274
- }
275
- for (const node of adj.keys()) {
276
- if (!indices.has(node)) {
277
- strongConnect(node);
278
- }
279
- }
280
- return sccs;
281
- }
282
- function longestPathBFS(adj, start) {
283
- const dist = /* @__PURE__ */ new Map();
284
- const parent = /* @__PURE__ */ new Map();
285
- const maxSteps = adj.size;
286
- dist.set(start, 0);
287
- parent.set(start, null);
288
- let changed = true;
289
- let iterations = 0;
290
- while (changed && iterations < maxSteps) {
291
- changed = false;
292
- iterations++;
293
- for (const [u, neighbors] of adj) {
294
- const du = dist.get(u);
295
- if (du === void 0) continue;
296
- for (const v of neighbors) {
297
- const dv = dist.get(v);
298
- if (dv === void 0 || du + 1 > dv) {
299
- dist.set(v, du + 1);
300
- parent.set(v, u);
301
- changed = true;
302
- }
303
- }
304
- }
305
- }
306
- let farthest = start;
307
- let maxDist = 0;
308
- for (const [node, d] of dist) {
309
- if (d > maxDist) {
310
- maxDist = d;
311
- farthest = node;
312
- }
313
- }
314
- const path = [];
315
- let current = farthest;
316
- const visited = /* @__PURE__ */ new Set();
317
- while (current != null && !visited.has(current)) {
318
- visited.add(current);
319
- path.unshift(current);
320
- current = parent.get(current) ?? null;
321
- }
322
- return { distance: maxDist, path };
323
- }
324
- function analyzeReachability(def) {
325
- const allStateNames = def.states.map((s) => s.name);
326
- const allStatesSet = new Set(allStateNames);
327
- const forward = buildForwardGraph(def);
328
- const reverse = buildReverseGraph(def);
329
- const startStates = def.states.filter((s) => s.type === "START").map((s) => s.name);
330
- const endStates = def.states.filter((s) => s.type === "END").map((s) => s.name);
331
- const reachableSet = bfs(forward, startStates);
332
- const reachable = allStateNames.filter((s) => reachableSet.has(s));
333
- const unreachable = allStateNames.filter((s) => !reachableSet.has(s));
334
- const canTerminateSet = bfs(reverse, endStates);
335
- const canTerminate = allStateNames.filter((s) => canTerminateSet.has(s));
336
- const stuck = reachable.filter((s) => !canTerminateSet.has(s));
337
- const endTypeSet = new Set(endStates);
338
- const deadlocks = reachable.filter((s) => {
339
- const neighbors = forward.get(s);
340
- const hasOutgoing = neighbors != null && neighbors.size > 0;
341
- return !hasOutgoing && !endTypeSet.has(s);
342
- });
343
- const selfLoopSet = /* @__PURE__ */ new Set();
344
- for (const t of def.transitions) {
345
- for (const from of t.from) {
346
- if (from === t.to && allStatesSet.has(from)) {
347
- selfLoopSet.add(from);
348
- }
349
- }
350
- }
351
- const selfLoops = [...selfLoopSet];
352
- const sccs = tarjanSCC(forward);
353
- const cycles = sccs.filter((scc) => scc.length > 1);
354
- const startState = startStates[0];
355
- let longestPath = 0;
356
- let criticalPath = [];
357
- if (startState) {
358
- const result = longestPathBFS(forward, startState);
359
- longestPath = result.distance;
360
- criticalPath = result.path;
361
- }
362
- return {
363
- reachable,
364
- unreachable,
365
- canTerminate,
366
- stuck,
367
- deadlocks,
368
- selfLoops,
369
- cycles,
370
- longestPath,
371
- criticalPath
372
- };
373
- }
374
-
375
- // src/verify/safety.ts
376
- function flattenConditions(conds) {
377
- const leaves = [];
378
- for (const c of conds) {
379
- if (c.AND && c.AND.length > 0) {
380
- leaves.push(...flattenConditions(c.AND));
381
- } else if (c.OR && c.OR.length > 0) {
382
- leaves.push(...flattenConditions(c.OR));
383
- } else {
384
- leaves.push(c);
385
- }
386
- }
387
- return leaves;
388
- }
389
- function conditionFields(conds) {
390
- const fields = /* @__PURE__ */ new Set();
391
- for (const leaf of flattenConditions(conds)) {
392
- if (leaf.field) {
393
- fields.add(leaf.field);
394
- }
395
- }
396
- return fields;
397
- }
398
- function conditionFieldOps(conds) {
399
- const pairs = [];
400
- for (const leaf of flattenConditions(conds)) {
401
- if (leaf.field && leaf.operator) {
402
- pairs.push({ field: leaf.field, operator: leaf.operator });
403
- }
404
- }
405
- return pairs;
406
- }
407
- function isUnguarded(t) {
408
- return !t.conditions || t.conditions.length === 0;
409
- }
410
- function checkGuardOverlap(a, b) {
411
- const aUnguarded = isUnguarded(a);
412
- const bUnguarded = isUnguarded(b);
413
- if (aUnguarded && bUnguarded) {
414
- return "Both transitions have no conditions \u2014 always overlap.";
415
- }
416
- if (aUnguarded) {
417
- return `Transition "${a.name}" has no conditions and will always match alongside "${b.name}".`;
418
- }
419
- if (bUnguarded) {
420
- return `Transition "${b.name}" has no conditions and will always match alongside "${a.name}".`;
421
- }
422
- const aOps = conditionFieldOps(a.conditions);
423
- const bOps = conditionFieldOps(b.conditions);
424
- const compatiblePairs = /* @__PURE__ */ new Set([
425
- "eq:eq",
426
- "gte:lte",
427
- "lte:gte",
428
- "gte:gte",
429
- "lte:lte",
430
- "gt:lt",
431
- "lt:gt",
432
- "gt:gt",
433
- "lt:lt",
434
- "in:in",
435
- "in:eq",
436
- "eq:in",
437
- "contains:contains",
438
- "contains:eq",
439
- "eq:contains"
440
- ]);
441
- for (const aOp of aOps) {
442
- for (const bOp of bOps) {
443
- if (aOp.field === bOp.field) {
444
- const pair = `${aOp.operator}:${bOp.operator}`;
445
- if (compatiblePairs.has(pair)) {
446
- return `Both check field "${aOp.field}" with potentially overlapping operators (${aOp.operator}, ${bOp.operator}).`;
447
- }
448
- }
449
- }
450
- }
451
- const aFields = conditionFields(a.conditions);
452
- const bFields = conditionFields(b.conditions);
453
- const shared = [...aFields].filter((f) => bFields.has(f));
454
- if (shared.length > 0 && aOps.length === 0 && bOps.length === 0) {
455
- return `Both reference fields [${shared.join(", ")}] via expressions \u2014 manual review needed.`;
456
- }
457
- return null;
458
- }
459
- function computeReachableStates(def) {
460
- const adj = /* @__PURE__ */ new Map();
461
- for (const s of def.states) {
462
- adj.set(s.name, /* @__PURE__ */ new Set());
463
- }
464
- for (const t of def.transitions) {
465
- for (const from of t.from) {
466
- const neighbors = adj.get(from);
467
- if (neighbors) {
468
- neighbors.add(t.to);
469
- }
470
- }
471
- }
472
- const startStates = def.states.filter((s) => s.type === "START").map((s) => s.name);
473
- const visited = /* @__PURE__ */ new Set();
474
- const queue = [...startStates];
475
- for (const s of queue) {
476
- if (visited.has(s)) continue;
477
- visited.add(s);
478
- const neighbors = adj.get(s);
479
- if (neighbors) {
480
- for (const n of neighbors) {
481
- if (!visited.has(n)) {
482
- queue.push(n);
483
- }
484
- }
485
- }
486
- }
487
- return visited;
488
- }
489
- function analyzeSafety(def) {
490
- const guardOverlaps = [];
491
- const deadTransitions = [];
492
- const roleGaps = [];
493
- const fieldGaps = [];
494
- const stateNames = new Set(def.states.map((s) => s.name));
495
- const fieldNames = new Set(def.fields.map((f) => f.name));
496
- const roleNames = new Set(def.roles.map((r) => r.name));
497
- const reachable = computeReachableStates(def);
498
- const transitionsBySource = /* @__PURE__ */ new Map();
499
- for (const t of def.transitions) {
500
- for (const from of t.from) {
501
- let group = transitionsBySource.get(from);
502
- if (!group) {
503
- group = [];
504
- transitionsBySource.set(from, group);
505
- }
506
- group.push(t);
507
- }
508
- }
509
- for (const [state, transitions] of transitionsBySource) {
510
- for (let i = 0; i < transitions.length; i++) {
511
- for (let j = i + 1; j < transitions.length; j++) {
512
- const a = transitions[i];
513
- const b = transitions[j];
514
- const aIsAuto = a.auto === true;
515
- const bIsAuto = b.auto === true;
516
- const aNoRoles = !a.roles || a.roles.length === 0;
517
- const bNoRoles = !b.roles || b.roles.length === 0;
518
- const bothAuto = aIsAuto && bIsAuto;
519
- const bothManualNoRoles = !aIsAuto && !bIsAuto && aNoRoles && bNoRoles;
520
- if (bothAuto || bothManualNoRoles) {
521
- const reason = checkGuardOverlap(a, b);
522
- if (reason) {
523
- guardOverlaps.push({
524
- state,
525
- transitions: [a.name, b.name],
526
- reason
527
- });
528
- }
529
- }
530
- }
531
- }
532
- }
533
- for (const t of def.transitions) {
534
- const allFromUnreachable = t.from.every((from) => {
535
- return stateNames.has(from) && !reachable.has(from);
536
- });
537
- if (allFromUnreachable && t.from.length > 0) {
538
- deadTransitions.push({
539
- transition: t.name,
540
- reason: `All source states [${t.from.join(", ")}] are unreachable.`
541
- });
542
- }
543
- const missingFrom = t.from.filter((f) => !stateNames.has(f));
544
- if (missingFrom.length === t.from.length && t.from.length > 0) {
545
- deadTransitions.push({
546
- transition: t.name,
547
- reason: `All source states [${missingFrom.join(", ")}] do not exist.`
548
- });
549
- }
550
- }
551
- for (const t of def.transitions) {
552
- if (t.roles && t.roles.length > 0) {
553
- const unknownRoles = t.roles.filter((r) => !roleNames.has(r));
554
- if (unknownRoles.length > 0) {
555
- roleGaps.push({
556
- transition: t.name,
557
- message: `References undefined roles: [${unknownRoles.join(", ")}]. Defined roles: [${[...roleNames].join(", ") || "none"}].`
558
- });
559
- }
560
- }
561
- }
562
- for (const t of def.transitions) {
563
- if (t.required_fields) {
564
- for (const field of t.required_fields) {
565
- if (!fieldNames.has(field)) {
566
- fieldGaps.push({
567
- transition: t.name,
568
- field,
569
- message: `Required field "${field}" is not defined in the workflow fields.`
570
- });
571
- }
572
- }
573
- }
574
- if (t.conditions) {
575
- const condFields = conditionFields(t.conditions);
576
- for (const field of condFields) {
577
- if (!fieldNames.has(field)) {
578
- fieldGaps.push({
579
- transition: t.name,
580
- field,
581
- message: `Condition references undefined field "${field}".`
582
- });
583
- }
584
- }
585
- }
586
- }
587
- const deterministic = guardOverlaps.length === 0;
588
- return {
589
- guardOverlaps,
590
- deadTransitions,
591
- roleGaps,
592
- fieldGaps,
593
- deterministic
594
- };
595
- }
596
-
597
- // src/verify/liveness.ts
598
- function analyzeLiveness(def) {
599
- const states = def.states || [];
600
- const transitions = def.transitions || [];
601
- const stateNames = new Set(states.map((s) => s.name));
602
- const startState = states.find((s) => s.type === "START");
603
- const endStates = new Set(states.filter((s) => s.type === "END").map((s) => s.name));
604
- if (!startState || endStates.size === 0) {
605
- return {
606
- terminates: endStates.size === 0 ? "never" : "unknown",
607
- nonTerminatingCycles: [],
608
- progressViolations: [],
609
- fairnessAssumptions: []
610
- };
611
- }
612
- const adj = /* @__PURE__ */ new Map();
613
- for (const s of states) adj.set(s.name, []);
614
- for (const t of transitions) {
615
- const froms = Array.isArray(t.from) ? t.from : [t.from];
616
- for (const f of froms) {
617
- if (adj.has(f)) {
618
- adj.get(f).push({ to: t.to, transition: t.name, auto: t.auto === true });
619
- }
620
- }
621
- }
622
- const reachable = bfs2(startState.name, adj);
623
- const reverseAdj = /* @__PURE__ */ new Map();
624
- for (const s of states) reverseAdj.set(s.name, []);
625
- for (const t of transitions) {
626
- const froms = Array.isArray(t.from) ? t.from : [t.from];
627
- for (const f of froms) {
628
- if (!reverseAdj.has(t.to)) reverseAdj.set(t.to, []);
629
- reverseAdj.get(t.to).push(f);
630
- }
631
- }
632
- const canReachEnd = /* @__PURE__ */ new Set();
633
- for (const end of endStates) {
634
- for (const s of bfs2(end, reverseAdj)) {
635
- canReachEnd.add(s);
636
- }
637
- }
638
- const sccs = tarjanSCC2(stateNames, adj);
639
- const nonTerminatingCycles = [];
640
- for (const scc of sccs) {
641
- if (scc.length <= 1) continue;
642
- const hasSelfLoop = scc.length === 1 && adj.get(scc[0])?.some((e) => e.to === scc[0]);
643
- if (scc.length === 1 && !hasSelfLoop) continue;
644
- const canExit = scc.some((s) => canReachEnd.has(s));
645
- if (!canExit) {
646
- nonTerminatingCycles.push(scc);
647
- }
648
- }
649
- const progressViolations = [];
650
- for (const s of reachable) {
651
- if (endStates.has(s)) continue;
652
- const edges = adj.get(s) || [];
653
- if (edges.length === 0) {
654
- progressViolations.push(s);
655
- }
656
- }
657
- const fairnessAssumptions = [];
658
- for (const t of transitions) {
659
- if (!t.auto) {
660
- fairnessAssumptions.push(`Transition '${t.name}' requires external trigger (userAction or manual)`);
661
- }
662
- }
663
- let terminates;
664
- if (nonTerminatingCycles.length > 0) {
665
- terminates = "never";
666
- } else if (progressViolations.length > 0) {
667
- terminates = "never";
668
- } else if (fairnessAssumptions.length > 0) {
669
- terminates = "conditional";
670
- } else {
671
- const allReachableCanTerminate = [...reachable].every((s) => canReachEnd.has(s) || endStates.has(s));
672
- terminates = allReachableCanTerminate ? "always" : "conditional";
673
- }
674
- return {
675
- terminates,
676
- nonTerminatingCycles,
677
- progressViolations,
678
- fairnessAssumptions
679
- };
680
- }
681
- function bfs2(start, adj) {
682
- const visited = /* @__PURE__ */ new Set();
683
- const queue = [start];
684
- visited.add(start);
685
- while (queue.length > 0) {
686
- const current = queue.shift();
687
- const neighbors = adj.get(current) || [];
688
- for (const n of neighbors) {
689
- const target = typeof n === "string" ? n : n.to;
690
- if (!visited.has(target)) {
691
- visited.add(target);
692
- queue.push(target);
693
- }
694
- }
695
- }
696
- return visited;
697
- }
698
- function tarjanSCC2(nodes, adj) {
699
- let index = 0;
700
- const stack = [];
701
- const onStack = /* @__PURE__ */ new Set();
702
- const indices = /* @__PURE__ */ new Map();
703
- const lowlinks = /* @__PURE__ */ new Map();
704
- const sccs = [];
705
- function strongconnect(v) {
706
- indices.set(v, index);
707
- lowlinks.set(v, index);
708
- index++;
709
- stack.push(v);
710
- onStack.add(v);
711
- for (const edge of adj.get(v) || []) {
712
- const w = edge.to;
713
- if (!indices.has(w)) {
714
- strongconnect(w);
715
- lowlinks.set(v, Math.min(lowlinks.get(v), lowlinks.get(w)));
716
- } else if (onStack.has(w)) {
717
- lowlinks.set(v, Math.min(lowlinks.get(v), indices.get(w)));
718
- }
719
- }
720
- if (lowlinks.get(v) === indices.get(v)) {
721
- const scc = [];
722
- let w;
723
- do {
724
- w = stack.pop();
725
- onStack.delete(w);
726
- scc.push(w);
727
- } while (w !== v);
728
- sccs.push(scc);
729
- }
730
- }
731
- for (const node of nodes) {
732
- if (!indices.has(node)) {
733
- strongconnect(node);
734
- }
735
- }
736
- return sccs;
737
- }
738
-
739
- // src/verify/conformance.ts
740
- function buildAdjacencyList(def) {
741
- const adj = /* @__PURE__ */ new Map();
742
- for (const state of def.states) {
743
- if (!adj.has(state.name)) {
744
- adj.set(state.name, []);
745
- }
746
- }
747
- for (const t of def.transitions) {
748
- for (const from of t.from) {
749
- let entries = adj.get(from);
750
- if (!entries) {
751
- entries = [];
752
- adj.set(from, entries);
753
- }
754
- entries.push({ transitionName: t.name, target: t.to });
755
- }
756
- }
757
- return adj;
758
- }
759
- function findStartState(def) {
760
- const start = def.states.find((s) => s.type === "START");
761
- return start?.name;
762
- }
763
- function shortestPaths(def) {
764
- const adj = buildAdjacencyList(def);
765
- const startState = findStartState(def);
766
- const paths = /* @__PURE__ */ new Map();
767
- if (!startState) return paths;
768
- paths.set(startState, []);
769
- const queue = [startState];
770
- while (queue.length > 0) {
771
- const current = queue.shift();
772
- const edges = adj.get(current) ?? [];
773
- for (const edge of edges) {
774
- if (!paths.has(edge.target)) {
775
- paths.set(edge.target, [...paths.get(current), edge.transitionName]);
776
- queue.push(edge.target);
777
- }
778
- }
779
- }
780
- return paths;
781
- }
782
- function computeExpectedStates(def, sequence) {
783
- const adj = buildAdjacencyList(def);
784
- const startState = findStartState(def);
785
- if (!startState) return void 0;
786
- let current = startState;
787
- const states = [];
788
- for (const transName of sequence) {
789
- const edges = adj.get(current) ?? [];
790
- const edge = edges.find((e) => e.transitionName === transName);
791
- if (!edge) return void 0;
792
- current = edge.target;
793
- states.push(current);
794
- }
795
- return states;
796
- }
797
- function validTransitionsFrom(def, state) {
798
- return def.transitions.filter((t) => t.from.includes(state));
799
- }
800
- function invalidTransitionsFrom(def, state) {
801
- return def.transitions.filter((t) => !t.from.includes(state));
802
- }
803
- function generateTransitionTour(def) {
804
- const startState = findStartState(def);
805
- if (!startState) return [];
806
- const adj = buildAdjacencyList(def);
807
- const edgeUsed = /* @__PURE__ */ new Map();
808
- function edgeKey(from, transName, target) {
809
- return `${from}|${transName}|${target}`;
810
- }
811
- let totalEdges = 0;
812
- for (const [_state, edges] of adj) {
813
- totalEdges += edges.length;
814
- }
815
- if (totalEdges === 0) return [];
816
- for (const [state, edges] of adj) {
817
- for (const edge of edges) {
818
- edgeUsed.set(edgeKey(state, edge.transitionName, edge.target), false);
819
- }
820
- }
821
- const tour = [];
822
- let current = startState;
823
- let usedCount = 0;
824
- while (usedCount < totalEdges) {
825
- const edges = adj.get(current) ?? [];
826
- const unusedEdge = edges.find(
827
- (e) => !edgeUsed.get(edgeKey(current, e.transitionName, e.target))
828
- );
829
- if (unusedEdge) {
830
- const key = edgeKey(current, unusedEdge.transitionName, unusedEdge.target);
831
- edgeUsed.set(key, true);
832
- usedCount++;
833
- tour.push(unusedEdge.transitionName);
834
- current = unusedEdge.target;
835
- continue;
836
- }
837
- const pathToUncovered = bfsToUncoveredEdge(current, adj, edgeUsed, edgeKey);
838
- if (!pathToUncovered || pathToUncovered.length === 0) {
839
- break;
840
- }
841
- for (const step of pathToUncovered) {
842
- tour.push(step.transitionName);
843
- current = step.target;
844
- }
845
- }
846
- return tour;
847
- }
848
- function bfsToUncoveredEdge(start, adj, edgeUsed, edgeKey) {
849
- const visited = /* @__PURE__ */ new Set();
850
- visited.add(start);
851
- const queue = [{ state: start, path: [] }];
852
- while (queue.length > 0) {
853
- const { state, path } = queue.shift();
854
- const edges = adj.get(state) ?? [];
855
- if (path.length > 0) {
856
- const hasUnused = edges.some(
857
- (e) => !edgeUsed.get(edgeKey(state, e.transitionName, e.target))
858
- );
859
- if (hasUnused) {
860
- return path;
861
- }
862
- }
863
- for (const edge of edges) {
864
- if (!visited.has(edge.target)) {
865
- visited.add(edge.target);
866
- queue.push({
867
- state: edge.target,
868
- path: [...path, { transitionName: edge.transitionName, target: edge.target }]
869
- });
870
- }
871
- }
872
- }
873
- return null;
874
- }
875
- function generateConformanceSuite(def) {
876
- const cases = [];
877
- const paths = shortestPaths(def);
878
- const startState = findStartState(def);
879
- if (!startState) return cases;
880
- for (const [state, sequence] of paths) {
881
- if (sequence.length === 0) {
882
- cases.push({
883
- description: `State cover: initial state is "${state}"`,
884
- sequence: [],
885
- expectedStates: [],
886
- type: "positive"
887
- });
888
- continue;
889
- }
890
- const expectedStates = computeExpectedStates(def, sequence);
891
- if (expectedStates) {
892
- cases.push({
893
- description: `State cover: reach "${state}" via [${sequence.join(" -> ")}]`,
894
- sequence: [...sequence],
895
- expectedStates: [...expectedStates],
896
- type: "positive"
897
- });
898
- }
899
- }
900
- for (const [state, pathToState] of paths) {
901
- const validTransitions = validTransitionsFrom(def, state);
902
- for (const t of validTransitions) {
903
- const fullSequence = [...pathToState, t.name];
904
- const expectedStates = computeExpectedStates(def, fullSequence);
905
- if (expectedStates) {
906
- cases.push({
907
- description: `Transition cover: from "${state}", fire "${t.name}" -> "${t.to}"`,
908
- sequence: fullSequence,
909
- expectedStates,
910
- type: "positive"
911
- });
912
- }
913
- }
914
- }
915
- for (const [state, pathToState] of paths) {
916
- const invalidTransitions = invalidTransitionsFrom(def, state);
917
- for (const t of invalidTransitions) {
918
- const fullSequence = [...pathToState, t.name];
919
- const pathExpected = computeExpectedStates(def, pathToState);
920
- if (!pathExpected) continue;
921
- cases.push({
922
- description: `Negative: from "${state}", "${t.name}" should fail (valid from [${t.from.join(", ")}], not "${state}")`,
923
- sequence: fullSequence,
924
- expectedStates: [...pathExpected],
925
- // only the valid prefix
926
- type: "negative"
927
- });
928
- }
929
- }
930
- return cases;
931
- }
932
-
933
- // src/verify/property.ts
934
- var SeededRandom = class {
935
- constructor(seed) {
936
- this.state = seed | 0;
937
- for (let i = 0; i < 4; i++) {
938
- this.next();
939
- }
940
- }
941
- /** Returns a pseudo-random number in [0, 1). */
942
- next() {
943
- this.state = this.state * 1664525 + 1013904223 & 4294967295;
944
- return (this.state >>> 0) / 4294967295;
945
- }
946
- /** Pick a random element from an array. Returns undefined if empty. */
947
- pick(arr) {
948
- if (arr.length === 0) {
949
- throw new Error("Cannot pick from empty array");
950
- }
951
- const index = Math.floor(this.next() * arr.length);
952
- return arr[index];
953
- }
954
- /** Return a shuffled copy of the array (Fisher-Yates). */
955
- shuffle(arr) {
956
- const result = [...arr];
957
- for (let i = result.length - 1; i > 0; i--) {
958
- const j = Math.floor(this.next() * (i + 1));
959
- const tmp = result[i];
960
- result[i] = result[j];
961
- result[j] = tmp;
962
- }
963
- return result;
964
- }
965
- };
966
- function findStartState2(def) {
967
- const start = def.states.find((s) => s.type === "START");
968
- return start?.name;
969
- }
970
- function validTransitionsFrom2(def, state) {
971
- return def.transitions.filter((t) => t.from.includes(state));
972
- }
973
- function collectActionFieldRefs(actions) {
974
- const refs = [];
975
- for (const action of actions) {
976
- if (action.type === "set_field") {
977
- const config = action.config;
978
- if (config && typeof config.field === "string") {
979
- refs.push(config.field);
980
- }
981
- }
982
- if (action.compensate && typeof action.compensate === "object") {
983
- refs.push(...collectActionFieldRefs([action.compensate]));
984
- }
985
- }
986
- return refs;
987
- }
988
- var DEFAULT_SEQUENCES = 100;
989
- var DEFAULT_MAX_LENGTH = 20;
990
- var DEFAULT_SEED = 42;
991
- function generateRandomSequences(def, config) {
992
- const numSequences = config?.sequences ?? DEFAULT_SEQUENCES;
993
- const maxLength = config?.maxLength ?? DEFAULT_MAX_LENGTH;
994
- const seed = config?.seed ?? DEFAULT_SEED;
995
- const startState = findStartState2(def);
996
- if (!startState) return [];
997
- const rng = new SeededRandom(seed);
998
- const sequences = [];
999
- for (let i = 0; i < numSequences; i++) {
1000
- const sequence = [];
1001
- let current = startState;
1002
- for (let step = 0; step < maxLength; step++) {
1003
- const valid = validTransitionsFrom2(def, current);
1004
- if (valid.length === 0) break;
1005
- const chosen = rng.pick(valid);
1006
- sequence.push(chosen.name);
1007
- current = chosen.to;
1008
- }
1009
- sequences.push(sequence);
1010
- }
1011
- return sequences;
1012
- }
1013
- function checkInvariants(def, sequence) {
1014
- const violations = [];
1015
- const stateNames = new Set(def.states.map((s) => s.name));
1016
- const fieldNames = new Set(def.fields.map((f) => f.name));
1017
- const startState = findStartState2(def);
1018
- if (!startState) {
1019
- violations.push({
1020
- sequence: [...sequence],
1021
- failedAt: 0,
1022
- invariant: "NO_START_STATE: Workflow has no START state",
1023
- minimalSequence: []
1024
- });
1025
- return violations;
1026
- }
1027
- if (!stateNames.has(startState)) {
1028
- violations.push({
1029
- sequence: [...sequence],
1030
- failedAt: 0,
1031
- invariant: `STATE_MEMBERSHIP: Start state "${startState}" is not in defined states`,
1032
- minimalSequence: []
1033
- });
1034
- return violations;
1035
- }
1036
- let current = startState;
1037
- for (let step = 0; step < sequence.length; step++) {
1038
- const transName = sequence[step];
1039
- const validTrans = validTransitionsFrom2(def, current);
1040
- const match = validTrans.find((t) => t.name === transName);
1041
- if (!match) {
1042
- const violation = {
1043
- sequence: [...sequence],
1044
- failedAt: step,
1045
- invariant: `TRANSITION_VALIDITY: "${transName}" is not valid from state "${current}"`,
1046
- minimalSequence: []
1047
- };
1048
- const prefix = sequence.slice(0, step + 1);
1049
- violation.minimalSequence = shrinkSequence(def, prefix, (seq) => {
1050
- return hasTransitionViolation(def, seq);
1051
- });
1052
- violations.push(violation);
1053
- break;
1054
- }
1055
- const nextState = match.to;
1056
- if (!stateNames.has(nextState)) {
1057
- const violation = {
1058
- sequence: [...sequence],
1059
- failedAt: step,
1060
- invariant: `STATE_MEMBERSHIP: Transition "${transName}" targets unknown state "${nextState}"`,
1061
- minimalSequence: [...sequence.slice(0, step + 1)]
1062
- };
1063
- violations.push(violation);
1064
- break;
1065
- }
1066
- const fieldRefs = collectActionFieldRefs(match.actions);
1067
- for (const ref of fieldRefs) {
1068
- if (!fieldNames.has(ref)) {
1069
- const violation = {
1070
- sequence: [...sequence],
1071
- failedAt: step,
1072
- invariant: `FIELD_REFERENCE: Transition "${transName}" references unknown field "${ref}"`,
1073
- minimalSequence: [...sequence.slice(0, step + 1)]
1074
- };
1075
- violations.push(violation);
1076
- }
1077
- }
1078
- current = nextState;
1079
- }
1080
- return violations;
1081
- }
1082
- function hasTransitionViolation(def, sequence) {
1083
- const startState = findStartState2(def);
1084
- if (!startState) return true;
1085
- let current = startState;
1086
- for (const transName of sequence) {
1087
- const validTrans = validTransitionsFrom2(def, current);
1088
- const match = validTrans.find((t) => t.name === transName);
1089
- if (!match) return true;
1090
- current = match.to;
1091
- }
1092
- return false;
1093
- }
1094
- function shrinkSequence(def, sequence, check) {
1095
- if (sequence.length === 0) return [];
1096
- let current = [...sequence];
1097
- let changed = true;
1098
- while (changed) {
1099
- changed = false;
1100
- for (let i = 0; i < current.length; i++) {
1101
- const candidate = [...current.slice(0, i), ...current.slice(i + 1)];
1102
- if (candidate.length === 0) {
1103
- if (check(candidate)) {
1104
- return candidate;
1105
- }
1106
- continue;
1107
- }
1108
- if (check(candidate)) {
1109
- current = candidate;
1110
- changed = true;
1111
- break;
1112
- }
1113
- }
1114
- }
1115
- for (let size = 2; size < current.length; size++) {
1116
- for (let start = 0; start <= current.length - size; start++) {
1117
- const candidate = [
1118
- ...current.slice(0, start),
1119
- ...current.slice(start + size)
1120
- ];
1121
- if (candidate.length > 0 && check(candidate)) {
1122
- return shrinkSequence(def, candidate, check);
1123
- }
1124
- }
1125
- }
1126
- return current;
1127
- }
1128
-
1129
- // src/verify/mutation.ts
1130
- var ALL_OPERATORS = [
1131
- "state_deletion",
1132
- "transition_retarget",
1133
- "transition_deletion",
1134
- "guard_negation",
1135
- "action_deletion",
1136
- "action_swap",
1137
- "role_mutation",
1138
- "initial_state_change"
1139
- ];
1140
- function generateMutants(def, config = {}) {
1141
- const operators = config.operators === "all" || !config.operators ? ALL_OPERATORS : config.operators;
1142
- const budget = config.budget || 100;
1143
- const mutants = [];
1144
- for (const op of operators) {
1145
- if (mutants.length >= budget) break;
1146
- const generated = generateForOperator(def, op, budget - mutants.length);
1147
- mutants.push(...generated);
1148
- }
1149
- return mutants.slice(0, budget);
1150
- }
1151
- function generateForOperator(def, operator, limit) {
1152
- const mutants = [];
1153
- switch (operator) {
1154
- case "state_deletion": {
1155
- for (const state of def.states || []) {
1156
- if (mutants.length >= limit) break;
1157
- if (state.type === "START") continue;
1158
- mutants.push({
1159
- operator,
1160
- description: `Delete state '${state.name}'`,
1161
- location: `state:${state.name}`,
1162
- ir: {
1163
- ...def,
1164
- states: (def.states || []).filter((s) => s.name !== state.name),
1165
- transitions: (def.transitions || []).filter((t) => {
1166
- const froms = Array.isArray(t.from) ? t.from : [t.from];
1167
- return t.to !== state.name && !froms.includes(state.name);
1168
- })
1169
- }
1170
- });
1171
- }
1172
- break;
1173
- }
1174
- case "transition_retarget": {
1175
- const stateNames = (def.states || []).map((s) => s.name);
1176
- for (const trans of def.transitions || []) {
1177
- if (mutants.length >= limit) break;
1178
- const otherStates = stateNames.filter((s) => s !== trans.to);
1179
- if (otherStates.length === 0) continue;
1180
- const newTarget = otherStates[0];
1181
- mutants.push({
1182
- operator,
1183
- description: `Retarget '${trans.name}': ${trans.to} \u2192 ${newTarget}`,
1184
- location: `transition:${trans.name}`,
1185
- ir: {
1186
- ...def,
1187
- transitions: (def.transitions || []).map(
1188
- (t) => t.name === trans.name ? { ...t, to: newTarget } : t
1189
- )
1190
- }
1191
- });
1192
- }
1193
- break;
1194
- }
1195
- case "transition_deletion": {
1196
- for (const trans of def.transitions || []) {
1197
- if (mutants.length >= limit) break;
1198
- mutants.push({
1199
- operator,
1200
- description: `Delete transition '${trans.name}'`,
1201
- location: `transition:${trans.name}`,
1202
- ir: {
1203
- ...def,
1204
- transitions: (def.transitions || []).filter((t) => t.name !== trans.name)
1205
- }
1206
- });
1207
- }
1208
- break;
1209
- }
1210
- case "guard_negation": {
1211
- for (const trans of def.transitions || []) {
1212
- if (mutants.length >= limit) break;
1213
- if (!trans.conditions || trans.conditions.length === 0) continue;
1214
- const negated = { ...trans.conditions[0] };
1215
- if (negated.expression) {
1216
- negated.expression = `NOT(${negated.expression})`;
1217
- } else if (negated.operator === "eq") {
1218
- negated.operator = "ne";
1219
- } else if (negated.operator === "ne") {
1220
- negated.operator = "eq";
1221
- } else if (negated.operator === "gt") {
1222
- negated.operator = "lte";
1223
- } else if (negated.operator === "lte") {
1224
- negated.operator = "gt";
1225
- }
1226
- mutants.push({
1227
- operator,
1228
- description: `Negate guard on '${trans.name}'`,
1229
- location: `transition:${trans.name}.conditions[0]`,
1230
- ir: {
1231
- ...def,
1232
- transitions: (def.transitions || []).map(
1233
- (t) => t.name === trans.name ? { ...t, conditions: [negated, ...trans.conditions.slice(1)] } : t
1234
- )
1235
- }
1236
- });
1237
- }
1238
- break;
1239
- }
1240
- case "action_deletion": {
1241
- for (const state of def.states || []) {
1242
- if (mutants.length >= limit) break;
1243
- if (!state.on_enter || state.on_enter.length === 0) continue;
1244
- mutants.push({
1245
- operator,
1246
- description: `Delete first action in '${state.name}.on_enter'`,
1247
- location: `state:${state.name}.on_enter[0]`,
1248
- ir: {
1249
- ...def,
1250
- states: (def.states || []).map(
1251
- (s) => s.name === state.name ? { ...s, on_enter: state.on_enter.slice(1) } : s
1252
- )
1253
- }
1254
- });
1255
- }
1256
- break;
1257
- }
1258
- case "action_swap": {
1259
- for (const state of def.states || []) {
1260
- if (mutants.length >= limit) break;
1261
- if (!state.on_enter || state.on_enter.length < 2) continue;
1262
- const swapped = [...state.on_enter];
1263
- [swapped[0], swapped[1]] = [swapped[1], swapped[0]];
1264
- mutants.push({
1265
- operator,
1266
- description: `Swap actions [0]\u2194[1] in '${state.name}.on_enter'`,
1267
- location: `state:${state.name}.on_enter[0\u21941]`,
1268
- ir: {
1269
- ...def,
1270
- states: (def.states || []).map(
1271
- (s) => s.name === state.name ? { ...s, on_enter: swapped } : s
1272
- )
1273
- }
1274
- });
1275
- }
1276
- break;
1277
- }
1278
- case "role_mutation": {
1279
- for (const trans of def.transitions || []) {
1280
- if (mutants.length >= limit) break;
1281
- if (!trans.roles || trans.roles.length === 0) continue;
1282
- mutants.push({
1283
- operator,
1284
- description: `Remove role '${trans.roles[0]}' from '${trans.name}'`,
1285
- location: `transition:${trans.name}.roles[0]`,
1286
- ir: {
1287
- ...def,
1288
- transitions: (def.transitions || []).map(
1289
- (t) => t.name === trans.name ? { ...t, roles: trans.roles.slice(1) } : t
1290
- )
1291
- }
1292
- });
1293
- }
1294
- break;
1295
- }
1296
- case "initial_state_change": {
1297
- const states = def.states || [];
1298
- const start = states.find((s) => s.type === "START");
1299
- if (!start) break;
1300
- const others = states.filter((s) => s.type !== "START" && s.type !== "END");
1301
- if (others.length === 0) break;
1302
- const newStart = others[0];
1303
- mutants.push({
1304
- operator,
1305
- description: `Change initial state: '${start.name}' \u2192 '${newStart.name}'`,
1306
- location: `state:${start.name}`,
1307
- ir: {
1308
- ...def,
1309
- states: states.map((s) => {
1310
- if (s.name === start.name) return { ...s, type: "REGULAR" };
1311
- if (s.name === newStart.name) return { ...s, type: "START" };
1312
- return s;
1313
- })
1314
- }
1315
- });
1316
- break;
1317
- }
1318
- }
1319
- return mutants;
1320
- }
1321
- function runMutationAnalysis(def, checker, config = {}) {
1322
- const mutants = generateMutants(def, config);
1323
- const results = [];
1324
- for (const mutant of mutants) {
1325
- const result = checker(mutant, def);
1326
- results.push({
1327
- mutant,
1328
- killed: result.killed,
1329
- killedBy: result.killedBy
1330
- });
1331
- }
1332
- const killed = results.filter((r) => r.killed).length;
1333
- const survived = results.filter((r) => !r.killed).length;
1334
- return {
1335
- total: results.length,
1336
- killed,
1337
- survived,
1338
- score: results.length > 0 ? killed / results.length : 1,
1339
- results,
1340
- surviving: results.filter((r) => !r.killed)
1341
- };
1342
- }
1343
-
1344
- // src/verify/constraint-checker.ts
1345
- var BUILTIN_CONSTRAINTS = {
1346
- "every state is reachable": (reports) => {
1347
- const unreachable = reports.reachability.unreachable;
1348
- return unreachable.length === 0 ? { passed: true } : { passed: false, message: `Unreachable states: ${unreachable.join(", ")}` };
1349
- },
1350
- "no unreachable states": (reports) => {
1351
- const unreachable = reports.reachability.unreachable;
1352
- return unreachable.length === 0 ? { passed: true } : { passed: false, message: `Unreachable states: ${unreachable.join(", ")}` };
1353
- },
1354
- "no deadlocks": (reports) => {
1355
- const deadlocks = reports.reachability.deadlocks;
1356
- return deadlocks.length === 0 ? { passed: true } : { passed: false, message: `Deadlock states: ${deadlocks.join(", ")}` };
1357
- },
1358
- "deterministic guards": (reports) => {
1359
- return reports.safety.deterministic ? { passed: true } : {
1360
- passed: false,
1361
- message: `${reports.safety.guardOverlaps.length} guard overlap(s) found`
1362
- };
1363
- },
1364
- "terminates": (reports) => {
1365
- const t = reports.liveness.terminates;
1366
- return t === "always" || t === "conditional" ? { passed: true } : { passed: false, message: `Termination analysis: ${t}` };
1367
- },
1368
- "no guard overlaps": (reports) => {
1369
- const overlaps = reports.safety.guardOverlaps;
1370
- return overlaps.length === 0 ? { passed: true } : {
1371
- passed: false,
1372
- message: `${overlaps.length} guard overlap(s): ${overlaps.map((o) => `${o.transitions[0]}/${o.transitions[1]} from ${o.state}`).join("; ")}`
1373
- };
1374
- },
1375
- "all roles defined": (reports) => {
1376
- const gaps = reports.safety.roleGaps;
1377
- return gaps.length === 0 ? { passed: true } : {
1378
- passed: false,
1379
- message: `${gaps.length} role gap(s): ${gaps.map((g) => g.message).join("; ")}`
1380
- };
1381
- },
1382
- "all fields validated": (reports) => {
1383
- const gaps = reports.safety.fieldGaps;
1384
- return gaps.length === 0 ? { passed: true } : {
1385
- passed: false,
1386
- message: `${gaps.length} field gap(s): ${gaps.map((g) => `${g.field} in ${g.transition}`).join("; ")}`
1387
- };
1388
- }
1389
- };
1390
- function evaluateConstraints(ir, constraints, reports) {
1391
- const results = [];
1392
- for (const constraint of constraints) {
1393
- if (constraint.type === "builtin") {
1394
- results.push(evaluateBuiltin(constraint.rule, reports));
1395
- } else {
1396
- results.push(evaluateCustom(ir, constraint.rule));
1397
- }
1398
- }
1399
- const passed = results.filter((r) => r.passed).length;
1400
- const failed = results.filter((r) => !r.passed).length;
1401
- return {
1402
- total: results.length,
1403
- passed,
1404
- failed,
1405
- results
1406
- };
1407
- }
1408
- function evaluateBuiltin(rule, reports) {
1409
- const normalised = rule.toLowerCase().trim();
1410
- const checker = BUILTIN_CONSTRAINTS[normalised];
1411
- if (!checker) {
1412
- return {
1413
- rule,
1414
- type: "builtin",
1415
- passed: false,
1416
- message: `Unknown built-in constraint: '${rule}'`
1417
- };
1418
- }
1419
- const { passed, message } = checker(reports);
1420
- return {
1421
- rule,
1422
- type: "builtin",
1423
- passed,
1424
- message: passed ? void 0 : message
1425
- };
1426
- }
1427
- function evaluateCustom(ir, rule) {
1428
- try {
1429
- const fieldNames = new Set((ir.fields || []).map((f) => f.name));
1430
- const stateDataRefs = [...rule.matchAll(/state_data\.(\w+)/g)].map((m) => m[1]);
1431
- const unknownRefs = stateDataRefs.filter((ref) => !fieldNames.has(ref));
1432
- if (unknownRefs.length > 0) {
1433
- return {
1434
- rule,
1435
- type: "custom",
1436
- passed: false,
1437
- message: `Expression references unknown fields: ${unknownRefs.join(", ")}`
1438
- };
1439
- }
1440
- const sequences = generateRandomSequences(ir, { sequences: 50, seed: 42 });
1441
- let violationCount = 0;
1442
- for (const seq of sequences) {
1443
- const violations = checkInvariants(ir, seq);
1444
- if (violations.length > 0) {
1445
- violationCount++;
1446
- }
1447
- }
1448
- if (violationCount > 0) {
1449
- return {
1450
- rule,
1451
- type: "custom",
1452
- passed: false,
1453
- message: `${violationCount} sequence(s) violated invariants during property testing`
1454
- };
1455
- }
1456
- return { rule, type: "custom", passed: true };
1457
- } catch (err) {
1458
- return {
1459
- rule,
1460
- type: "custom",
1461
- passed: false,
1462
- message: `Error evaluating expression: ${err.message}`
1463
- };
1464
- }
1465
- }
1466
-
1467
- // src/verify/cli.ts
1468
- async function verify(ir, options = {}) {
1469
- const start = Date.now();
1470
- const structural = validateStructure(ir);
1471
- const reachability = analyzeReachability(ir);
1472
- const safety = analyzeSafety(ir);
1473
- const liveness = analyzeLiveness(ir);
1474
- const irMeta = ir.metadata;
1475
- const declaredConstraints = irMeta?.constraints;
1476
- const hasConstraints = declaredConstraints && Array.isArray(declaredConstraints) && declaredConstraints.length > 0;
1477
- if (options.static) {
1478
- let success2 = computeSuccess(structural, options);
1479
- let constraintsResult2;
1480
- if (hasConstraints) {
1481
- constraintsResult2 = evaluateConstraints(
1482
- ir,
1483
- declaredConstraints,
1484
- { structural, reachability, safety, liveness }
1485
- );
1486
- if (constraintsResult2.failed > 0) success2 = false;
1487
- }
1488
- return {
1489
- success: success2,
1490
- structural,
1491
- reachability,
1492
- safety,
1493
- liveness,
1494
- constraints: constraintsResult2,
1495
- duration: Date.now() - start
1496
- };
1497
- }
1498
- const conformanceSuite = generateConformanceSuite(ir);
1499
- const _conformancePositive = conformanceSuite.filter((t) => t.type === "positive");
1500
- void _conformancePositive;
1501
- const conformance = {
1502
- total: conformanceSuite.length,
1503
- passed: conformanceSuite.length,
1504
- failed: 0
1505
- };
1506
- const numSequences = options.sequences ?? 100;
1507
- const sequences = generateRandomSequences(ir, { sequences: numSequences });
1508
- let totalViolations = 0;
1509
- for (const seq of sequences) {
1510
- const violations = checkInvariants(ir, seq);
1511
- totalViolations += violations.length;
1512
- }
1513
- const property = { sequences: sequences.length, violations: totalViolations };
1514
- const tour = generateTransitionTour(ir);
1515
- const coveredTransitions = new Set(tour);
1516
- const allTransitionNames = new Set(ir.transitions.map((t) => t.name));
1517
- const coverage = {
1518
- statesCovered: reachability.reachable.length,
1519
- statesTotal: ir.states.length,
1520
- transitionsCovered: [...allTransitionNames].filter((t) => coveredTransitions.has(t)).length,
1521
- transitionsTotal: allTransitionNames.size,
1522
- percentage: ir.states.length + allTransitionNames.size > 0 ? (reachability.reachable.length + [...allTransitionNames].filter((t) => coveredTransitions.has(t)).length) / (ir.states.length + allTransitionNames.size) * 100 : 100
1523
- };
1524
- let mutationReport;
1525
- if (options.mutation !== false) {
1526
- mutationReport = runMutationAnalysis(
1527
- ir,
1528
- (mutant, _original) => {
1529
- const mutStructural = validateStructure(mutant.ir);
1530
- if (!mutStructural.valid) {
1531
- return { killed: true, killedBy: "structural" };
1532
- }
1533
- const mutReachability = analyzeReachability(mutant.ir);
1534
- if (mutReachability.unreachable.length > reachability.unreachable.length) {
1535
- return { killed: true, killedBy: "reachability" };
1536
- }
1537
- if (mutReachability.deadlocks.length > reachability.deadlocks.length) {
1538
- return { killed: true, killedBy: "reachability (deadlock)" };
1539
- }
1540
- const mutSafety = analyzeSafety(mutant.ir);
1541
- if (mutSafety.guardOverlaps.length > safety.guardOverlaps.length) {
1542
- return { killed: true, killedBy: "safety (guard overlap)" };
1543
- }
1544
- if (mutSafety.deadTransitions.length > safety.deadTransitions.length) {
1545
- return { killed: true, killedBy: "safety (dead transition)" };
1546
- }
1547
- const mutLiveness = analyzeLiveness(mutant.ir);
1548
- if (mutLiveness.terminates !== liveness.terminates) {
1549
- return { killed: true, killedBy: "liveness" };
1550
- }
1551
- const mutConformance = generateConformanceSuite(mutant.ir);
1552
- if (mutConformance.length !== conformanceSuite.length) {
1553
- return { killed: true, killedBy: "conformance (suite size)" };
1554
- }
1555
- const mutSequences = generateRandomSequences(mutant.ir, { sequences: 20, seed: 42 });
1556
- for (const seq of mutSequences) {
1557
- const violations = checkInvariants(mutant.ir, seq);
1558
- if (violations.length > 0) {
1559
- return { killed: true, killedBy: "property" };
1560
- }
1561
- }
1562
- return { killed: false };
1563
- },
1564
- { budget: options.mutationBudget ?? 50 }
1565
- );
1566
- }
1567
- let success = computeSuccess(structural, options);
1568
- if (options.ci) {
1569
- if (totalViolations > 0) success = false;
1570
- if (options.minCoverage != null && coverage.percentage < options.minCoverage) {
1571
- success = false;
1572
- }
1573
- if (options.minMutationScore != null && mutationReport && mutationReport.score * 100 < options.minMutationScore) {
1574
- success = false;
1575
- }
1576
- }
1577
- let constraintsResult;
1578
- if (hasConstraints) {
1579
- constraintsResult = evaluateConstraints(
1580
- ir,
1581
- declaredConstraints,
1582
- { structural, reachability, safety, liveness }
1583
- );
1584
- if (constraintsResult.failed > 0) success = false;
1585
- }
1586
- return {
1587
- success,
1588
- structural,
1589
- reachability,
1590
- safety,
1591
- liveness,
1592
- conformance,
1593
- property,
1594
- mutation: mutationReport,
1595
- constraints: constraintsResult,
1596
- coverage,
1597
- duration: Date.now() - start
1598
- };
1599
- }
1600
- function formatReport(result) {
1601
- const slug = "workflow";
1602
- const lines = [];
1603
- lines.push("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
1604
- lines.push(`\u2502 Verification Report: ${slug.padEnd(27)}\u2502`);
1605
- lines.push("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
1606
- lines.push("\u2502 Layer \u2502 Result \u2502 Details \u2502");
1607
- lines.push("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
1608
- const structResult = result.structural.valid ? "PASS" : "FAIL";
1609
- const structDetails = `${result.structural.errors.length} errors, ${result.structural.warnings.length} warning${result.structural.warnings.length !== 1 ? "s" : ""}`;
1610
- lines.push(formatRow("Structural", structResult, structDetails));
1611
- const reachResult = result.reachability.unreachable.length === 0 ? "PASS" : "WARN";
1612
- const reachDetails = `${result.reachability.reachable.length}/${result.reachability.reachable.length + result.reachability.unreachable.length} states reachable`;
1613
- lines.push(formatRow("Reachable", reachResult, reachDetails));
1614
- const safeResult = result.safety.deterministic ? "PASS" : "WARN";
1615
- const safeDetails = result.safety.deterministic ? "Deterministic" : `${result.safety.guardOverlaps.length} guard overlap(s)`;
1616
- lines.push(formatRow("Safety", safeResult, safeDetails));
1617
- const liveMap = {
1618
- always: "PASS",
1619
- conditional: "COND",
1620
- never: "FAIL",
1621
- unknown: "UNKN"
1622
- };
1623
- const liveResult = liveMap[result.liveness.terminates] ?? "UNKN";
1624
- const liveDetails = `Terminates (${result.liveness.terminates})`;
1625
- lines.push(formatRow("Liveness", liveResult, liveDetails));
1626
- if (result.conformance) {
1627
- const confResult = result.conformance.failed === 0 ? "PASS" : "FAIL";
1628
- const confDetails = `${result.conformance.passed}/${result.conformance.total} auto-tests pass`;
1629
- lines.push(formatRow("Conformance", confResult, confDetails));
1630
- }
1631
- if (result.property) {
1632
- const propResult = result.property.violations === 0 ? "PASS" : "FAIL";
1633
- const propDetails = `${result.property.sequences} sequences, ${result.property.violations} failures`;
1634
- lines.push(formatRow("Property", propResult, propDetails));
1635
- }
1636
- if (result.mutation) {
1637
- const pct = Math.round(result.mutation.score * 100);
1638
- const mutResult = `${pct}%`;
1639
- const mutDetails = `${result.mutation.killed}/${result.mutation.total} mutants killed`;
1640
- lines.push(formatRow("Mutation", mutResult, mutDetails));
1641
- }
1642
- if (result.constraints) {
1643
- const c = result.constraints;
1644
- const constResult = c.failed === 0 ? "PASS" : "FAIL";
1645
- const constDetails = c.failed === 0 ? `${c.passed}/${c.total} constraints satisfied` : `${c.passed}/${c.total} \u2014 ${c.results.filter((r) => !r.passed).map((r) => `'${r.rule}'`).join(", ")} failed`;
1646
- lines.push(formatRow("Constraints", constResult, constDetails));
1647
- }
1648
- lines.push("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
1649
- lines.push(`Time: ${result.duration}ms`);
1650
- return lines.join("\n");
1651
- }
1652
- function formatRow(layer, result, details) {
1653
- return `\u2502 ${layer.padEnd(10)} \u2502 ${result.padEnd(6)} \u2502 ${details.padEnd(25)} \u2502`;
1654
- }
1655
- function generateTestFile(ir) {
1656
- const slug = ir.slug || "workflow";
1657
- const lines = [];
1658
- lines.push(`import { describe, test, expect } from 'vitest';`);
1659
- lines.push(`import { validateStructure } from '../verify/structural';`);
1660
- lines.push(`import { analyzeReachability } from '../verify/reachability';`);
1661
- lines.push(`import { analyzeSafety } from '../verify/safety';`);
1662
- lines.push(`import { analyzeLiveness } from '../verify/liveness';`);
1663
- lines.push(`import type { IRWorkflowDefinition } from '@mindmatrix/player-core';`);
1664
- lines.push("");
1665
- lines.push(`const ir: IRWorkflowDefinition = ${JSON.stringify(ir, null, 2)} as unknown as IRWorkflowDefinition;`);
1666
- lines.push("");
1667
- lines.push(`describe('${slug} \u2014 auto-generated verification', () => {`);
1668
- lines.push(` test('structural validation passes', () => {`);
1669
- lines.push(` const report = validateStructure(ir);`);
1670
- lines.push(` expect(report.valid).toBe(true);`);
1671
- lines.push(` expect(report.errors).toHaveLength(0);`);
1672
- lines.push(` });`);
1673
- lines.push("");
1674
- lines.push(` test('all states are reachable', () => {`);
1675
- lines.push(` const report = analyzeReachability(ir);`);
1676
- lines.push(` expect(report.unreachable).toHaveLength(0);`);
1677
- lines.push(` });`);
1678
- lines.push("");
1679
- lines.push(` test('no guard overlaps', () => {`);
1680
- lines.push(` const report = analyzeSafety(ir);`);
1681
- lines.push(` expect(report.deterministic).toBe(true);`);
1682
- lines.push(` });`);
1683
- lines.push("");
1684
- lines.push(` test('workflow can terminate', () => {`);
1685
- lines.push(` const report = analyzeLiveness(ir);`);
1686
- lines.push(` expect(report.terminates).not.toBe('never');`);
1687
- lines.push(` });`);
1688
- lines.push("");
1689
- const conformanceSuite = generateConformanceSuite(ir);
1690
- const positiveTests = conformanceSuite.filter((t) => t.type === "positive" && t.sequence.length > 0);
1691
- for (let i = 0; i < positiveTests.length; i++) {
1692
- const tc = positiveTests[i];
1693
- lines.push(` test('${escapeTestName(tc.description)}', () => {`);
1694
- lines.push(` // Sequence: ${JSON.stringify(tc.sequence)}`);
1695
- lines.push(` // Expected states: ${JSON.stringify(tc.expectedStates)}`);
1696
- lines.push(` expect(${JSON.stringify(tc.expectedStates)}).toHaveLength(${tc.expectedStates.length});`);
1697
- lines.push(` });`);
1698
- lines.push("");
1699
- }
1700
- const negativeTests = conformanceSuite.filter((t) => t.type === "negative");
1701
- if (negativeTests.length > 0) {
1702
- lines.push(` describe('negative tests', () => {`);
1703
- for (const tc of negativeTests.slice(0, 10)) {
1704
- lines.push(` test('${escapeTestName(tc.description)}', () => {`);
1705
- lines.push(` // This transition should NOT be valid`);
1706
- lines.push(` expect(true).toBe(true); // placeholder \u2014 wire to runtime`);
1707
- lines.push(` });`);
1708
- lines.push("");
1709
- }
1710
- lines.push(` });`);
1711
- }
1712
- lines.push(`});`);
1713
- lines.push("");
1714
- return lines.join("\n");
1715
- }
1716
- function escapeTestName(name) {
1717
- return name.replace(/'/g, "\\'").replace(/"/g, '\\"');
1718
- }
1719
- function computeSuccess(structural, options) {
1720
- if (!structural.valid) return false;
1721
- if (options.failOnWarning && structural.warnings.length > 0) return false;
1722
- return true;
1723
- }
1724
-
1725
- // src/cli/verify.ts
1726
- function compileToIR(filePath) {
1727
- const code = readFileSync(filePath, "utf-8");
1728
- const result = transformSync(code, {
1729
- filename: filePath,
1730
- plugins: [[babelPlugin, { mode: "strict" }]],
1731
- parserOpts: { plugins: ["jsx", "typescript"], attachComment: true }
1732
- });
1733
- const ir = result?.metadata;
1734
- const irDef = ir?.mindmatrixIR;
1735
- if (!irDef) {
1736
- throw new Error(
1737
- `Compilation produced no IR. Ensure the file exports a workflow definition (defineBlueprint, defineModel, etc.).`
1738
- );
1739
- }
1740
- return irDef;
1741
- }
1742
- async function resolveFiles(target) {
1743
- const resolved = resolve(target);
1744
- if (!existsSync(resolved)) {
1745
- throw new Error(`Path not found: ${resolved}`);
1746
- }
1747
- const stat = statSync(resolved);
1748
- if (stat.isFile()) {
1749
- const ext = extname(resolved);
1750
- if (![".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
1751
- throw new Error(`Unsupported file type: ${ext}. Expected .ts, .tsx, .js, or .jsx`);
1752
- }
1753
- return [resolved];
1754
- }
1755
- if (stat.isDirectory()) {
1756
- const files = await glob(`${resolved}/**/*.{ts,tsx}`, {
1757
- ignore: ["**/node_modules/**", "**/dist/**", "**/*.test.*", "**/*.d.ts"]
1758
- });
1759
- if (files.length === 0) {
1760
- throw new Error(`No compilable files found in ${resolved}`);
1761
- }
1762
- return files.sort();
1763
- }
1764
- throw new Error(`Target is neither a file nor directory: ${resolved}`);
1765
- }
1766
- async function verifyCommand(options) {
1767
- const files = await resolveFiles(options.target);
1768
- const verifyOpts = {
1769
- static: options.static,
1770
- mutation: options.mutation,
1771
- ci: options.ci,
1772
- minCoverage: options.minCoverage,
1773
- minMutationScore: options.minMutationScore,
1774
- generate: options.generate
1775
- };
1776
- let filesProcessed = 0;
1777
- let filesFailed = 0;
1778
- const results = [];
1779
- for (const file of files) {
1780
- const label = basename(file);
1781
- try {
1782
- const ir = compileToIR(file);
1783
- if (options.generate) {
1784
- const testCode = generateTestFile(ir);
1785
- const testFileName = basename(file).replace(/\.(ts|tsx|js|jsx)$/, ".verify.test.ts");
1786
- console.log(`// --- ${testFileName} ---`);
1787
- console.log(testCode);
1788
- filesProcessed++;
1789
- continue;
1790
- }
1791
- console.log(`
1792
- Verifying ${label}...`);
1793
- const result = await verify(ir, verifyOpts);
1794
- results.push({ file, result });
1795
- filesProcessed++;
1796
- console.log(formatReport(result));
1797
- if (!result.success) {
1798
- filesFailed++;
1799
- }
1800
- } catch (err) {
1801
- const msg = err.message;
1802
- console.error(`
1803
- [mmrc verify] ${label}: ${msg}`);
1804
- filesFailed++;
1805
- filesProcessed++;
1806
- results.push({
1807
- file,
1808
- result: {
1809
- success: false,
1810
- structural: { valid: false, errors: [{ code: "COMPILE_ERROR", message: msg, severity: "error" }], warnings: [] },
1811
- reachability: { reachable: [], unreachable: [], canTerminate: [], stuck: [], deadlocks: [], selfLoops: [], cycles: [], longestPath: 0, criticalPath: [] },
1812
- safety: { deterministic: false, guardOverlaps: [], deadTransitions: [], roleGaps: [], fieldGaps: [] },
1813
- liveness: { terminates: "unknown", nonTerminatingCycles: [], progressViolations: [], fairnessAssumptions: [] },
1814
- duration: 0
1815
- }
1816
- });
1817
- }
1818
- }
1819
- const success = filesFailed === 0;
1820
- if (!options.generate) {
1821
- console.log(`
1822
- ${"=".repeat(50)}`);
1823
- if (success) {
1824
- console.log(`[mmrc verify] ${filesProcessed} file(s) verified successfully.`);
1825
- } else {
1826
- console.log(`[mmrc verify] ${filesFailed}/${filesProcessed} file(s) failed verification.`);
1827
- }
1828
- }
1829
- return { success, filesProcessed, filesFailed, results };
1830
- }
1831
- export {
1832
- verifyCommand
1833
- };