@directive-run/core 1.4.0 → 1.6.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 (73) hide show
  1. package/dist/adapter-utils.cjs +1 -1
  2. package/dist/adapter-utils.d.cts +1 -1
  3. package/dist/adapter-utils.d.ts +1 -1
  4. package/dist/adapter-utils.js +1 -1
  5. package/dist/{chunk-FK7BD7XT.js → chunk-EH2Q754B.js} +3 -3
  6. package/dist/chunk-EH2Q754B.js.map +1 -0
  7. package/dist/chunk-EOLY64E6.cjs +3 -0
  8. package/dist/chunk-EOLY64E6.cjs.map +1 -0
  9. package/dist/chunk-K3KVGWLP.cjs +3 -0
  10. package/dist/chunk-K3KVGWLP.cjs.map +1 -0
  11. package/dist/chunk-OVNPYGYJ.js +3 -0
  12. package/dist/chunk-OVNPYGYJ.js.map +1 -0
  13. package/dist/chunk-QOK7CHOW.js +16 -0
  14. package/dist/chunk-QOK7CHOW.js.map +1 -0
  15. package/dist/{chunk-DDUARSUH.cjs → chunk-S3CFYDIB.cjs} +3 -3
  16. package/dist/chunk-S3CFYDIB.cjs.map +1 -0
  17. package/dist/chunk-T4ZO4IYL.cjs +16 -0
  18. package/dist/chunk-T4ZO4IYL.cjs.map +1 -0
  19. package/dist/chunk-T6IJUWYR.js +3 -0
  20. package/dist/chunk-T6IJUWYR.js.map +1 -0
  21. package/dist/{helpers-BUY1lYCX.d.cts → helpers-BwAThjnJ.d.ts} +12 -2
  22. package/dist/{helpers-D6LcRum7.d.ts → helpers-CG27mEGG.d.cts} +12 -2
  23. package/dist/index.cjs +4 -1
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +413 -5
  26. package/dist/index.d.ts +413 -5
  27. package/dist/index.js +4 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/internals.cjs +1 -1
  30. package/dist/internals.d.cts +86 -40
  31. package/dist/internals.d.ts +86 -40
  32. package/dist/internals.js +1 -1
  33. package/dist/plugins/index.cjs +1 -1
  34. package/dist/plugins/index.cjs.map +1 -1
  35. package/dist/plugins/index.d.cts +11 -8
  36. package/dist/plugins/index.d.ts +11 -8
  37. package/dist/plugins/index.js +1 -1
  38. package/dist/plugins/index.js.map +1 -1
  39. package/dist/{plugins-Dy1C8GtT.d.cts → plugins-DvrsPhzx.d.cts} +471 -68
  40. package/dist/{plugins-Dy1C8GtT.d.ts → plugins-DvrsPhzx.d.ts} +471 -68
  41. package/dist/system-5BSCMT63.cjs +2 -0
  42. package/dist/{system-2THXJBFM.cjs.map → system-5BSCMT63.cjs.map} +1 -1
  43. package/dist/system-DMJ6XEJ7.js +2 -0
  44. package/dist/{system-JIO36ALC.js.map → system-DMJ6XEJ7.js.map} +1 -1
  45. package/dist/testing.cjs +1 -1
  46. package/dist/testing.cjs.map +1 -1
  47. package/dist/testing.d.cts +1 -1
  48. package/dist/testing.d.ts +1 -1
  49. package/dist/testing.js +1 -1
  50. package/dist/testing.js.map +1 -1
  51. package/dist/worker.cjs +1 -1
  52. package/dist/worker.cjs.map +1 -1
  53. package/dist/worker.d.cts +1 -1
  54. package/dist/worker.d.ts +1 -1
  55. package/dist/worker.js +1 -1
  56. package/dist/worker.js.map +1 -1
  57. package/package.json +1 -1
  58. package/dist/chunk-4CMO5OVZ.js +0 -3
  59. package/dist/chunk-4CMO5OVZ.js.map +0 -1
  60. package/dist/chunk-BEJ6ICA7.cjs +0 -3
  61. package/dist/chunk-BEJ6ICA7.cjs.map +0 -1
  62. package/dist/chunk-DDUARSUH.cjs.map +0 -1
  63. package/dist/chunk-E2WETPLH.js +0 -3
  64. package/dist/chunk-E2WETPLH.js.map +0 -1
  65. package/dist/chunk-FK7BD7XT.js.map +0 -1
  66. package/dist/chunk-LFMRWCIG.js +0 -16
  67. package/dist/chunk-LFMRWCIG.js.map +0 -1
  68. package/dist/chunk-TUS5WDVE.cjs +0 -3
  69. package/dist/chunk-TUS5WDVE.cjs.map +0 -1
  70. package/dist/chunk-VSHSYVSY.cjs +0 -16
  71. package/dist/chunk-VSHSYVSY.cjs.map +0 -1
  72. package/dist/system-2THXJBFM.cjs +0 -2
  73. package/dist/system-JIO36ALC.js +0 -2
