modality-ts 0.0.15 → 0.0.17

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 (177) hide show
  1. package/dist/check/check-model.d.ts +4 -0
  2. package/dist/check/check-model.d.ts.map +1 -0
  3. package/dist/check/check-model.js +159 -0
  4. package/dist/check/check-model.js.map +1 -0
  5. package/dist/check/index.d.ts +2 -2
  6. package/dist/check/index.d.ts.map +1 -1
  7. package/dist/check/index.js +2 -2
  8. package/dist/check/index.js.map +1 -1
  9. package/dist/check/model-api.d.ts.map +1 -0
  10. package/dist/check/model-api.js +8 -0
  11. package/dist/check/model-api.js.map +1 -0
  12. package/dist/check/native.d.ts +6 -0
  13. package/dist/check/native.d.ts.map +1 -0
  14. package/dist/check/native.js +69 -0
  15. package/dist/check/native.js.map +1 -0
  16. package/dist/check/serialize-properties.d.ts +4 -0
  17. package/dist/check/serialize-properties.d.ts.map +1 -0
  18. package/dist/check/serialize-properties.js +4 -0
  19. package/dist/check/serialize-properties.js.map +1 -0
  20. package/dist/cli/features/check/command.d.ts.map +1 -1
  21. package/dist/cli/features/check/command.js +25 -6
  22. package/dist/cli/features/check/command.js.map +1 -1
  23. package/dist/cli/features/extract/command.d.ts +4 -1
  24. package/dist/cli/features/extract/command.d.ts.map +1 -1
  25. package/dist/cli/features/extract/command.js +262 -193
  26. package/dist/cli/features/extract/command.js.map +1 -1
  27. package/dist/cli/features/extract/project.d.ts +36 -0
  28. package/dist/cli/features/extract/project.d.ts.map +1 -0
  29. package/dist/cli/features/extract/project.js +783 -0
  30. package/dist/cli/features/extract/project.js.map +1 -0
  31. package/dist/cli/features/init/command.d.ts.map +1 -1
  32. package/dist/cli/features/init/command.js +0 -1
  33. package/dist/cli/features/init/command.js.map +1 -1
  34. package/dist/cli/runtime/index.d.ts.map +1 -1
  35. package/dist/cli/runtime/index.js +2 -1
  36. package/dist/cli/runtime/index.js.map +1 -1
  37. package/dist/core/artifacts/index.d.ts +3 -0
  38. package/dist/core/artifacts/index.d.ts.map +1 -1
  39. package/dist/core/artifacts/index.js +238 -0
  40. package/dist/core/artifacts/index.js.map +1 -1
  41. package/dist/core/index.d.ts +1 -0
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +1 -0
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/ir/eval.d.ts +6 -0
  46. package/dist/core/ir/eval.d.ts.map +1 -0
  47. package/dist/core/ir/eval.js +104 -0
  48. package/dist/core/ir/eval.js.map +1 -0
  49. package/dist/core/ir/types.d.ts +83 -0
  50. package/dist/core/ir/types.d.ts.map +1 -1
  51. package/dist/core/props/index.d.ts +23 -54
  52. package/dist/core/props/index.d.ts.map +1 -1
  53. package/dist/core/props/index.js +182 -116
  54. package/dist/core/props/index.js.map +1 -1
  55. package/dist/core/report/types.d.ts +7 -0
  56. package/dist/core/report/types.d.ts.map +1 -1
  57. package/dist/extract/engine/pipeline/index.d.ts +4 -0
  58. package/dist/extract/engine/pipeline/index.d.ts.map +1 -1
  59. package/dist/extract/engine/pipeline/index.js +39 -15
  60. package/dist/extract/engine/pipeline/index.js.map +1 -1
  61. package/dist/extract/engine/spi/index.d.ts +32 -0
  62. package/dist/extract/engine/spi/index.d.ts.map +1 -1
  63. package/dist/extract/engine/ts/react-source-transitions.d.ts +3 -0
  64. package/dist/extract/engine/ts/react-source-transitions.d.ts.map +1 -1
  65. package/dist/extract/engine/ts/react-source-transitions.js +18 -0
  66. package/dist/extract/engine/ts/react-source-transitions.js.map +1 -1
  67. package/dist/extract/engine/ts/transition/handlers.d.ts +2 -2
  68. package/dist/extract/engine/ts/transition/handlers.d.ts.map +1 -1
  69. package/dist/extract/engine/ts/transition/handlers.js +112 -10
  70. package/dist/extract/engine/ts/transition/handlers.js.map +1 -1
  71. package/dist/extract/sources/router/index.d.ts.map +1 -1
  72. package/dist/extract/sources/router/index.js +5 -0
  73. package/dist/extract/sources/router/index.js.map +1 -1
  74. package/dist/extract/sources/router/module-roles.d.ts +7 -0
  75. package/dist/extract/sources/router/module-roles.d.ts.map +1 -0
  76. package/dist/extract/sources/router/module-roles.js +153 -0
  77. package/dist/extract/sources/router/module-roles.js.map +1 -0
  78. package/native/index.d.ts +8 -0
  79. package/native/index.js +317 -0
  80. package/native/modality-checker.linux-x64-gnu.node +0 -0
  81. package/package.json +14 -4
  82. package/dist/check/diagnostics/bounds.d.ts +0 -5
  83. package/dist/check/diagnostics/bounds.d.ts.map +0 -1
  84. package/dist/check/diagnostics/bounds.js +0 -25
  85. package/dist/check/diagnostics/bounds.js.map +0 -1
  86. package/dist/check/diagnostics/vacuity.d.ts +0 -3
  87. package/dist/check/diagnostics/vacuity.d.ts.map +0 -1
  88. package/dist/check/diagnostics/vacuity.js +0 -22
  89. package/dist/check/diagnostics/vacuity.js.map +0 -1
  90. package/dist/check/engine/check-model.d.ts +0 -7
  91. package/dist/check/engine/check-model.d.ts.map +0 -1
  92. package/dist/check/engine/check-model.js +0 -527
  93. package/dist/check/engine/check-model.js.map +0 -1
  94. package/dist/check/engine/initial-states.d.ts +0 -3
  95. package/dist/check/engine/initial-states.d.ts.map +0 -1
  96. package/dist/check/engine/initial-states.js +0 -11
  97. package/dist/check/engine/initial-states.js.map +0 -1
  98. package/dist/check/engine/model-api.d.ts.map +0 -1
  99. package/dist/check/engine/model-api.js +0 -17
  100. package/dist/check/engine/model-api.js.map +0 -1
  101. package/dist/check/engine/mounts.d.ts +0 -3
  102. package/dist/check/engine/mounts.d.ts.map +0 -1
  103. package/dist/check/engine/mounts.js +0 -13
  104. package/dist/check/engine/mounts.js.map +0 -1
  105. package/dist/check/engine/stabilize.d.ts +0 -4
  106. package/dist/check/engine/stabilize.d.ts.map +0 -1
  107. package/dist/check/engine/stabilize.js +0 -104
  108. package/dist/check/engine/stabilize.js.map +0 -1
  109. package/dist/check/engine/state-utils.d.ts +0 -13
  110. package/dist/check/engine/state-utils.d.ts.map +0 -1
  111. package/dist/check/engine/state-utils.js +0 -43
  112. package/dist/check/engine/state-utils.js.map +0 -1
  113. package/dist/check/engine/transitions.d.ts +0 -12
  114. package/dist/check/engine/transitions.d.ts.map +0 -1
  115. package/dist/check/engine/transitions.js +0 -42
  116. package/dist/check/engine/transitions.js.map +0 -1
  117. package/dist/check/properties/checked-state.d.ts +0 -3
  118. package/dist/check/properties/checked-state.d.ts.map +0 -1
  119. package/dist/check/properties/checked-state.js +0 -21
  120. package/dist/check/properties/checked-state.js.map +0 -1
  121. package/dist/check/properties/finalize.d.ts +0 -5
  122. package/dist/check/properties/finalize.d.ts.map +0 -1
  123. package/dist/check/properties/finalize.js +0 -107
  124. package/dist/check/properties/finalize.js.map +0 -1
  125. package/dist/check/properties/leads-to.d.ts +0 -8
  126. package/dist/check/properties/leads-to.d.ts.map +0 -1
  127. package/dist/check/properties/leads-to.js +0 -70
  128. package/dist/check/properties/leads-to.js.map +0 -1
  129. package/dist/check/properties/observe.d.ts +0 -6
  130. package/dist/check/properties/observe.d.ts.map +0 -1
  131. package/dist/check/properties/observe.js +0 -56
  132. package/dist/check/properties/observe.js.map +0 -1
  133. package/dist/check/properties/reachable-from.d.ts +0 -10
  134. package/dist/check/properties/reachable-from.d.ts.map +0 -1
  135. package/dist/check/properties/reachable-from.js +0 -19
  136. package/dist/check/properties/reachable-from.js.map +0 -1
  137. package/dist/check/runtime/domains.d.ts +0 -5
  138. package/dist/check/runtime/domains.d.ts.map +0 -1
  139. package/dist/check/runtime/domains.js +0 -53
  140. package/dist/check/runtime/domains.js.map +0 -1
  141. package/dist/check/runtime/effects.d.ts +0 -9
  142. package/dist/check/runtime/effects.d.ts.map +0 -1
  143. package/dist/check/runtime/effects.js +0 -86
  144. package/dist/check/runtime/effects.js.map +0 -1
  145. package/dist/check/runtime/expr.d.ts +0 -7
  146. package/dist/check/runtime/expr.d.ts.map +0 -1
  147. package/dist/check/runtime/expr.js +0 -49
  148. package/dist/check/runtime/expr.js.map +0 -1
  149. package/dist/check/runtime/navigation.d.ts +0 -7
  150. package/dist/check/runtime/navigation.d.ts.map +0 -1
  151. package/dist/check/runtime/navigation.js +0 -60
  152. package/dist/check/runtime/navigation.js.map +0 -1
  153. package/dist/check/runtime/opaque.d.ts +0 -3
  154. package/dist/check/runtime/opaque.d.ts.map +0 -1
  155. package/dist/check/runtime/opaque.js +0 -72
  156. package/dist/check/runtime/opaque.js.map +0 -1
  157. package/dist/check/runtime/paths.d.ts +0 -4
  158. package/dist/check/runtime/paths.d.ts.map +0 -1
  159. package/dist/check/runtime/paths.js +0 -28
  160. package/dist/check/runtime/paths.js.map +0 -1
  161. package/dist/check/runtime/pending.d.ts +0 -9
  162. package/dist/check/runtime/pending.d.ts.map +0 -1
  163. package/dist/check/runtime/pending.js +0 -5
  164. package/dist/check/runtime/pending.js.map +0 -1
  165. package/dist/check/runtime/tokens.d.ts +0 -7
  166. package/dist/check/runtime/tokens.d.ts.map +0 -1
  167. package/dist/check/runtime/tokens.js +0 -36
  168. package/dist/check/runtime/tokens.js.map +0 -1
  169. package/dist/check/traces/step-facts.d.ts +0 -3
  170. package/dist/check/traces/step-facts.d.ts.map +0 -1
  171. package/dist/check/traces/step-facts.js +0 -35
  172. package/dist/check/traces/step-facts.js.map +0 -1
  173. package/dist/check/traces/trace.d.ts +0 -13
  174. package/dist/check/traces/trace.d.ts.map +0 -1
  175. package/dist/check/traces/trace.js +0 -47
  176. package/dist/check/traces/trace.js.map +0 -1
  177. /package/dist/check/{engine/model-api.d.ts → model-api.d.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modality-ts",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Model-checking-based testing tools for React state-transition bugs.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -11,6 +11,7 @@
11
11
  "types": "./dist/cli/cli.d.ts",
12
12
  "files": [
13
13
  "dist/**",
14
+ "native/**",
14
15
  "README.md",
15
16
  "LICENSE"
16
17
  ],
@@ -126,14 +127,15 @@
126
127
  "packageManager": "pnpm@10.12.4",
127
128
  "scripts": {
128
129
  "architecture": "depcruise --config tools/depcruise.config.cjs src",
129
- "build": "tsc -b",
130
- "clean": "rm -rf dist tsconfig.tsbuildinfo",
130
+ "build:rust": "napi build --platform --release --cargo-cwd crates/checker native",
131
+ "build": "pnpm build:rust && tsc -b",
132
+ "clean": "rm -rf dist native tsconfig.tsbuildinfo && cargo clean --manifest-path crates/checker/Cargo.toml",
131
133
  "ci:examples": "tsx tools/examples-ci.ts",
132
134
  "demo": "vitest run test/modality/demo-acceptance.test.ts",
133
135
  "fix": "biome lint --write . && biome format --write .",
134
136
  "phase7": "tsx tools/phase7-differential.ts",
135
137
  "publish:npm": "npm publish --access public",
136
- "test": "vitest run",
138
+ "test": "pnpm build:rust && vitest run",
137
139
  "typecheck": "tsc -b"
138
140
  },
139
141
  "dependencies": {
@@ -142,9 +144,17 @@
142
144
  },
143
145
  "devDependencies": {
144
146
  "@biomejs/biome": "2.5.0",
147
+ "@napi-rs/cli": "^2.18.4",
145
148
  "@types/node": "^24.10.1",
146
149
  "dependency-cruiser": "^17.4.3",
147
150
  "tsx": "^4.20.6",
148
151
  "vitest": "^4.0.14"
152
+ },
153
+ "napi": {
154
+ "name": "modality-checker",
155
+ "triples": {
156
+ "defaults": true,
157
+ "additional": []
158
+ }
149
159
  }
150
160
  }
