@demlik/tea 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +11 -2
  2. package/dist/agent/index.d.ts +36 -23
  3. package/dist/agent/index.js +12 -10
  4. package/dist/{chunk-UHAQ4PI6.js → chunk-2A5XTKQQ.js} +205 -59
  5. package/dist/chunk-2A5XTKQQ.js.map +1 -0
  6. package/dist/{chunk-JAKM53JF.js → chunk-7K7SEYCH.js} +16 -15
  7. package/dist/chunk-7K7SEYCH.js.map +1 -0
  8. package/dist/{chunk-RKVUVALV.js → chunk-A3QPMX5M.js} +31 -9
  9. package/dist/chunk-A3QPMX5M.js.map +1 -0
  10. package/dist/{chunk-DVNXEB7V.js → chunk-BVFS3ICC.js} +3 -3
  11. package/dist/chunk-BVFS3ICC.js.map +1 -0
  12. package/dist/{chunk-FTCYY2DO.js → chunk-CPE5ZQXQ.js} +3 -3
  13. package/dist/{chunk-FTCYY2DO.js.map → chunk-CPE5ZQXQ.js.map} +1 -1
  14. package/dist/chunk-DEAPNMKO.js +11 -0
  15. package/dist/chunk-DEAPNMKO.js.map +1 -0
  16. package/dist/{chunk-XWIE5V7R.js → chunk-ETRNSJCR.js} +4 -4
  17. package/dist/{chunk-XWIE5V7R.js.map → chunk-ETRNSJCR.js.map} +1 -1
  18. package/dist/chunk-FXWDDNGD.js +98 -0
  19. package/dist/chunk-FXWDDNGD.js.map +1 -0
  20. package/dist/chunk-GFGOQIEC.js +10 -0
  21. package/dist/chunk-GFGOQIEC.js.map +1 -0
  22. package/dist/{chunk-2R62Q5UK.js → chunk-IAHEYPYJ.js} +3 -3
  23. package/dist/{chunk-2R62Q5UK.js.map → chunk-IAHEYPYJ.js.map} +1 -1
  24. package/dist/{chunk-NGYFBD5G.js → chunk-J5JNLXO7.js} +3 -3
  25. package/dist/{chunk-NGYFBD5G.js.map → chunk-J5JNLXO7.js.map} +1 -1
  26. package/dist/{chunk-72CN3VPY.js → chunk-NFOGYDRS.js} +5 -5
  27. package/dist/{chunk-72CN3VPY.js.map → chunk-NFOGYDRS.js.map} +1 -1
  28. package/dist/{chunk-A73IJ27X.js → chunk-R2YNDRGL.js} +3 -3
  29. package/dist/{chunk-A73IJ27X.js.map → chunk-R2YNDRGL.js.map} +1 -1
  30. package/dist/{chunk-5HVKDHY6.js → chunk-R5NXOPL2.js} +3 -3
  31. package/dist/{chunk-5HVKDHY6.js.map → chunk-R5NXOPL2.js.map} +1 -1
  32. package/dist/chunk-RMBZ7YH7.js +52 -0
  33. package/dist/chunk-RMBZ7YH7.js.map +1 -0
  34. package/dist/{chunk-AIXC33SE.js → chunk-SHXUSTHM.js} +62 -15
  35. package/dist/chunk-SHXUSTHM.js.map +1 -0
  36. package/dist/{chunk-TAKPC6SD.js → chunk-UGTC33VH.js} +4 -4
  37. package/dist/{chunk-TAKPC6SD.js.map → chunk-UGTC33VH.js.map} +1 -1
  38. package/dist/{chunk-EA47MI5Q.js → chunk-VH5MYJ2K.js} +4 -4
  39. package/dist/{chunk-EA47MI5Q.js.map → chunk-VH5MYJ2K.js.map} +1 -1
  40. package/dist/{chunk-2774I242.js → chunk-WZEDIUWR.js} +21 -20
  41. package/dist/chunk-WZEDIUWR.js.map +1 -0
  42. package/dist/{chunk-TGJLXZAU.js → chunk-ZRRET3RP.js} +11 -11
  43. package/dist/chunk-ZRRET3RP.js.map +1 -0
  44. package/dist/{core-CtvAyPpH.d.ts → core-DNDAEhO2.d.ts} +9 -12
  45. package/dist/devtools/index.d.ts +4 -4
  46. package/dist/do/index.d.ts +103 -38
  47. package/dist/do/index.js +40 -21
  48. package/dist/do/index.js.map +1 -1
  49. package/dist/{drive-loop-Dw1sGSsW.d.ts → drive-loop-DlOEv49c.d.ts} +2 -2
  50. package/dist/{durable-effects-Ump5lSdO.d.ts → durable-effects-wLWEdy9H.d.ts} +1 -1
  51. package/dist/effect/index.d.ts +121 -28
  52. package/dist/effect/index.js +45 -31
  53. package/dist/effect/index.js.map +1 -1
  54. package/dist/extension/index.d.ts +3 -3
  55. package/dist/extension/index.js +2 -2
  56. package/dist/extension/index.js.map +1 -1
  57. package/dist/flow/index.d.ts +17 -13
  58. package/dist/flow/index.js +15 -12
  59. package/dist/flow/index.js.map +1 -1
  60. package/dist/idempotency/index.d.ts +3 -3
  61. package/dist/idempotency/index.js +3 -3
  62. package/dist/{index-CrcoVfd_.d.ts → index-9P1PaXIV.d.ts} +2 -2
  63. package/dist/{index-D3NY3VCN.d.ts → index-BBLI2WWN.d.ts} +1 -1
  64. package/dist/{index-Dl71V7kt.d.ts → index-C1WhBZ6a.d.ts} +4 -5
  65. package/dist/{index-CKtEKGyp.d.ts → index-C92SgNWz.d.ts} +3 -3
  66. package/dist/{index-CZZ3HI-Z.d.ts → index-CAsUE_d1.d.ts} +4 -4
  67. package/dist/{index-EIgfl83J.d.ts → index-CO34esWT.d.ts} +2 -2
  68. package/dist/{index-Bk2hBUdK.d.ts → index-COeX9Bmz.d.ts} +11 -9
  69. package/dist/{index-CVU7SK3a.d.ts → index-Cg0QXnzf.d.ts} +2 -2
  70. package/dist/index.d.ts +9 -9
  71. package/dist/index.js +2 -2
  72. package/dist/jev/index.d.ts +15 -15
  73. package/dist/jev/index.js +5 -5
  74. package/dist/jev/index.js.map +1 -1
  75. package/dist/laws-DTtCDDlU.d.ts +135 -0
  76. package/dist/loop-DReKGV1X.d.ts +70 -0
  77. package/dist/{machine-Dtre2TmN.d.ts → machine-C0WhOAyB.d.ts} +7 -7
  78. package/dist/machine-viz/index.d.ts +1 -1
  79. package/dist/machine-viz/index.js +2 -2
  80. package/dist/mem/index.d.ts +4 -3
  81. package/dist/mem/index.js +2 -2
  82. package/dist/mem/index.js.map +1 -1
  83. package/dist/node/index.d.ts +57 -9
  84. package/dist/node/index.js +175 -68
  85. package/dist/node/index.js.map +1 -1
  86. package/dist/otel/index.d.ts +28 -12
  87. package/dist/otel/index.js +25 -21
  88. package/dist/otel/index.js.map +1 -1
  89. package/dist/paginate/index.d.ts +4 -4
  90. package/dist/paginate/index.js +5 -5
  91. package/dist/parity/index.d.ts +11 -15
  92. package/dist/parity/index.js +4 -9
  93. package/dist/parity/index.js.map +1 -1
  94. package/dist/pbt/index.d.ts +279 -67
  95. package/dist/pbt/index.js +299 -58
  96. package/dist/pbt/index.js.map +1 -1
  97. package/dist/persistence/index.d.ts +10 -12
  98. package/dist/persistence/index.js +4 -4
  99. package/dist/promise/index.d.ts +118 -21
  100. package/dist/promise/index.js +6 -4
  101. package/dist/react/index.d.ts +34 -21
  102. package/dist/react/index.js +271 -42
  103. package/dist/react/index.js.map +1 -1
  104. package/dist/resilience/index.d.ts +6 -6
  105. package/dist/resilience/index.js +5 -5
  106. package/dist/{run-fiBe7pOG.d.ts → run-Cnw4qXj2.d.ts} +105 -23
  107. package/dist/{runtime-types-DEluQgGO.d.ts → runtime-types-CP-ykwgf.d.ts} +157 -29
  108. package/dist/testing/effect/index.d.ts +162 -9
  109. package/dist/testing/effect/index.js +110 -10
  110. package/dist/testing/effect/index.js.map +1 -1
  111. package/dist/testing/index.d.ts +6 -8
  112. package/dist/testing/index.js +4 -24
  113. package/dist/testing/index.js.map +1 -1
  114. package/dist/testing/promise/index.d.ts +4 -4
  115. package/dist/testing/promise/index.js +5 -5
  116. package/dist/timing/index.d.ts +4 -4
  117. package/dist/timing/index.js +4 -4
  118. package/dist/{types-CXNzlbAg.d.ts → types-S53WDy_h.d.ts} +1 -1
  119. package/dist/work-queue/index.d.ts +4 -4
  120. package/dist/work-queue/index.js +2 -2
  121. package/package.json +19 -17
  122. package/dist/chunk-2774I242.js.map +0 -1
  123. package/dist/chunk-AIXC33SE.js.map +0 -1
  124. package/dist/chunk-DVNXEB7V.js.map +0 -1
  125. package/dist/chunk-HX3C6LDK.js +0 -34
  126. package/dist/chunk-HX3C6LDK.js.map +0 -1
  127. package/dist/chunk-JAKM53JF.js.map +0 -1
  128. package/dist/chunk-RKVUVALV.js.map +0 -1
  129. package/dist/chunk-TGJLXZAU.js.map +0 -1
  130. package/dist/chunk-UHAQ4PI6.js.map +0 -1