package/dist/index.d.cts CHANGED
@@ -1,8 +1,408 @@
1
- import { S as SchemaType, D as DefinitionMeta, M as ModuleSchema, F as Facts, T as TypedDerivationsDef, a as TypedEventsDef, E as EffectsDef, b as TypedConstraintsDef, c as TypedResolversDef, d as ModuleHooks, C as CrossModuleDeps, e as CrossModuleDerivationsDef, f as CrossModuleEffectsDef, g as CrossModuleConstraintsDef, h as ModuleDef, i as CreateSystemOptionsSingle, j as SingleModuleSystem, k as ModulesMap, l as CreateSystemOptionsNamed, N as NamespacedSystem, P as Plugin, m as TraceOption, n as ErrorBoundaryConfig, R as RequirementWithId, o as Requirement, p as RequirementKeyFn } from './plugins-Dy1C8GtT.cjs';
2
- export { A as AnySystem, B as BatchConfig, q as DirectiveError, r as DistributableSnapshot, s as DistributableSnapshotOptions, t as DynamicConstraintDef, u as DynamicEffectDef, v as DynamicResolverDef, w as FactsSnapshot, H as HistoryAPI, x as HistoryOption, y as HistoryState, I as InferDerivations, z as InferEvents, G as InferFacts, J as InferRequirementTypes, K as InferRequirements, L as InferSchemaType, O as InferSelectorState, Q as MetaAccessor, U as MetaMatch, V as ObservationEvent, W as RetryPolicy, X as Schema, Y as Snapshot, Z as System, _ as SystemConfig, $ as SystemInspection, a0 as SystemMode, a1 as SystemSnapshot, a2 as TraceEntry, a3 as isNamespacedSystem, a4 as isSingleModuleSystem } from './plugins-Dy1C8GtT.cjs';
3
- export { D as DerivationDefWithMeta, t as typedConstraint, a as typedResolver } from './helpers-BUY1lYCX.cjs';
1
+ import { P as PatchSpec, C as ClauseResult, F as FactTemplate, a as FactPredicate, S as SchemaType, D as DefinitionMeta, M as ModuleSchema, b as Facts, T as TypedDerivationsDef, c as TypedEventsDef, E as EffectsDef, d as TypedConstraintsDef, e as TypedResolversDef, f as ModuleHooks, g as CrossModuleDeps, h as CrossModuleDerivationsDef, i as CrossModuleEffectsDef, j as CrossModuleConstraintsDef, k as ModuleDef, l as CreateSystemOptionsSingle, m as SingleModuleSystem, n as ModulesMap, o as CreateSystemOptionsNamed, N as NamespacedSystem, p as Plugin, q as TraceOption, r as ErrorBoundaryConfig, R as RequirementWithId, s as Requirement, t as RequirementKeyFn } from './plugins-DvrsPhzx.cjs';
2
+ export { A as AnySystem, B as BatchConfig, u as DirectiveError, v as DistributableSnapshot, w as DistributableSnapshotOptions, x as DynamicConstraintDef, y as DynamicEffectDef, z as DynamicResolverDef, G as FactsSnapshot, H as HistoryAPI, I as HistoryOption, J as HistoryState, K as InferDerivations, L as InferEvents, O as InferFacts, Q as InferRequirementTypes, U as InferRequirements, V as InferSchemaType, W as InferSelectorState, X as KeySelector, Y as MetaAccessor, Z as MetaMatch, _ as ObservationEvent, $ as OperatorObject, a0 as PatchValue, a1 as PayloadRef, a2 as PredicateClause, a3 as PredicateCombinator, a4 as PredicateCombinatorKey, a5 as PredicateObject, a6 as PredicateOp, a7 as RetryPolicy, a8 as Schema, a9 as Snapshot, aa as System, ab as SystemConfig, ac as SystemInspection, ad as SystemMode, ae as SystemSnapshot, af as TraceEntry, ag as isNamespacedSystem, ah as isSingleModuleSystem } from './plugins-DvrsPhzx.cjs';
3
+ export { D as DerivationDefWithMeta, t as typedConstraint, a as typedResolver } from './helpers-CG27mEGG.cjs';
4
4
  export { D as DistributableSnapshotLike, S as SignedSnapshot, a as SnapshotDiff, b as SnapshotDiffEntry, d as diffSnapshots, i as isSignedSnapshot, c as isSnapshotExpired, s as shallowEqual, e as signSnapshot, v as validateSnapshot, f as verifySnapshotSignature } from './utils-BnQajqPu.cjs';
