modality-ts 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/check/diagnostics/bounds.d.ts +2 -1
- package/dist/check/diagnostics/bounds.d.ts.map +1 -1
- package/dist/check/diagnostics/bounds.js +3 -3
- package/dist/check/diagnostics/bounds.js.map +1 -1
- package/dist/check/engine/check-model.d.ts +3 -0
- package/dist/check/engine/check-model.d.ts.map +1 -1
- package/dist/check/engine/check-model.js +408 -43
- package/dist/check/engine/check-model.js.map +1 -1
- package/dist/check/engine/model-api.d.ts.map +1 -1
- package/dist/check/engine/model-api.js +5 -3
- package/dist/check/engine/model-api.js.map +1 -1
- package/dist/check/engine/stabilize.d.ts +2 -1
- package/dist/check/engine/stabilize.d.ts.map +1 -1
- package/dist/check/engine/stabilize.js +31 -9
- package/dist/check/engine/stabilize.js.map +1 -1
- package/dist/check/engine/state-utils.d.ts +3 -2
- package/dist/check/engine/state-utils.d.ts.map +1 -1
- package/dist/check/engine/state-utils.js +20 -7
- package/dist/check/engine/state-utils.js.map +1 -1
- package/dist/check/engine/transitions.d.ts +9 -1
- package/dist/check/engine/transitions.d.ts.map +1 -1
- package/dist/check/engine/transitions.js +32 -5
- package/dist/check/engine/transitions.js.map +1 -1
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.d.ts.map +1 -1
- package/dist/check/index.js.map +1 -1
- package/dist/check/properties/finalize.d.ts +4 -3
- package/dist/check/properties/finalize.d.ts.map +1 -1
- package/dist/check/properties/finalize.js +45 -9
- package/dist/check/properties/finalize.js.map +1 -1
- package/dist/check/properties/leads-to.d.ts.map +1 -1
- package/dist/check/properties/leads-to.js +4 -3
- package/dist/check/properties/leads-to.js.map +1 -1
- package/dist/check/properties/observe.d.ts +4 -3
- package/dist/check/properties/observe.d.ts.map +1 -1
- package/dist/check/properties/observe.js +5 -5
- package/dist/check/properties/observe.js.map +1 -1
- package/dist/check/properties/reachable-from.d.ts +4 -2
- package/dist/check/properties/reachable-from.d.ts.map +1 -1
- package/dist/check/properties/reachable-from.js +2 -2
- package/dist/check/properties/reachable-from.js.map +1 -1
- package/dist/check/slicing/slice-model.d.ts +1 -0
- package/dist/check/slicing/slice-model.d.ts.map +1 -1
- package/dist/check/slicing/slice-model.js +38 -12
- package/dist/check/slicing/slice-model.js.map +1 -1
- package/dist/check/traces/trace.d.ts +7 -2
- package/dist/check/traces/trace.d.ts.map +1 -1
- package/dist/check/traces/trace.js +9 -4
- package/dist/check/traces/trace.js.map +1 -1
- package/dist/check/types.d.ts +83 -3
- package/dist/check/types.d.ts.map +1 -1
- package/dist/cli/cli.js +110 -13
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/defaults.d.ts +12 -0
- package/dist/cli/defaults.d.ts.map +1 -1
- package/dist/cli/defaults.js +19 -3
- package/dist/cli/defaults.js.map +1 -1
- package/dist/cli/features/check/command.d.ts +6 -0
- package/dist/cli/features/check/command.d.ts.map +1 -1
- package/dist/cli/features/check/command.js +74 -1
- package/dist/cli/features/check/command.js.map +1 -1
- package/dist/cli/features/conform/command.d.ts.map +1 -1
- package/dist/cli/features/conform/command.js +3 -1
- package/dist/cli/features/conform/command.js.map +1 -1
- package/dist/cli/features/export/command.js +12 -4
- package/dist/cli/features/export/command.js.map +1 -1
- package/dist/cli/features/extract/command.d.ts.map +1 -1
- package/dist/cli/features/extract/command.js +302 -92
- package/dist/cli/features/extract/command.js.map +1 -1
- package/dist/cli/features/init/command.js +1 -1
- package/dist/cli/features/init/command.js.map +1 -1
- package/dist/cli/harness/index.d.ts.map +1 -1
- package/dist/cli/harness/index.js +17 -4
- package/dist/cli/harness/index.js.map +1 -1
- package/dist/cli/registry/index.d.ts +4 -4
- package/dist/cli/registry/index.d.ts.map +1 -1
- package/dist/cli/registry/index.js +6 -4
- package/dist/cli/registry/index.js.map +1 -1
- package/dist/core/ir/domains.d.ts +2 -0
- package/dist/core/ir/domains.d.ts.map +1 -1
- package/dist/core/ir/domains.js +71 -0
- package/dist/core/ir/domains.js.map +1 -1
- package/dist/core/ir/validator.d.ts +4 -1
- package/dist/core/ir/validator.d.ts.map +1 -1
- package/dist/core/ir/validator.js +45 -12
- package/dist/core/ir/validator.js.map +1 -1
- package/dist/core/props/index.d.ts.map +1 -1
- package/dist/core/props/index.js.map +1 -1
- package/dist/core/report/types.d.ts +68 -0
- package/dist/core/report/types.d.ts.map +1 -1
- package/dist/extract/engine/pipeline/index.d.ts +3 -1
- package/dist/extract/engine/pipeline/index.d.ts.map +1 -1
- package/dist/extract/engine/pipeline/index.js +10 -23
- package/dist/extract/engine/pipeline/index.js.map +1 -1
- package/dist/extract/engine/spi/index.d.ts +38 -7
- package/dist/extract/engine/spi/index.d.ts.map +1 -1
- package/dist/extract/engine/spi/index.js +1 -1
- package/dist/extract/engine/spi/index.js.map +1 -1
- package/dist/extract/engine/ts/domains.d.ts +1 -1
- package/dist/extract/engine/ts/domains.d.ts.map +1 -1
- package/dist/extract/engine/ts/domains.js +25 -17
- package/dist/extract/engine/ts/domains.js.map +1 -1
- package/dist/extract/engine/ts/react-source-transitions.d.ts +2 -1
- package/dist/extract/engine/ts/react-source-transitions.d.ts.map +1 -1
- package/dist/extract/engine/ts/react-source-transitions.js +17 -7
- package/dist/extract/engine/ts/react-source-transitions.js.map +1 -1
- package/dist/extract/engine/ts/routes.d.ts +2 -2
- package/dist/extract/engine/ts/routes.d.ts.map +1 -1
- package/dist/extract/engine/ts/routes.js +5 -24
- package/dist/extract/engine/ts/routes.js.map +1 -1
- package/dist/extract/engine/ts/static-navigation.d.ts +2 -1
- package/dist/extract/engine/ts/static-navigation.d.ts.map +1 -1
- package/dist/extract/engine/ts/static-navigation.js +30 -20
- package/dist/extract/engine/ts/static-navigation.js.map +1 -1
- package/dist/extract/engine/ts/transition/async.d.ts +2 -10
- package/dist/extract/engine/ts/transition/async.d.ts.map +1 -1
- package/dist/extract/engine/ts/transition/async.js +30 -61
- package/dist/extract/engine/ts/transition/async.js.map +1 -1
- package/dist/extract/engine/ts/transition/effects.d.ts.map +1 -1
- package/dist/extract/engine/ts/transition/effects.js +7 -4
- package/dist/extract/engine/ts/transition/effects.js.map +1 -1
- package/dist/extract/engine/ts/transition/expressions.d.ts.map +1 -1
- package/dist/extract/engine/ts/transition/expressions.js +3 -1
- package/dist/extract/engine/ts/transition/expressions.js.map +1 -1
- package/dist/extract/engine/ts/transition/guards.d.ts.map +1 -1
- package/dist/extract/engine/ts/transition/guards.js +1 -1
- package/dist/extract/engine/ts/transition/guards.js.map +1 -1
- package/dist/extract/engine/ts/transition/handlers.d.ts.map +1 -1
- package/dist/extract/engine/ts/transition/handlers.js +7 -5
- package/dist/extract/engine/ts/transition/handlers.js.map +1 -1
- package/dist/extract/engine/ts/transition/locals.d.ts.map +1 -1
- package/dist/extract/engine/ts/transition/locals.js +2 -1
- package/dist/extract/engine/ts/transition/locals.js.map +1 -1
- package/dist/extract/engine/ts/transition/navigation.d.ts +7 -5
- package/dist/extract/engine/ts/transition/navigation.d.ts.map +1 -1
- package/dist/extract/engine/ts/transition/navigation.js +85 -41
- package/dist/extract/engine/ts/transition/navigation.js.map +1 -1
- package/dist/extract/engine/ts/transition/plugin-calls.d.ts.map +1 -1
- package/dist/extract/engine/ts/transition/plugin-calls.js +1 -1
- package/dist/extract/engine/ts/transition/plugin-calls.js.map +1 -1
- package/dist/extract/engine/ts/transition/timers.d.ts.map +1 -1
- package/dist/extract/engine/ts/transition/timers.js +5 -3
- package/dist/extract/engine/ts/transition/timers.js.map +1 -1
- package/dist/extract/sources/jotai/domains.d.ts +2 -1
- package/dist/extract/sources/jotai/domains.d.ts.map +1 -1
- package/dist/extract/sources/jotai/domains.js +2 -164
- package/dist/extract/sources/jotai/domains.js.map +1 -1
- package/dist/extract/sources/jotai/harness.d.ts.map +1 -1
- package/dist/extract/sources/jotai/harness.js +5 -2
- package/dist/extract/sources/jotai/harness.js.map +1 -1
- package/dist/extract/sources/jotai/transitions.d.ts.map +1 -1
- package/dist/extract/sources/jotai/transitions.js +1 -4
- package/dist/extract/sources/jotai/transitions.js.map +1 -1
- package/dist/extract/sources/router/discover.d.ts +9 -0
- package/dist/extract/sources/router/discover.d.ts.map +1 -0
- package/dist/extract/sources/router/discover.js +135 -0
- package/dist/extract/sources/router/discover.js.map +1 -0
- package/dist/extract/sources/router/index.d.ts +7 -3
- package/dist/extract/sources/router/index.d.ts.map +1 -1
- package/dist/extract/sources/router/index.js +21 -12
- package/dist/extract/sources/router/index.js.map +1 -1
- package/dist/extract/sources/router/navigation.d.ts +3 -4
- package/dist/extract/sources/router/navigation.d.ts.map +1 -1
- package/dist/extract/sources/router/navigation.js +33 -1
- package/dist/extract/sources/router/navigation.js.map +1 -1
- package/dist/extract/sources/router/redirects.d.ts +4 -0
- package/dist/extract/sources/router/redirects.d.ts.map +1 -0
- package/dist/extract/sources/router/redirects.js +37 -0
- package/dist/extract/sources/router/redirects.js.map +1 -0
- package/dist/extract/sources/router/routes.d.ts +2 -2
- package/dist/extract/sources/router/routes.d.ts.map +1 -1
- package/dist/extract/sources/router/routes.js +24 -6
- package/dist/extract/sources/router/routes.js.map +1 -1
- package/dist/extract/sources/shared/react-transition-extract.d.ts +1 -0
- package/dist/extract/sources/shared/react-transition-extract.d.ts.map +1 -1
- package/dist/extract/sources/shared/react-transition-extract.js +1 -0
- package/dist/extract/sources/shared/react-transition-extract.js.map +1 -1
- package/dist/extract/sources/swr/domains.d.ts +3 -2
- package/dist/extract/sources/swr/domains.d.ts.map +1 -1
- package/dist/extract/sources/swr/domains.js +3 -96
- package/dist/extract/sources/swr/domains.js.map +1 -1
- package/dist/extract/sources/swr/harness.d.ts.map +1 -1
- package/dist/extract/sources/swr/harness.js +13 -6
- package/dist/extract/sources/swr/harness.js.map +1 -1
- package/dist/extract/sources/swr/template.js +1 -1
- package/dist/extract/sources/swr/template.js.map +1 -1
- package/dist/extract/sources/swr/transitions.d.ts.map +1 -1
- package/dist/extract/sources/swr/transitions.js.map +1 -1
- package/dist/extract/sources/swr/writes.d.ts.map +1 -1
- package/dist/extract/sources/swr/writes.js +1 -1
- package/dist/extract/sources/swr/writes.js.map +1 -1
- package/dist/extract/sources/use-state/harness.d.ts.map +1 -1
- package/dist/extract/sources/use-state/harness.js +5 -2
- package/dist/extract/sources/use-state/harness.js.map +1 -1
- package/dist/extract/sources/use-state/index.d.ts.map +1 -1
- package/dist/extract/sources/use-state/index.js +1 -166
- package/dist/extract/sources/use-state/index.js.map +1 -1
- package/dist/extract/sources/use-state/transitions.d.ts.map +1 -1
- package/dist/extract/sources/use-state/transitions.js +1 -0
- package/dist/extract/sources/use-state/transitions.js.map +1 -1
- package/dist/extract/sources/use-state/types.d.ts +1 -0
- package/dist/extract/sources/use-state/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,6 +36,8 @@ Check the extracted model against a property file:
|
|
|
36
36
|
modality check
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
`modality check` applies conservative default search limits (`--max-states`, `--max-edges`, `--max-frontier`, `--memory-guard-mb`). Use `--no-search-limits` for intentionally unbounded runs.
|
|
40
|
+
|
|
39
41
|
When a property fails, replay the generated counterexample trace:
|
|
40
42
|
|
|
41
43
|
```bash
|
|
@@ -53,7 +55,7 @@ Useful commands:
|
|
|
53
55
|
```bash
|
|
54
56
|
modality init
|
|
55
57
|
modality extract [source.tsx ...]
|
|
56
|
-
modality check [model.json] [props.mjs ...]
|
|
58
|
+
modality check [model.json] [props.mjs ...] [--max-states N] [--max-edges N] [--max-frontier N] [--memory-guard-mb N] [--no-search-limits]
|
|
57
59
|
modality replay <trace.json>
|
|
58
60
|
modality conform --count 8 --depth 4
|
|
59
61
|
modality export
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Model, ModelState, Transition } from "modality-ts/core";
|
|
2
|
-
|
|
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;
|
|
3
4
|
export declare function effectContainsEnqueue(effect: Transition["effect"]): boolean;
|
|
4
5
|
//# sourceMappingURL=bounds.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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,10 +1,10 @@
|
|
|
1
|
-
import { enabledTransitions } from "../engine/transitions.js";
|
|
2
|
-
export function recordMaxDepthBoundHits(model, frontier, enabledTransitionIds, boundHits) {
|
|
1
|
+
import { enabledTransitions, } from "../engine/transitions.js";
|
|
2
|
+
export function recordMaxDepthBoundHits(model, frontier, enabledTransitionIds, boundHits, index) {
|
|
3
3
|
if (frontier.length === 0)
|
|
4
4
|
return;
|
|
5
5
|
const blockedTransitions = new Set();
|
|
6
6
|
for (const state of frontier) {
|
|
7
|
-
for (const transition of enabledTransitions(model, state)) {
|
|
7
|
+
for (const transition of enabledTransitions(model, state, index)) {
|
|
8
8
|
enabledTransitionIds.add(transition.id);
|
|
9
9
|
blockedTransitions.add(transition.id);
|
|
10
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bounds.js","sourceRoot":"","sources":["../../../src/check/diagnostics/bounds.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
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,4 +1,7 @@
|
|
|
1
1
|
import type { Model, Property } from "modality-ts/core";
|
|
2
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;
|
|
3
6
|
export declare function checkModel(model: Model, properties: readonly Property[], options?: CheckOptions): CheckResult;
|
|
4
7
|
//# sourceMappingURL=check-model.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-model.d.ts","sourceRoot":"","sources":["../../../src/check/engine/check-model.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
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"}
|
|
@@ -4,44 +4,184 @@ import { vacuityWarnings } from "../diagnostics/vacuity.js";
|
|
|
4
4
|
import { finalizeProperties } from "../properties/finalize.js";
|
|
5
5
|
import { observeEdge, observeStates } from "../properties/observe.js";
|
|
6
6
|
import { applyEffect } from "../runtime/effects.js";
|
|
7
|
-
import { sliceModelForProperty } from "../slicing/slice-model.js";
|
|
7
|
+
import { canSliceProperty, sliceModelForProperty, } from "../slicing/slice-model.js";
|
|
8
8
|
import { facts } from "../traces/step-facts.js";
|
|
9
9
|
import { initialStates } from "./initial-states.js";
|
|
10
10
|
import { stabilize } from "./stabilize.js";
|
|
11
|
-
import { changedVars,
|
|
12
|
-
import { enabledTransitions, installEnabledHook } from "./transitions.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
|
+
}
|
|
13
82
|
export function checkModel(model, properties, options = {}) {
|
|
83
|
+
const slicingDiagnostics = buildSlicingRequestDiagnostics(properties, options.slicing === true);
|
|
14
84
|
if (options.slicing &&
|
|
15
85
|
properties.length > 0 &&
|
|
16
86
|
properties.every((property) => property.reads !== undefined)) {
|
|
17
|
-
|
|
87
|
+
const result = checkModelSliced(model, properties, options);
|
|
88
|
+
return {
|
|
89
|
+
...result,
|
|
90
|
+
diagnostics: mergeDiagnostics(result.diagnostics, {
|
|
91
|
+
slicing: slicingDiagnostics,
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
18
94
|
}
|
|
19
|
-
|
|
95
|
+
const result = checkModelCore(model, properties, options);
|
|
96
|
+
return {
|
|
97
|
+
...result,
|
|
98
|
+
diagnostics: mergeDiagnostics(result.diagnostics, {
|
|
99
|
+
slicing: slicingDiagnostics,
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
20
102
|
}
|
|
21
|
-
function
|
|
22
|
-
|
|
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 });
|
|
23
121
|
if (!validation.ok)
|
|
24
122
|
return invalidModelResult(properties, validation.errors);
|
|
123
|
+
const startedAt = options.trackElapsed ? Date.now() : undefined;
|
|
25
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
|
+
};
|
|
26
135
|
const parents = new Map();
|
|
27
136
|
const states = new Map();
|
|
28
|
-
const
|
|
137
|
+
const graph = createGraphRecording(resolveEdgeRecordingMode(properties));
|
|
138
|
+
const traceCtx = { model, parents, states };
|
|
29
139
|
const enabledTransitionIds = new Set();
|
|
30
140
|
const boundHits = new Set();
|
|
31
|
-
|
|
141
|
+
const tracker = createSearchTracker(model);
|
|
142
|
+
let frontier = seedFrontier(model, parents, states, tracker, transitionIndex, canon);
|
|
32
143
|
const verdicts = new Map();
|
|
33
144
|
let depth = 0;
|
|
34
145
|
let edgeCount = 0;
|
|
35
|
-
observeStates(model, properties, frontier,
|
|
36
|
-
|
|
37
|
-
|
|
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);
|
|
38
159
|
frontier = result.next;
|
|
39
160
|
edgeCount += result.edges;
|
|
40
|
-
observeStates(model, properties, frontier,
|
|
161
|
+
observeStates(model, properties, frontier, traceCtx, verdicts);
|
|
162
|
+
recordDominantVars(model, frontier, tracker);
|
|
41
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);
|
|
42
184
|
}
|
|
43
|
-
recordMaxDepthBoundHits(model, frontier, enabledTransitionIds, boundHits);
|
|
44
|
-
finalizeProperties(model, properties, parents, states, edges, verdicts);
|
|
45
185
|
return {
|
|
46
186
|
verdicts: properties.map((property) => verdicts.get(property.name) ?? {
|
|
47
187
|
status: "verified-within-bounds",
|
|
@@ -50,6 +190,7 @@ function checkModelCore(model, properties) {
|
|
|
50
190
|
stats: { states: parents.size, edges: edgeCount, depth },
|
|
51
191
|
vacuityWarnings: vacuityWarnings(model, states, enabledTransitionIds),
|
|
52
192
|
boundHits: [...boundHits].sort(),
|
|
193
|
+
diagnostics: buildSearchDiagnostics(tracker, startedAt, buildStorageDiagnostics(parents, states, graph), transitionIndex),
|
|
53
194
|
};
|
|
54
195
|
}
|
|
55
196
|
function invalidModelResult(properties, errors) {
|
|
@@ -64,30 +205,125 @@ function invalidModelResult(properties, errors) {
|
|
|
64
205
|
boundHits: [],
|
|
65
206
|
};
|
|
66
207
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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) {
|
|
71
218
|
for (const state of frontier) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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);
|
|
81
315
|
}
|
|
82
316
|
}
|
|
83
317
|
return frontier;
|
|
84
318
|
}
|
|
85
|
-
function exploreDepth(model, properties, frontier, parents, states,
|
|
319
|
+
function exploreDepth(model, properties, frontier, parents, states, graph, traceCtx, verdicts, enabledTransitionIds, boundHits, tracker, options, startingEdgeCount, depth, index, canon) {
|
|
86
320
|
const next = [];
|
|
87
321
|
let edgeCount = 0;
|
|
88
322
|
for (const pre of frontier) {
|
|
89
|
-
|
|
90
|
-
|
|
323
|
+
if (tracker.limitHit !== null)
|
|
324
|
+
break;
|
|
325
|
+
const preCanon = canon(pre);
|
|
326
|
+
for (const transition of enabledTransitions(model, pre, index)) {
|
|
91
327
|
enabledTransitionIds.add(transition.id);
|
|
92
328
|
const rawPosts = applyEffect(model, pre, transition.effect, {
|
|
93
329
|
onBoundHit: (hit) => {
|
|
@@ -100,35 +336,92 @@ function exploreDepth(model, properties, frontier, parents, states, edges, verdi
|
|
|
100
336
|
boundHits.add(`pending cap saturated at ${transition.id}`);
|
|
101
337
|
}
|
|
102
338
|
for (const rawPost of rawPosts) {
|
|
103
|
-
for (const post of stabilize(model, rawPost, changedVars(pre, rawPost))) {
|
|
339
|
+
for (const post of stabilize(model, rawPost, changedVars(pre, rawPost, model), index, canon)) {
|
|
104
340
|
edgeCount += 1;
|
|
105
|
-
const postCanon =
|
|
341
|
+
const postCanon = canon(post);
|
|
106
342
|
const step = facts(pre, post, transition);
|
|
107
|
-
|
|
108
|
-
observeEdge(model, properties, pre, post, transition, step,
|
|
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
|
+
}
|
|
109
348
|
if (!parents.has(postCanon)) {
|
|
110
|
-
parents.set(postCanon, {
|
|
349
|
+
parents.set(postCanon, {
|
|
350
|
+
parent: preCanon,
|
|
351
|
+
transitionId: transition.id,
|
|
352
|
+
});
|
|
111
353
|
states.set(postCanon, post);
|
|
112
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
|
+
}
|
|
113
359
|
}
|
|
114
360
|
}
|
|
361
|
+
if (tracker.limitHit !== null)
|
|
362
|
+
break;
|
|
115
363
|
}
|
|
364
|
+
if (tracker.limitHit !== null)
|
|
365
|
+
break;
|
|
116
366
|
}
|
|
367
|
+
if (tracker.limitHit !== null)
|
|
368
|
+
break;
|
|
117
369
|
}
|
|
118
|
-
return { next: next
|
|
370
|
+
return { next: sortStatesByCanon(next, canon), edges: edgeCount };
|
|
119
371
|
}
|
|
120
|
-
function checkModelSliced(model, properties) {
|
|
372
|
+
function checkModelSliced(model, properties, options = {}) {
|
|
121
373
|
const groups = new Map();
|
|
374
|
+
const sliceSummaries = [];
|
|
375
|
+
let sliceIndex = 0;
|
|
122
376
|
for (const property of properties) {
|
|
123
|
-
const slice =
|
|
377
|
+
const slice = canSliceProperty(property)
|
|
378
|
+
? sliceModelForProperty(model, property)
|
|
379
|
+
: model;
|
|
124
380
|
const key = slice.vars.map((decl) => decl.id).join("\0");
|
|
125
381
|
const group = groups.get(key);
|
|
126
|
-
if (group)
|
|
382
|
+
if (group) {
|
|
127
383
|
group.properties.push(property);
|
|
128
|
-
|
|
129
|
-
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
groups.set(key, {
|
|
387
|
+
model: slice,
|
|
388
|
+
properties: [property],
|
|
389
|
+
index: sliceIndex,
|
|
390
|
+
});
|
|
391
|
+
sliceIndex += 1;
|
|
392
|
+
}
|
|
130
393
|
}
|
|
131
|
-
|
|
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
|
+
};
|
|
132
425
|
}
|
|
133
426
|
function combineSlicedResults(properties, results) {
|
|
134
427
|
const verdicts = new Map();
|
|
@@ -137,6 +430,7 @@ function combineSlicedResults(properties, results) {
|
|
|
137
430
|
let depth = 0;
|
|
138
431
|
const vacuity = new Set();
|
|
139
432
|
const boundHits = new Set();
|
|
433
|
+
let diagnostics;
|
|
140
434
|
for (const result of results) {
|
|
141
435
|
for (const verdict of result.verdicts)
|
|
142
436
|
verdicts.set(verdict.property, verdict);
|
|
@@ -147,6 +441,7 @@ function combineSlicedResults(properties, results) {
|
|
|
147
441
|
states += result.stats.states;
|
|
148
442
|
edges += result.stats.edges;
|
|
149
443
|
depth = Math.max(depth, result.stats.depth);
|
|
444
|
+
diagnostics = mergeSearchDiagnostics(diagnostics, result.diagnostics);
|
|
150
445
|
}
|
|
151
446
|
return {
|
|
152
447
|
verdicts: properties.map((property) => verdicts.get(property.name) ?? {
|
|
@@ -157,6 +452,76 @@ function combineSlicedResults(properties, results) {
|
|
|
157
452
|
stats: { states, edges, depth },
|
|
158
453
|
vacuityWarnings: [...vacuity].sort(),
|
|
159
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,
|
|
160
478
|
};
|
|
161
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
|
+
}
|
|
162
527
|
//# sourceMappingURL=check-model.js.map
|