@@ -1,9 +1,9 @@
1
- import { C as Cmd, b as CmdDef, T as TimerDeps } from '../core-CtvAyPpH.js';
1
+ import { C as Cmd, b as CmdDef, T as TimerDeps } from '../core-DNDAEhO2.js';
2
2
  import { RetryPolicy } from '../retry-backoff/index.js';
3
- import { a as DeadlineSub } from '../index-CVU7SK3a.js';
4
- import { b as ResilientState, a as ResilientTimerMsg, R as RunCmd, F as FailMsg, S as SucceedMsg, g as CircuitConfig, l as RateLimitConfig, m as DeadlineConfig } from '../index-CKtEKGyp.js';
3
+ import { a as DeadlineSub } from '../index-Cg0QXnzf.js';
4
+ import { b as ResilientState, a as ResilientTimerMsg, R as RunCmd, F as FailMsg, S as SucceedMsg, g as CircuitConfig, l as RateLimitConfig, m as DeadlineConfig } from '../index-C92SgNWz.js';
5
5
  import '@standard-schema/spec';
6
- import '../types-CXNzlbAg.js';
6
+ import '../types-S53WDy_h.js';
7
7
 
8
8
  /**
9
9
  * internal/paginate/paginator — the cursor/offset/page-token walk loop as pure
@@ -1,9 +1,9 @@
1
- export { PAGE_KEY, createPaginatedWalk, defaultPaginatorPolicy, drain, initPaginator, isDone, liftWalk, recordPage, resume, start } from '../chunk-FTCYY2DO.js';
2
- import '../chunk-72CN3VPY.js';
3
- import '../chunk-TAKPC6SD.js';
1
+ export { PAGE_KEY, createPaginatedWalk, defaultPaginatorPolicy, drain, initPaginator, isDone, liftWalk, recordPage, resume, start } from '../chunk-CPE5ZQXQ.js';
2
+ import '../chunk-NFOGYDRS.js';
3
+ import '../chunk-UGTC33VH.js';
4
4
  import '../chunk-VBDU4JL6.js';
5
5
  import '../chunk-4LPY67E4.js';
6
- import '../chunk-DVNXEB7V.js';
7
- import '../chunk-RKVUVALV.js';
6
+ import '../chunk-BVFS3ICC.js';
7
+ import '../chunk-A3QPMX5M.js';
8
8
  //# sourceMappingURL=index.js.map
9
9
  //# sourceMappingURL=index.js.map
@@ -1,16 +1,16 @@
1
- import { B as BootingRuntime } from '../runtime-types-DEluQgGO.js';
2
- import { C as Cmd, S as Sub, M as Machine } from '../core-CtvAyPpH.js';
3
- import { T as Trace, R as RecorderOptions } from '../index-Dl71V7kt.js';
1
+ import { B as BootingRuntime } from '../runtime-types-CP-ykwgf.js';
2
+ import { C as Cmd, c as Sub, M as Machine } from '../core-DNDAEhO2.js';
3
+ import { T as Trace, R as RecorderOptions } from '../index-C1WhBZ6a.js';
4
4
  import '@standard-schema/spec';
5
5
 
6
6
  /**
7
7
  * the record → replay → normalized-diff go/no-go gate.
8
8
  *
9
9
  * The parity spike proved the mechanism already composes from in-tree
10
- * primitives: `recorder` captures a run's Msg sequence (the recording), `replay`
11
- * re-folds it purely, and `trace-replay`'s order-insensitive deep-equal diffs
10
+ * primitives: `recorder` captures the starting State and Msg sequence,
11
+ * `replayTrace` re-folds it purely, and `trace-replay`'s order-insensitive deep-equal diffs
12
12
  * the result. Non-determinism is a RECORD-TIME property — ids/timestamps freeze
13
- * into the recording, `replay` never re-mints them — so the same recording
13
+ * into the recording, `replayTrace` never re-mints them — so the same recording
14
14
  * replays byte-identical every time.
15
15
  *
16
16
  * This module is the thin packaging over that substrate plus the one genuinely
@@ -23,7 +23,7 @@ import '@standard-schema/spec';
23
23
  *
24
24
  * ```text
25
25
  * recordRun(runtime) → Recording // wraps the recorder
26
- * goldenReplay(machine, rec) → State // wraps replay
26
+ * goldenReplay(machine, rec) → State // wraps replayTrace
27
27
  * normalizeForParity(schema) → (finding) => norm // strip + stable-sort
28
28
  * parityEqual(a, b) → boolean // normalized deep-equal
29
29
  * ```
@@ -66,15 +66,11 @@ declare function recordRun<S, M extends {
66
66
  type: string;
67
67
  }>(runtime: BootingRuntime<S, M>, opts?: RecorderOptions): Recording<S, M>;
68
68
  /**
69
- * Reproduce a recording's final state by folding its msgs through the pure
70
- * `replay` — `init(loaded, ctx)` then `update` per msg, never `interpret`,
71
- * never a `Store`, never a live subscription. Accepts a live {@link Recording}
72
- * (snapshotted here) or a bare {@link Trace}.
69
+ * Reproduce a recording's final State by folding from its captured post-init
70
+ * State. Calls no init, handlers, Store operations or Sub runners. Accepts a
71
+ * live {@link Recording} or a bare {@link Trace}.
73
72
  *
74
- * `ctx` is the ENVIRONMENT supplied fresh at replay time (the trace is the
75
- * INPUT: `loaded` + `msgs`), mirroring `replayTrace`. It is optional so a
76
- * void-ctx machine can call `goldenReplay(machine, rec)`; a machine that reads
77
- * ctx passes it explicitly.
73
+ * `ctx` is an optional legacy argument; recorded replay needs no init context.
78
74
  */