5
5
 
6
+ /**
7
+ * Runtime for data-configuration predicates and templates.
8
+ *
9
+ * Pure module — imports only its own types. Reads facts through whatever
10
+ * object it is handed (the reactive `Facts` proxy in production, a plain
11
+ * snapshot in tests), so it never depends on the engine, store, or tracking.
12
+ */
13
+
14
+ /** A readable scope — the `Facts` proxy and a plain snapshot both satisfy it. */
15
+ type Scope = Record<string, unknown>;
16
+ /**
17
+ * True when `v` is a data-form spec (predicate object/array) rather than a
18
+ * function. Excludes class instances (Date, RegExp, Map, Set, Promise, etc.)
19
+ * — only plain `{}` literals and arrays of plain clause shapes qualify.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * isPredicate({ phase: "red" }); // true
24
+ * isPredicate((f) => f.phase === "red"); // false
25
+ * isPredicate([{ fact: "phase", op: "$eq", value: "red" }]); // true
26
+ * ```
27
+ */
28
+ declare function isPredicate(v: unknown): boolean;
29
+ /**
30
+ * True when `v` is a {@link FactTemplate} (`{ $template: string }`).
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * isTemplate({ $template: "Hi ${name}" }); // true
35
+ * isTemplate({ $set: { name: "x" } }); // false
36
+ * ```
37
+ */
38
+ declare function isTemplate(v: unknown): v is FactTemplate;
39
+ /**
40
+ * Throw when a predicate spec contains an operand that cannot survive a
41
+ * JSON round-trip — i.e. that would silently mis-evaluate if the spec was
42
+ * loaded from `JSON.parse`.
43
+ *
44
+ * Three failure classes are detected:
45
+ *
46
+ * - **Lost `RegExp` operand.** A `$matches` operand that is not a
47
+ * `RegExp` instance. `JSON.parse` reconstructs a serialized regex as
48
+ * `{}`, so a `$matches` clause with an empty-object operand is the
49
+ * signature of a regex that did not survive serialization. Reify it
50
+ * with `new RegExp(pattern, flags)` before installing the predicate.
51
+ * - **`bigint` operand.** `JSON.stringify` throws on `bigint`, so a
52
+ * `bigint` operand cannot have been produced by a JSON pipeline and
53
+ * cannot be persisted by one either.
54
+ * - **`Set` / `Map` operand.** Both serialize to `{}` and lose all
55
+ * members; a predicate carrying one is not JSON-safe.
56
+ *
57
+ * This is an opt-in helper — the engine does not call it automatically.
58
+ * Users who load predicates from JSON should call it after `JSON.parse`
59
+ * to fail loud rather than silently mis-evaluate. See the
60
+ * "Serialization" section of RFC-0004.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * validatePredicate({ phase: { $matches: {} } });
65
+ * // throws — empty object where a RegExp is required
66
+ *
67
+ * validatePredicate({ phase: "red", elapsed: { $gte: 30 } });
68
+ * // ok — JSON-clean operands
69
+ * ```
70
+ */
71
+ declare function validatePredicate(spec: unknown, path?: string): void;
72
+ /**
73
+ * Evaluate a {@link FactPredicate} against a fact scope. `prev` (a previous
74
+ * snapshot) is consulted only by the `$changed` operator.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * evaluatePredicate({ phase: "red", elapsed: { $gte: 30 } }, { phase: "red", elapsed: 45 });
79
+ * // → true
80
+ * evaluatePredicate({ $any: [{ phase: "red" }, { phase: "yellow" }] }, { phase: "green" });
81
+ * // → false
82
+ * ```
83
+ */
84
+ declare function evaluatePredicate(spec: unknown, facts: Scope, prev?: Scope, depth?: number): boolean;
85
+ /**
86
+ * Evaluate a predicate and return a per-clause breakdown — the data feed for
87
+ * devtools, `system.explain()`, and `directive explain`.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * evaluatePredicateExplained(
92
+ * { phase: "red", elapsed: { $gte: 30 } },
93
+ * { phase: "red", elapsed: 20 },
94
+ * );
95
+ * // → [
96
+ * // { path: "phase", op: "$eq", expected: "red", actual: "red", pass: true },
97
+ * // { path: "elapsed", op: "$gte", expected: 30, actual: 20, pass: false },
98
+ * // ]
99
+ * ```
100
+ */
101
+ declare function evaluatePredicateExplained(spec: unknown, facts: Scope, prev?: Scope, pathPrefix?: string): ClauseResult[];
102
+ /**
103
+ * Memoize a predicate as a reusable evaluation closure.
104
+ *
105
+ * The returned function accepts any `facts` scope (the reactive proxy in
106
+ * production, a plain object in tests) plus an optional `prev` snapshot for
107
+ * `$changed`. The closure is cached **by predicate identity** in a
108
+ * `WeakMap`, so passing the same `predicate` reference repeatedly is
109
+ * allocation-free; cleanup is automatic once the predicate is no longer
110
+ * reachable.
111
+ *
112
+ * Note: no actual compilation happens — the returned closure re-walks the
113
+ * spec on every call via `evaluatePredicate`. The name reflects what the
114
+ * function does (closure memoization keyed by predicate identity), not a
115
+ * bytecode/AST compile step.
116
+ *
117
+ * Intended for advanced users who want a stable function reference per
118
+ * predicate (custom devtools, batched analyses). Regular module code does
119
+ * not need to call this — the engine wraps data-form `when` / `on` specs
120
+ * automatically at registration.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * const predicate = { phase: "red", elapsed: { $gte: 30 } };
125
+ * const check = memoizePredicate(predicate);
126
+ * check({ phase: "red", elapsed: 45 }); // → true
127
+ * check({ phase: "red", elapsed: 5 }); // → false
128
+ * ```
129
+ */
130
+ declare function memoizePredicate(predicate: object): (facts: Scope, prev?: Scope) => boolean;
131
+ /**
132
+ * Collect the fact keys a predicate references. Used for static analysis,
133
+ * devtools, and effect `on` dependency wiring. Nested predicates contribute
134
+ * dotted keys (`auth.token`).
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * extractDeps({ phase: "red", elapsed: { $gte: 30 } });
139
+ * // → Set { "phase", "elapsed" }
140
+ * extractDeps({ self: { phase: "red" }, auth: { token: { $exists: true } } });
141
+ * // → Set { "self.phase", "auth.token" }
142
+ * ```
143
+ */
144
+ declare function extractDeps(spec: unknown, prefix?: string): Set<string>;
145
+ /**
146
+ * Interpolate a {@link FactTemplate} against a scope. Single-pass character
147
+ * scanner: `${ident}` interpolates `scope[ident]`; `$${` emits a literal
148
+ * `${`; unknown keys dev-warn and yield an empty string.
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * evaluateTemplate({ $template: "Hi ${name}!" }, { name: "Ada" });
153
+ * // → "Hi Ada!"
154
+ * evaluateTemplate({ $template: "$${price}" }, {});
155
+ * // → "${price}"
156
+ * ```
157
+ */
158
+ declare function evaluateTemplate(spec: FactTemplate, scope: Scope): string;
159
+ /**
160
+ * Collect the placeholder keys referenced by a template. The static-analysis
161
+ * counterpart to {@link extractDeps} — useful for devtools, codegen, and
162
+ * "which facts does this template read" inspections. Only valid identifier
163
+ * placeholders are collected; malformed ones are ignored.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * extractTemplateKeys({ $template: "${firstName} ${lastName}" });
168
+ * // → Set { "firstName", "lastName" }
169
+ * extractTemplateKeys({ $template: "$${literal}" });
170
+ * // → Set {} (escaped — not a placeholder)
171
+ * ```
172
+ */
173
+ declare function extractTemplateKeys(spec: FactTemplate): Set<string>;
174
+ /**
175
+ * Build a stable dedup key by selecting fields from a requirement payload.
176
+ * Order-as-declared; values are stable-stringified (keys sorted recursively)
177
+ * so two payloads with the same fields in different orders dedupe to the
178
+ * same key.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * evaluateKeySelector(["url", "method"], { url: "/a", method: "GET" });
183
+ * // → '"/a"|"GET"'
184
+ * evaluateKeySelector(["id"], { id: 42 });
185
+ * // → '42'
186
+ * ```
187
+ */
188
+ declare function evaluateKeySelector(selector: readonly string[], source: Record<string, unknown>): string;
189
+ /**
190
+ * Apply a {@link PatchSpec} — assign facts from literals, payload copies
191
+ * (`$ref`), or interpolated strings (`$template`). Mutates through the passed
192
+ * `facts` proxy so change-tracking and downstream invalidation fire.
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * const spec = {
197
+ * $set: {
198
+ * active: true,
199
+ * userId: { $ref: "id" },
200
+ * label: { $template: "user ${name}" },
201
+ * },
202
+ * };
203
+ * applyPatch(spec, facts, { id: "u_1", name: "Ada" });
204
+ * // facts.active = true; facts.userId = "u_1"; facts.label = "user Ada"
205
+ * ```
206
+ */
207
+ declare function applyPatch(spec: PatchSpec<Record<string, unknown>, Record<string, unknown>>, facts: Record<string, unknown>, payload: Record<string, unknown>): void;
208
+
209
+ /**
210
+ * Predicate backtest — rule-change impact.
211
+ *
212
+ * Given a recorded sequence of fact-state frames and a proposed replacement
213
+ * for a constraint's `when` predicate, re-score the frames against BOTH the
214
+ * original and the proposed predicate and produce a backtest report: how
215
+ * many frames each matched, plus the per-frame diff (frames that newly
216
+ * match / no longer match). It answers "how many recorded frames would the
217
+ * proposed rule have matched differently" against real recorded history.
218
+ *
219
+ * This is a *static* backtest, not a behavioral simulation — the engine is
220
+ * not re-run. Recorded frames are re-scored against a new predicate; the
221
+ * resulting requirements, resolvers, and downstream fact changes are NOT
222
+ * modeled. Treat the numbers as a divergence scan, not a forecast.
223
+ *
224
+ * Pure module — imports only the predicate runtime. No engine, store, or
225
+ * tracking dependency. Replay walks frames in order; for `$changed`-style
226
+ * predicates each frame is evaluated against the previous frame's facts.
227
+ */
228
+
229
+ /**
230
+ * Upper bound on the number of frames a single {@link replayUnder} call will
231
+ * process. A history file is held entirely in memory; an unbounded array is a
232
+ * denial-of-service surface. A history larger than this should be split or
233
+ * down-sampled before replay.
234
+ */
235
+ declare const MAX_REPLAY_FRAMES = 1000000;
236
+ /** One recorded fact-state frame. */
237
+ interface ReplayFrame<F = Record<string, unknown>> {
238
+ /** Stable identifier — a snapshot id, an index, a session key. */
239
+ id: string | number;
240
+ /** Optional wall-clock time (ms epoch). */
241
+ timestamp?: number;
242
+ /** The fact state at this frame. */
243
+ facts: F;
244
+ }
245
+ interface ReplayUnderOptions<F = Record<string, unknown>> {
246
+ /** Recorded frames, chronological order. */
247
+ frames: readonly ReplayFrame<F>[];
248
+ /** The constraint's current `when` predicate (the baseline). */
249
+ original: FactPredicate<F>;
250
+ /** The proposed replacement `when` predicate. */
251
+ proposed: FactPredicate<F>;
252
+ /** Max diff samples to attach per bucket (default 20). */
253
+ maxSamples?: number;
254
+ /**
255
+ * A fact key identifying the entity a frame belongs to (e.g. `"userId"`,
256
+ * `"sessionId"`). When set, the report also reports distinct-entity counts
257
+ * — how many unique entities matched, not just how many frames. Without it
258
+ * the unit is frames: one fact snapshot, not one user or session.
259
+ */
260
+ entityKey?: string;
261
+ }
262
+ /** A frame where the original and proposed predicate disagree. */
263
+ interface ReplayDiffSample {
264
+ frameId: string | number;
265
+ timestamp?: number;
266
+ /** The fact state at this frame. */
267
+ facts: Record<string, unknown>;
268
+ /** Per-clause breakdown under the original predicate. */
269
+ originalExplain: ClauseResult[];
270
+ /** Per-clause breakdown under the proposed predicate. */
271
+ proposedExplain: ClauseResult[];
272
+ }
273
+ /**
274
+ * The outcome of replaying a recorded history under two predicates — a
275
+ * static backtest of a proposed rule change, not a behavioral simulation.
276
+ */
277
+ interface PredicateBacktestReport {
278
+ /** Total frames evaluated. */
279
+ framesEvaluated: number;
280
+ /**
281
+ * Frames where the original predicate matched (was true). `matchedEntities`
282
+ * is the count of distinct `entityKey` values among matched frames — only
283
+ * populated when {@link ReplayUnderOptions.entityKey} was supplied.
284
+ */
285
+ original: {
286
+ matched: number;
287
+ matchedEntities?: number;
288
+ };
289
+ /** Frames where the proposed predicate matched. */
290
+ proposed: {
291
+ matched: number;
292
+ matchedEntities?: number;
293
+ };
294
+ /** proposed.matched - original.matched. */
295
+ delta: number;
296
+ /** Total count of frames that newly match (original false -> proposed true). */
297
+ newMatchCount: number;
298
+ /** Total count of frames that no longer match (original true -> proposed false). */
299
+ lostMatchCount: number;
300
+ /** Frames where original and proposed agree. */
301
+ unchanged: number;
302
+ /** Sampled new-match frames (capped at maxSamples), with clause explain. */
303
+ newMatches: ReplayDiffSample[];
304
+ /** Sampled lost-match frames (capped at maxSamples), with clause explain. */
305
+ lostMatches: ReplayDiffSample[];
306
+ }
307
+ /**
308
+ * Normalize a parsed-from-JSON history value into a `ReplayFrame[]`.
309
+ *
310
+ * Accepts the three documented history shapes:
311
+ *
312
+ * 1. A bare array of frames: `[{ id, timestamp?, facts }, ...]`
313
+ * 2. An object wrapping them: `{ frames: [{ id, ..., facts }, ...] }`
314
+ * 3. A bare array of fact objects: `[{ phase: "red", ... }, ...]`
315
+ * — each element is wrapped as a frame.
316
+ *
317
+ * Frame ids are kept unambiguous: a frame that supplies its own `id` keeps
318
+ * it; a frame missing an `id` (and every bare-fact frame) gets an
319
+ * **index-derived** id prefixed with `#` (`"#0"`, `"#1"`, …) so a fallback
320
+ * id can never collide with an explicit numeric or string id in a mixed
321
+ * history. This is a pure helper — it **throws** a clear `Error` on bad
322
+ * input and never calls `process.exit`, so library users can catch it.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * toReplayFrames([{ id: "s1", facts: { phase: "red" } }]);
327
+ * // → [{ id: "s1", facts: { phase: "red" } }]
328
+ * toReplayFrames([{ phase: "red" }, { phase: "green" }]);
329
+ * // → [{ id: "#0", facts: { phase: "red" } }, { id: "#1", facts: { phase: "green" } }]
330
+ * ```
331
+ */
332
+ declare function toReplayFrames(raw: unknown): ReplayFrame[];
333
+ /**
334
+ * Convert a history-manager export (the JSON produced by
335
+ * `system.history.export()`) into a `ReplayFrame[]` ready for
336
+ * {@link replayUnder}.
337
+ *
338
+ * The history manager records a ring buffer of `{ id, timestamp, facts,
339
+ * trigger }` snapshots; `export()` wraps them as `{ version, snapshots,
340
+ * currentIndex }`. Each snapshot's `facts` is the fact state at that point,
341
+ * so the snapshot sequence is exactly a frame stream.
342
+ *
343
+ * Accepts either the parsed export object, the raw JSON string, or a bare
344
+ * array of snapshots. Throws a clear `Error` on bad input.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * const frames = framesFromHistory(system.history.export());
349
+ * const report = replayUnder({ frames, original, proposed });
350
+ * ```
351
+ */
352
+ declare function framesFromHistory(historyExport: unknown): ReplayFrame[];
353
+ /**
354
+ * Convert an array of `{ id, timestamp?, facts }` snapshots — e.g. the
355
+ * `snapshots` field of a history export, or an array assembled by pushing
356
+ * `system.getSnapshot()` on each reconcile — into a `ReplayFrame[]`.
357
+ *
358
+ * Throws a clear `Error` if the argument is not an array. Each snapshot is
359
+ * normalized through the same {@link toReplayFrames} id logic.
360
+ *
361
+ * @example
362
+ * ```ts
363
+ * const snapshots: typeof history[] = [];
364
+ * system.observe(() => snapshots.push(system.getSnapshot()));
365
+ * // ...later
366
+ * const frames = framesFromSnapshots(snapshots);
367
+ * ```
368
+ */
369
+ declare function framesFromSnapshots(snapshots: unknown): ReplayFrame[];
370
+ /**
371
+ * Replay a recorded fact-frame history through two predicates — the
372
+ * constraint's current `when` and a proposed replacement — and report how
373
+ * their match sets differ.
374
+ *
375
+ * This is a **predicate backtest**: the engine is not re-run. Each recorded
376
+ * frame is statically re-scored against both specs; the previous frame's
377
+ * facts are threaded as `prev` so a replayed effect-`on` predicate using
378
+ * `$changed` still works (for a constraint `when` it is harmless). The
379
+ * report counts matches under each spec and buckets the disagreements into
380
+ * new matches (original false, proposed true) and lost matches (original
381
+ * true, proposed false). Up to `maxSamples` diff frames per bucket carry a
382
+ * per-clause `evaluatePredicateExplained` breakdown for inspection.
383
+ *
384
+ * Both specs are validated up front — a malformed spec throws a clear
385
+ * `[Directive] replayUnder:` error identifying which spec failed, rather
386
+ * than letting a raw `evaluatePredicate` error escape mid-loop.
387
+ *
388
+ * @example
389
+ * ```ts
390
+ * const report = replayUnder({
391
+ * frames: [
392
+ * { id: 0, facts: { phase: "red", elapsed: 10 } },
393
+ * { id: 1, facts: { phase: "red", elapsed: 35 } },
394
+ * ],
395
+ * original: { phase: "red" },
396
+ * proposed: { phase: "red", elapsed: { $gte: 30 } },
397
+ * });
398
+ * // report.original.matched === 2
399
+ * // report.proposed.matched === 1
400
+ * // report.delta === -1
401
+ * // report.lostMatchCount === 1
402
+ * ```
403
+ */
404
+ declare function replayUnder<F = Record<string, unknown>>(options: ReplayUnderOptions<F>): PredicateBacktestReport;
405
+
6
406
  /** Brand symbol for branded types */
