@directive-run/core 1.21.0 → 1.23.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.
- package/README.md +2 -2
- package/dist/adapter-utils.cjs +1 -1
- package/dist/adapter-utils.d.cts +1 -1
- package/dist/adapter-utils.d.ts +1 -1
- package/dist/adapter-utils.js +1 -1
- package/dist/chunk-AFPLATPV.js +16 -0
- package/dist/chunk-AFPLATPV.js.map +1 -0
- package/dist/{chunk-4OXCKDKS.cjs → chunk-FCCJUQB2.cjs} +3 -3
- package/dist/chunk-FCCJUQB2.cjs.map +1 -0
- package/dist/chunk-IZNOPHPL.js +3 -0
- package/dist/chunk-IZNOPHPL.js.map +1 -0
- package/dist/chunk-LN5H74CU.js +2 -0
- package/dist/{chunk-RBF653NR.js.map → chunk-LN5H74CU.js.map} +1 -1
- package/dist/chunk-PJIIBQTK.js +2 -0
- package/dist/chunk-PJIIBQTK.js.map +1 -0
- package/dist/chunk-PTT4MWNL.cjs +3 -0
- package/dist/chunk-PTT4MWNL.cjs.map +1 -0
- package/dist/{chunk-GKMJ7NMP.cjs → chunk-RD2HO5NZ.cjs} +2 -2
- package/dist/{chunk-GKMJ7NMP.cjs.map → chunk-RD2HO5NZ.cjs.map} +1 -1
- package/dist/{chunk-GACC2DMS.cjs → chunk-S6W6Y44G.cjs} +2 -2
- package/dist/{chunk-GACC2DMS.cjs.map → chunk-S6W6Y44G.cjs.map} +1 -1
- package/dist/{chunk-6PF2FRBG.js → chunk-WD47DB52.js} +2 -2
- package/dist/{chunk-6PF2FRBG.js.map → chunk-WD47DB52.js.map} +1 -1
- package/dist/chunk-WKGNK4QK.cjs +2 -0
- package/dist/chunk-WKGNK4QK.cjs.map +1 -0
- package/dist/chunk-WVP3GOY2.js +3 -0
- package/dist/chunk-WVP3GOY2.js.map +1 -0
- package/dist/chunk-XKXTRVJF.cjs +3 -0
- package/dist/chunk-XKXTRVJF.cjs.map +1 -0
- package/dist/{index-CvQuu0tu.d.cts → index-DpvgHVHR.d.cts} +29 -2
- package/dist/{index-DUbpbGw8.d.ts → index-n8oQmdOZ.d.ts} +29 -2
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -164
- package/dist/index.d.ts +46 -164
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +1 -1
- package/dist/internals.d.cts +45 -25
- package/dist/internals.d.ts +45 -25
- package/dist/internals.js +1 -1
- package/dist/plugins/index.cjs +2 -2
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.d.cts +358 -3
- package/dist/plugins/index.d.ts +358 -3
- package/dist/plugins/index.js +2 -2
- package/dist/plugins/index.js.map +1 -1
- package/dist/{plugins-4IfhJV32.d.cts → plugins-CaPMTICa.d.cts} +400 -31
- package/dist/{plugins-4IfhJV32.d.ts → plugins-CaPMTICa.d.ts} +400 -31
- package/dist/{predicate-DBTPnlg6.d.cts → predicate-CU0YC1i6.d.cts} +1 -1
- package/dist/{predicate-BW05x5el.d.ts → predicate-CgpwJqf4.d.ts} +1 -1
- package/dist/system-HOP37Z4V.js +2 -0
- package/dist/{system-SJBP4TO5.js.map → system-HOP37Z4V.js.map} +1 -1
- package/dist/system-VZUHL56W.cjs +2 -0
- package/dist/{system-SB7JYMRB.cjs.map → system-VZUHL56W.cjs.map} +1 -1
- package/dist/testing.cjs +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/worker.cjs +1 -1
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +1 -1
- package/dist/worker.d.ts +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4OXCKDKS.cjs.map +0 -1
- package/dist/chunk-6QAUJFQH.js +0 -3
- package/dist/chunk-6QAUJFQH.js.map +0 -1
- package/dist/chunk-E53A4NHM.js +0 -16
- package/dist/chunk-E53A4NHM.js.map +0 -1
- package/dist/chunk-GBCWXTXW.cjs +0 -3
- package/dist/chunk-GBCWXTXW.cjs.map +0 -1
- package/dist/chunk-KGTBTGOY.js +0 -2
- package/dist/chunk-KGTBTGOY.js.map +0 -1
- package/dist/chunk-M5PEB553.cjs +0 -3
- package/dist/chunk-M5PEB553.cjs.map +0 -1
- package/dist/chunk-MARZI3EY.cjs +0 -2
- package/dist/chunk-MARZI3EY.cjs.map +0 -1
- package/dist/chunk-RBF653NR.js +0 -2
- package/dist/chunk-SFUVPP4L.js +0 -3
- package/dist/chunk-SFUVPP4L.js.map +0 -1
- package/dist/system-SB7JYMRB.cjs +0 -2
- package/dist/system-SJBP4TO5.js +0 -2
|
@@ -568,26 +568,29 @@ interface ConstraintDef<S extends Schema, R extends Requirement = Requirement> {
|
|
|
568
568
|
/** Timeout for async constraints (ms) */
|
|
569
569
|
timeout?: number;
|
|
570
570
|
/**
|
|
571
|
-
* Fact keys
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
* aborted.
|
|
571
|
+
* Fact keys this resolver **aborts on** when they change mid-flight.
|
|
572
|
+
* The engine snapshots their values at resolver dispatch; if any of
|
|
573
|
+
* them changes before the resolver writes, the resolver's writes are
|
|
574
|
+
* dropped and the resolver aborted.
|
|
575
575
|
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
576
|
+
* Reads aloud as "abort on changes to these facts." This is the
|
|
577
|
+
* opposite shape of a lock — the resolver yields to whoever wrote
|
|
578
|
+
* first; it does NOT prevent other writers. Value-based per-fact
|
|
579
|
+
* compare-and-swap with one-shot fact-level poisoning, not document
|
|
580
|
+
* versioning. Writes to facts not listed always land; `when()` is not
|
|
581
|
+
* consulted. Omit for no binding (default). Ignored on async
|
|
582
|
+
* constraints.
|
|
580
583
|
*
|
|
581
584
|
* @example
|
|
582
585
|
* ```ts
|
|
583
586
|
* executeAction: {
|
|
584
587
|
* when: (f) => f.status === 'mutating',
|
|
585
588
|
* require: { type: 'EXECUTE_ACTION' },
|
|
586
|
-
*
|
|
589
|
+
* abortOn: ['status'], // abort if `status` changes mid-flight
|
|
587
590
|
* }
|
|
588
591
|
* ```
|
|
589
592
|
*/
|
|
590
|
-
|
|
593
|
+
abortOn?: readonly string[];
|
|
591
594
|
/**
|
|
592
595
|
* Constraint IDs whose resolvers must complete before this constraint is evaluated.
|
|
593
596
|
* If a dependency's `when()` returns false (no requirements), this constraint proceeds.
|
|
@@ -1072,6 +1075,60 @@ type EventsDef<S extends Schema> = Record<string, FlexibleEventHandler<S>>;
|
|
|
1072
1075
|
type JitterStrategy = "none" | "full" | "equal" | {
|
|
1073
1076
|
maxMs: number;
|
|
1074
1077
|
};
|
|
1078
|
+
/**
|
|
1079
|
+
* Why a resolver attempt was aborted, surfaced to {@link RetryPolicy.shouldRetry}
|
|
1080
|
+
* so the policy can distinguish "retry on race-loss" from "fail loud on bug."
|
|
1081
|
+
*
|
|
1082
|
+
* - `"clobbered"` — an `abortOn`-listed fact was written by something
|
|
1083
|
+
* outside the resolver between the resolver's baseline read and its
|
|
1084
|
+
* next write (RFC 0003 optimistic-concurrency check). The retry is
|
|
1085
|
+
* typically safe — the resolver re-reads the current value and tries
|
|
1086
|
+
* again with fresh context.
|
|
1087
|
+
* - `"timeout"` — the resolver's `timeout` ms elapsed. Retrying may
|
|
1088
|
+
* succeed if the underlying I/O was just slow; may fail again if the
|
|
1089
|
+
* timeout is set too tight for the workload.
|
|
1090
|
+
* - `"cancelled"` — `system.stop()` was called, or external code aborted
|
|
1091
|
+
* the resolver's `ctx.signal`. Retrying is almost always wrong — the
|
|
1092
|
+
* cancellation was the caller's explicit intent.
|
|
1093
|
+
* - `"error"` — the resolver threw. Default for back-compat: any abort
|
|
1094
|
+
* without a more specific reason maps here.
|
|
1095
|
+
*
|
|
1096
|
+
* Core has no supersession concept today; if a future package wants to
|
|
1097
|
+
* surface supersession, it can widen the union additively without
|
|
1098
|
+
* breaking back-compat.
|
|
1099
|
+
*
|
|
1100
|
+
* @public
|
|
1101
|
+
*/
|
|
1102
|
+
type ResolverAbortReason = "clobbered" | "timeout" | "cancelled" | "error";
|
|
1103
|
+
/**
|
|
1104
|
+
* Context object passed as the optional third argument to
|
|
1105
|
+
* {@link RetryPolicy.shouldRetry}. Lets the policy decide based on WHY
|
|
1106
|
+
* the attempt failed — not just the thrown error.
|
|
1107
|
+
*
|
|
1108
|
+
* Two-argument `shouldRetry(error, attempt)` continues to work; the
|
|
1109
|
+
* context argument is additive. Existing callers see no behavior change.
|
|
1110
|
+
*
|
|
1111
|
+
* @public
|
|
1112
|
+
*/
|
|
1113
|
+
interface ShouldRetryContext {
|
|
1114
|
+
/**
|
|
1115
|
+
* Why the attempt was aborted. Undefined when the engine couldn't
|
|
1116
|
+
* classify the cause (very rare — defaults to `"error"` in practice).
|
|
1117
|
+
*/
|
|
1118
|
+
reason?: ResolverAbortReason;
|
|
1119
|
+
/**
|
|
1120
|
+
* Populated only when `reason === "clobbered"`. Names the `abortOn`
|
|
1121
|
+
* fact whose mid-flight change tripped the binding, plus the expected
|
|
1122
|
+
* baseline value and the actual current value. Useful for
|
|
1123
|
+
* `shouldRetry: (_, _, ctx) => ctx?.clobber?.fact === "cart.discount"`
|
|
1124
|
+
* patterns that only retry contention on specific facts.
|
|
1125
|
+
*/
|
|
1126
|
+
clobber?: {
|
|
1127
|
+
fact: string;
|
|
1128
|
+
expected: unknown;
|
|
1129
|
+
actual: unknown;
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1075
1132
|
/** Retry policy configuration */
|
|
1076
1133
|
interface RetryPolicy {
|
|
1077
1134
|
/** Maximum number of attempts */
|
|
@@ -1098,10 +1155,24 @@ interface RetryPolicy {
|
|
|
1098
1155
|
* Return `true` to retry, `false` to stop immediately.
|
|
1099
1156
|
* If omitted, all errors are retried (up to `attempts`).
|
|
1100
1157
|
*
|
|
1158
|
+
* The optional third `context` argument carries the abort reason and,
|
|
1159
|
+
* for clobbers, the fact + expected/actual values. Existing two-arg
|
|
1160
|
+
* callers continue to work — the third arg is undefined when omitted.
|
|
1161
|
+
*
|
|
1162
|
+
* @example Retry on contention, fail loud on bug
|
|
1163
|
+
* ```ts
|
|
1164
|
+
* shouldRetry: (err, attempt, ctx) => {
|
|
1165
|
+
* if (ctx?.reason === "clobbered") return attempt < 5;
|
|
1166
|
+
* if (ctx?.reason === "timeout") return attempt < 2;
|
|
1167
|
+
* return false; // "error" / "cancelled" → don't retry
|
|
1168
|
+
* }
|
|
1169
|
+
* ```
|
|
1170
|
+
*
|
|
1101
1171
|
* @param error - The error that occurred
|
|
1102
1172
|
* @param attempt - The attempt number that just failed (1-based)
|
|
1173
|
+
* @param context - Optional abort context (added v1.23.0); undefined for callers that don't pass it
|
|
1103
1174
|
*/
|
|
1104
|
-
shouldRetry?: (error: Error, attempt: number) => boolean;
|
|
1175
|
+
shouldRetry?: (error: Error, attempt: number, context?: ShouldRetryContext) => boolean;
|
|
1105
1176
|
}
|
|
1106
1177
|
/** Batch configuration */
|
|
1107
1178
|
interface BatchConfig {
|
|
@@ -1312,6 +1383,128 @@ type ResolverStatus = {
|
|
|
1312
1383
|
canceledAt: number;
|
|
1313
1384
|
};
|
|
1314
1385
|
|
|
1386
|
+
/**
|
|
1387
|
+
* Structural diff between two snapshots of a system's constraint `whenSpec`
|
|
1388
|
+
* map. The "git diff for business rules" — operates on the predicate AST
|
|
1389
|
+
* instead of source-text lines.
|
|
1390
|
+
*
|
|
1391
|
+
* A predicate is a tree of leaf clauses (`{ fact: operator(value) }`) and
|
|
1392
|
+
* combinators (`$all` / `$any` / `$not`). `diffRules` flattens both trees
|
|
1393
|
+
* into path-keyed leaf lists and compares: missing path → added/removed,
|
|
1394
|
+
* same path with different operand → changed (with relaxed/tightened
|
|
1395
|
+
* classification for numeric thresholds).
|
|
1396
|
+
*
|
|
1397
|
+
* Pure module — imports only utils. No engine / store / predicate-runtime
|
|
1398
|
+
* dependency: predicates are walked as plain JSON.
|
|
1399
|
+
*/
|
|
1400
|
+
/** A leaf clause extracted from a predicate tree, keyed by its dotted path. */
|
|
1401
|
+
interface LeafClause {
|
|
1402
|
+
/** Dotted path through the predicate. E.g. `phase`, `$all[0].elapsed`, `$not.paused`. */
|
|
1403
|
+
path: string;
|
|
1404
|
+
/** Operator name. `$eq` is implied for bare-value equality. */
|
|
1405
|
+
op: string;
|
|
1406
|
+
/** Operand. */
|
|
1407
|
+
value: unknown;
|
|
1408
|
+
}
|
|
1409
|
+
/** Kind of change observed for a single clause. */
|
|
1410
|
+
type ChangeKind = "added" | "removed" | "changed" | "relaxed" | "tightened";
|
|
1411
|
+
/** A single change between two predicates at a specific path. */
|
|
1412
|
+
interface Change {
|
|
1413
|
+
path: string;
|
|
1414
|
+
kind: ChangeKind;
|
|
1415
|
+
before?: {
|
|
1416
|
+
op: string;
|
|
1417
|
+
value: unknown;
|
|
1418
|
+
};
|
|
1419
|
+
after?: {
|
|
1420
|
+
op: string;
|
|
1421
|
+
value: unknown;
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
/** Status of a single constraint across the two snapshots. */
|
|
1425
|
+
type ConstraintStatus = "added" | "removed" | "changed" | "unchanged";
|
|
1426
|
+
interface ConstraintDiff {
|
|
1427
|
+
id: string;
|
|
1428
|
+
status: ConstraintStatus;
|
|
1429
|
+
/** Empty for `unchanged`. For `added` / `removed`, every leaf is listed once. */
|
|
1430
|
+
changes: Change[];
|
|
1431
|
+
}
|
|
1432
|
+
interface RulesDiffReport {
|
|
1433
|
+
constraints: ConstraintDiff[];
|
|
1434
|
+
summary: {
|
|
1435
|
+
added: number;
|
|
1436
|
+
removed: number;
|
|
1437
|
+
changed: number;
|
|
1438
|
+
unchanged: number;
|
|
1439
|
+
totalClauseChanges: number;
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
interface DiffRulesOptions {
|
|
1443
|
+
before: RulesMapInput;
|
|
1444
|
+
after: RulesMapInput;
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Accepted shapes for the `before` / `after` predicate maps:
|
|
1448
|
+
* - A plain map `{ [constraintId]: whenSpec }`
|
|
1449
|
+
* - The `system.inspect().constraints` array form `Array<{ id, whenSpec? }>`
|
|
1450
|
+
* - Either wrapped as `{ constraints: <one-of-the-above> }`
|
|
1451
|
+
*
|
|
1452
|
+
* Anything else throws at `toRulesMap` so a bad input fails loud.
|
|
1453
|
+
*/
|
|
1454
|
+
type RulesMapInput = unknown;
|
|
1455
|
+
/**
|
|
1456
|
+
* Coerce supported input shapes into a flat `Record<constraintId, whenSpec>`.
|
|
1457
|
+
* Constraints without a `whenSpec` (function-form `when`) are dropped with
|
|
1458
|
+
* an undefined entry so callers can distinguish them from absent constraints
|
|
1459
|
+
* if they care; the diff treats `undefined` as "no data" and skips clause
|
|
1460
|
+
* walking.
|
|
1461
|
+
*/
|
|
1462
|
+
declare function toRulesMap(raw: RulesMapInput): Record<string, unknown>;
|
|
1463
|
+
/**
|
|
1464
|
+
* Walk a predicate tree and emit every leaf clause with its dotted path.
|
|
1465
|
+
* Combinators (`$all` / `$any` / `$not`) become indexed path segments.
|
|
1466
|
+
* Bare-value equality (`{ phase: "red" }`) emits as `op: "$eq"`.
|
|
1467
|
+
*
|
|
1468
|
+
* Array-form predicates (`[{ fact, op, value }, ...]`) are also accepted —
|
|
1469
|
+
* each clause's `fact` becomes the path, the operator is `op`, and the
|
|
1470
|
+
* value is `value`.
|
|
1471
|
+
*
|
|
1472
|
+
* **What gets dropped:** non-object combinator children (e.g. `$not: "red"`
|
|
1473
|
+
* with a string operand instead of a predicate object), function-form
|
|
1474
|
+
* predicates (the engine accepts a function for `when`; this walker only
|
|
1475
|
+
* sees data), and bare primitive predicates (a top-level `true` or `"x"`).
|
|
1476
|
+
* The walker is a tree visitor — anything not shaped like a predicate node
|
|
1477
|
+
* is silently skipped, never thrown on.
|
|
1478
|
+
*/
|
|
1479
|
+
declare function flattenPredicate(spec: unknown, pathPrefix?: string, out?: LeafClause[]): LeafClause[];
|
|
1480
|
+
/**
|
|
1481
|
+
* Diff two snapshots of a system's constraint whenSpec map.
|
|
1482
|
+
*
|
|
1483
|
+
* @example
|
|
1484
|
+
* ```ts
|
|
1485
|
+
* const report = diffRules({
|
|
1486
|
+
* before: { blockCheckout: { cartTotal: { $gte: 100 } } },
|
|
1487
|
+
* after: { blockCheckout: { cartTotal: { $gte: 50 } } },
|
|
1488
|
+
* });
|
|
1489
|
+
*
|
|
1490
|
+
* report.constraints[0].status; // "changed"
|
|
1491
|
+
* report.constraints[0].changes[0].kind; // "relaxed"
|
|
1492
|
+
* report.summary.totalClauseChanges; // 1
|
|
1493
|
+
* ```
|
|
1494
|
+
*
|
|
1495
|
+
* The input shape is forgiving — either a flat `{ id: whenSpec }` map, the
|
|
1496
|
+
* `system.inspect().constraints` array form, or either wrapped as
|
|
1497
|
+
* `{ constraints: ... }`. Constraints whose `when` is a function (no
|
|
1498
|
+
* `whenSpec`) are tracked as added/removed but cannot be clause-diffed
|
|
1499
|
+
* (the function form is opaque).
|
|
1500
|
+
*/
|
|
1501
|
+
declare function diffRules(options: DiffRulesOptions): RulesDiffReport;
|
|
1502
|
+
/**
|
|
1503
|
+
* Diff two predicate trees and return the list of leaf-level changes.
|
|
1504
|
+
* Exported for reuse beyond the full per-constraint report.
|
|
1505
|
+
*/
|
|
1506
|
+
declare function diffClauses(before: unknown, after: unknown): Change[];
|
|
1507
|
+
|
|
1315
1508
|
/**
|
|
1316
1509
|
* Error Types - Type definitions for error handling
|
|
1317
1510
|
*/
|
|
@@ -2172,12 +2365,13 @@ interface TypedConstraintDef<M extends ModuleSchema> {
|
|
|
2172
2365
|
/** Timeout for async constraints (ms) */
|
|
2173
2366
|
timeout?: number;
|
|
2174
2367
|
/**
|
|
2175
|
-
* Fact keys
|
|
2176
|
-
*
|
|
2177
|
-
*
|
|
2178
|
-
* aborted. Omit for no binding (default).
|
|
2368
|
+
* Fact keys this resolver aborts on when they change mid-flight. The
|
|
2369
|
+
* engine snapshots their values at resolver dispatch; if any of them
|
|
2370
|
+
* changes before the resolver writes, the resolver's writes are
|
|
2371
|
+
* dropped and the resolver aborted. Omit for no binding (default).
|
|
2372
|
+
* Ignored on async constraints.
|
|
2179
2373
|
*/
|
|
2180
|
-
|
|
2374
|
+
abortOn?: readonly string[];
|
|
2181
2375
|
/**
|
|
2182
2376
|
* Constraint IDs whose resolvers must complete before this constraint is evaluated.
|
|
2183
2377
|
* If a dependency's `when()` returns false (no requirements), this constraint proceeds.
|
|
@@ -2223,12 +2417,13 @@ interface CrossModuleConstraintDef<M extends ModuleSchema, Deps extends CrossMod
|
|
|
2223
2417
|
/** Timeout for async constraints (ms) */
|
|
2224
2418
|
timeout?: number;
|
|
2225
2419
|
/**
|
|
2226
|
-
* Fact keys
|
|
2227
|
-
*
|
|
2228
|
-
*
|
|
2229
|
-
* aborted. Omit for no binding (default).
|
|
2420
|
+
* Fact keys this resolver aborts on when they change mid-flight. The
|
|
2421
|
+
* engine snapshots their values at resolver dispatch; if any of them
|
|
2422
|
+
* changes before the resolver writes, the resolver's writes are
|
|
2423
|
+
* dropped and the resolver aborted. Omit for no binding (default).
|
|
2424
|
+
* Ignored on async constraints.
|
|
2230
2425
|
*/
|
|
2231
|
-
|
|
2426
|
+
abortOn?: readonly string[];
|
|
2232
2427
|
/**
|
|
2233
2428
|
* Constraint IDs whose resolvers must complete before this constraint is evaluated.
|
|
2234
2429
|
* If a dependency's `when()` returns false (no requirements), this constraint proceeds.
|
|
@@ -2644,16 +2839,17 @@ interface SystemInspection {
|
|
|
2644
2839
|
*/
|
|
2645
2840
|
whenSpec?: FactPredicate<Record<string, unknown>>;
|
|
2646
2841
|
/**
|
|
2647
|
-
*
|
|
2648
|
-
* constraint definition's `
|
|
2649
|
-
* so it can flag candidates that would race
|
|
2650
|
-
* Absent when the constraint declares no
|
|
2842
|
+
* Abort-binding fact list for RFC-0003 constraint binding. Populated
|
|
2843
|
+
* from the constraint definition's `abortOn:` field. Exposed for
|
|
2844
|
+
* `doctor.checkAbortOn()` so it can flag candidates that would race
|
|
2845
|
+
* or shadow these writes. Absent when the constraint declares no
|
|
2846
|
+
* `abortOn`.
|
|
2651
2847
|
*/
|
|
2652
|
-
readonly
|
|
2848
|
+
readonly abortOn?: readonly string[];
|
|
2653
2849
|
/**
|
|
2654
2850
|
* Fact paths the constraint `bind:`s to. v2 promise — the runtime
|
|
2655
2851
|
* does not yet emit a `bind` field on inspect snapshots, but the
|
|
2656
|
-
* type slot is reserved so `doctor.
|
|
2852
|
+
* type slot is reserved so `doctor.checkAbortOn()` is stable across
|
|
2657
2853
|
* the rollout. (F1)
|
|
2658
2854
|
*/
|
|
2659
2855
|
readonly bind?: readonly string[];
|
|
@@ -3071,6 +3267,46 @@ interface MetaAccessor {
|
|
|
3071
3267
|
/** Find all definitions matching a tag across all types. */
|
|
3072
3268
|
byTag(tag: string): MetaMatch[];
|
|
3073
3269
|
}
|
|
3270
|
+
/**
|
|
3271
|
+
* Discriminated proof of why two resolvers' `when:` predicates fire on
|
|
3272
|
+
* the same state — attached to `resolver.clobber.loop.detected` events
|
|
3273
|
+
* so the warning can point at the specific clauses that co-fire instead
|
|
3274
|
+
* of stopping at "these resolvers fight."
|
|
3275
|
+
*
|
|
3276
|
+
* - `matched` — both predicates have identical structural clauses.
|
|
3277
|
+
* The strongest verdict; the rules are syntactic duplicates.
|
|
3278
|
+
* - `overlap` — clauses share at least one path and at least one
|
|
3279
|
+
* pairwise comparison says they co-fire (with no direct
|
|
3280
|
+
* contradictions). Strong verdict, slightly weaker than `matched`.
|
|
3281
|
+
* - `indeterminate` — a non-COMPARABLE operator (`$regex`, `$elemMatch`,
|
|
3282
|
+
* `$matches`, custom) appeared. The proof builder declines to assert
|
|
3283
|
+
* overlap; the message says so explicitly rather than falsely
|
|
3284
|
+
* reporting a contradiction.
|
|
3285
|
+
* - `function-form-opaque` — at least one constraint uses a function
|
|
3286
|
+
* `when:` (`(facts) => ...`), so structural comparison is impossible.
|
|
3287
|
+
* The warning text disclaims; if audit-ledger is mounted and its
|
|
3288
|
+
* `whenSourceCache` is available, identifying hashes of the function
|
|
3289
|
+
* source are included for cross-version diffing.
|
|
3290
|
+
*
|
|
3291
|
+
* @public
|
|
3292
|
+
*/
|
|
3293
|
+
type PredicateOverlapProof = {
|
|
3294
|
+
verdict: "matched";
|
|
3295
|
+
coFireClauses: readonly LeafClause[];
|
|
3296
|
+
conflictingClauses: readonly never[];
|
|
3297
|
+
} | {
|
|
3298
|
+
verdict: "overlap";
|
|
3299
|
+
coFireClauses: readonly LeafClause[];
|
|
3300
|
+
conflictingClauses: readonly LeafClause[];
|
|
3301
|
+
} | {
|
|
3302
|
+
verdict: "indeterminate";
|
|
3303
|
+
reason: "non-comparable-operator";
|
|
3304
|
+
coFireClauses: readonly LeafClause[];
|
|
3305
|
+
} | {
|
|
3306
|
+
verdict: "function-form-opaque";
|
|
3307
|
+
reason: "one-or-both-when-is-a-function";
|
|
3308
|
+
whenSourceHashes?: readonly string[];
|
|
3309
|
+
};
|
|
3074
3310
|
/** Typed events emitted by system.observe(). */
|
|
3075
3311
|
type ObservationEvent = {
|
|
3076
3312
|
type: "fact.change";
|
|
@@ -3090,6 +3326,26 @@ type ObservationEvent = {
|
|
|
3090
3326
|
type: "constraint.error";
|
|
3091
3327
|
id: string;
|
|
3092
3328
|
error: unknown;
|
|
3329
|
+
}
|
|
3330
|
+
/**
|
|
3331
|
+
* Fired when the engine silently disables a constraint's `abortOn:`
|
|
3332
|
+
* binding because the constraint is async (declared `async: true` OR
|
|
3333
|
+
* runtime-promoted because its `when()` returned a Promise). The
|
|
3334
|
+
* dev-mode `console.warn` is the human-facing signal; this event is
|
|
3335
|
+
* the SIEM-facing one. Without it, a production constraint loses its
|
|
3336
|
+
* clobber-protection with no plugin / observer trail.
|
|
3337
|
+
*
|
|
3338
|
+
* - `reason: "async-declared"` — the constraint def has `async: true`.
|
|
3339
|
+
* The author opted in; the warning + event are advisory.
|
|
3340
|
+
* - `reason: "async-promoted"` — the constraint's `when()` returned
|
|
3341
|
+
* a Promise at runtime. The author probably did not realize. This
|
|
3342
|
+
* is the more dangerous case — the binding silently disables and
|
|
3343
|
+
* the clobber check no-ops.
|
|
3344
|
+
*/
|
|
3345
|
+
| {
|
|
3346
|
+
type: "constraint.binding.disabled";
|
|
3347
|
+
id: string;
|
|
3348
|
+
reason: "async-declared" | "async-promoted";
|
|
3093
3349
|
} | {
|
|
3094
3350
|
type: "requirement.created";
|
|
3095
3351
|
id: string;
|
|
@@ -3117,7 +3373,7 @@ type ObservationEvent = {
|
|
|
3117
3373
|
error: unknown;
|
|
3118
3374
|
} | {
|
|
3119
3375
|
/**
|
|
3120
|
-
* A real per-write rejection. A bound resolver's
|
|
3376
|
+
* A real per-write rejection. A bound resolver's abort-bound write was
|
|
3121
3377
|
* dropped because the fact was changed by something outside the
|
|
3122
3378
|
* resolver between the resolver's baseline and its next write (RFC-0003
|
|
3123
3379
|
* optimistic-concurrency check). `reason` keeps the observation protocol
|
|
@@ -3162,6 +3418,52 @@ type ObservationEvent = {
|
|
|
3162
3418
|
requirementId: string;
|
|
3163
3419
|
reason: "clobbered";
|
|
3164
3420
|
dropped: number;
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* v1.23.0 — fires when `clobberLoopPlugin` detects a sustained pattern
|
|
3424
|
+
* of clobbers on a fact involving the same set of resolvers above
|
|
3425
|
+
* threshold within a time window. A single clobber is fine; the
|
|
3426
|
+
* binding catches the race and the audit ledger records it. A *loop*
|
|
3427
|
+
* is two or more resolvers whose `when:` predicates both satisfy a
|
|
3428
|
+
* shared state and rewrite the fact every reconcile tick.
|
|
3429
|
+
*
|
|
3430
|
+
* `participants` is the unordered, distinct set of resolver IDs
|
|
3431
|
+
* contributing to the loop. `predicateOverlap` (when both sides use a
|
|
3432
|
+
* data-form `when:`) explains WHY the loop occurs — which clauses
|
|
3433
|
+
* co-fire — so the suggested fix (add `priority:`, narrow `when:`,
|
|
3434
|
+
* merge) is grounded in the actual rules, not a guess.
|
|
3435
|
+
*/
|
|
3436
|
+
| {
|
|
3437
|
+
type: "resolver.clobber.loop.detected";
|
|
3438
|
+
systemId: string;
|
|
3439
|
+
fact: string;
|
|
3440
|
+
participants: readonly string[];
|
|
3441
|
+
participantModules: readonly string[];
|
|
3442
|
+
count: number;
|
|
3443
|
+
windowMs: number;
|
|
3444
|
+
firstAt: number;
|
|
3445
|
+
lastAt: number;
|
|
3446
|
+
predicateOverlap?: PredicateOverlapProof;
|
|
3447
|
+
severity: "warn" | "error";
|
|
3448
|
+
factTags: readonly string[];
|
|
3449
|
+
suppressedSinceLastEmit: number;
|
|
3450
|
+
rejectionSeqs: readonly number[];
|
|
3451
|
+
}
|
|
3452
|
+
/**
|
|
3453
|
+
* v1.23.0 — fires when a previously-detected clobber loop closes
|
|
3454
|
+
* cleanly, so dashboards can show "5 active loops" not "47
|
|
3455
|
+
* historical loops". A loop is considered resolved when the
|
|
3456
|
+
* `(fact, participantSet)` goes a quiet window without further
|
|
3457
|
+
* `resolver.write.rejected` events, OR a participant is unregistered,
|
|
3458
|
+
* OR a constraint re-registration changes a participant's `whenSpec`.
|
|
3459
|
+
*/
|
|
3460
|
+
| {
|
|
3461
|
+
type: "resolver.clobber.loop.resolved";
|
|
3462
|
+
systemId: string;
|
|
3463
|
+
fact: string;
|
|
3464
|
+
participants: readonly string[];
|
|
3465
|
+
durationMs: number;
|
|
3466
|
+
resolution: "no-recurrence-in-window" | "participant-disabled" | "predicate-narrowed";
|
|
3165
3467
|
} | {
|
|
3166
3468
|
type: "effect.run";
|
|
3167
3469
|
id: string;
|
|
@@ -3349,6 +3651,31 @@ interface System<M extends ModuleSchema = ModuleSchema> {
|
|
|
3349
3651
|
*/
|
|
3350
3652
|
readonly notify: {
|
|
3351
3653
|
guardrailBlocked(plugin: string, key: string, kind: "redact" | "alert" | "detect", count: number, category?: string): void;
|
|
3654
|
+
/**
|
|
3655
|
+
* v1.23.0 — plugin authoring surface for emitting the
|
|
3656
|
+
* `"resolver.clobber.loop.detected"` ObservationEvent. Used by
|
|
3657
|
+
* `clobberLoopPlugin` (and any compatible third-party detector). The
|
|
3658
|
+
* call fans out to all registered plugins' `onClobberLoopDetected`
|
|
3659
|
+
* hooks (including the synthetic plugins that back `system.observe()`)
|
|
3660
|
+
* so audit-ledger, devtools, and OTel exporters capture the event
|
|
3661
|
+
* without taking a dependency on the originating detector.
|
|
3662
|
+
*
|
|
3663
|
+
* Application code should never call this directly — use
|
|
3664
|
+
* `system.observe()` to subscribe.
|
|
3665
|
+
*/
|
|
3666
|
+
clobberLoopDetected(event: ObservationEvent & {
|
|
3667
|
+
type: "resolver.clobber.loop.detected";
|
|
3668
|
+
}): void;
|
|
3669
|
+
/**
|
|
3670
|
+
* v1.23.0 — companion to {@link clobberLoopDetected}. Fires when a
|
|
3671
|
+
* previously-detected loop is considered resolved (quiet window
|
|
3672
|
+
* elapsed, participant unregistered, or predicate narrowed). Lets
|
|
3673
|
+
* monitoring dashboards show "active loops" rather than
|
|
3674
|
+
* "historical loops."
|
|
3675
|
+
*/
|
|
3676
|
+
clobberLoopResolved(event: ObservationEvent & {
|
|
3677
|
+
type: "resolver.clobber.loop.resolved";
|
|
3678
|
+
}): void;
|
|
3352
3679
|
};
|
|
3353
3680
|
/** Per-run trace entries (null if trace is not enabled) */
|
|
3354
3681
|
readonly trace: TraceEntry[] | null;
|
|
@@ -3639,6 +3966,27 @@ interface Plugin<M extends ModuleSchema = ModuleSchema> {
|
|
|
3639
3966
|
* @param error - The error that was thrown
|
|
3640
3967
|
*/
|
|
3641
3968
|
onConstraintError?: (id: string, error: unknown) => void;
|
|
3969
|
+
/**
|
|
3970
|
+
* Called when the engine silently disables a constraint's `abortOn:`
|
|
3971
|
+
* binding because the constraint is async. Pairs with the dev-mode
|
|
3972
|
+
* `console.warn` for SIEM-side visibility — without this signal, a
|
|
3973
|
+
* production constraint loses its clobber-protection with no
|
|
3974
|
+
* plugin / observer trail.
|
|
3975
|
+
*
|
|
3976
|
+
* - `"async-declared"` — the constraint def has `async: true`. The
|
|
3977
|
+
* author opted in; treat as advisory.
|
|
3978
|
+
* - `"async-promoted"` — the constraint's `when()` returned a Promise
|
|
3979
|
+
* at runtime, even though `async: true` was not set. The author
|
|
3980
|
+
* probably did not realize. This is the dangerous case — escalate.
|
|
3981
|
+
*
|
|
3982
|
+
* Fired at most once per `getConstraintBinding` lookup; deduping
|
|
3983
|
+
* across reconcile ticks is the consumer's responsibility (the
|
|
3984
|
+
* audit-ledger plugin does this with a per-id sticky bit).
|
|
3985
|
+
*
|
|
3986
|
+
* @param id - The constraint ID whose binding was disabled
|
|
3987
|
+
* @param reason - Whether the async state was author-declared or runtime-promoted
|
|
3988
|
+
*/
|
|
3989
|
+
onConstraintBindingDisabled?: (id: string, reason: "async-declared" | "async-promoted") => void;
|
|
3642
3990
|
/**
|
|
3643
3991
|
* Called when a new requirement is created by a constraint.
|
|
3644
3992
|
* @param req - The requirement with its computed ID
|
|
@@ -3691,8 +4039,9 @@ interface Plugin<M extends ModuleSchema = ModuleSchema> {
|
|
|
3691
4039
|
/**
|
|
3692
4040
|
* Called when a resolver's write is rejected by the runtime. The only
|
|
3693
4041
|
* `reason` today is `"clobbered"`: a bound resolver (RFC-0003) dropped a
|
|
3694
|
-
* write to an
|
|
3695
|
-
*
|
|
4042
|
+
* write to an abort-bound fact (one listed in the constraint's
|
|
4043
|
+
* `abortOn:`) because the fact was changed by something outside the
|
|
4044
|
+
* resolver between the resolver's baseline and its next write — the
|
|
3696
4045
|
* resolver's `AbortController` is aborted in the same step. See
|
|
3697
4046
|
* {@link createBoundFacts} for the per-fact optimistic-concurrency model.
|
|
3698
4047
|
* `reason` keeps the hook backend-neutral.
|
|
@@ -3826,6 +4175,26 @@ interface Plugin<M extends ModuleSchema = ModuleSchema> {
|
|
|
3826
4175
|
* payloads.
|
|
3827
4176
|
*/
|
|
3828
4177
|
onGuardrailBlocked?: (plugin: string, key: string, kind: "redact" | "alert" | "detect", count: number, category?: string) => void;
|
|
4178
|
+
/**
|
|
4179
|
+
* Called when `clobberLoopPlugin` (or any equivalent detector) publishes
|
|
4180
|
+
* a structured loop event through `system.notify.clobberLoopDetected(...)`.
|
|
4181
|
+
* Surfaces as the `"resolver.clobber.loop.detected"` `ObservationEvent`
|
|
4182
|
+
* for `system.observe()` subscribers + audit-ledger + devtools, so
|
|
4183
|
+
* downstream backends can capture loops without taking a direct
|
|
4184
|
+
* dependency on `clobberLoopPlugin`.
|
|
4185
|
+
*/
|
|
4186
|
+
onClobberLoopDetected?: (event: ObservationEvent & {
|
|
4187
|
+
type: "resolver.clobber.loop.detected";
|
|
4188
|
+
}) => void;
|
|
4189
|
+
/**
|
|
4190
|
+
* Called when a previously-detected loop is considered resolved (quiet
|
|
4191
|
+
* window elapsed, participant unregistered, or predicate narrowed).
|
|
4192
|
+
* Pairs with `onClobberLoopDetected` so dashboards can show "active
|
|
4193
|
+
* loops" rather than "historical loops."
|
|
4194
|
+
*/
|
|
4195
|
+
onClobberLoopResolved?: (event: ObservationEvent & {
|
|
4196
|
+
type: "resolver.clobber.loop.resolved";
|
|
4197
|
+
}) => void;
|
|
3829
4198
|
/**
|
|
3830
4199
|
* Called when a history snapshot is taken.
|
|
3831
4200
|
* @param snapshot - The snapshot that was captured
|
|
@@ -3884,4 +4253,4 @@ interface Plugin<M extends ModuleSchema = ModuleSchema> {
|
|
|
3884
4253
|
onTraceComplete?: (entry: TraceEntry) => void;
|
|
3885
4254
|
}
|
|
3886
4255
|
|
|
3887
|
-
export { type
|
|
4256
|
+
export { type InferDerivations as $, type AnySystem as A, type BatchConfig as B, type ClauseResult as C, type DefinitionMeta as D, type EffectsDef as E, type FactPredicate as F, type CrossModuleEffectDef as G, type DiffRulesOptions as H, DirectiveError as I, type DistributableSnapshot as J, type DistributableSnapshotOptions as K, type DynamicConstraintDef as L, type ModuleSchema as M, type NamespacedSystem as N, type DynamicEffectDef as O, type Plugin as P, type DynamicResolverDef as Q, type RequirementWithId as R, type SchemaType as S, type TypedDerivationsDef as T, type EffectDef as U, type EventsDef as V, type FactTemplate as W, type FactsSnapshot as X, type HistoryAPI as Y, type HistoryOption as Z, type HistoryState as _, type Facts as a, type CrossModuleDerivationFn as a$, type InferEvents as a0, type InferFacts as a1, type InferRequirementTypes as a2, type InferRequirements as a3, type InferSchemaType as a4, type InferSelectorState as a5, type KeySelector as a6, type LeafClause as a7, type MetaAccessor as a8, type MetaMatch as a9, type System as aA, type SystemConfig as aB, type SystemDerived as aC, type SystemFacts as aD, type SystemInspection as aE, type SystemMode as aF, type SystemSnapshot as aG, type TraceEntry as aH, type TypedConstraintDef as aI, type TypedResolverDef as aJ, diffClauses as aK, diffRules as aL, flattenPredicate as aM, isNamespacedSystem as aN, isSingleModuleSystem as aO, toRulesMap as aP, type FactsStore as aQ, type ConstraintState as aR, type ResolverStatus as aS, type FactChange as aT, type ReconcileResult as aU, type RecoveryStrategy as aV, type ErrorSource as aW, type RetryLaterConfig as aX, type BatchItemResult as aY, type BatchResolveResults as aZ, type ConstraintsControl as a_, type ObservationEvent as aa, type OperatorObject as ab, type PatchSpec as ac, type PatchValue as ad, type PayloadRef as ae, type PredicateClause as af, type PredicateCombinator as ag, type PredicateCombinatorKey as ah, type PredicateObject as ai, type PredicateOp as aj, type PredicateOverlapProof as ak, type ResolverAbortReason as al, type ResolverDef as am, type ResolversDef as an, type RetryPolicy as ao, type RulesDiffReport as ap, type RulesMapInput as aq, type Schema as ar, type SchemaTypedResolversDef as as, type ShouldRetryContext as at, type Snapshot as au, type SourceDef as av, type SourceDropReason as aw, type SourcePublish as ax, type SourceReportError as ay, type SourceUnsubscribe as az, type TypedEventsDef as b, type CrossModuleFactsWithSelf as b0, type DerivationKeys as b1, type DerivationReturnType as b2, type DerivationsControl as b3, type DerivationsSchema as b4, type DeriveAccessor as b5, type DispatchEventsFromSchema as b6, type EffectCleanup as b7, type EffectsControl as b8, type EventPayloadSchema as b9, type RequirementOutput$1 as bA, type EventsAccessor as ba, type EventsAccessorFromSchema as bb, type EventsSchema as bc, type FactKeys as bd, type FactReturnType as be, type FlexibleEventHandler as bf, type HistoryConfig as bg, type InferEventPayloadFromSchema as bh, type InferRequirementPayloadFromSchema as bi, type InferSchema as bj, type MutableNamespacedFacts as bk, type NamespacedDerivations as bl, type NamespacedEventsAccessor as bm, type NamespacedFacts as bn, type ObservableKeys as bo, type RequirementExplanation as bp, type RequirementOutput as bq, type RequirementPayloadSchema$1 as br, type RequirementsSchema as bs, type ResolverContext as bt, type ResolversControl as bu, type SnapshotMeta as bv, type SystemEvent as bw, type TraceConfig as bx, type TypedResolverContext as by, type UnionEvents as bz, type SourcesDef as c, type TypedConstraintsDef as d, type TypedResolversDef as e, type ModuleHooks as f, type CrossModuleDeps as g, type CrossModuleDerivationsDef as h, type CrossModuleEffectsDef as i, type CrossModuleConstraintsDef as j, type ModuleDef as k, type CreateSystemOptionsSingle as l, type SingleModuleSystem as m, type ModulesMap as n, type CreateSystemOptionsNamed as o, type TraceOption as p, type ErrorBoundaryConfig as q, type Requirement as r, type RequirementKeyFn as s, type Change as t, type ChangeKind as u, type ConstraintDef as v, type ConstraintDiff as w, type ConstraintStatus as x, type ConstraintsDef as y, type CrossModuleConstraintDef as z };
|