79
75
  declare function goldenReplay<S, M extends {
80
76
  type: string;
@@ -1,7 +1,7 @@
1
- import { recorder, deepEqual } from '../chunk-TGJLXZAU.js';
1
+ import { recorder, replayTrace, deepEqual } from '../chunk-ZRRET3RP.js';
2
2
  import '../chunk-HWVW4AXC.js';
3
- import '../chunk-DVNXEB7V.js';
4
- import { replay } from '../chunk-RKVUVALV.js';
3
+ import '../chunk-BVFS3ICC.js';
4
+ import '../chunk-A3QPMX5M.js';
5
5
 
6
6
  // src/parity/index.ts
7
7
  function recordRun(runtime, opts) {
@@ -14,12 +14,7 @@ function recordRun(runtime, opts) {
14
14
  }
15
15
  function goldenReplay(machine, recording, ctx) {
16
16
  const trace = asTrace(recording);
17
- const { state } = replay(machine, {
18
- msgs: trace.msgs,
19
- ctx,
20
- loaded: trace.loaded
21
- });
22
- return state;
17
+ return replayTrace(machine, trace, ctx).actual;
23
18
  }
24
19
  function asTrace(recording) {
25
20
  return typeof recording.trace === "function" ? recording.trace() : recording;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/parity/index.ts"],"names":["key"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * the record → replay → normalized-diff go/no-go gate.\n *\n * The parity spike proved the mechanism already composes from in-tree\n * primitives: `recorder` captures a run's Msg sequence (the recording), `replay`\n * re-folds it purely, and `trace-replay`'s order-insensitive deep-equal diffs\n * the result. Non-determinism is a RECORD-TIME property — ids/timestamps freeze\n * into the recording, `replay` never re-mints them — so the same recording\n * replays byte-identical every time.\n *\n * This module is the thin packaging over that substrate plus the one genuinely\n * new helper, `normalizeForParity`: the finding-normalizer that strips\n * record-time non-determinism (generated ids/timestamps) and stable-key-sorts\n * collections, so an OLD-engine golden run and a NEW-engine run diff as equal\n * exactly when they are behaviourally equivalent. Every domain (audit, auth,\n * ingest, …) gets its old-vs-new parity check without reinventing the capture,\n * the normalization, and the diff.\n *\n * ```text\n * recordRun(runtime) → Recording // wraps the recorder\n * goldenReplay(machine, rec) → State // wraps replay\n * normalizeForParity(schema) → (finding) => norm // strip + stable-sort\n * parityEqual(a, b) → boolean // normalized deep-equal\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { BootingRuntime, Cmd, Machine, Sub } from \"../index\";\nimport { replay } from \"../index\";\nimport {\n type RecorderOptions,\n recorder,\n type Trace,\n} from \"../internal/persistence/recorder\";\nimport { deepEqual } from \"../internal/persistence/trace-replay\";\n\n/**\n * The two recorder types this module's own signatures name — `Trace` is what\n * {@link Recording.trace} returns and what {@link goldenReplay} accepts bare,\n * `RecorderOptions` is what {@link recordRun} takes. The recorder itself is\n * internal, so this door is the only place either type can be named from\n * outside the package.\n */\nexport type { RecorderOptions, Trace } from \"../internal/persistence/recorder\";\n\n/**\n * A live parity recording attached to a Runtime — the go/no-go gate's golden\n * artifact. Wraps a {@link recorder}: drive the runtime, then snapshot with\n * {@link Recording.trace} (or hand the live `Recording` straight to\n * {@link goldenReplay}, which snapshots for you).\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport interface Recording<S, M> {\n /**\n * Snapshot the recording so far as a replayable {@link Trace} — `loaded`\n * (boot state), the ordered non-null `msgs`, and `finalState`. Sufficient to\n * reproduce the run via {@link goldenReplay}. Fresh object each call.\n */\n trace(): Trace<S, M>;\n /** Serialize the recording as JSONL (see {@link recorder}'s `toJSONL`). */\n toJSONL(): string;\n /** Detach the underlying observer. Idempotent. */\n stop(): void;\n}\n\n/**\n * Attach a parity recording to a Runtime, reusing the `recorder`/`historyTracker`\n * capture (the boot observe for `loaded` + every non-null msg in order) rather\n * than a fresh observer path.\n *\n * Attach on the synchronous `run()` handle so the boot transition is captured\n * as `loaded`; drive the runtime; then `trace()` (or pass the handle to\n * {@link goldenReplay}). See {@link RecorderOptions} for `captureSteps` (needed\n * only for the per-step / resume-cache-prefix inspection, not for replay).\n */\nexport function recordRun<S, M extends { type: string }>(\n runtime: BootingRuntime<S, M>,\n opts?: RecorderOptions,\n): Recording<S, M> {\n const rec = recorder(runtime, opts);\n return {\n trace: () => rec.dump(),\n toJSONL: () => rec.toJSONL(),\n stop: () => rec.stop(),\n };\n}\n\n/**\n * Reproduce a recording's final state by folding its msgs through the pure\n * `replay` — `init(loaded, ctx)` then `update` per msg, never `interpret`,\n * never a `Store`, never a live subscription. Accepts a live {@link Recording}\n * (snapshotted here) or a bare {@link Trace}.\n *\n * `ctx` is the ENVIRONMENT supplied fresh at replay time (the trace is the\n * INPUT: `loaded` + `msgs`), mirroring `replayTrace`. It is optional so a\n * void-ctx machine can call `goldenReplay(machine, rec)`; a machine that reads\n * ctx passes it explicitly.\n */\nexport function goldenReplay<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n recording: Recording<S, M> | Trace<S, M>,\n ctx?: Ctx,\n): S {\n const trace = asTrace(recording);\n const { state } = replay(machine, {\n msgs: trace.msgs,\n ctx: ctx as Ctx,\n loaded: trace.loaded,\n });\n return state;\n}\n\n/** Narrow a `Recording | Trace` to a `Trace`, snapshotting a live recording. */\nfunction asTrace<S, M>(recording: Recording<S, M> | Trace<S, M>): Trace<S, M> {\n return typeof (recording as Recording<S, M>).trace === \"function\"\n ? (recording as Recording<S, M>).trace()\n : (recording as Trace<S, M>);\n}\n\n/** Configuration for {@link normalizeForParity}. */\nexport interface ParitySchema {\n /**\n * Object keys stripped wherever they appear — the record-time\n * non-determinism `replay` never re-mints. Default: `id`, `runId`,\n * `timestamp`.\n */\n readonly stripKeys?: readonly string[];\n /**\n * Candidate keys used to stable-sort arrays of objects. For each array the\n * first candidate present (as a primitive) on the items decides the order;\n * arrays with no candidate fall back to canonical-JSON order. Default:\n * `ruleId`, `selector`, `url`.\n */\n readonly sortKeys?: readonly string[];\n}\n\n// Defaults proven by the spike: strip the generated id/runId/timestamp,\n// stable-key-sort by ruleId | selector | url. Fan-out ordering is already\n// deterministic on this substrate (array order); the sort only defends against\n// intra-effect concurrency (Promise.all / Set).\nconst DEFAULT_STRIP_KEYS: readonly string[] = [\"id\", \"runId\", \"timestamp\"];\nconst DEFAULT_SORT_KEYS: readonly string[] = [\"ruleId\", \"selector\", \"url\"];\n\n/**\n * Build a finding-normalizer: a pure `(value) => normalized` that strips the\n * schema's `stripKeys`, recursively sorts object keys, and stable-key-sorts\n * arrays by the schema's `sortKeys`. Two runs that differ only in generated\n * ids/timestamps or intra-effect ordering normalize to a byte-identical value,\n * so {@link parityEqual} on the normalized pair reflects behavioural (not incidental)\n * difference.\n */\nexport function normalizeForParity(\n schema?: ParitySchema,\n): (value: unknown) => unknown {\n const stripKeys = new Set(schema?.stripKeys ?? DEFAULT_STRIP_KEYS);\n const sortKeys = schema?.sortKeys ?? DEFAULT_SORT_KEYS;\n\n const normalize = (value: unknown): unknown => {\n if (Array.isArray(value)) {\n const items = value.map(normalize);\n // Decorate-sort-undecorate: primary key = first present sortKey value,\n // secondary = canonical JSON (items are already normalized so keys are\n // sorted), original index last for a stable total order.\n return items\n .map((item, index) => ({\n item,\n index,\n primary: primarySortKey(item, sortKeys),\n json: JSON.stringify(item) ?? \"\",\n }))\n .sort(\n (a, b) =>\n compareStrings(a.primary, b.primary) ||\n compareStrings(a.json, b.json) ||\n a.index - b.index,\n )\n .map((decorated) => decorated.item);\n }\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)\n .filter((key) => !stripKeys.has(key))\n .sort()) {\n out[key] = normalize(value[key]);\n }\n return out;\n }\n return value;\n };\n\n return normalize;\n}\n\n/** The first present `sortKeys` candidate on `item` as a string, or `\"\"`. */\nfunction primarySortKey(item: unknown, sortKeys: readonly string[]): string {\n if (!isPlainObject(item)) return \"\";\n for (const key of sortKeys) {\n const candidate = item[key];\n if (\n typeof candidate === \"string\" ||\n typeof candidate === \"number\" ||\n typeof candidate === \"boolean\"\n ) {\n return String(candidate);\n }\n }\n return \"\";\n}\n\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * The parity verdict: `true` iff `a` and `b` are structurally equal. Reuses\n * `trace-replay`'s order-insensitive deep-equal (the comparator the\n * record/replay lane owns) rather than adding another boolean deep-compare —\n * feed it the pair produced by {@link normalizeForParity} for the\n * normalized-JSON equality the gate rests on.\n */\nexport function parityEqual(a: unknown, b: unknown): boolean {\n return deepEqual(a, b);\n}\n"]}
1
+ {"version":3,"sources":["../../src/parity/index.ts"],"names":["key"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * the record → replay → normalized-diff go/no-go gate.\n *\n * The parity spike proved the mechanism already composes from in-tree\n * primitives: `recorder` captures the starting State and Msg sequence,\n * `replayTrace` re-folds it purely, and `trace-replay`'s order-insensitive deep-equal diffs\n * the result. Non-determinism is a RECORD-TIME property — ids/timestamps freeze\n * into the recording, `replayTrace` never re-mints them — so the same recording\n * replays byte-identical every time.\n *\n * This module is the thin packaging over that substrate plus the one genuinely\n * new helper, `normalizeForParity`: the finding-normalizer that strips\n * record-time non-determinism (generated ids/timestamps) and stable-key-sorts\n * collections, so an OLD-engine golden run and a NEW-engine run diff as equal\n * exactly when they are behaviourally equivalent. Every domain (audit, auth,\n * ingest, …) gets its old-vs-new parity check without reinventing the capture,\n * the normalization, and the diff.\n *\n * ```text\n * recordRun(runtime) → Recording // wraps the recorder\n * goldenReplay(machine, rec) → State // wraps replayTrace\n * normalizeForParity(schema) → (finding) => norm // strip + stable-sort\n * parityEqual(a, b) → boolean // normalized deep-equal\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { BootingRuntime, Cmd, Machine, Sub } from \"../index\";\nimport {\n type RecorderOptions,\n recorder,\n type Trace,\n} from \"../internal/persistence/recorder\";\nimport { deepEqual, replayTrace } from \"../internal/persistence/trace-replay\";\n\n/**\n * The two recorder types this module's own signatures name — `Trace` is what\n * {@link Recording.trace} returns and what {@link goldenReplay} accepts bare,\n * `RecorderOptions` is what {@link recordRun} takes. The recorder itself is\n * internal, so this subpath is the only place either type can be named from\n * outside the package.\n */\nexport type { RecorderOptions, Trace } from \"../internal/persistence/recorder\";\n\n/**\n * A live parity recording attached to a Runtime — the go/no-go gate's golden\n * artifact. Wraps a {@link recorder}: drive the runtime, then snapshot with\n * {@link Recording.trace} (or hand the live `Recording` straight to\n * {@link goldenReplay}, which snapshots for you).\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport interface Recording<S, M> {\n /**\n * Snapshot the recording so far as a replayable {@link Trace} — `loaded`\n * (boot state), the ordered non-null `msgs`, and `finalState`. Sufficient to\n * reproduce the run via {@link goldenReplay}. Fresh object each call.\n */\n trace(): Trace<S, M>;\n /** Serialize the recording as JSONL (see {@link recorder}'s `toJSONL`). */\n toJSONL(): string;\n /** Detach the underlying observer. Idempotent. */\n stop(): void;\n}\n\n/**\n * Attach a parity recording to a Runtime, reusing the `recorder`/`historyTracker`\n * capture (the boot observe for `loaded` + every non-null msg in order) rather\n * than a fresh observer path.\n *\n * Attach on the synchronous `run()` handle so the boot transition is captured\n * as `loaded`; drive the runtime; then `trace()` (or pass the handle to\n * {@link goldenReplay}). See {@link RecorderOptions} for `captureSteps` (needed\n * only for the per-step / resume-cache-prefix inspection, not for replay).\n */\nexport function recordRun<S, M extends { type: string }>(\n runtime: BootingRuntime<S, M>,\n opts?: RecorderOptions,\n): Recording<S, M> {\n const rec = recorder(runtime, opts);\n return {\n trace: () => rec.dump(),\n toJSONL: () => rec.toJSONL(),\n stop: () => rec.stop(),\n };\n}\n\n/**\n * Reproduce a recording's final State by folding from its captured post-init\n * State. Calls no init, handlers, Store operations or Sub runners. Accepts a\n * live {@link Recording} or a bare {@link Trace}.\n *\n * `ctx` is an optional legacy argument; recorded replay needs no init context.\n */\nexport function goldenReplay<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n recording: Recording<S, M> | Trace<S, M>,\n ctx?: Ctx,\n): S {\n const trace = asTrace(recording);\n return replayTrace(machine, trace, ctx).actual;\n}\n\n/** Narrow a `Recording | Trace` to a `Trace`, snapshotting a live recording. */\nfunction asTrace<S, M>(recording: Recording<S, M> | Trace<S, M>): Trace<S, M> {\n return typeof (recording as Recording<S, M>).trace === \"function\"\n ? (recording as Recording<S, M>).trace()\n : (recording as Trace<S, M>);\n}\n\n/** Configuration for {@link normalizeForParity}. */\nexport interface ParitySchema {\n /**\n * Object keys stripped wherever they appear — the record-time\n * non-determinism `replay` never re-mints. Default: `id`, `runId`,\n * `timestamp`.\n */\n readonly stripKeys?: readonly string[];\n /**\n * Candidate keys used to stable-sort arrays of objects. For each array the\n * first candidate present (as a primitive) on the items decides the order;\n * arrays with no candidate fall back to canonical-JSON order. Default:\n * `ruleId`, `selector`, `url`.\n */\n readonly sortKeys?: readonly string[];\n}\n\n// Defaults proven by the spike: strip the generated id/runId/timestamp,\n// stable-key-sort by ruleId | selector | url. Fan-out ordering is already\n// deterministic on this substrate (array order); the sort only defends against\n// intra-effect concurrency (Promise.all / Set).\nconst DEFAULT_STRIP_KEYS: readonly string[] = [\"id\", \"runId\", \"timestamp\"];\nconst DEFAULT_SORT_KEYS: readonly string[] = [\"ruleId\", \"selector\", \"url\"];\n\n/**\n * Build a finding-normalizer: a pure `(value) => normalized` that strips the\n * schema's `stripKeys`, recursively sorts object keys, and stable-key-sorts\n * arrays by the schema's `sortKeys`. Two runs that differ only in generated\n * ids/timestamps or intra-effect ordering normalize to a byte-identical value,\n * so {@link parityEqual} on the normalized pair reflects behavioural (not incidental)\n * difference.\n */\nexport function normalizeForParity(\n schema?: ParitySchema,\n): (value: unknown) => unknown {\n const stripKeys = new Set(schema?.stripKeys ?? DEFAULT_STRIP_KEYS);\n const sortKeys = schema?.sortKeys ?? DEFAULT_SORT_KEYS;\n\n const normalize = (value: unknown): unknown => {\n if (Array.isArray(value)) {\n const items = value.map(normalize);\n // Decorate-sort-undecorate: primary key = first present sortKey value,\n // secondary = canonical JSON (items are already normalized so keys are\n // sorted), original index last for a stable total order.\n return items\n .map((item, index) => ({\n item,\n index,\n primary: primarySortKey(item, sortKeys),\n json: JSON.stringify(item) ?? \"\",\n }))\n .sort(\n (a, b) =>\n compareStrings(a.primary, b.primary) ||\n compareStrings(a.json, b.json) ||\n a.index - b.index,\n )\n .map((decorated) => decorated.item);\n }\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)\n .filter((key) => !stripKeys.has(key))\n .sort()) {\n out[key] = normalize(value[key]);\n }\n return out;\n }\n return value;\n };\n\n return normalize;\n}\n\n/** The first present `sortKeys` candidate on `item` as a string, or `\"\"`. */\nfunction primarySortKey(item: unknown, sortKeys: readonly string[]): string {\n if (!isPlainObject(item)) return \"\";\n for (const key of sortKeys) {\n const candidate = item[key];\n if (\n typeof candidate === \"string\" ||\n typeof candidate === \"number\" ||\n typeof candidate === \"boolean\"\n ) {\n return String(candidate);\n }\n }\n return \"\";\n}\n\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * The parity verdict: `true` iff `a` and `b` are structurally equal. Reuses\n * `trace-replay`'s order-insensitive deep-equal (the comparator the\n * record/replay lane owns) rather than adding another boolean deep-compare —\n * feed it the pair produced by {@link normalizeForParity} for the\n * normalized-JSON equality the gate rests on.\n */\nexport function parityEqual(a: unknown, b: unknown): boolean {\n return deepEqual(a, b);\n}\n"]}
@@ -1,5 +1,7 @@
1
1
  import * as fc from 'fast-check';
2
- import { C as Cmd, S as Sub, M as Machine, g as Reducer, i as Transitions, U as UpdateForm } from '../core-CtvAyPpH.js';
2
+ import { C as Cmd, c as Sub, M as Machine, g as Reducer, i as Transitions, U as UpdateForm } from '../core-DNDAEhO2.js';
3
+ import { S as Step } from '../laws-DTtCDDlU.js';
4
+ export { F as Fold, L as LawBreak, f as foldEvents, l as lawInvariant, a as lawTerminates, b as lawTrace } from '../laws-DTtCDDlU.js';
3
5
  import '@standard-schema/spec';
4
6
 
5
7
  /**
@@ -137,7 +139,8 @@ declare function arbRecordMsg<T extends string, R>(type: T, fields: {
137
139
  *
138
140
  * - `minLength` (default 0) and `maxLength` (default 30) bound the random
139
141
  * array length. `prefix` is prepended AFTER the random tail is built; the
140
- * final length is `prefix.length + tail.length`.
142
+ * final length is `prefix.length + tail.length`. `size` decides how much of
143
+ * that bound the draws actually use.
141
144
  * - `prefix` is the load-bearing escape hatch for "the first msg is the
142
145
  * one that exercises the cell I care about" (e.g. Test A's
143
146
  * `[{ type: "ws:client:hello" }]` prefix to land in the
@@ -153,6 +156,7 @@ declare function arbRecordMsg<T extends string, R>(type: T, fields: {
153
156
  *
154
157
  * arbMsgSequence(msgArb, { minLength: 1, maxLength: 30 });
155
158
  * arbMsgSequence(msgArb, { prefix: [{ type: "ws:client:hello" }], maxLength: 20 });
159
+ * arbMsgSequence(msgArb, { maxLength: 60, size: "max" });
156
160
  * ```
157
161
  */
158
162
  declare function arbMsgSequence<M extends {
@@ -161,85 +165,280 @@ declare function arbMsgSequence<M extends {
161
165
  readonly minLength?: number;
162
166
  readonly maxLength?: number;
163
167
  readonly prefix?: readonly NoInfer<M>[];
168
+ /**
169
+ * Forwarded to `fc.array`. Left out, fast-check's default size applies:
170
+ * `"small"` unless `fc.configureGlobal` changed it, which draws at most
171
+ * `2 * minLength + 10` Msgs however large `maxLength` is. So `maxLength: 60`
172
+ * comes out about 10 long, and a state that takes more steps than that is
173
+ * never reached. Pass `"max"` to draw up to `maxLength`.
174
+ */
175
+ readonly size?: fc.SizeForArbitrary;
164
176
  }): fc.Arbitrary<readonly M[]>;
165
177
  /**
166
- * State-aware Msg sequence arbitrary — generates only msgs whose
167
- * precondition holds in the current state. Backed by `fc.commands`
168
- * internally. Planned for a later phase.
178
+ * How one type of Cmd gets answered. `draw` runs before the fold, so a shrink
179
+ * can shrink its value, and it cannot see the state. `make` runs in the fold and
180
+ * gets the Cmd really sent, so an answer is `make: (cmd, v) => fetch.ok(cmd, v)`.
181
+ * To draw both outcomes, have `draw` choose between them.
182
+ */
183
+ interface AnswerCell<K extends Cmd, V, M> {
184
+ readonly draw: fc.Arbitrary<V>;
185
+ readonly make: (cmd: K, value: V) => NoInfer<M>;
186
+ }
187
+ /**
188
+ * What {@link arbGuidedSequence} draws from. `outside` holds the Msgs nothing
189
+ * sends a Cmd for, by Msg type. `answers` holds an {@link AnswerCell} by Cmd type,
190
+ * and `A` is the value each one draws, inferred from its `draw`. A Cmd type with no
191
+ * cell is never answered. A Msg type in neither table is never drawn.
192
+ */
193
+ interface GuidedCells<M extends {
194
+ type: string;
195
+ }, C extends Cmd, A extends {
196
+ readonly [K in C["type"]]?: unknown;
197
+ }> {
198
+ readonly outside?: {
199
+ readonly [K in M["type"]]?: fc.Arbitrary<Extract<M, {
200
+ type: K;
201
+ }>>;
202
+ };
203
+ readonly answers?: {
204
+ readonly [K in keyof A]: K extends C["type"] ? AnswerCell<Extract<C, {
205
+ type: K;
206
+ }>, A[K], M> : never;
207
+ };
208
+ }
209
+ /**
210
+ * Draw Msg lists in which every answer answers a Cmd that was sent and not yet
211
+ * answered, so a law that something must happen can be checked on random lists.
212
+ * The result is a plain list for any runner, such as `propertyTerminates`.
213
+ *
214
+ * It draws blind events, then folds them through the machine. An answer event
215
+ * holds a pick and one value per answer cell, and the fold reads the pick modulo
216
+ * the Cmds waiting at that point. With nothing waiting, the event is dropped.
217
+ *
218
+ * **Refusals.** An event whose Msg the state at that point has no cell for is
219
+ * dropped too, in either update form, so a transitions machine can leave a Msg out
220
+ * of a state and no list holds it there. A Cmd whose answer was refused keeps
221
+ * waiting, and a later event can still answer it. A list therefore never tests
222
+ * that a state refuses a Msg: send that Msg by hand for that.
223
+ *
224
+ * **Waiting** holds every Cmd that `init` and the steps have sent, that has a cell
225
+ * in `answers`, and that has no answer yet. A Cmd sent twice waits twice. The fold
226
+ * reads it off what the reducer returned, not `machine.cmds`, so a machine with no
227
+ * `cmds` field gets the same lists, and a hand-written Cmd is answered like a
228
+ * defined one. No answer is matched by its `cmd` field: the pick names the Cmd.
229
+ *
230
+ * A shrink deletes events and shrinks picks and values, and the fold reads what
231
+ * is left, so a shrunk list holds to the same rule. Blind picks find a narrow
232
+ * order less often than a guided draw would, which more runs pay for.
233
+ *
234
+ * A step that throws ends its list on the Msg that threw, so a runner's fold meets
235
+ * the same throw and reports it as that list's failure.
236
+ *
237
+ * - `maxLength` (default 60) caps the events drawn, and dropped ones count, so a
238
+ * list comes out shorter. `size` (default `"max"`) is forwarded to `fc.array`:
239
+ * with most answers dropped, fast-check's `"small"` rarely gets a run anywhere.
240
+ * - `loaded` starts the fold from a saved state. Give a runner the same one, or
241
+ * its fold reads the list from a different start.
242
+ *
243
+ * Throws when `cells` has no cell at all, since that could only draw empty lists.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * import type { Machine } from "@demlik/tea";
248
+ * import {
249
+ * arbConstantMsg,
250
+ * arbGuidedSequence,
251
+ * propertyTrace,
252
+ * stubCtxThrowingProxy,
253
+ * } from "@demlik/tea/pbt";
254
+ * import * as fc from "fast-check";
255
+ *
256
+ * type Merge = { readonly type: "merge"; readonly pr: number };
257
+ * type State = { type: "idle" } | { type: "merging" } | { type: "landed" };
258
+ * type Msg = { type: "start" } | { type: "merged"; cmd: Merge; sha: string };
259
+ * declare const machine: Machine<State, Msg, Merge, never, object>;
260
+ * const ctx = stubCtxThrowingProxy<object>();
261
+ *
262
+ * const lists = arbGuidedSequence(machine, ctx, {
263
+ * outside: { start: arbConstantMsg("start") },
264
+ * answers: {
265
+ * merge: {
266
+ * draw: fc.string(),
267
+ * make: (cmd, sha) => ({ type: "merged", cmd, sha }),
268
+ * },
269
+ * },
270
+ * });
169
271
  *
170
- * **NOT IMPLEMENTED IN v1.** The export exists today so the surface is
171
- * forward-compatible; this ships in a later phase alongside
172
- * shrinkers. Calling it throws.
272
+ * propertyTrace(
273
+ * machine,
274
+ * ctx,
275
+ * lists,
276
+ * (steps, final) =>
277
+ * !steps.some((step) => step.msg.type === "merged") ||
278
+ * final.type === "landed",
279
+ * );
280
+ * ```
173
281
  */
174
282
  declare function arbGuidedSequence<S, M extends {
175
283
  type: string;
176
- }, C extends Cmd, U extends Sub, Ctx>(_machine: Machine<S, M, C, U, Ctx>, _ctx: Ctx, _cells: {
177
- [K in M["type"]]?: (state: S) => fc.Arbitrary<Extract<M, {
178
- type: K;
179
- }>> | null;
180
- }, _opts?: {
181
- maxLength?: number;
284
+ }, C extends Cmd, U extends Sub, Ctx, A extends {
285
+ readonly [K in C["type"]]?: unknown;
286
+ }>(machine: Machine<S, M, C, U, Ctx>, ctx: NoInfer<Ctx>, cells: GuidedCells<M, C, A>, opts?: {
287
+ readonly maxLength?: number;
288
+ readonly size?: fc.SizeForArbitrary;
289
+ readonly loaded?: NoInfer<S> | null;
182
290
  }): fc.Arbitrary<readonly M[]>;
183
291
 
292
+ /** What a property run's lists did with one Msg type. */
293
+ interface MsgCount {
294
+ /** How many of this Msg type the run's lists held. */
295
+ readonly drawn: number;
296
+ /**
297
+ * How many of those acted: the step's `next` differs from its `prev` by
298
+ * `structuralHash`, or the step sent a Cmd.
299
+ */
300
+ readonly acted: number;
301
+ }
302
+
303
+ /**
304
+ * What every property runner takes: the saved state to start from, the two checks
305
+ * below, and fast-check's `Parameters` (`numRuns` defaults to 200 here). Five of
306
+ * those settings are left out, each because it would break what a runner promises:
307
+ *
308
+ * - `reporter` and `asyncReporter` stop a failing run from throwing. One set
309
+ * through `fc.configureGlobal` still applies, as it does for `fc.assert`.
310
+ * - `examples` has the shape of the drawn tuple, which an arbitrary `loaded`
311
+ * changes.
312
+ * - `timeout` applies only to async properties, and these runners are sync.
313
+ * `interruptAfterTimeLimit` with `markInterruptAsFailure` is the limit that
314
+ * works here.
315
+ * - `plugins` is left out on every fast-check. Only the newest 4s have it, so a
316
+ * call using it would compile and run on some of the versions tea promises and
317
+ * not on others. `fc.timeout`, which it can carry, has the same limit as
318
+ * `timeout`. fast-check 4.10.2 marks `interruptAfterTimeLimit`,
319
+ * `markInterruptAsFailure`, `skipEqualValues`, `ignoreEqualValues` and `unbiased`
320
+ * as deprecated in favor of plugins, and they still work on every version tea
321
+ * promises, so they stay.
322
+ */
323
+ interface PropertyOpts<S> extends Omit<fc.Parameters, "reporter" | "asyncReporter" | "examples" | "timeout" | "plugins"> {
324
+ /**
325
+ * Saved state passed to `machine.init(loaded, ctx)`. Default `null`. An
326
+ * arbitrary draws one per run, and a failure's counterexample shows the drawn
327
+ * state beside the shrunk list. It can draw `null` as well, the nothing storage
328
+ * hands back on a fresh start, which boots the machine fresh.
329
+ */
330
+ readonly loaded?: S | null | fc.Arbitrary<S | null>;
331
+ /**
332
+ * Fail a run whose laws all held when a Msg type never acted, naming each such
333
+ * type, so a cut path can't stay green. Default `false`.
334
+ *
335
+ * A deep path needs a `maxLength` well past its depth as well as `size: "max"`
336
+ * on `arbMsgSequence`. Lists too short to reach a healthy machine's deep state
337
+ * name that healthy path as never acted, in the same report a cut path gives. At
338
+ * the default `maxLength` of 30, a state 20 steps deep on 6 Msg types drawn evenly
339
+ * fails this way, since only one draw in 6 climbs. Allow about the depth times the
340
+ * number of Msg types, here `maxLength: 120`.
341
+ *
342
+ * This stays out of any run with a non-empty `path`, so drop `path` to have it
343
+ * check again. A path with a colon, such as `"0:0"`, replays one shrunk failure. A
344
+ * path with no colon, such as `"3"`, is what a failure with no shrinks reports, and
345
+ * it is no replay once that failure is gone: fast-check draws a full `numRuns` of
346
+ * lists starting from that one, and a cut path reads green. `path: ""` is no path
347
+ * to fast-check, so this applies there.
348
+ *
349
+ * A value `structuralHash` refuses (a `Date`, a `Map`, a class instance) leaves
350
+ * "acted" unreadable when a step's comparison reaches it, so this fails the run
351
+ * saying so. The comparison stops at the first difference and doesn't look inside
352
+ * an object `prev` and `next` share, so a refused value stays unread, and fails
353
+ * nothing, when it sits in a shared object or past a difference. The answer is
354
+ * right either way: a shared object didn't change, and a difference means the
355
+ * step acted. Fields are compared in the order the state's keys were set.
356
+ */
357
+ readonly failOnNeverActed?: boolean;
358
+ /**
359
+ * Fail a run when `init` or a step sends a Cmd whose `type` isn't in
360
+ * `machine.cmds`, naming that Cmd. Default `false`, because tea lets a machine
361
+ * keep a hand-written Cmd beside its defined ones. A machine with no `cmds` field
362
+ * has no list to check against, so this adds nothing there. `cmds: []` is a list
363
+ * with nothing on it, so every Cmd fails.
364
+ *
365
+ * A value with no string `type` is never a Cmd, so it fails with this off too, on
366
+ * every machine.
367
+ */
368
+ readonly failOnUndeclaredCmd?: boolean;
369
+ }
184
370
  /**
185
- * One unit in a fold trace — the four pieces of data a property
186
- * invariant typically asserts on. `prev` is the state before the msg
187
- * applied; `next` is the state after; `cmds` is the cmds emitted by this
188
- * single step (NOT the running total).
371
+ * What a property run did. A passing runner returns it, and a failing one throws
372
+ * it on {@link PropertyFailure.run}, so a caller reads one shape on both arms and
373
+ * never parses error text.
189
374
  */
190
- interface Step<S, M, C> {
191
- readonly prev: S;
192
- readonly msg: M;
193
- readonly next: S;
194
- readonly cmds: readonly C[];
375
+ interface PropertyRun {
376
+ /** Lists tried. On a failure, this counts up to and including the failing one. */
377
+ readonly numRuns: number;
378
+ /** Shrink steps a failure took. `0` on a pass. */
379
+ readonly numShrinks: number;
380
+ /** The seed this run drew from. Pass it back as `seed` to draw the same lists. */
381
+ readonly seed: number;
382
+ /**
383
+ * Where the shrunk failure sits. Pass it back as `path`, with `seed`, to replay
384
+ * that one failure. `null` when no list failed: on a pass, on an interrupt
385
+ * marked as a failure, when too many `fc.pre` skips fail the run, or when
386
+ * `failOnNeverActed` fails it.
387
+ */
388
+ readonly path: string | null;
389
+ /**
390
+ * What the run's lists did, per Msg type: every type the machine has a cell for,
391
+ * then any other that was drawn. A type never drawn reads zero. On a failure this
392
+ * stops at the failing list, so shrinking adds nothing, and a list skipped with
393
+ * `fc.pre` adds nothing either. A list whose reducer threw adds nothing as well,
394
+ * though `numRuns` includes it: its fold hands back no steps to count.
395
+ *
396
+ * A step counts as acted when its comparison reaches a value `structuralHash`
397
+ * refuses (a `Date`, a `Map`, a class instance). One in an object `prev` and `next`
398
+ * share, or past a difference, is never reached, so it changes no count.
399
+ */
400
+ readonly msgCounts: ReadonlyMap<string, MsgCount>;
195
401
  }
196
402
  /**
197
- * Fold a Msg sequence through a machine's pure reducer, returning the
198
- * per-step trace, every state in order, and the final state. Initial
199
- * state comes from `replay(machine, { msgs: [], ctx, loaded })` — i.e.
200
- * `machine.init(loaded ?? null, ctx)` faithfully. Init's emitted cmds
201
- * are NOT included in any `Step` (they're the boot moment, not a Msg).
202
- *
203
- * @param machine The machine under test.
204
- * @param ctx The Ctx — typically `stubCtxThrowingProxy<Ctx>()` when
205
- * init is pure. Pass a real ctx if init reads from it.
206
- * @param loaded Optional pre-init snapshot (the `loaded` arg to
207
- * `machine.init`). Defaults to `null` (fresh boot).
208
- * @param msgs The Msg sequence to fold.
403
+ * Raised when a property fails. The message is fast-check's report, and
404
+ * {@link PropertyFailure.run} holds the same {@link PropertyRun} a pass returns.
405
+ * What the check threw sits where `fc.assert` puts it on the installed fast-check:
406
+ * on 4, in `cause` by default and in the message with `includeErrorInReport`; on 3,
407
+ * in the message by default and in `cause` with `errorWithCause`, on the 3s that have
408
+ * that setting. A reason the check returned is always in the message: when it went to
409
+ * `cause`, the message is the report plus a `reason:` line. A run that `failOnNeverActed` fails broke no list,
410
+ * so fast-check has no report: the message names the Msg types instead.
209
411
  *
210
412
  * @example
211
413
  * ```ts
212
414
  * import type { Machine } from "@demlik/tea";
213
- * import { foldEvents } from "@demlik/tea/pbt";
415
+ * import { PropertyFailure, propertyInvariant } from "@demlik/tea/pbt";
416
+ * import type * as fc from "fast-check";
214
417
  *
215
- * type State = { type: "idle" } | { type: "counting"; n: number } | { type: "stopped" };
216
- * type Msg = { type: "start" } | { type: "tick" } | { type: "stop" };
217
- * interface Ctx { readonly tabId: number }
218
- * declare const machine: Machine<State, Msg, never, never, Ctx>;
219
- * declare const ctx: Ctx;
418
+ * type State = { type: "idle" } | { type: "done" };
419
+ * type Msg = { type: "finish" };
420
+ * declare const machine: Machine<State, Msg, never, never, object>;
421
+ * declare const seqArb: fc.Arbitrary<readonly Msg[]>;
220
422
  *
221
- * const { states, steps, finalState } = foldEvents(machine, ctx, null, [
222
- * { type: "start" }, { type: "tick" }, { type: "stop" },
223
- * ]);
224
- * // steps[1] = { prev: { type: "counting", n: 0 }, msg: { type: "tick" }, ... }
423
+ * try {
424
+ * propertyInvariant(machine, {}, seqArb, ({ next }) => next.type === "idle");
425
+ * } catch (error) {
426
+ * if (error instanceof PropertyFailure) {
427
+ * // Replay this one failure with `{ seed, path }`.
428
+ * const { numShrinks, seed, path } = error.run;
429
+ * console.log(numShrinks, seed, path);
430
+ * }
431
+ * }
225
432
  * ```
226
433
  */
227
- declare function foldEvents<S, M extends {
228
- type: string;
229
- }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: NoInfer<Ctx>, loaded: NoInfer<S> | null, msgs: readonly NoInfer<M>[]): {
230
- readonly states: readonly S[];
231
- readonly steps: readonly Step<S, M, C>[];
232
- readonly finalState: S;
233
- };
234
-
235
- /** Common options shared by every property runner. */
236
- interface PropertyOpts<S> {
237
- /** Pre-init snapshot — passed to `machine.init(loaded, ctx)`. Default `null`. */
238
- readonly loaded?: S | null;
239
- /** Number of generated sequences to test. Forwarded to `fc.assert`. */
240
- readonly numRuns?: number;
241
- /** Optional fast-check seed for reproducibility. */
242
- readonly seed?: number;
434
+ declare class PropertyFailure extends Error {
435
+ /** The run that failed. */
436
+ readonly run: PropertyRun;
437
+ readonly name = "PropertyFailure";
438
+ readonly _tag: "PropertyFailure";
439
+ constructor(report: string,
440
+ /** The run that failed. */
441
+ run: PropertyRun, options?: ErrorOptions);
243
442
  }
244
443
  /**
245
444
  * Assert every generated Msg sequence ends in a terminal state. `terminal`
@@ -250,6 +449,8 @@ interface PropertyOpts<S> {
250
449
  * non-terminal state and no further msg can escape) AND silent loops
251
450
  * (every msg variant is a noop from a given state).
252
451
  *
452
+ * `terminal` also gets the Cmds `init` sent on that list's start.
453
+ *
253
454
  * @example
254
455
  * ```ts
255
456
  * import type { Machine } from "@demlik/tea";
@@ -278,11 +479,17 @@ interface PropertyOpts<S> {
278
479
  */
279
480
  declare function propertyTerminates<S, M extends {
280
481
  type: string;
281
- }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: NoInfer<Ctx>, seqArb: fc.Arbitrary<readonly NoInfer<M>[]>, terminal: (state: NoInfer<S>) => boolean, opts?: NoInfer<PropertyOpts<S>>): void;
482
+ }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: NoInfer<Ctx>, seqArb: fc.Arbitrary<readonly NoInfer<M>[]>, terminal: (state: NoInfer<S>, initCmds: readonly NoInfer<C>[]) => boolean, opts?: NoInfer<PropertyOpts<S>>): PropertyRun;
282
483
  /**
283
484
  * Assert a predicate holds on EVERY transition step in a generated
284
- * sequence. The predicate sees `{ prev, msg, next, cmds }` per step;
285
- * return true to accept, false (or throw) to fail.
485
+ * sequence. The predicate sees `{ prev, msg, next, cmds }` per step. Return
486
+ * `true` or `null` to accept. Return `false`, or a string saying why, to fail: the
487
+ * thrown error's message names that string. An empty string fails too. A throw
488
+ * still fails with its own message.
489
+ *
490
+ * The predicate's second argument is the Cmds `init` sent on that list's start. It
491
+ * runs once per step, so an empty list never shows them to it: use `propertyTrace`
492
+ * for a rule about `init` alone.
286
493
  *
287
494
  * Use to pin a "when X happens in state Y, cmd Z must be emitted" rule.
288
495
  * The shrinker minimizes the offending sequence to the smallest Msg list
@@ -317,13 +524,18 @@ declare function propertyTerminates<S, M extends {
317
524
  */
