@orcalang/orca-lang 0.1.19 → 0.1.24

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 (53) hide show
  1. package/dist/compiler/dt-compiler.d.ts +4 -0
  2. package/dist/compiler/dt-compiler.d.ts.map +1 -1
  3. package/dist/compiler/dt-compiler.js +354 -4
  4. package/dist/compiler/dt-compiler.js.map +1 -1
  5. package/dist/health-check.js +75 -0
  6. package/dist/health-check.js.map +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +5 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/parser/ast-to-markdown.d.ts.map +1 -1
  11. package/dist/parser/ast-to-markdown.js +3 -1
  12. package/dist/parser/ast-to-markdown.js.map +1 -1
  13. package/dist/parser/ast.d.ts +1 -0
  14. package/dist/parser/ast.d.ts.map +1 -1
  15. package/dist/parser/dt-ast.d.ts +11 -1
  16. package/dist/parser/dt-ast.d.ts.map +1 -1
  17. package/dist/parser/dt-parser.d.ts.map +1 -1
  18. package/dist/parser/dt-parser.js +40 -8
  19. package/dist/parser/dt-parser.js.map +1 -1
  20. package/dist/parser/markdown-parser.d.ts.map +1 -1
  21. package/dist/parser/markdown-parser.js +14 -4
  22. package/dist/parser/markdown-parser.js.map +1 -1
  23. package/dist/skills.d.ts +3 -2
  24. package/dist/skills.d.ts.map +1 -1
  25. package/dist/skills.js +486 -28
  26. package/dist/skills.js.map +1 -1
  27. package/dist/tools.js +4 -4
  28. package/dist/tools.js.map +1 -1
  29. package/dist/verifier/dt-verifier.d.ts +28 -1
  30. package/dist/verifier/dt-verifier.d.ts.map +1 -1
  31. package/dist/verifier/dt-verifier.js +591 -32
  32. package/dist/verifier/dt-verifier.js.map +1 -1
  33. package/dist/verifier/properties.d.ts +4 -0
  34. package/dist/verifier/properties.d.ts.map +1 -1
  35. package/dist/verifier/properties.js +56 -20
  36. package/dist/verifier/properties.js.map +1 -1
  37. package/dist/verifier/structural.d.ts.map +1 -1
  38. package/dist/verifier/structural.js +6 -1
  39. package/dist/verifier/structural.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/compiler/dt-compiler.ts +374 -4
  42. package/src/health-check.ts +79 -0
  43. package/src/index.ts +5 -1
  44. package/src/parser/ast-to-markdown.ts +2 -1
  45. package/src/parser/ast.ts +1 -0
  46. package/src/parser/dt-ast.ts +4 -2
  47. package/src/parser/dt-parser.ts +46 -8
  48. package/src/parser/markdown-parser.ts +11 -3
  49. package/src/skills.ts +520 -30
  50. package/src/tools.ts +4 -4
  51. package/src/verifier/dt-verifier.ts +639 -30
  52. package/src/verifier/properties.ts +78 -23
  53. package/src/verifier/structural.ts +5 -1
@@ -1,29 +1,66 @@
1
- import { MachineDef, Property, ReachabilityProperty, PassesThroughProperty, RespondsProperty, InvariantProperty, GuardRef, GuardDef } from '../parser/ast.js';
1
+ import { MachineDef, Property, ReachabilityProperty, PassesThroughProperty, RespondsProperty, InvariantProperty, GuardRef, GuardDef, GuardExpression } from '../parser/ast.js';
2
2
  import { VerificationResult, VerificationError, MachineAnalysis, StateInfo } from './types.js';
3
3
  import { analyzeMachine, flattenStates, FlattenedState } from './structural.js';
4
4
  import { resolveGuardExpression, isExpressionStaticallyFalse } from './determinism.js';
5
5
 
6
6
  const DEFAULT_MAX_STATES = 64;
7
7
 