7
407
  declare const Brand: unique symbol;
8
408
  /** Branded type - adds a unique brand to a base type */
@@ -793,6 +1193,14 @@ declare function createSystemWithStatus<M extends ModuleSchema>(options: CreateS
793
1193
  * - Requirement comparison and hashing
794
1194
  */
795
1195
 
1196
+ /**
1197
+ * Computes a stable identifier for a requirement, used for coalescing
1198
+ * duplicate fetches across the constraint graph.
1199
+ *
1200
+ * @param req - the requirement to identify
1201
+ * @param keyFn - optional custom key function; if omitted, a stableStringify-based default is used (memoized via WeakMap)
1202
+ * @returns the requirement's stable string ID
1203
+ */
796
1204
  declare function generateRequirementId(req: Requirement, keyFn?: RequirementKeyFn): string;
797
1205
  /**
798
1206
  * Create a typed requirement factory for a given requirement type string.
@@ -1141,7 +1549,7 @@ declare function resumeTimer(state: TimerFactState, nowMs: number): TimerFactSta
1141
1549
  declare function resetTimer(): TimerFactState;
1142
1550
  /**
1143
1551
  * Transition: mark the timer completed. For countdown mode when
1144
- * elapsed >= ms; for repeat mode when consumer wants to halt.
1552
+ * elapsed reaches `ms`; for repeat mode when consumer wants to halt.
1145
1553
  */
1146
1554
  declare function completeTimer(state: TimerFactState): TimerFactState;
1147
1555
  /**
@@ -1255,4 +1663,4 @@ declare const Backoff: {
1255
1663
  readonly Exponential: "exponential";
1256
1664
  };
1257
1665
 
1258
- export { Backoff, type Branded, type ChainableSchemaType, CreateSystemOptionsNamed, CreateSystemOptionsSingle, CrossModuleDeps, DefinitionMeta, ErrorBoundaryConfig, type ExtendedSchemaType, Facts, type ModuleConfig, type ModuleConfigWithDeps, ModuleDef, ModuleHooks, ModuleSchema, ModulesMap, NamespacedSystem, Plugin, Requirement, RequirementSet, type RequirementTypeStatus, RequirementWithId, SchemaType, type SignalClock, SingleModuleSystem, type TimerFactOpts, type TimerFactState, TraceOption, completeTimer, createModule, createModuleFactory, createRequirementStatusPlugin, createStatusHook, createSystem, createSystemWithStatus, defaultClock, elapsedMs, forType, generateRequirementId, initialTimerState, isRequirementType, pauseTimer, realClock, registerRepeat, remainingMs, req, resetTimer, resumeTimer, startTimer, t, tickTimer, timerOps, virtualClock };
1666
+ export { Backoff, type Branded, type ChainableSchemaType, ClauseResult, CreateSystemOptionsNamed, CreateSystemOptionsSingle, CrossModuleDeps, DefinitionMeta, ErrorBoundaryConfig, type ExtendedSchemaType, FactPredicate, FactTemplate, Facts, MAX_REPLAY_FRAMES, type ModuleConfig, type ModuleConfigWithDeps, ModuleDef, ModuleHooks, ModuleSchema, ModulesMap, NamespacedSystem, PatchSpec, Plugin, type PredicateBacktestReport, type ReplayDiffSample, type ReplayFrame, type ReplayUnderOptions, Requirement, RequirementSet, type RequirementTypeStatus, RequirementWithId, SchemaType, type SignalClock, SingleModuleSystem, type TimerFactOpts, type TimerFactState, TraceOption, applyPatch, completeTimer, createModule, createModuleFactory, createRequirementStatusPlugin, createStatusHook, createSystem, createSystemWithStatus, defaultClock, elapsedMs, evaluateKeySelector, evaluatePredicate, evaluatePredicateExplained, evaluateTemplate, extractDeps, extractTemplateKeys, forType, framesFromHistory, framesFromSnapshots, generateRequirementId, initialTimerState, isPredicate, isRequirementType, isTemplate, memoizePredicate, pauseTimer, realClock, registerRepeat, remainingMs, replayUnder, req, resetTimer, resumeTimer, startTimer, t, tickTimer, timerOps, toReplayFrames, validatePredicate, virtualClock };