318
525
  declare function propertyInvariant<S, M extends {
319
526
  type: string;
320
- }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: NoInfer<Ctx>, seqArb: fc.Arbitrary<readonly NoInfer<M>[]>, invariant: (step: Step<NoInfer<S>, NoInfer<M>, NoInfer<C>>) => boolean, opts?: NoInfer<PropertyOpts<S>>): void;
527
+ }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: NoInfer<Ctx>, seqArb: fc.Arbitrary<readonly NoInfer<M>[]>, invariant: (step: Step<NoInfer<S>, NoInfer<M>, NoInfer<C>>, initCmds: readonly NoInfer<C>[]) => boolean | string | null, opts?: NoInfer<PropertyOpts<S>>): PropertyRun;
321
528
  /**
322
529
  * Assert a predicate over the full trace plus the final state. Use when
323
530
  * the property is "some Msg appeared in the trace before reaching the
324
531
  * final state" or "no two consecutive steps had the same state.type" —
325
532
  * predicates that need the full sequence, not a single step.
326
533
  *
534
+ * Return `true` or `null` to accept. Return `false`, or a string saying why, to
535
+ * fail: the thrown error's message names that string. An empty string fails too.
536
+ * A throw still fails with its own message. The last argument is the Cmds `init`
537
+ * sent on that list's start.
538
+ *
327
539
  * @example
328
540
  * ```ts
329
541
  * import type { Machine } from "@demlik/tea";
@@ -349,7 +561,7 @@ declare function propertyInvariant<S, M extends {
349
561
  */