@@ -1,5 +0,0 @@
1
- import type { Model, ModelState, Transition } from "modality-ts/core";
2
- import { type TransitionIndex } from "../engine/transitions.js";
3
- export declare function recordMaxDepthBoundHits(model: Model, frontier: readonly ModelState[], enabledTransitionIds: Set<string>, boundHits: Set<string>, index?: TransitionIndex): void;
4
- export declare function effectContainsEnqueue(effect: Transition["effect"]): boolean;
5
- //# sourceMappingURL=bounds.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bounds.d.ts","sourceRoot":"","sources":["../../../src/check/diagnostics/bounds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,0BAA0B,CAAC;AAElC,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,SAAS,UAAU,EAAE,EAC/B,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,EACjC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,KAAK,CAAC,EAAE,eAAe,GACtB,IAAI,CAYN;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,OAAO,CAQ3E"}
@@ -1,25 +0,0 @@
1
- import { enabledTransitions, } from "../engine/transitions.js";
2
- export function recordMaxDepthBoundHits(model, frontier, enabledTransitionIds, boundHits, index) {
3
- if (frontier.length === 0)
4
- return;
5
- const blockedTransitions = new Set();
6
- for (const state of frontier) {
7
- for (const transition of enabledTransitions(model, state, index)) {
8
- enabledTransitionIds.add(transition.id);
9
- blockedTransitions.add(transition.id);
10
- }
11
- }
12
- for (const id of [...blockedTransitions].sort()) {
13
- boundHits.add(`maxDepth reached before ${id}`);
14
- }
15
- }
16
- export function effectContainsEnqueue(effect) {
17
- if (effect.kind === "enqueue")
18
- return true;
19
- if (effect.kind === "seq")
20
- return effect.effects.some(effectContainsEnqueue);
21
- if (effect.kind === "if")
22
- return (effectContainsEnqueue(effect.then) || effectContainsEnqueue(effect.else));
23
- return false;
24
- }
25
- //# sourceMappingURL=bounds.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bounds.js","sourceRoot":"","sources":["../../../src/check/diagnostics/bounds.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,GAEnB,MAAM,0BAA0B,CAAC;AAElC,MAAM,UAAU,uBAAuB,CACrC,KAAY,EACZ,QAA+B,EAC/B,oBAAiC,EACjC,SAAsB,EACtB,KAAuB;IAEvB,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAClC,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC7C,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,KAAK,MAAM,UAAU,IAAI,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;YACjE,oBAAoB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YACxC,kBAAkB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,kBAAkB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAChD,SAAS,CAAC,GAAG,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC;IACjD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAA4B;IAChE,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC7E,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;QACtB,OAAO,CACL,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,CACzE,CAAC;IACJ,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -1,3 +0,0 @@
1
- import type { Model, ModelState } from "modality-ts/core";
2
- export declare function vacuityWarnings(model: Model, states: Map<string, ModelState>, enabledTransitionIds: Set<string>): string[];
3
- //# sourceMappingURL=vacuity.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"vacuity.d.ts","sourceRoot":"","sources":["../../../src/check/diagnostics/vacuity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE1D,wBAAgB,eAAe,CAC7B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EAC/B,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,GAChC,MAAM,EAAE,CAuBV"}
@@ -1,22 +0,0 @@
1
- export function vacuityWarnings(model, states, enabledTransitionIds) {
2
- const warnings = [];
3
- for (const transition of model.transitions) {
4
- if (transition.cls !== "internal" &&
5
- !enabledTransitionIds.has(transition.id)) {
6
- warnings.push(`transition never enabled: ${transition.id}`);
7
- }
8
- }
9
- for (const decl of model.vars) {
10
- if (decl.domain.kind !== "enum")
11
- continue;
12
- const inhabited = new Set([...states.values()]
13
- .map((state) => state[decl.id])
14
- .filter((value) => typeof value === "string"));
15
- for (const value of decl.domain.values) {
16
- if (!inhabited.has(value))
17
- warnings.push(`enum value never inhabited: ${decl.id}=${value}`);
18
- }
19
- }
20
- return warnings.sort();
21
- }
22
- //# sourceMappingURL=vacuity.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"vacuity.js","sourceRoot":"","sources":["../../../src/check/diagnostics/vacuity.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAC7B,KAAY,EACZ,MAA+B,EAC/B,oBAAiC;IAEjC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QAC3C,IACE,UAAU,CAAC,GAAG,KAAK,UAAU;YAC7B,CAAC,oBAAoB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,EACxC,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC,6BAA6B,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM;YAAE,SAAS;QAC1C,MAAM,SAAS,GAAG,IAAI,GAAG,CACvB,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;aACjB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;aAC9B,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CACjE,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACvC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;gBACvB,QAAQ,CAAC,IAAI,CAAC,+BAA+B,IAAI,CAAC,EAAE,IAAI,KAAK,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC"}
@@ -1,7 +0,0 @@
1
- import type { Model, Property } from "modality-ts/core";
2
- import type { CheckOptions, CheckResult } from "../types.js";
3
- export declare function needsRecordedEdges(properties: readonly Property[]): boolean;
4
- export declare function needsReverseGraph(properties: readonly Property[]): boolean;
5
- export declare function needsStepMonitoring(properties: readonly Property[]): boolean;
6
- export declare function checkModel(model: Model, properties: readonly Property[], options?: CheckOptions): CheckResult;
7
- //# sourceMappingURL=check-model.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"check-model.d.ts","sourceRoot":"","sources":["../../../src/check/engine/check-model.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,KAAK,EAEL,QAAQ,EAGT,MAAM,kBAAkB,CAAC;AAe1B,OAAO,KAAK,EAEV,YAAY,EACZ,WAAW,EAOZ,MAAM,aAAa,CAAC;AAuBrB,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,SAAS,QAAQ,EAAE,GAAG,OAAO,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,SAAS,QAAQ,EAAE,GAAG,OAAO,CAE1E;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,SAAS,QAAQ,EAAE,GAAG,OAAO,CAE5E;AAqFD,wBAAgB,UAAU,CACxB,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,SAAS,QAAQ,EAAE,EAC/B,OAAO,GAAE,YAAiB,GACzB,WAAW,CAyBb"}
@@ -1,527 +0,0 @@
1
- import { canonicalState, validateModel } from "modality-ts/core";
2
- import { effectContainsEnqueue, recordMaxDepthBoundHits, } from "../diagnostics/bounds.js";
3
- import { vacuityWarnings } from "../diagnostics/vacuity.js";
4
- import { finalizeProperties } from "../properties/finalize.js";
5
- import { observeEdge, observeStates } from "../properties/observe.js";
6
- import { applyEffect } from "../runtime/effects.js";
7
- import { canSliceProperty, sliceModelForProperty, } from "../slicing/slice-model.js";
8
- import { facts } from "../traces/step-facts.js";
9
- import { initialStates } from "./initial-states.js";
10
- import { stabilize } from "./stabilize.js";
11
- import { changedVars, initialChangedVars, sortStatesByCanon, } from "./state-utils.js";
12
- import { buildTransitionIndex, enabledTransitions, installEnabledHook, } from "./transitions.js";
13
- export function needsRecordedEdges(properties) {
14
- return properties.some((property) => property.kind === "leadsToWithin");
15
- }
16
- export function needsReverseGraph(properties) {
17
- return properties.some((property) => property.kind === "reachableFrom");
18
- }
19
- export function needsStepMonitoring(properties) {
20
- return properties.some((property) => property.kind === "alwaysStep");
21
- }
22
- function resolveEdgeRecordingMode(properties) {
23
- if (needsRecordedEdges(properties))
24
- return "compact";
25
- if (needsReverseGraph(properties))
26
- return "reverse";
27
- return "none";
28
- }
29
- function createGraphRecording(mode) {
30
- return {
31
- mode,
32
- compactEdges: [],
33
- reverseEdges: [],
34
- fullEdges: [],
35
- };
36
- }
37
- function recordExploredEdge(graph, properties, preCanon, postCanon, pre, post, transition, step) {
38
- switch (graph.mode) {
39
- case "full":
40
- graph.fullEdges.push({
41
- preCanon,
42
- postCanon,
43
- pre,
44
- post,
45
- transition,
46
- step,
47
- });
48
- break;
49
- case "compact":
50
- graph.compactEdges.push({
51
- preCanon,
52
- postCanon,
53
- transitionId: transition.id,
54
- triggeredProperties: properties
55
- .filter((property) => property.kind === "leadsToWithin")
56
- .filter((property) => property.trigger(step))
57
- .map((property) => property.name),
58
- });
59
- break;
60
- case "reverse":
61
- graph.reverseEdges.push({ preCanon, postCanon });
62
- break;
63
- case "none":
64
- break;
65
- }
66
- }
67
- function buildStorageDiagnostics(parents, states, graph) {
68
- const recordedEdges = graph.mode === "none"
69
- ? 0
70
- : graph.mode === "reverse"
71
- ? graph.reverseEdges.length
72
- : graph.mode === "compact"
73
- ? graph.compactEdges.length
74
- : graph.fullEdges.length;
75
- return {
76
- recordedEdges,
77
- storedStates: states.size,
78
- parentEntries: parents.size,
79
- edgeRecordingMode: graph.mode,
80
- };
81
- }
82
- export function checkModel(model, properties, options = {}) {
83
- const slicingDiagnostics = buildSlicingRequestDiagnostics(properties, options.slicing === true);
84
- if (options.slicing &&
85
- properties.length > 0 &&
86
- properties.every((property) => property.reads !== undefined)) {
87
- const result = checkModelSliced(model, properties, options);
88
- return {
89
- ...result,
90
- diagnostics: mergeDiagnostics(result.diagnostics, {
91
- slicing: slicingDiagnostics,
92
- }),
93
- };
94
- }
95
- const result = checkModelCore(model, properties, options);
96
- return {
97
- ...result,
98
- diagnostics: mergeDiagnostics(result.diagnostics, {
99
- slicing: slicingDiagnostics,
100
- }),
101
- };
102
- }
103
- function buildSlicingRequestDiagnostics(properties, slicingRequested) {
104
- if (!slicingRequested) {
105
- return { enabled: false };
106
- }
107
- if (properties.length === 0) {
108
- return { enabled: false, skipped: true, skipReason: "no properties" };
109
- }
110
- if (!properties.every((property) => property.reads !== undefined)) {
111
- return {
112
- enabled: false,
113
- skipped: true,
114
- skipReason: "property missing reads",
115
- };
116
- }
117
- return { enabled: true };
118
- }
119
- function checkModelCore(model, properties, options = {}) {
120
- const validation = validateModel(model, { sliced: options.slicedModel });
121
- if (!validation.ok)
122
- return invalidModelResult(properties, validation.errors);
123
- const startedAt = options.trackElapsed ? Date.now() : undefined;
124
- installEnabledHook(model);
125
- const transitionIndex = buildTransitionIndex(model);
126
- const canonCache = new WeakMap();
127
- const canon = (state) => {
128
- const cached = canonCache.get(state);
129
- if (cached !== undefined)
130
- return cached;
131
- const encoded = canonicalState(model, state);
132
- canonCache.set(state, encoded);
133
- return encoded;
134
- };
135
- const parents = new Map();
136
- const states = new Map();
137
- const graph = createGraphRecording(resolveEdgeRecordingMode(properties));
138
- const traceCtx = { model, parents, states };
139
- const enabledTransitionIds = new Set();
140
- const boundHits = new Set();
141
- const tracker = createSearchTracker(model);
142
- let frontier = seedFrontier(model, parents, states, tracker, transitionIndex, canon);
143
- const verdicts = new Map();
144
- let depth = 0;
145
- let edgeCount = 0;
146
- observeStates(model, properties, frontier, traceCtx, verdicts);
147
- recordDominantVars(model, frontier, tracker);
148
- while (frontier.length > 0 &&
149
- depth < model.bounds.maxDepth &&
150
- tracker.limitHit === null) {
151
- tracker.maxFrontier = Math.max(tracker.maxFrontier, frontier.length);
152
- tracker.finalFrontier = frontier.length;
153
- const limit = checkSearchLimits(options, parents.size, edgeCount, frontier.length, depth);
154
- if (limit) {
155
- tracker.limitHit = limit;
156
- break;
157
- }
158
- const result = exploreDepth(model, properties, frontier, parents, states, graph, traceCtx, verdicts, enabledTransitionIds, boundHits, tracker, options, edgeCount, depth, transitionIndex, canon);
159
- frontier = result.next;
160
- edgeCount += result.edges;
161
- observeStates(model, properties, frontier, traceCtx, verdicts);
162
- recordDominantVars(model, frontier, tracker);
163
- depth += 1;
164
- tracker.expandedDepths = depth;
165
- options.onProgress?.({
166
- depth,
167
- frontier: frontier.length,
168
- nextFrontier: frontier.length,
169
- states: parents.size,
170
- edges: edgeCount,
171
- });
172
- const postLimit = checkSearchLimits(options, parents.size, edgeCount, frontier.length, depth);
173
- if (postLimit) {
174
- tracker.limitHit = postLimit;
175
- break;
176
- }
177
- }
178
- recordMaxDepthBoundHits(model, frontier, enabledTransitionIds, boundHits, transitionIndex);
179
- if (tracker.limitHit) {
180
- applySearchLimitVerdicts(properties, verdicts, tracker.limitHit);
181
- }
182
- else {
183
- finalizeProperties(model, properties, traceCtx, graph, verdicts);
184
- }
185
- return {
186
- verdicts: properties.map((property) => verdicts.get(property.name) ?? {
187
- status: "verified-within-bounds",
188
- property: property.name,
189
- }),
190
- stats: { states: parents.size, edges: edgeCount, depth },
191
- vacuityWarnings: vacuityWarnings(model, states, enabledTransitionIds),
192
- boundHits: [...boundHits].sort(),
193
- diagnostics: buildSearchDiagnostics(tracker, startedAt, buildStorageDiagnostics(parents, states, graph), transitionIndex),
194
- };
195
- }
196
- function invalidModelResult(properties, errors) {
197
- return {
198
- verdicts: properties.map((property) => ({
199
- status: "error",
200
- property: property.name,
201
- message: errors.join("; "),
202
- })),
203
- stats: { states: 0, edges: 0, depth: 0 },
204
- vacuityWarnings: [],
205
- boundHits: [],
206
- };
207
- }
208
- function createSearchTracker(model) {
209
- return {
210
- maxFrontier: 0,
211
- finalFrontier: 0,
212
- expandedDepths: 0,
213
- dominantVarValues: new Map(model.vars.map((decl) => [decl.id, new Set()])),
214
- limitHit: null,
215
- };
216
- }
217
- function recordDominantVars(model, frontier, tracker) {
218
- for (const state of frontier) {
219
- for (const decl of model.vars) {
220
- const value = JSON.stringify(state[decl.id]);
221
- tracker.dominantVarValues.get(decl.id)?.add(value);
222
- }
223
- }
224
- }
225
- function buildSearchDiagnostics(tracker, startedAt, storage, transitionIndex) {
226
- const dominantVars = [...tracker.dominantVarValues.entries()]
227
- .map(([varId, values]) => ({ varId, distinctValues: values.size }))
228
- .filter((entry) => entry.distinctValues > 0)
229
- .sort((left, right) => right.distinctValues - left.distinctValues)
230
- .slice(0, 5);
231
- const search = {
232
- maxFrontier: tracker.maxFrontier,
233
- finalFrontier: tracker.finalFrontier,
234
- expandedDepths: tracker.expandedDepths,
235
- };
236
- if (startedAt !== undefined) {
237
- search.elapsedMs = Date.now() - startedAt;
238
- }
239
- return {
240
- search,
241
- storage,
242
- hotPath: {
243
- canonicalCache: true,
244
- transitionIndex: true,
245
- internalTransitionIndex: transitionIndex.internalTransitions.length > 0,
246
- },
247
- ...(tracker.limitHit ? { limits: tracker.limitHit } : {}),
248
- ...(dominantVars.length > 0 ? { dominantVars } : {}),
249
- };
250
- }
251
- function hitSearchLimit(tracker, options, states, edges, frontier, depth) {
252
- if (tracker.limitHit !== null)
253
- return true;
254
- const limit = checkSearchLimits(options, states, edges, frontier, depth);
255
- if (!limit)
256
- return false;
257
- tracker.limitHit = limit;
258
- return true;
259
- }
260
- function checkSearchLimits(options, states, edges, frontier, depth) {
261
- if (options.maxStates !== undefined && states >= options.maxStates) {
262
- return {
263
- reason: `search limit exceeded: maxStates=${options.maxStates}`,
264
- maxStates: options.maxStates,
265
- };
266
- }
267
- if (options.maxEdges !== undefined && edges >= options.maxEdges) {
268
- return {
269
- reason: `search limit exceeded: maxEdges=${options.maxEdges}`,
270
- maxEdges: options.maxEdges,
271
- };
272
- }
273
- if (options.maxFrontier !== undefined && frontier >= options.maxFrontier) {
274
- return {
275
- reason: `search limit exceeded: maxFrontier=${options.maxFrontier}`,
276
- maxFrontier: options.maxFrontier,
277
- };
278
- }
279
- const maxHeap = options.memoryGuard?.maxHeapUsedBytes;
280
- if (maxHeap !== undefined && process.memoryUsage().heapUsed >= maxHeap) {
281
- return {
282
- reason: `search limit exceeded: memoryGuard=${maxHeap}`,
283
- memoryGuardBytes: maxHeap,
284
- };
285
- }
286
- void depth;
287
- return null;
288
- }
289
- function applySearchLimitVerdicts(properties, verdicts, limit) {
290
- for (const property of properties) {
291
- const verdict = verdicts.get(property.name);
292
- if (verdict &&
293
- (verdict.status === "violated" ||
294
- verdict.status === "reachable" ||
295
- verdict.status === "vacuous-warning" ||
296
- verdict.status === "error")) {
297
- continue;
298
- }
299
- verdicts.set(property.name, {
300
- status: "error",
301
- property: property.name,
302
- message: limit.reason,
303
- });
304
- }
305
- }
306
- function seedFrontier(model, parents, states, tracker, index, canon) {
307
- const frontier = sortStatesByCanon(initialStates(model).flatMap((state) => stabilize(model, state, initialChangedVars(model), index, canon)), canon);
308
- tracker.maxFrontier = Math.max(tracker.maxFrontier, frontier.length);
309
- tracker.finalFrontier = frontier.length;
310
- for (const state of frontier) {
311
- const key = canon(state);
312
- if (!parents.has(key)) {
313
- parents.set(key, { parent: null, transitionId: null });
314
- states.set(key, state);
315
- }
316
- }
317
- return frontier;
318
- }
319
- function exploreDepth(model, properties, frontier, parents, states, graph, traceCtx, verdicts, enabledTransitionIds, boundHits, tracker, options, startingEdgeCount, depth, index, canon) {
320
- const next = [];
321
- let edgeCount = 0;
322
- for (const pre of frontier) {
323
- if (tracker.limitHit !== null)
324
- break;
325
- const preCanon = canon(pre);
326
- for (const transition of enabledTransitions(model, pre, index)) {
327
- enabledTransitionIds.add(transition.id);
328
- const rawPosts = applyEffect(model, pre, transition.effect, {
329
- onBoundHit: (hit) => {
330
- boundHits.add(hit.startsWith("token cap exhausted")
331
- ? `token cap exhausted at ${transition.id}`
332
- : `${hit} at ${transition.id}`);
333
- },
334
- });
335
- if (rawPosts.length === 0 && effectContainsEnqueue(transition.effect)) {
336
- boundHits.add(`pending cap saturated at ${transition.id}`);
337
- }
338
- for (const rawPost of rawPosts) {
339
- for (const post of stabilize(model, rawPost, changedVars(pre, rawPost, model), index, canon)) {
340
- edgeCount += 1;
341
- const postCanon = canon(post);
342
- const step = facts(pre, post, transition);
343
- recordExploredEdge(graph, properties, preCanon, postCanon, pre, post, transition, step);
344
- observeEdge(model, properties, pre, post, transition, step, traceCtx, verdicts);
345
- if (hitSearchLimit(tracker, options, parents.size, startingEdgeCount + edgeCount, next.length, depth)) {
346
- break;
347
- }
348
- if (!parents.has(postCanon)) {
349
- parents.set(postCanon, {
350
- parent: preCanon,
351
- transitionId: transition.id,
352
- });
353
- states.set(postCanon, post);
354
- next.push(post);
355
- tracker.maxFrontier = Math.max(tracker.maxFrontier, next.length);
356
- if (hitSearchLimit(tracker, options, parents.size, startingEdgeCount + edgeCount, next.length, depth)) {
357
- break;
358
- }
359
- }
360
- }
361
- if (tracker.limitHit !== null)
362
- break;
363
- }
364
- if (tracker.limitHit !== null)
365
- break;
366
- }
367
- if (tracker.limitHit !== null)
368
- break;
369
- }
370
- return { next: sortStatesByCanon(next, canon), edges: edgeCount };
371
- }
372
- function checkModelSliced(model, properties, options = {}) {
373
- const groups = new Map();
374
- const sliceSummaries = [];
375
- let sliceIndex = 0;
376
- for (const property of properties) {
377
- const slice = canSliceProperty(property)
378
- ? sliceModelForProperty(model, property)
379
- : model;
380
- const key = slice.vars.map((decl) => decl.id).join("\0");
381
- const group = groups.get(key);
382
- if (group) {
383
- group.properties.push(property);
384
- }
385
- else {
386
- groups.set(key, {
387
- model: slice,
388
- properties: [property],
389
- index: sliceIndex,
390
- });
391
- sliceIndex += 1;
392
- }
393
- }
394
- const results = [...groups.values()].map((group) => {
395
- const result = checkModelCore(group.model, group.properties, {
396
- ...options,
397
- slicedModel: true,
398
- });
399
- sliceSummaries.push({
400
- index: group.index,
401
- properties: group.properties.map((property) => property.name),
402
- vars: group.model.vars.length,
403
- transitions: group.model.transitions.length,
404
- states: result.stats.states,
405
- edges: result.stats.edges,
406
- depth: result.stats.depth,
407
- });
408
- return result;
409
- });
410
- sliceSummaries.sort((left, right) => left.index - right.index);
411
- const combined = combineSlicedResults(properties, results);
412
- return {
413
- ...combined,
414
- diagnostics: mergeDiagnostics(combined.diagnostics, {
415
- slicing: {
416
- enabled: true,
417
- slices: sliceSummaries.length,
418
- sliceSummaries,
419
- },
420
- search: combined.diagnostics?.search,
421
- limits: combined.diagnostics?.limits,
422
- dominantVars: combined.diagnostics?.dominantVars,
423
- }),
424
- };
425
- }
426
- function combineSlicedResults(properties, results) {
427
- const verdicts = new Map();
428
- let states = 0;
429
- let edges = 0;
430
- let depth = 0;
431
- const vacuity = new Set();
432
- const boundHits = new Set();
433
- let diagnostics;
434
- for (const result of results) {
435
- for (const verdict of result.verdicts)
436
- verdicts.set(verdict.property, verdict);
437
- for (const warning of result.vacuityWarnings)
438
- vacuity.add(warning);
439
- for (const hit of result.boundHits)
440
- boundHits.add(hit);
441
- states += result.stats.states;
442
- edges += result.stats.edges;
443
- depth = Math.max(depth, result.stats.depth);
444
- diagnostics = mergeSearchDiagnostics(diagnostics, result.diagnostics);
445
- }
446
- return {
447
- verdicts: properties.map((property) => verdicts.get(property.name) ?? {
448
- status: "error",
449
- property: property.name,
450
- message: "missing sliced verdict",
451
- }),
452
- stats: { states, edges, depth },
453
- vacuityWarnings: [...vacuity].sort(),
454
- boundHits: [...boundHits].sort(),
455
- diagnostics,
456
- };
457
- }
458
- function mergeDiagnostics(base, overlay) {
459
- if (!base && !overlay)
460
- return undefined;
461
- return {
462
- ...base,
463
- ...overlay,
464
- slicing: base?.slicing || overlay?.slicing
465
- ? {
466
- enabled: overlay?.slicing?.enabled ?? base?.slicing?.enabled ?? false,
467
- slices: overlay?.slicing?.slices ?? base?.slicing?.slices,
468
- skipped: overlay?.slicing?.skipped ?? base?.slicing?.skipped,
469
- skipReason: overlay?.slicing?.skipReason ?? base?.slicing?.skipReason,
470
- sliceSummaries: overlay?.slicing?.sliceSummaries ?? base?.slicing?.sliceSummaries,
471
- }
472
- : undefined,
473
- search: overlay?.search ?? base?.search,
474
- limits: overlay?.limits ?? base?.limits,
475
- dominantVars: overlay?.dominantVars ?? base?.dominantVars,
476
- storage: overlay?.storage ?? base?.storage,
477
- hotPath: overlay?.hotPath ?? base?.hotPath,
478
- };
479
- }
480
- function mergeSearchDiagnostics(left, right) {
481
- if (!left)
482
- return right;
483
- if (!right)
484
- return left;
485
- const dominant = mergeDominantVars(left.dominantVars, right.dominantVars);
486
- return {
487
- slicing: left.slicing ?? right.slicing,
488
- search: {
489
- maxFrontier: Math.max(left.search?.maxFrontier ?? 0, right.search?.maxFrontier ?? 0),
490
- finalFrontier: Math.max(left.search?.finalFrontier ?? 0, right.search?.finalFrontier ?? 0),
491
- expandedDepths: Math.max(left.search?.expandedDepths ?? 0, right.search?.expandedDepths ?? 0),
492
- elapsedMs: left.search?.elapsedMs !== undefined ||
493
- right.search?.elapsedMs !== undefined
494
- ? (left.search?.elapsedMs ?? 0) + (right.search?.elapsedMs ?? 0)
495
- : undefined,
496
- },
497
- limits: left.limits ?? right.limits,
498
- dominantVars: dominant,
499
- storage: mergeStorageDiagnostics(left.storage, right.storage),
500
- hotPath: left.hotPath ?? right.hotPath,
501
- };
502
- }
503
- function mergeStorageDiagnostics(left, right) {
504
- if (!left)
505
- return right;
506
- if (!right)
507
- return left;
508
- return {
509
- recordedEdges: left.recordedEdges + right.recordedEdges,
510
- storedStates: left.storedStates + right.storedStates,
511
- parentEntries: left.parentEntries + right.parentEntries,
512
- edgeRecordingMode: left.edgeRecordingMode === right.edgeRecordingMode
513
- ? left.edgeRecordingMode
514
- : "property-specific",
515
- };
516
- }
517
- function mergeDominantVars(left, right) {
518
- const counts = new Map();
519
- for (const entry of [...(left ?? []), ...(right ?? [])]) {
520
- counts.set(entry.varId, Math.max(counts.get(entry.varId) ?? 0, entry.distinctValues));
521
- }
522
- return [...counts.entries()]
523
- .map(([varId, distinctValues]) => ({ varId, distinctValues }))
524
- .sort((a, b) => b.distinctValues - a.distinctValues)
525
- .slice(0, 5);
526
- }
527
- //# sourceMappingURL=check-model.js.map