@directive-run/core 1.22.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/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-YJEDX2JX.js → chunk-AFPLATPV.js} +3 -3
- package/dist/chunk-AFPLATPV.js.map +1 -0
- package/dist/{chunk-OAPD3HIG.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-BgKC_ZT_.d.ts → index-DpvgHVHR.d.cts} +29 -2
- package/dist/{index-DRPkZ8HE.d.cts → index-n8oQmdOZ.d.ts} +29 -2
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -128
- package/dist/index.d.ts +6 -128
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +1 -1
- package/dist/internals.d.cts +10 -4
- package/dist/internals.d.ts +10 -4
- 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 +202 -3
- package/dist/plugins/index.d.ts +202 -3
- package/dist/plugins/index.js +2 -2
- package/dist/plugins/index.js.map +1 -1
- package/dist/{plugins-5Supk7it.d.cts → plugins-CaPMTICa.d.cts} +323 -2
- package/dist/{plugins-5Supk7it.d.ts → plugins-CaPMTICa.d.ts} +323 -2
- package/dist/{predicate-DYb-3Mvl.d.cts → predicate-CU0YC1i6.d.cts} +1 -1
- package/dist/{predicate-C9oMO_ny.d.ts → predicate-CgpwJqf4.d.ts} +1 -1
- package/dist/system-HOP37Z4V.js +2 -0
- package/dist/{system-6YWCXQOQ.js.map → system-HOP37Z4V.js.map} +1 -1
- package/dist/system-VZUHL56W.cjs +2 -0
- package/dist/{system-5FKUBLLV.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-6NCC6RBC.js +0 -3
- package/dist/chunk-6NCC6RBC.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-MARZI3EY.cjs +0 -2
- package/dist/chunk-MARZI3EY.cjs.map +0 -1
- package/dist/chunk-OAPD3HIG.cjs.map +0 -1
- package/dist/chunk-QTY2FXZY.cjs +0 -3
- package/dist/chunk-QTY2FXZY.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/chunk-YJEDX2JX.js.map +0 -1
- package/dist/system-5FKUBLLV.cjs +0 -2
- package/dist/system-6YWCXQOQ.js +0 -2
package/dist/plugins/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { A as AuditEntry, a as AuditEntryKind, b as AuditLedger, c as AuditLedgerOptions, d as AuditLedgerSink, Q as QueryFilter, e as createAuditLedger, m as memorySink } from '../index-
|
|
2
|
-
import { M as ModuleSchema, P as Plugin,
|
|
1
|
+
export { A as AuditEntry, a as AuditEntryKind, b as AuditLedger, c as AuditLedgerOptions, d as AuditLedgerSink, Q as QueryFilter, e as createAuditLedger, m as memorySink } from '../index-DpvgHVHR.cjs';
|
|
2
|
+
import { M as ModuleSchema, P as Plugin, aA as System, ak as PredicateOverlapProof } from '../plugins-CaPMTICa.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Logging Plugin - Console logging for Directive events
|
|
@@ -1001,4 +1001,203 @@ interface ClobberSummaryEvent {
|
|
|
1001
1001
|
*/
|
|
1002
1002
|
declare function clobberAlertPlugin<M extends ModuleSchema>(options?: ClobberAlertPluginOptions): Plugin<M>;
|
|
1003
1003
|
|
|
1004
|
-
|
|
1004
|
+
/**
|
|
1005
|
+
* Clobber Loop Detector Plugin – fires a structured warning when the
|
|
1006
|
+
* same set of resolvers clobber the same fact above threshold within a
|
|
1007
|
+
* time window. A single clobber is fine — the `abortOn:` binding caught
|
|
1008
|
+
* the race and the audit ledger recorded it. A *loop* is two or more
|
|
1009
|
+
* resolvers whose `when:` predicates both satisfy a shared state and
|
|
1010
|
+
* keep rewriting the fact every reconcile tick, indefinitely.
|
|
1011
|
+
*
|
|
1012
|
+
* Today the symptom surfaces as a value flapping between two states in
|
|
1013
|
+
* a customer screenshot. The audit ledger holds the forensic evidence
|
|
1014
|
+
* (800 clobbers/sec on `cart.discount`), but nobody looks until a
|
|
1015
|
+
* customer complains. This plugin closes the loop: one structured
|
|
1016
|
+
* warning per detected loop, with a **predicate-overlap proof** built
|
|
1017
|
+
* from the participants' `whenSpec` so the warning points at the
|
|
1018
|
+
* specific clauses that co-fire instead of stopping at "these resolvers
|
|
1019
|
+
* fight."
|
|
1020
|
+
*
|
|
1021
|
+
* @public
|
|
1022
|
+
*/
|
|
1023
|
+
|
|
1024
|
+
/** Configuration for the {@link clobberLoopPlugin}. */
|
|
1025
|
+
interface ClobberLoopPluginOptions {
|
|
1026
|
+
/**
|
|
1027
|
+
* Window over which clobber-rejection events are aggregated. A loop
|
|
1028
|
+
* fires when `threshold` distinct-requirement rejections involving
|
|
1029
|
+
* ≥2 distinct resolvers land on the same fact within `windowMs`.
|
|
1030
|
+
*
|
|
1031
|
+
* Default: 1000ms. Catches the canonical "5 clobbers in <500ms" case
|
|
1032
|
+
* while sitting well outside tick-boundary noise.
|
|
1033
|
+
*/
|
|
1034
|
+
windowMs?: number;
|
|
1035
|
+
/**
|
|
1036
|
+
* Minimum distinct-requirement rejection count within `windowMs` to
|
|
1037
|
+
* trigger a loop event. Distinct by `requirement.id`, so a single
|
|
1038
|
+
* resolver's retry storm (which shares one requirement id) counts as
|
|
1039
|
+
* one rejection and never crosses the threshold alone — only true
|
|
1040
|
+
* multi-participant contention does.
|
|
1041
|
+
*
|
|
1042
|
+
* Default: 5.
|
|
1043
|
+
*/
|
|
1044
|
+
threshold?: number;
|
|
1045
|
+
/**
|
|
1046
|
+
* After a loop fires on a `(fact, participantSet)`, suppress same-key
|
|
1047
|
+
* re-fire for this duration. Mirrors `clobberAlertPlugin`'s
|
|
1048
|
+
* cooldown precedent.
|
|
1049
|
+
*
|
|
1050
|
+
* Default: 5000ms.
|
|
1051
|
+
*/
|
|
1052
|
+
cooldownMs?: number;
|
|
1053
|
+
/**
|
|
1054
|
+
* Quiet window after the last contributing clobber rejection before
|
|
1055
|
+
* the loop is considered closed and a `resolver.clobber.loop.resolved`
|
|
1056
|
+
* event fires. The resolver may have shipped a fix (priority added,
|
|
1057
|
+
* `when:` narrowed), or the surrounding state changed enough that
|
|
1058
|
+
* both participants no longer co-fire.
|
|
1059
|
+
*
|
|
1060
|
+
* Default: 30000ms.
|
|
1061
|
+
*/
|
|
1062
|
+
resolvedAfterMs?: number;
|
|
1063
|
+
/**
|
|
1064
|
+
* Global LRU cap on the number of facts the detector tracks. A
|
|
1065
|
+
* hot-then-cold attacker fact key can churn the map, but LRU
|
|
1066
|
+
* eviction ensures the legitimate hot fact stays resident. Memory
|
|
1067
|
+
* bound: ~32 ring-buffer entries × `maxTrackedFacts` slim records.
|
|
1068
|
+
*
|
|
1069
|
+
* Default: 256.
|
|
1070
|
+
*/
|
|
1071
|
+
maxTrackedFacts?: number;
|
|
1072
|
+
/**
|
|
1073
|
+
* Per-fact cap on participant resolver tracking. Above this, a single
|
|
1074
|
+
* "N-way contention on fact X" event fires once and detailed
|
|
1075
|
+
* participant tracking pauses for the cooldown window.
|
|
1076
|
+
*
|
|
1077
|
+
* Default: 16.
|
|
1078
|
+
*/
|
|
1079
|
+
maxParticipantsPerFact?: number;
|
|
1080
|
+
/**
|
|
1081
|
+
* Global emission cap (loop-events per second across all facts). A
|
|
1082
|
+
* storm where every fact enters a loop simultaneously should not blow
|
|
1083
|
+
* up the observation bus or pager. Above-cap detections increment a
|
|
1084
|
+
* counter and surface in the next emitted event's
|
|
1085
|
+
* `suppressedSinceLastEmit` field.
|
|
1086
|
+
*
|
|
1087
|
+
* Default: 10.
|
|
1088
|
+
*/
|
|
1089
|
+
maxEmissionsPerSec?: number;
|
|
1090
|
+
/**
|
|
1091
|
+
* Callback fired when a loop is detected. The payload is the
|
|
1092
|
+
* fully-constructed event including PII-redacted `predicateOverlap`
|
|
1093
|
+
* (when applicable).
|
|
1094
|
+
*
|
|
1095
|
+
* Default in dev mode: `console.warn` with the structured warning
|
|
1096
|
+
* text. Default in production: `console.error` to stderr — NOT noop,
|
|
1097
|
+
* so the signal still lands in CloudWatch/Loki/Datadog log pipelines
|
|
1098
|
+
* even when consumers haven't explicitly wired routing.
|
|
1099
|
+
*/
|
|
1100
|
+
onLoop?: (event: ClobberLoopDetectedEvent) => void;
|
|
1101
|
+
/**
|
|
1102
|
+
* Callback fired when a previously-detected loop is considered
|
|
1103
|
+
* resolved (quiet window elapsed, participant unregistered, or
|
|
1104
|
+
* predicate narrowed).
|
|
1105
|
+
*
|
|
1106
|
+
* Default: no-op. Wire when monitoring needs "5 active loops" rather
|
|
1107
|
+
* than "47 historical loops."
|
|
1108
|
+
*/
|
|
1109
|
+
onResolved?: (event: ClobberLoopResolvedEvent) => void;
|
|
1110
|
+
/**
|
|
1111
|
+
* Opt-out of PII redaction in the emitted event's `predicateOverlap`
|
|
1112
|
+
* clauses. The default (`false`) routes `whenSpec` operand pointers
|
|
1113
|
+
* through `redactWhenSpec` against `system.meta.byTag("pii")` BEFORE
|
|
1114
|
+
* the event is constructed, so downstream sinks see `[redacted]` in
|
|
1115
|
+
* place of the literal operand.
|
|
1116
|
+
*
|
|
1117
|
+
* Set to `true` only when the deployment has a data-processing
|
|
1118
|
+
* addendum that permits unredacted operand capture.
|
|
1119
|
+
*
|
|
1120
|
+
* Default: `false`.
|
|
1121
|
+
*/
|
|
1122
|
+
capturePII?: boolean;
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Emitted payload for {@link ClobberLoopPluginOptions.onLoop}. Identical
|
|
1126
|
+
* shape to the `"resolver.clobber.loop.detected"` ObservationEvent so
|
|
1127
|
+
* consumers can route the same code path through either surface.
|
|
1128
|
+
*
|
|
1129
|
+
* @public
|
|
1130
|
+
*/
|
|
1131
|
+
interface ClobberLoopDetectedEvent {
|
|
1132
|
+
readonly systemId: string;
|
|
1133
|
+
readonly fact: string;
|
|
1134
|
+
readonly participants: readonly string[];
|
|
1135
|
+
readonly participantModules: readonly string[];
|
|
1136
|
+
readonly count: number;
|
|
1137
|
+
readonly windowMs: number;
|
|
1138
|
+
readonly firstAt: number;
|
|
1139
|
+
readonly lastAt: number;
|
|
1140
|
+
readonly predicateOverlap?: PredicateOverlapProof;
|
|
1141
|
+
readonly severity: "warn" | "error";
|
|
1142
|
+
readonly factTags: readonly string[];
|
|
1143
|
+
readonly suppressedSinceLastEmit: number;
|
|
1144
|
+
readonly rejectionSeqs: readonly number[];
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Emitted payload for {@link ClobberLoopPluginOptions.onResolved}.
|
|
1148
|
+
*
|
|
1149
|
+
* @public
|
|
1150
|
+
*/
|
|
1151
|
+
interface ClobberLoopResolvedEvent {
|
|
1152
|
+
readonly systemId: string;
|
|
1153
|
+
readonly fact: string;
|
|
1154
|
+
readonly participants: readonly string[];
|
|
1155
|
+
readonly durationMs: number;
|
|
1156
|
+
readonly resolution: "no-recurrence-in-window" | "participant-disabled" | "predicate-narrowed";
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Runtime kill-switch handle returned alongside the plugin. Lets an SRE
|
|
1160
|
+
* flip the detector off during incident response without redeploying.
|
|
1161
|
+
* `disable()` stops emission but preserves buffer state so `enable()`
|
|
1162
|
+
* resumes cleanly without warm-up drift.
|
|
1163
|
+
*
|
|
1164
|
+
* @public
|
|
1165
|
+
*/
|
|
1166
|
+
interface ClobberLoopPluginHandle<M extends ModuleSchema> {
|
|
1167
|
+
readonly plugin: Plugin<M>;
|
|
1168
|
+
disable(): void;
|
|
1169
|
+
enable(): void;
|
|
1170
|
+
isEnabled(): boolean;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Create a clobber-loop detector plugin. Returns a {@link
|
|
1174
|
+
* ClobberLoopPluginHandle} so callers can flip the detector off at
|
|
1175
|
+
* runtime without redeploying. The `.plugin` field is the value to
|
|
1176
|
+
* pass into `createSystem({ plugins: [...] })`.
|
|
1177
|
+
*
|
|
1178
|
+
* @example
|
|
1179
|
+
* ```ts
|
|
1180
|
+
* const loopDetector = clobberLoopPlugin({
|
|
1181
|
+
* threshold: 5,
|
|
1182
|
+
* windowMs: 1000,
|
|
1183
|
+
* onLoop: (e) => pagerduty.trigger({
|
|
1184
|
+
* severity: e.severity,
|
|
1185
|
+
* summary: `Clobber loop on ${e.fact}: ${e.participants.join(" vs ")}`,
|
|
1186
|
+
* details: e,
|
|
1187
|
+
* }),
|
|
1188
|
+
* });
|
|
1189
|
+
*
|
|
1190
|
+
* createSystem({
|
|
1191
|
+
* module: myModule,
|
|
1192
|
+
* plugins: [loopDetector.plugin],
|
|
1193
|
+
* });
|
|
1194
|
+
*
|
|
1195
|
+
* // During incident response:
|
|
1196
|
+
* loopDetector.disable();
|
|
1197
|
+
* ```
|
|
1198
|
+
*
|
|
1199
|
+
* @public
|
|
1200
|
+
*/
|
|
1201
|
+
declare function clobberLoopPlugin<M extends ModuleSchema>(options?: ClobberLoopPluginOptions): ClobberLoopPluginHandle<M>;
|
|
1202
|
+
|
|
1203
|
+
export { type AggregatedMetric, type AlertConfig, type AlertEvent, type CircuitBreaker, type CircuitBreakerConfig, CircuitBreakerOpenError, type CircuitBreakerStats, type CircuitState, type ClobberAlertEvent, type ClobberAlertPluginOptions, type ClobberLoopDetectedEvent, type ClobberLoopPluginHandle, type ClobberLoopPluginOptions, type ClobberLoopResolvedEvent, type ConstraintMetrics, DEVTOOLS_EVENT_NAME, type DashboardData, type DevtoolsPluginOptions, type EffectMetrics, type HistogramBucket, type LoggingPluginOptions, type MetricDataPoint, type MetricType, type OTLPExporter, type OTLPExporterConfig, type ObservabilityConfig, type ObservabilityInstance, type PerformancePluginOptions, type PerformanceSnapshot, type PersistencePluginOptions, type ReconcileMetrics, type ResolverMetrics, type TraceEvent, type TraceSpan, clobberAlertPlugin, clobberLoopPlugin, createAgentMetrics, createCircuitBreaker, createOTLPExporter, createObservability, devtoolsPlugin, emitDevToolsEvent, loggingPlugin, performancePlugin, persistencePlugin };
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { A as AuditEntry, a as AuditEntryKind, b as AuditLedger, c as AuditLedgerOptions, d as AuditLedgerSink, Q as QueryFilter, e as createAuditLedger, m as memorySink } from '../index-
|
|
2
|
-
import { M as ModuleSchema, P as Plugin,
|
|
1
|
+
export { A as AuditEntry, a as AuditEntryKind, b as AuditLedger, c as AuditLedgerOptions, d as AuditLedgerSink, Q as QueryFilter, e as createAuditLedger, m as memorySink } from '../index-n8oQmdOZ.js';
|
|
2
|
+
import { M as ModuleSchema, P as Plugin, aA as System, ak as PredicateOverlapProof } from '../plugins-CaPMTICa.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Logging Plugin - Console logging for Directive events
|
|
@@ -1001,4 +1001,203 @@ interface ClobberSummaryEvent {
|
|
|
1001
1001
|
*/
|
|
1002
1002
|
declare function clobberAlertPlugin<M extends ModuleSchema>(options?: ClobberAlertPluginOptions): Plugin<M>;
|
|
1003
1003
|
|
|
1004
|
-
|
|
1004
|
+
/**
|
|
1005
|
+
* Clobber Loop Detector Plugin – fires a structured warning when the
|
|
1006
|
+
* same set of resolvers clobber the same fact above threshold within a
|
|
1007
|
+
* time window. A single clobber is fine — the `abortOn:` binding caught
|
|
1008
|
+
* the race and the audit ledger recorded it. A *loop* is two or more
|
|
1009
|
+
* resolvers whose `when:` predicates both satisfy a shared state and
|
|
1010
|
+
* keep rewriting the fact every reconcile tick, indefinitely.
|
|
1011
|
+
*
|
|
1012
|
+
* Today the symptom surfaces as a value flapping between two states in
|
|
1013
|
+
* a customer screenshot. The audit ledger holds the forensic evidence
|
|
1014
|
+
* (800 clobbers/sec on `cart.discount`), but nobody looks until a
|
|
1015
|
+
* customer complains. This plugin closes the loop: one structured
|
|
1016
|
+
* warning per detected loop, with a **predicate-overlap proof** built
|
|
1017
|
+
* from the participants' `whenSpec` so the warning points at the
|
|
1018
|
+
* specific clauses that co-fire instead of stopping at "these resolvers
|
|
1019
|
+
* fight."
|
|
1020
|
+
*
|
|
1021
|
+
* @public
|
|
1022
|
+
*/
|
|
1023
|
+
|
|
1024
|
+
/** Configuration for the {@link clobberLoopPlugin}. */
|
|
1025
|
+
interface ClobberLoopPluginOptions {
|
|
1026
|
+
/**
|
|
1027
|
+
* Window over which clobber-rejection events are aggregated. A loop
|
|
1028
|
+
* fires when `threshold` distinct-requirement rejections involving
|
|
1029
|
+
* ≥2 distinct resolvers land on the same fact within `windowMs`.
|
|
1030
|
+
*
|
|
1031
|
+
* Default: 1000ms. Catches the canonical "5 clobbers in <500ms" case
|
|
1032
|
+
* while sitting well outside tick-boundary noise.
|
|
1033
|
+
*/
|
|
1034
|
+
windowMs?: number;
|
|
1035
|
+
/**
|
|
1036
|
+
* Minimum distinct-requirement rejection count within `windowMs` to
|
|
1037
|
+
* trigger a loop event. Distinct by `requirement.id`, so a single
|
|
1038
|
+
* resolver's retry storm (which shares one requirement id) counts as
|
|
1039
|
+
* one rejection and never crosses the threshold alone — only true
|
|
1040
|
+
* multi-participant contention does.
|
|
1041
|
+
*
|
|
1042
|
+
* Default: 5.
|
|
1043
|
+
*/
|
|
1044
|
+
threshold?: number;
|
|
1045
|
+
/**
|
|
1046
|
+
* After a loop fires on a `(fact, participantSet)`, suppress same-key
|
|
1047
|
+
* re-fire for this duration. Mirrors `clobberAlertPlugin`'s
|
|
1048
|
+
* cooldown precedent.
|
|
1049
|
+
*
|
|
1050
|
+
* Default: 5000ms.
|
|
1051
|
+
*/
|
|
1052
|
+
cooldownMs?: number;
|
|
1053
|
+
/**
|
|
1054
|
+
* Quiet window after the last contributing clobber rejection before
|
|
1055
|
+
* the loop is considered closed and a `resolver.clobber.loop.resolved`
|
|
1056
|
+
* event fires. The resolver may have shipped a fix (priority added,
|
|
1057
|
+
* `when:` narrowed), or the surrounding state changed enough that
|
|
1058
|
+
* both participants no longer co-fire.
|
|
1059
|
+
*
|
|
1060
|
+
* Default: 30000ms.
|
|
1061
|
+
*/
|
|
1062
|
+
resolvedAfterMs?: number;
|
|
1063
|
+
/**
|
|
1064
|
+
* Global LRU cap on the number of facts the detector tracks. A
|
|
1065
|
+
* hot-then-cold attacker fact key can churn the map, but LRU
|
|
1066
|
+
* eviction ensures the legitimate hot fact stays resident. Memory
|
|
1067
|
+
* bound: ~32 ring-buffer entries × `maxTrackedFacts` slim records.
|
|
1068
|
+
*
|
|
1069
|
+
* Default: 256.
|
|
1070
|
+
*/
|
|
1071
|
+
maxTrackedFacts?: number;
|
|
1072
|
+
/**
|
|
1073
|
+
* Per-fact cap on participant resolver tracking. Above this, a single
|
|
1074
|
+
* "N-way contention on fact X" event fires once and detailed
|
|
1075
|
+
* participant tracking pauses for the cooldown window.
|
|
1076
|
+
*
|
|
1077
|
+
* Default: 16.
|
|
1078
|
+
*/
|
|
1079
|
+
maxParticipantsPerFact?: number;
|
|
1080
|
+
/**
|
|
1081
|
+
* Global emission cap (loop-events per second across all facts). A
|
|
1082
|
+
* storm where every fact enters a loop simultaneously should not blow
|
|
1083
|
+
* up the observation bus or pager. Above-cap detections increment a
|
|
1084
|
+
* counter and surface in the next emitted event's
|
|
1085
|
+
* `suppressedSinceLastEmit` field.
|
|
1086
|
+
*
|
|
1087
|
+
* Default: 10.
|
|
1088
|
+
*/
|
|
1089
|
+
maxEmissionsPerSec?: number;
|
|
1090
|
+
/**
|
|
1091
|
+
* Callback fired when a loop is detected. The payload is the
|
|
1092
|
+
* fully-constructed event including PII-redacted `predicateOverlap`
|
|
1093
|
+
* (when applicable).
|
|
1094
|
+
*
|
|
1095
|
+
* Default in dev mode: `console.warn` with the structured warning
|
|
1096
|
+
* text. Default in production: `console.error` to stderr — NOT noop,
|
|
1097
|
+
* so the signal still lands in CloudWatch/Loki/Datadog log pipelines
|
|
1098
|
+
* even when consumers haven't explicitly wired routing.
|
|
1099
|
+
*/
|
|
1100
|
+
onLoop?: (event: ClobberLoopDetectedEvent) => void;
|
|
1101
|
+
/**
|
|
1102
|
+
* Callback fired when a previously-detected loop is considered
|
|
1103
|
+
* resolved (quiet window elapsed, participant unregistered, or
|
|
1104
|
+
* predicate narrowed).
|
|
1105
|
+
*
|
|
1106
|
+
* Default: no-op. Wire when monitoring needs "5 active loops" rather
|
|
1107
|
+
* than "47 historical loops."
|
|
1108
|
+
*/
|
|
1109
|
+
onResolved?: (event: ClobberLoopResolvedEvent) => void;
|
|
1110
|
+
/**
|
|
1111
|
+
* Opt-out of PII redaction in the emitted event's `predicateOverlap`
|
|
1112
|
+
* clauses. The default (`false`) routes `whenSpec` operand pointers
|
|
1113
|
+
* through `redactWhenSpec` against `system.meta.byTag("pii")` BEFORE
|
|
1114
|
+
* the event is constructed, so downstream sinks see `[redacted]` in
|
|
1115
|
+
* place of the literal operand.
|
|
1116
|
+
*
|
|
1117
|
+
* Set to `true` only when the deployment has a data-processing
|
|
1118
|
+
* addendum that permits unredacted operand capture.
|
|
1119
|
+
*
|
|
1120
|
+
* Default: `false`.
|
|
1121
|
+
*/
|
|
1122
|
+
capturePII?: boolean;
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Emitted payload for {@link ClobberLoopPluginOptions.onLoop}. Identical
|
|
1126
|
+
* shape to the `"resolver.clobber.loop.detected"` ObservationEvent so
|
|
1127
|
+
* consumers can route the same code path through either surface.
|
|
1128
|
+
*
|
|
1129
|
+
* @public
|
|
1130
|
+
*/
|
|
1131
|
+
interface ClobberLoopDetectedEvent {
|
|
1132
|
+
readonly systemId: string;
|
|
1133
|
+
readonly fact: string;
|
|
1134
|
+
readonly participants: readonly string[];
|
|
1135
|
+
readonly participantModules: readonly string[];
|
|
1136
|
+
readonly count: number;
|
|
1137
|
+
readonly windowMs: number;
|
|
1138
|
+
readonly firstAt: number;
|
|
1139
|
+
readonly lastAt: number;
|
|
1140
|
+
readonly predicateOverlap?: PredicateOverlapProof;
|
|
1141
|
+
readonly severity: "warn" | "error";
|
|
1142
|
+
readonly factTags: readonly string[];
|
|
1143
|
+
readonly suppressedSinceLastEmit: number;
|
|
1144
|
+
readonly rejectionSeqs: readonly number[];
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Emitted payload for {@link ClobberLoopPluginOptions.onResolved}.
|
|
1148
|
+
*
|
|
1149
|
+
* @public
|
|
1150
|
+
*/
|
|
1151
|
+
interface ClobberLoopResolvedEvent {
|
|
1152
|
+
readonly systemId: string;
|
|
1153
|
+
readonly fact: string;
|
|
1154
|
+
readonly participants: readonly string[];
|
|
1155
|
+
readonly durationMs: number;
|
|
1156
|
+
readonly resolution: "no-recurrence-in-window" | "participant-disabled" | "predicate-narrowed";
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Runtime kill-switch handle returned alongside the plugin. Lets an SRE
|
|
1160
|
+
* flip the detector off during incident response without redeploying.
|
|
1161
|
+
* `disable()` stops emission but preserves buffer state so `enable()`
|
|
1162
|
+
* resumes cleanly without warm-up drift.
|
|
1163
|
+
*
|
|
1164
|
+
* @public
|
|
1165
|
+
*/
|
|
1166
|
+
interface ClobberLoopPluginHandle<M extends ModuleSchema> {
|
|
1167
|
+
readonly plugin: Plugin<M>;
|
|
1168
|
+
disable(): void;
|
|
1169
|
+
enable(): void;
|
|
1170
|
+
isEnabled(): boolean;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Create a clobber-loop detector plugin. Returns a {@link
|
|
1174
|
+
* ClobberLoopPluginHandle} so callers can flip the detector off at
|
|
1175
|
+
* runtime without redeploying. The `.plugin` field is the value to
|
|
1176
|
+
* pass into `createSystem({ plugins: [...] })`.
|
|
1177
|
+
*
|
|
1178
|
+
* @example
|
|
1179
|
+
* ```ts
|
|
1180
|
+
* const loopDetector = clobberLoopPlugin({
|
|
1181
|
+
* threshold: 5,
|
|
1182
|
+
* windowMs: 1000,
|
|
1183
|
+
* onLoop: (e) => pagerduty.trigger({
|
|
1184
|
+
* severity: e.severity,
|
|
1185
|
+
* summary: `Clobber loop on ${e.fact}: ${e.participants.join(" vs ")}`,
|
|
1186
|
+
* details: e,
|
|
1187
|
+
* }),
|
|
1188
|
+
* });
|
|
1189
|
+
*
|
|
1190
|
+
* createSystem({
|
|
1191
|
+
* module: myModule,
|
|
1192
|
+
* plugins: [loopDetector.plugin],
|
|
1193
|
+
* });
|
|
1194
|
+
*
|
|
1195
|
+
* // During incident response:
|
|
1196
|
+
* loopDetector.disable();
|
|
1197
|
+
* ```
|
|
1198
|
+
*
|
|
1199
|
+
* @public
|
|
1200
|
+
*/
|
|
1201
|
+
declare function clobberLoopPlugin<M extends ModuleSchema>(options?: ClobberLoopPluginOptions): ClobberLoopPluginHandle<M>;
|
|
1202
|
+
|
|
1203
|
+
export { type AggregatedMetric, type AlertConfig, type AlertEvent, type CircuitBreaker, type CircuitBreakerConfig, CircuitBreakerOpenError, type CircuitBreakerStats, type CircuitState, type ClobberAlertEvent, type ClobberAlertPluginOptions, type ClobberLoopDetectedEvent, type ClobberLoopPluginHandle, type ClobberLoopPluginOptions, type ClobberLoopResolvedEvent, type ConstraintMetrics, DEVTOOLS_EVENT_NAME, type DashboardData, type DevtoolsPluginOptions, type EffectMetrics, type HistogramBucket, type LoggingPluginOptions, type MetricDataPoint, type MetricType, type OTLPExporter, type OTLPExporterConfig, type ObservabilityConfig, type ObservabilityInstance, type PerformancePluginOptions, type PerformanceSnapshot, type PersistencePluginOptions, type ReconcileMetrics, type ResolverMetrics, type TraceEvent, type TraceSpan, clobberAlertPlugin, clobberLoopPlugin, createAgentMetrics, createCircuitBreaker, createOTLPExporter, createObservability, devtoolsPlugin, emitDevToolsEvent, loggingPlugin, performancePlugin, persistencePlugin };
|