8
+ /**
9
+ * Check if a guard expression is statically false given DT output domain constraints.
10
+ * A comparison `ctx.field == value` is false if the DT never outputs `value` for `field`.
11
+ * Handles AND (false if either branch is domain-blocked) and OR (false only if both are).
12
+ */
13
+ function isExpressionBlockedByDomain(
14
+ expr: GuardExpression,
15
+ domain: Map<string, Set<string>>
16
+ ): boolean {
17
+ if (expr.kind === 'compare' && expr.op === 'eq') {
18
+ if (expr.left.path.length === 2 && expr.left.path[0] === 'ctx') {
19
+ const field = expr.left.path[1];
20
+ const value = String(expr.right.value);
21
+ const possible = domain.get(field);
22
+ if (possible !== undefined && !possible.has(value)) return true;
23
+ }
24
+ }
25
+ if (expr.kind === 'and') {
26
+ return isExpressionBlockedByDomain(expr.left, domain) ||
27
+ isExpressionBlockedByDomain(expr.right, domain);
28
+ }
29
+ if (expr.kind === 'or') {
30
+ return isExpressionBlockedByDomain(expr.left, domain) &&
31
+ isExpressionBlockedByDomain(expr.right, domain);
32
+ }
33
+ return false;
34
+ }
35
+
8
36
  /**
9
37
  * Check if a transition's guard is statically false (can never fire).
10
38
  * Used in guard-aware BFS to prune impossible transitions.
39
+ * When dtOutputDomain is provided, also prunes guards that compare DT output
40
+ * fields against values the DT never produces.
11
41
  */
12
42
  function isTransitionStaticallyBlocked(
13
43
  transition: { guard?: GuardRef },
14
- guardDefs: Map<string, GuardDef>
44
+ guardDefs: Map<string, GuardDef>,
45
+ dtOutputDomain?: Map<string, Set<string>>
15
46
  ): boolean {
16
47
  if (!transition.guard) return false; // No guard = always possible
17
48
 
18
49
  const resolved = resolveGuardExpression(transition.guard, guardDefs);
19
50
  if (!resolved) return false; // Can't resolve = assume possible
20
51
 
21
- return isExpressionStaticallyFalse(resolved);
52
+ if (isExpressionStaticallyFalse(resolved)) return true;
53
+
54
+ if (dtOutputDomain && isExpressionBlockedByDomain(resolved, dtOutputDomain)) return true;
55
+
56
+ return false;
22
57
  }
23
58
 
24
59
  /**
25
60
  * BFS from source state, returning reachable set and parent map for counterexample traces.
26
61
  * Supports guard-aware mode: skips transitions with statically false guards.
62
+ * When dtOutputDomain is provided, also skips transitions whose guards are impossible
63
+ * given the DT output domain constraints.
27
64
  */