350
562
  declare function propertyTrace<S, M extends {
351
563
  type: string;
352
- }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: NoInfer<Ctx>, seqArb: fc.Arbitrary<readonly NoInfer<M>[]>, predicate: (steps: readonly Step<NoInfer<S>, NoInfer<M>, NoInfer<C>>[], finalState: NoInfer<S>) => boolean, opts?: NoInfer<PropertyOpts<S>>): void;
564
+ }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: NoInfer<Ctx>, seqArb: fc.Arbitrary<readonly NoInfer<M>[]>, predicate: (steps: readonly Step<NoInfer<S>, NoInfer<M>, NoInfer<C>>[], finalState: NoInfer<S>, initCmds: readonly NoInfer<C>[]) => boolean | string | null, opts?: NoInfer<PropertyOpts<S>>): PropertyRun;
353
565
 
354
566
  /**
355
567
  * Extract the Msg discriminant set at runtime from a machine. Useful for
@@ -388,4 +600,4 @@ declare function msgTypeKeys<S, M extends {
388
600
  __form?: UpdateForm;
389
601
  }): readonly string[];
390
602
 
391
- export { type MsgArbitraryTable, type Step, arbConstantMsg, arbGuidedSequence, arbMsg, arbMsgSequence, arbRecordMsg, foldEvents, msgTypeKeys, propertyInvariant, propertyTerminates, propertyTrace, stubCtxThrowingProxy };
603
+ export { type AnswerCell, type GuidedCells, type MsgArbitraryTable, type MsgCount, PropertyFailure, type PropertyOpts, type PropertyRun, Step, arbConstantMsg, arbGuidedSequence, arbMsg, arbMsgSequence, arbRecordMsg, msgTypeKeys, propertyInvariant, propertyTerminates, propertyTrace, stubCtxThrowingProxy };