28
65
  function bfs(
29
66
  stateMap: Map<string, StateInfo>,
@@ -31,7 +68,8 @@ function bfs(
31
68
  options?: {
32
69
  excludeState?: string;
33
70
  maxDepth?: number;
34
- guardDefs?: Map<string, GuardDef>; // Enable guard-aware pruning
71
+ guardDefs?: Map<string, GuardDef>;
72
+ dtOutputDomain?: Map<string, Set<string>>;
35
73
  }
36
74
  ): { reachable: Set<string>; parent: Map<string, { state: string; event: string; guard?: string }> } {
37
75
  const reachable = new Set<string>();
@@ -56,8 +94,8 @@ function bfs(
56
94
  // Skip excluded state
57
95
  if (options?.excludeState && target === options.excludeState) continue;
58
96
 
59
- // Guard-aware: skip transitions with statically false guards
60
- if (options?.guardDefs && isTransitionStaticallyBlocked(t, options.guardDefs)) continue;
97
+ // Guard-aware: skip transitions with statically false guards (including DT domain constraints)
98
+ if (options?.guardDefs && isTransitionStaticallyBlocked(t, options.guardDefs, options.dtOutputDomain)) continue;
61
99
 
62
100
  if (!reachable.has(target)) {
63
101
  reachable.add(target);
@@ -157,7 +195,8 @@ function checkReachable(
157
195
  prop: ReachabilityProperty,
158
196
  analysis: MachineAnalysis,
159
197
  flattenedStates: FlattenedState[],
160
- guardDefs: Map<string, GuardDef>
198
+ guardDefs: Map<string, GuardDef>,
199
+ dtOutputDomain?: Map<string, Set<string>>
161
200
  ): VerificationError[] {
162
201
  const errors: VerificationError[] = [];
163
202
 
@@ -166,7 +205,7 @@ function checkReachable(
166
205
  const toRes = resolveStateName(prop.to, flattenedStates);
167
206
  if (toRes.error) return [toRes.error];
168
207
 
169
- const { reachable } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs });
208
+ const { reachable } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs, dtOutputDomain });
170
209
 
171
210
  if (!reachable.has(toRes.resolved)) {
172
211
  errors.push({
@@ -185,7 +224,8 @@ function checkUnreachable(
185
224
  prop: ReachabilityProperty,
186
225
  analysis: MachineAnalysis,
187
226
  flattenedStates: FlattenedState[],
188
- guardDefs: Map<string, GuardDef>
227
+ guardDefs: Map<string, GuardDef>,
228
+ dtOutputDomain?: Map<string, Set<string>>
189
229
  ): VerificationError[] {
190
230
  const errors: VerificationError[] = [];
191
231
 
@@ -194,7 +234,7 @@ function checkUnreachable(
194
234
  const toRes = resolveStateName(prop.to, flattenedStates);
195
235
  if (toRes.error) return [toRes.error];
196
236
 
197
- const { reachable, parent } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs });
237
+ const { reachable, parent } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs, dtOutputDomain });
198
238
 
199
239
  if (reachable.has(toRes.resolved)) {
200
240
  const path = reconstructPath(parent, fromRes.resolved, toRes.resolved);
@@ -218,7 +258,8 @@ function checkPassesThrough(
218
258
  prop: PassesThroughProperty,
219
259
  analysis: MachineAnalysis,
220
260
  flattenedStates: FlattenedState[],
221
- guardDefs: Map<string, GuardDef>
261
+ guardDefs: Map<string, GuardDef>,
262
+ dtOutputDomain?: Map<string, Set<string>>
222
263
  ): VerificationError[] {
223
264
  const errors: VerificationError[] = [];
224
265
 
@@ -230,7 +271,7 @@ function checkPassesThrough(
230
271
  if (throughRes.error) return [throughRes.error];
231
272
 
232
273
  // First check: is target reachable from source at all?
233
- const { reachable: fullReachable } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs });
274
+ const { reachable: fullReachable } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs, dtOutputDomain });
234
275
  if (!fullReachable.has(toRes.resolved)) {
235
276
  errors.push({
236
277
  code: 'PROPERTY_PATH_FAIL',
@@ -246,6 +287,7 @@ function checkPassesThrough(
246
287
  const { reachable: withoutThrough, parent } = bfs(analysis.stateMap, fromRes.resolved, {
247
288
  excludeState: throughRes.resolved,
248
289
  guardDefs,
290
+ dtOutputDomain,
249
291
  });
250
292
 
251
293
  if (withoutThrough.has(toRes.resolved)) {
@@ -265,14 +307,15 @@ function checkPassesThrough(
265
307
  function checkLive(
266
308
  analysis: MachineAnalysis,
267
309
  flattenedStates: FlattenedState[],
268
- guardDefs: Map<string, GuardDef>
310
+ guardDefs: Map<string, GuardDef>,
311
+ dtOutputDomain?: Map<string, Set<string>>
269
312
  ): VerificationError[] {
270
313
  const errors: VerificationError[] = [];
271
314
 
272
315
  if (!analysis.initialState) return errors;
273
316
 
274
317
  // Find all reachable states from initial
275
- const { reachable: reachableFromInitial } = bfs(analysis.stateMap, analysis.initialState.name, { guardDefs });
318
+ const { reachable: reachableFromInitial } = bfs(analysis.stateMap, analysis.initialState.name, { guardDefs, dtOutputDomain });
276
319
 
277
320
  // Find all final state names
278
321
  const finalStateNames = new Set(analysis.finalStates.map(s => s.name));
@@ -285,7 +328,7 @@ function checkLive(
285
328
  const fs = flattenedStates.find(f => f.name === stateName);
286
329
  if (fs && (fs.isCompound || fs.isRegion)) continue;
287
330
 
288
- const { reachable: reachableFromState } = bfs(analysis.stateMap, stateName, { guardDefs });
331
+ const { reachable: reachableFromState } = bfs(analysis.stateMap, stateName, { guardDefs, dtOutputDomain });
289
332
 
290
333
  let canReachFinal = false;
291
334
  for (const finalName of finalStateNames) {
@@ -313,7 +356,8 @@ function checkResponds(
313
356
  prop: RespondsProperty,
314
357
  analysis: MachineAnalysis,
315
358
  flattenedStates: FlattenedState[],
316
- guardDefs: Map<string, GuardDef>
359
+ guardDefs: Map<string, GuardDef>,
360
+ dtOutputDomain?: Map<string, Set<string>>
317
361
  ): VerificationError[] {
318
362
  const errors: VerificationError[] = [];
319
363
 
@@ -325,11 +369,12 @@ function checkResponds(
325
369
  const { reachable } = bfs(analysis.stateMap, fromRes.resolved, {
326
370
  maxDepth: prop.within,
327
371
  guardDefs,
372
+ dtOutputDomain,
328
373
  });
329
374
 
330
375
  if (!reachable.has(toRes.resolved)) {
331
376
  // Check if it's reachable at all (just beyond the bound)
332
- const { reachable: unbounded } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs });
377
+ const { reachable: unbounded } = bfs(analysis.stateMap, fromRes.resolved, { guardDefs, dtOutputDomain });
333
378
  const reachableButBeyondBound = unbounded.has(toRes.resolved);
334
379
 
335
380
  const suffix = reachableButBeyondBound
@@ -453,7 +498,16 @@ function checkMachineSize(
453
498
 
454
499
  // --- Main entry point ---
455
500
 
456
- export function checkProperties(machine: MachineDef, options?: { maxStates?: number }): VerificationResult {
501
+ export function checkProperties(
502
+ machine: MachineDef,
503
+ options?: {
504
+ maxStates?: number;
505
+ /** DT output domain from co-located aligned decision tables. When provided,
506
+ * the guard-aware BFS will prune transitions whose guards compare DT output
507
+ * fields against values the DT never produces, giving more precise results. */
508
+ dtOutputDomain?: Map<string, Set<string>>;
509
+ }
510
+ ): VerificationResult {
457
511
  const maxStates = options?.maxStates ?? DEFAULT_MAX_STATES;
458
512
  const flattenedStates = flattenStates(machine.states);
459
513
  const errors: VerificationError[] = [];
@@ -470,6 +524,7 @@ export function checkProperties(machine: MachineDef, options?: { maxStates?: num
470
524
  }
471
525
 
472
526
  const analysis = analyzeMachine(machine);
527
+ const dtOutputDomain = options?.dtOutputDomain;
473
528
 
474
529
  // Build guard definition map for guard-aware BFS
475
530
  const guardDefs = new Map<string, GuardDef>();
@@ -480,19 +535,19 @@ export function checkProperties(machine: MachineDef, options?: { maxStates?: num
480
535
  for (const prop of machine.properties) {
481
536
  switch (prop.kind) {
482
537
  case 'reachable':
483
- errors.push(...checkReachable(prop, analysis, flattenedStates, guardDefs));
538
+ errors.push(...checkReachable(prop, analysis, flattenedStates, guardDefs, dtOutputDomain));
484
539
  break;
485
540
  case 'unreachable':
486
- errors.push(...checkUnreachable(prop, analysis, flattenedStates, guardDefs));
541
+ errors.push(...checkUnreachable(prop, analysis, flattenedStates, guardDefs, dtOutputDomain));
487
542
  break;
488
543
  case 'passes_through':
489
- errors.push(...checkPassesThrough(prop, analysis, flattenedStates, guardDefs));
544
+ errors.push(...checkPassesThrough(prop, analysis, flattenedStates, guardDefs, dtOutputDomain));
490
545
  break;
491
546
  case 'live':
492
- errors.push(...checkLive(analysis, flattenedStates, guardDefs));
547
+ errors.push(...checkLive(analysis, flattenedStates, guardDefs, dtOutputDomain));
493
548
  break;
494
549
  case 'responds':
495
- errors.push(...checkResponds(prop, analysis, flattenedStates, guardDefs));
550
+ errors.push(...checkResponds(prop, analysis, flattenedStates, guardDefs, dtOutputDomain));
496
551
  break;
497
552
  case 'invariant':
498
553
  errors.push(...checkInvariant(prop, machine, flattenedStates));
@@ -183,7 +183,11 @@ export function analyzeMachine(machine: MachineDef): MachineAnalysis {
183
183
  if (fs) {
184
184
  // Find the original state definition
185
185
  const originalState = findOriginalState(machine.states, fs.simpleName, fs.parentName);
186
- if (originalState?.ignoredEvents) {
186
+ if (originalState?.ignoredAll) {
187
+ for (const event of machine.events) {
188
+ info.eventsIgnored.add(event.name);
189
+ }
190
+ } else if (originalState?.ignoredEvents) {
187
191
  for (const event of originalState.ignoredEvents) {
188
192
  info.eventsIgnored.add(event);
189
193
  }