@flowdot.ai/guardian-agent 0.1.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 (188) hide show
  1. package/LICENSE +40 -0
  2. package/README.md +281 -0
  3. package/ROADMAP.md +109 -0
  4. package/dist/audit/attestor.d.ts +102 -0
  5. package/dist/audit/attestor.d.ts.map +1 -0
  6. package/dist/audit/attestor.js +103 -0
  7. package/dist/audit/attestor.js.map +1 -0
  8. package/dist/audit/chain.d.ts +30 -0
  9. package/dist/audit/chain.d.ts.map +1 -0
  10. package/dist/audit/chain.js +65 -0
  11. package/dist/audit/chain.js.map +1 -0
  12. package/dist/audit/correlation.d.ts +114 -0
  13. package/dist/audit/correlation.d.ts.map +1 -0
  14. package/dist/audit/correlation.js +259 -0
  15. package/dist/audit/correlation.js.map +1 -0
  16. package/dist/audit/index.d.ts +13 -0
  17. package/dist/audit/index.d.ts.map +1 -0
  18. package/dist/audit/index.js +8 -0
  19. package/dist/audit/index.js.map +1 -0
  20. package/dist/audit/reader.d.ts +30 -0
  21. package/dist/audit/reader.d.ts.map +1 -0
  22. package/dist/audit/reader.js +85 -0
  23. package/dist/audit/reader.js.map +1 -0
  24. package/dist/audit/signature.d.ts +39 -0
  25. package/dist/audit/signature.d.ts.map +1 -0
  26. package/dist/audit/signature.js +73 -0
  27. package/dist/audit/signature.js.map +1 -0
  28. package/dist/audit/stats.d.ts +106 -0
  29. package/dist/audit/stats.d.ts.map +1 -0
  30. package/dist/audit/stats.js +196 -0
  31. package/dist/audit/stats.js.map +1 -0
  32. package/dist/audit/writer.d.ts +96 -0
  33. package/dist/audit/writer.d.ts.map +1 -0
  34. package/dist/audit/writer.js +263 -0
  35. package/dist/audit/writer.js.map +1 -0
  36. package/dist/cli/guardian-baseline.d.ts +42 -0
  37. package/dist/cli/guardian-baseline.d.ts.map +1 -0
  38. package/dist/cli/guardian-baseline.js +265 -0
  39. package/dist/cli/guardian-baseline.js.map +1 -0
  40. package/dist/cli/guardian-correlator.d.ts +47 -0
  41. package/dist/cli/guardian-correlator.d.ts.map +1 -0
  42. package/dist/cli/guardian-correlator.js +217 -0
  43. package/dist/cli/guardian-correlator.js.map +1 -0
  44. package/dist/cli/guardian-verify.d.ts +30 -0
  45. package/dist/cli/guardian-verify.d.ts.map +1 -0
  46. package/dist/cli/guardian-verify.js +149 -0
  47. package/dist/cli/guardian-verify.js.map +1 -0
  48. package/dist/errors.d.ts +28 -0
  49. package/dist/errors.d.ts.map +1 -0
  50. package/dist/errors.js +40 -0
  51. package/dist/errors.js.map +1 -0
  52. package/dist/estop/heartbeat.d.ts +94 -0
  53. package/dist/estop/heartbeat.d.ts.map +1 -0
  54. package/dist/estop/heartbeat.js +135 -0
  55. package/dist/estop/heartbeat.js.map +1 -0
  56. package/dist/estop/hub.d.ts +76 -0
  57. package/dist/estop/hub.d.ts.map +1 -0
  58. package/dist/estop/hub.js +167 -0
  59. package/dist/estop/hub.js.map +1 -0
  60. package/dist/estop/index.d.ts +12 -0
  61. package/dist/estop/index.d.ts.map +1 -0
  62. package/dist/estop/index.js +6 -0
  63. package/dist/estop/index.js.map +1 -0
  64. package/dist/estop/local.d.ts +31 -0
  65. package/dist/estop/local.d.ts.map +1 -0
  66. package/dist/estop/local.js +101 -0
  67. package/dist/estop/local.js.map +1 -0
  68. package/dist/estop/middleware.d.ts +36 -0
  69. package/dist/estop/middleware.d.ts.map +1 -0
  70. package/dist/estop/middleware.js +40 -0
  71. package/dist/estop/middleware.js.map +1 -0
  72. package/dist/estop/poller.d.ts +36 -0
  73. package/dist/estop/poller.d.ts.map +1 -0
  74. package/dist/estop/poller.js +85 -0
  75. package/dist/estop/poller.js.map +1 -0
  76. package/dist/estop/types.d.ts +31 -0
  77. package/dist/estop/types.d.ts.map +1 -0
  78. package/dist/estop/types.js +5 -0
  79. package/dist/estop/types.js.map +1 -0
  80. package/dist/gate/async-callback.d.ts +27 -0
  81. package/dist/gate/async-callback.d.ts.map +1 -0
  82. package/dist/gate/async-callback.js +79 -0
  83. package/dist/gate/async-callback.js.map +1 -0
  84. package/dist/gate/cli.d.ts +29 -0
  85. package/dist/gate/cli.d.ts.map +1 -0
  86. package/dist/gate/cli.js +83 -0
  87. package/dist/gate/cli.js.map +1 -0
  88. package/dist/gate/data-channel.d.ts +41 -0
  89. package/dist/gate/data-channel.d.ts.map +1 -0
  90. package/dist/gate/data-channel.js +132 -0
  91. package/dist/gate/data-channel.js.map +1 -0
  92. package/dist/gate/index.d.ts +13 -0
  93. package/dist/gate/index.d.ts.map +1 -0
  94. package/dist/gate/index.js +7 -0
  95. package/dist/gate/index.js.map +1 -0
  96. package/dist/gate/options.d.ts +90 -0
  97. package/dist/gate/options.d.ts.map +1 -0
  98. package/dist/gate/options.js +131 -0
  99. package/dist/gate/options.js.map +1 -0
  100. package/dist/gate/programmatic.d.ts +9 -0
  101. package/dist/gate/programmatic.d.ts.map +1 -0
  102. package/dist/gate/programmatic.js +20 -0
  103. package/dist/gate/programmatic.js.map +1 -0
  104. package/dist/gate/two-key.d.ts +90 -0
  105. package/dist/gate/two-key.d.ts.map +1 -0
  106. package/dist/gate/two-key.js +78 -0
  107. package/dist/gate/two-key.js.map +1 -0
  108. package/dist/gate/types.d.ts +25 -0
  109. package/dist/gate/types.d.ts.map +1 -0
  110. package/dist/gate/types.js +5 -0
  111. package/dist/gate/types.js.map +1 -0
  112. package/dist/index.d.ts +33 -0
  113. package/dist/index.d.ts.map +1 -0
  114. package/dist/index.js +26 -0
  115. package/dist/index.js.map +1 -0
  116. package/dist/notify/console.d.ts +13 -0
  117. package/dist/notify/console.d.ts.map +1 -0
  118. package/dist/notify/console.js +27 -0
  119. package/dist/notify/console.js.map +1 -0
  120. package/dist/notify/index.d.ts +8 -0
  121. package/dist/notify/index.d.ts.map +1 -0
  122. package/dist/notify/index.js +4 -0
  123. package/dist/notify/index.js.map +1 -0
  124. package/dist/notify/multi.d.ts +14 -0
  125. package/dist/notify/multi.d.ts.map +1 -0
  126. package/dist/notify/multi.js +22 -0
  127. package/dist/notify/multi.js.map +1 -0
  128. package/dist/notify/types.d.ts +21 -0
  129. package/dist/notify/types.d.ts.map +1 -0
  130. package/dist/notify/types.js +5 -0
  131. package/dist/notify/types.js.map +1 -0
  132. package/dist/notify/webhook.d.ts +21 -0
  133. package/dist/notify/webhook.d.ts.map +1 -0
  134. package/dist/notify/webhook.js +37 -0
  135. package/dist/notify/webhook.js.map +1 -0
  136. package/dist/policy/attribution.d.ts +61 -0
  137. package/dist/policy/attribution.d.ts.map +1 -0
  138. package/dist/policy/attribution.js +116 -0
  139. package/dist/policy/attribution.js.map +1 -0
  140. package/dist/policy/evaluator.d.ts +36 -0
  141. package/dist/policy/evaluator.d.ts.map +1 -0
  142. package/dist/policy/evaluator.js +211 -0
  143. package/dist/policy/evaluator.js.map +1 -0
  144. package/dist/policy/index.d.ts +11 -0
  145. package/dist/policy/index.d.ts.map +1 -0
  146. package/dist/policy/index.js +7 -0
  147. package/dist/policy/index.js.map +1 -0
  148. package/dist/policy/integrity.d.ts +17 -0
  149. package/dist/policy/integrity.d.ts.map +1 -0
  150. package/dist/policy/integrity.js +31 -0
  151. package/dist/policy/integrity.js.map +1 -0
  152. package/dist/policy/loader.d.ts +9 -0
  153. package/dist/policy/loader.d.ts.map +1 -0
  154. package/dist/policy/loader.js +124 -0
  155. package/dist/policy/loader.js.map +1 -0
  156. package/dist/policy/site-key.d.ts +22 -0
  157. package/dist/policy/site-key.d.ts.map +1 -0
  158. package/dist/policy/site-key.js +48 -0
  159. package/dist/policy/site-key.js.map +1 -0
  160. package/dist/policy/store.d.ts +45 -0
  161. package/dist/policy/store.d.ts.map +1 -0
  162. package/dist/policy/store.js +223 -0
  163. package/dist/policy/store.js.map +1 -0
  164. package/dist/policy/types.d.ts +72 -0
  165. package/dist/policy/types.d.ts.map +1 -0
  166. package/dist/policy/types.js +5 -0
  167. package/dist/policy/types.js.map +1 -0
  168. package/dist/runtime/capability.d.ts +125 -0
  169. package/dist/runtime/capability.d.ts.map +1 -0
  170. package/dist/runtime/capability.js +121 -0
  171. package/dist/runtime/capability.js.map +1 -0
  172. package/dist/runtime/honeytokens.d.ts +104 -0
  173. package/dist/runtime/honeytokens.d.ts.map +1 -0
  174. package/dist/runtime/honeytokens.js +115 -0
  175. package/dist/runtime/honeytokens.js.map +1 -0
  176. package/dist/runtime/multi-rate-limiter.d.ts +90 -0
  177. package/dist/runtime/multi-rate-limiter.d.ts.map +1 -0
  178. package/dist/runtime/multi-rate-limiter.js +133 -0
  179. package/dist/runtime/multi-rate-limiter.js.map +1 -0
  180. package/dist/runtime/runtime.d.ts +94 -0
  181. package/dist/runtime/runtime.d.ts.map +1 -0
  182. package/dist/runtime/runtime.js +276 -0
  183. package/dist/runtime/runtime.js.map +1 -0
  184. package/dist/types.d.ts +97 -0
  185. package/dist/types.d.ts.map +1 -0
  186. package/dist/types.js +5 -0
  187. package/dist/types.js.map +1 -0
  188. package/package.json +83 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Policy types. SPEC §3.
3
+ */
4
+ /** Persistence scope. */
5
+ export type PolicyScope = 'once' | 'session' | 'forever' | 'banned';
6
+ /** Effective decision. */
7
+ export type PolicyDecision = 'allow' | 'deny' | 'prompt';
8
+ /**
9
+ * Conditional clause: a rule only matches when these model attributes
10
+ * match the request. Strings are matched with the same shell-style globs
11
+ * (`*`, `?`, `[seq]`) used for tool names — except `attribution_path`, which
12
+ * uses flat-glob (where `*` also matches `/`).
13
+ *
14
+ * v0.6+. SPEC §3 open question (resolved).
15
+ *
16
+ * Multiple fields are conjunctive: the rule matches only when every
17
+ * provided clause matches.
18
+ */
19
+ export interface PolicyWhen {
20
+ /** Match on `model.provider` (e.g., 'anthropic', 'openai', 'ollama'). */
21
+ 'model.provider'?: string;
22
+ /** Match on `model.id` (e.g., 'claude-*-4.5*', 'gpt-5*', 'llama3*'). */
23
+ 'model.id'?: string;
24
+ /**
25
+ * Match on the rendered attribution path
26
+ * `surface/aggregator/provider/id`. Flat-glob: `*` matches across `/`.
27
+ * Examples: `'*\/RedPill/*\/*'`, `'FlowDot/*\/Anthropic/claude-*-4.*'`,
28
+ * `'*claude-opus*'`. See `policy/attribution.ts`.
29
+ */
30
+ attribution_path?: string;
31
+ }
32
+ /** A single rule in the policy. */
33
+ export interface PolicyRule {
34
+ /**
35
+ * Tool pattern. Supports shell-style globs ( * ? [seq] ). May also be
36
+ * `category:<name>` to target a category (SPEC §3.7).
37
+ */
38
+ tool: string;
39
+ /** Persistence scope of this rule. */
40
+ scope: PolicyScope;
41
+ /**
42
+ * Decision when the rule matches. Omit for `scope: banned` (implies `deny`).
43
+ * For `scope: once|session|forever`, defaults to `allow`.
44
+ */
45
+ decision?: Exclude<PolicyDecision, 'prompt'>;
46
+ /** Optional conditional clause. The rule only matches when all entries hold. */
47
+ when?: PolicyWhen;
48
+ /** Optional human note. Ignored by the evaluator. */
49
+ notes?: string;
50
+ }
51
+ /** Loaded policy: defaults + rules. */
52
+ export interface Policy {
53
+ /** Spec version of the policy file (e.g., '0.2'). */
54
+ version: string;
55
+ /** Agent id this policy applies to. */
56
+ agent_id: string;
57
+ /** Default behavior when no rule matches. */
58
+ defaults: {
59
+ scope: 'prompt' | PolicyScope;
60
+ decision?: Exclude<PolicyDecision, 'prompt'>;
61
+ };
62
+ /** Rules in declaration order. */
63
+ rules: PolicyRule[];
64
+ }
65
+ /** Result of evaluating a tool name against a policy. */
66
+ export interface PolicyEvaluation {
67
+ decision: PolicyDecision;
68
+ matchedRule: PolicyRule | undefined;
69
+ matchedAt: 'exact' | 'wildcard' | 'category' | 'default';
70
+ scope: PolicyScope | 'prompt';
71
+ }
72
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/policy/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yBAAyB;AACzB,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEpE,0BAA0B;AAC1B,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,mCAAmC;AACnC,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,KAAK,EAAE,WAAW,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;IAC7C,gFAAgF;IAChF,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,uCAAuC;AACvC,MAAM,WAAW,MAAM;IACrB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,QAAQ,EAAE;QACR,KAAK,EAAE,QAAQ,GAAG,WAAW,CAAC;QAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;KAC9C,CAAC;IACF,kCAAkC;IAClC,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW,EAAE,UAAU,GAAG,SAAS,CAAC;IACpC,SAAS,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;IACzD,KAAK,EAAE,WAAW,GAAG,QAAQ,CAAC;CAC/B"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Policy types. SPEC §3.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/policy/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Capability tags + sliding-window combination detection. SPEC §4 extension
3
+ * (v0.3.0+).
4
+ *
5
+ * Each tool is tagged with one or more capability classes (the canonical
6
+ * set is documented below but consumers can register additional strings).
7
+ * The supervisor maintains a per-session sliding window of recent tool
8
+ * calls + their capability sets. Rules describe "suspicious combinations"
9
+ * — a list of classes that, if all observed within `window_ms`, fire an
10
+ * event.
11
+ *
12
+ * v0.8 ships YELLOW-LINE ONLY. A combination match writes
13
+ * `x_capability_yellow` to the audit log; dispatch is NOT blocked. The
14
+ * point is to collect calibration data. Red-line auto-stop ships in v0.10
15
+ * after the v0.8 corpus shows zero false positives at proposed thresholds.
16
+ *
17
+ * Pure mechanism: tag lookup + set membership over a fixed-window event
18
+ * list. Constant memory per session (oldest events drop as window slides).
19
+ */
20
+ /**
21
+ * Canonical capability classes. The string-union form lets consumers extend
22
+ * with their own strings (e.g., `'database-write'` for a SQL-tool surface)
23
+ * without forking the library.
24
+ *
25
+ * Class meanings (canonical):
26
+ * - `read` — pure read of agent-accessible data
27
+ * - `write` — local write of agent-accessible data
28
+ * - `delete` — destruction of agent-accessible data
29
+ * - `execute` — run a subprocess / arbitrary code
30
+ * - `network-egress` — outbound network call
31
+ * - `network-ingress` — accept inbound network call
32
+ * - `credential` — read or write credentials
33
+ * - `system-path` — touch OS-level paths (`/etc`, `~/.ssh`, etc.)
34
+ * - `bulk` — operation over many items (>N, configurable per-tool)
35
+ * - `unknown` — fallback for untagged tools
36
+ */
37
+ export type CapabilityClass = 'read' | 'write' | 'delete' | 'execute' | 'network-egress' | 'network-ingress' | 'credential' | 'system-path' | 'bulk' | 'unknown' | (string & {});
38
+ /**
39
+ * One rule in the capability-rule set. A rule matches when every class in
40
+ * `combination` has been observed within the last `window_ms`.
41
+ *
42
+ * `level` is `'yellow'` in v0.8 (audit-only). `'red'` (auto-E-stop) lands
43
+ * in v0.10 once Yellow data justifies thresholds.
44
+ */
45
+ export interface CapabilityRule {
46
+ /** Stable id used in audit records. ASCII, short. */
47
+ id: string;
48
+ /** Free-form human note. Ignored by the matcher. */
49
+ description?: string;
50
+ /** Classes that must all appear within the window. Order is irrelevant. */
51
+ combination: CapabilityClass[];
52
+ /** Time window in milliseconds. */
53
+ window_ms: number;
54
+ /** v0.8: always 'yellow'. v0.10 adds 'red'. */
55
+ level: 'yellow' | 'red';
56
+ }
57
+ /**
58
+ * Event recorded into the sliding window. The supervisor synthesizes these
59
+ * after every dispatched tool call; they hold only what the matcher needs.
60
+ */
61
+ export interface CapabilityEvent {
62
+ /** Wall-clock ms (monotonic preferred when consumer supplies a `now`). */
63
+ ts: number;
64
+ /** Capability classes of THIS specific tool call. */
65
+ classes: CapabilityClass[];
66
+ /** Audit event_id, copied so a fired rule can cite the contributing events. */
67
+ eventId: string;
68
+ }
69
+ /**
70
+ * Result of recording one event. Lists every rule that fired on this event.
71
+ * Most events fire nothing; this array is typically empty.
72
+ */
73
+ export interface CapabilityMatch {
74
+ ruleId: string;
75
+ level: 'yellow' | 'red';
76
+ combination: CapabilityClass[];
77
+ window_ms: number;
78
+ /** event_ids of the events that contributed to the match, in chronological order. */
79
+ contributingEventIds: string[];
80
+ }
81
+ export interface CapabilityWindowOptions {
82
+ rules: CapabilityRule[];
83
+ /** Override for time source (testing). Defaults to Date.now. */
84
+ now?: () => number;
85
+ /**
86
+ * Maximum events kept in the window irrespective of age. Defensive cap so
87
+ * a runaway agent can't grow the buffer without bound. Default 10000.
88
+ */
89
+ maxEvents?: number;
90
+ }
91
+ /**
92
+ * Per-session sliding-window state. One instance per supervisor.
93
+ *
94
+ * Memory: O(max window_ms × call rate) in the worst case, capped at
95
+ * `maxEvents`. Events older than the longest rule window are dropped on
96
+ * every `record` call.
97
+ */
98
+ export declare class CapabilityWindow {
99
+ private readonly rules;
100
+ private readonly now;
101
+ private readonly maxEvents;
102
+ private readonly maxWindowMs;
103
+ private events;
104
+ constructor(options: CapabilityWindowOptions);
105
+ /**
106
+ * Record a tool dispatch + evaluate all rules against the current window.
107
+ *
108
+ * Returns the list of rules that fired (often empty). Caller is
109
+ * responsible for converting fires into audit rows + (in v0.10) E-stop
110
+ * presses.
111
+ */
112
+ record(classes: CapabilityClass[], eventId: string): CapabilityMatch[];
113
+ /** Snapshot of currently-buffered events (for tests + introspection). */
114
+ buffered(): readonly CapabilityEvent[];
115
+ /**
116
+ * Test whether `rule` is satisfied by the current window ending at `now`.
117
+ * Returns the contributing event ids on match, null otherwise.
118
+ *
119
+ * Algorithm: for each class in `combination`, find the most-recent event
120
+ * (within the window) that includes that class. If every class found a
121
+ * contributor, the rule matches.
122
+ */
123
+ private evaluateRule;
124
+ }
125
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../../src/runtime/capability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,OAAO,GACP,QAAQ,GACR,SAAS,GACT,gBAAgB,GAChB,iBAAiB,GACjB,YAAY,GACZ,aAAa,GACb,MAAM,GACN,SAAS,GACT,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,EAAE,EAAE,MAAM,CAAC;IACX,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,KAAK,EAAE,QAAQ,GAAG,KAAK,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,QAAQ,GAAG,KAAK,CAAC;IACxB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,MAAM,CAAyB;gBAE3B,OAAO,EAAE,uBAAuB;IAmB5C;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE;IA0BtE,yEAAyE;IACzE,QAAQ,IAAI,SAAS,eAAe,EAAE;IAItC;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;CA0BrB"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Capability tags + sliding-window combination detection. SPEC §4 extension
3
+ * (v0.3.0+).
4
+ *
5
+ * Each tool is tagged with one or more capability classes (the canonical
6
+ * set is documented below but consumers can register additional strings).
7
+ * The supervisor maintains a per-session sliding window of recent tool
8
+ * calls + their capability sets. Rules describe "suspicious combinations"
9
+ * — a list of classes that, if all observed within `window_ms`, fire an
10
+ * event.
11
+ *
12
+ * v0.8 ships YELLOW-LINE ONLY. A combination match writes
13
+ * `x_capability_yellow` to the audit log; dispatch is NOT blocked. The
14
+ * point is to collect calibration data. Red-line auto-stop ships in v0.10
15
+ * after the v0.8 corpus shows zero false positives at proposed thresholds.
16
+ *
17
+ * Pure mechanism: tag lookup + set membership over a fixed-window event
18
+ * list. Constant memory per session (oldest events drop as window slides).
19
+ */
20
+ /**
21
+ * Per-session sliding-window state. One instance per supervisor.
22
+ *
23
+ * Memory: O(max window_ms × call rate) in the worst case, capped at
24
+ * `maxEvents`. Events older than the longest rule window are dropped on
25
+ * every `record` call.
26
+ */
27
+ export class CapabilityWindow {
28
+ rules;
29
+ now;
30
+ maxEvents;
31
+ maxWindowMs;
32
+ events = [];
33
+ constructor(options) {
34
+ this.rules = options.rules;
35
+ this.now = options.now ?? Date.now;
36
+ this.maxEvents = options.maxEvents ?? 10_000;
37
+ this.maxWindowMs = this.rules.reduce((m, r) => Math.max(m, r.window_ms), 0);
38
+ // Validate rule shapes once.
39
+ for (const r of this.rules) {
40
+ if (r.combination.length === 0) {
41
+ throw new Error(`CapabilityRule ${JSON.stringify(r.id)} has empty combination`);
42
+ }
43
+ if (r.window_ms <= 0) {
44
+ throw new Error(`CapabilityRule ${JSON.stringify(r.id)} has non-positive window_ms`);
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * Record a tool dispatch + evaluate all rules against the current window.
50
+ *
51
+ * Returns the list of rules that fired (often empty). Caller is
52
+ * responsible for converting fires into audit rows + (in v0.10) E-stop
53
+ * presses.
54
+ */
55
+ record(classes, eventId) {
56
+ const ts = this.now();
57
+ const event = { ts, classes, eventId };
58
+ this.events.push(event);
59
+ // Age-based prune.
60
+ if (this.maxWindowMs > 0) {
61
+ const cutoff = ts - this.maxWindowMs;
62
+ while (this.events.length > 0 && this.events[0].ts < cutoff) {
63
+ this.events.shift();
64
+ }
65
+ }
66
+ // Hard cap.
67
+ while (this.events.length > this.maxEvents) {
68
+ this.events.shift();
69
+ }
70
+ // Evaluate each rule against the current window.
71
+ const matches = [];
72
+ for (const rule of this.rules) {
73
+ const m = this.evaluateRule(rule, ts);
74
+ if (m)
75
+ matches.push(m);
76
+ }
77
+ return matches;
78
+ }
79
+ /** Snapshot of currently-buffered events (for tests + introspection). */
80
+ buffered() {
81
+ return this.events;
82
+ }
83
+ /**
84
+ * Test whether `rule` is satisfied by the current window ending at `now`.
85
+ * Returns the contributing event ids on match, null otherwise.
86
+ *
87
+ * Algorithm: for each class in `combination`, find the most-recent event
88
+ * (within the window) that includes that class. If every class found a
89
+ * contributor, the rule matches.
90
+ */
91
+ evaluateRule(rule, now) {
92
+ const cutoff = now - rule.window_ms;
93
+ const required = new Set(rule.combination);
94
+ const contributors = new Map();
95
+ for (let i = this.events.length - 1; i >= 0; i--) {
96
+ const ev = this.events[i];
97
+ if (ev.ts < cutoff)
98
+ break;
99
+ for (const cls of ev.classes) {
100
+ if (required.has(cls) && !contributors.has(cls)) {
101
+ contributors.set(cls, ev);
102
+ }
103
+ }
104
+ if (contributors.size === required.size)
105
+ break;
106
+ }
107
+ if (contributors.size < required.size)
108
+ return null;
109
+ // Order contributing events chronologically + dedupe (a single
110
+ // multi-class event may satisfy several required classes).
111
+ const ids = Array.from(new Set(Array.from(contributors.values()).sort((a, b) => a.ts - b.ts).map((e) => e.eventId)));
112
+ return {
113
+ ruleId: rule.id,
114
+ level: rule.level,
115
+ combination: rule.combination,
116
+ window_ms: rule.window_ms,
117
+ contributingEventIds: ids,
118
+ };
119
+ }
120
+ }
121
+ //# sourceMappingURL=capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.js","sourceRoot":"","sources":["../../src/runtime/capability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Honeytoken detection. SPEC §11 (v0.3.0+).
3
+ *
4
+ * The library exposes two zero-false-positive-by-construction defenses:
5
+ *
6
+ * 1. **Value match.** Consumer registers a set of decoy strings or regexes
7
+ * that should NEVER legitimately appear in a tool call's arguments
8
+ * (e.g., a fake `AKIA...` key stashed in a never-read env var, a
9
+ * phantom file path `~/.flowdot/keys/recovery.pem` that exists but
10
+ * contains only a marker). The matcher walks the canonical-JSON
11
+ * representation of every tool call's args; any hit means an agent has
12
+ * been probing.
13
+ *
14
+ * 2. **Phantom tool.** Consumer registers a tool name that is documented
15
+ * internally as "do not call" — never advertised to legitimate agents.
16
+ * Any dispatch to that tool name → immediate fire.
17
+ *
18
+ * Both modes are deterministic predicates over input. Zero false positives
19
+ * by construction: if a legitimate workflow ever references a honeytoken,
20
+ * the operator either misconfigured the set or the workflow actually does
21
+ * touch sensitive data and the supervisor's catch is correct.
22
+ *
23
+ * The library SHIPS NO DEFAULT TOKENS. Anti-pattern: a library shipping
24
+ * fake-looking AWS keys gets picked up by secret scanners + creates support
25
+ * load. Consumers register their own tokens via `HoneytokenSet`.
26
+ */
27
+ /**
28
+ * A single decoy. Exactly one of `value` or `pattern` must be set.
29
+ *
30
+ * - `value` matches substring (case-sensitive). Use for fixed marker strings.
31
+ * - `pattern` matches against the canonical-JSON of args via regex. Use for
32
+ * shape-based tokens (`/AKIA[0-9A-Z]{16}/`, etc.).
33
+ */
34
+ export interface Honeytoken {
35
+ /** Stable id. Recorded in audit + estop reason. ASCII, short. */
36
+ id: string;
37
+ /** Free-form description for ops review. Ignored by the matcher. */
38
+ description?: string;
39
+ /** Literal substring to match. Mutually exclusive with `pattern`. */
40
+ value?: string;
41
+ /** Regex to test against canonical-JSON of args. Mutually exclusive with `value`. */
42
+ pattern?: RegExp;
43
+ }
44
+ /**
45
+ * A collection of honeytokens for one supervisor instance. Order does not
46
+ * affect detection; the matcher returns the first hit.
47
+ *
48
+ * Ids must be unique within the set.
49
+ */
50
+ export interface HoneytokenSet {
51
+ /** Stable identifier for the set itself. */
52
+ id: string;
53
+ /** Optional description. */
54
+ description?: string;
55
+ /** Value/pattern tokens scanned against `tool.args`. */
56
+ tokens: Honeytoken[];
57
+ /**
58
+ * Phantom tool names. Any dispatch to one of these names triggers
59
+ * immediately, regardless of args.
60
+ */
61
+ phantomTools?: string[];
62
+ }
63
+ /**
64
+ * Result of a single match attempt.
65
+ */
66
+ export type HoneytokenHit = {
67
+ kind: 'value_in_args';
68
+ tokenId: string;
69
+ } | {
70
+ kind: 'phantom_tool';
71
+ toolName: string;
72
+ };
73
+ /**
74
+ * Build a honeytoken set with id-uniqueness + xor-validation enforced.
75
+ *
76
+ * Throws on:
77
+ * - empty `tokens` AND empty `phantomTools` (the set would never fire)
78
+ * - duplicate token ids
79
+ * - duplicate phantom tool names
80
+ * - a token with neither `value` nor `pattern`
81
+ * - a token with BOTH `value` and `pattern`
82
+ */
83
+ export declare function defineHoneytokenSet(id: string, tokens: Honeytoken[], phantomTools?: string[], description?: string): HoneytokenSet;
84
+ /**
85
+ * Test whether a tool name is a phantom tool in the set. Returns the hit
86
+ * descriptor on match, `null` otherwise.
87
+ */
88
+ export declare function matchPhantomTool(set: HoneytokenSet, toolName: string): HoneytokenHit | null;
89
+ /**
90
+ * Test whether any honeytoken value or pattern appears in the canonical-JSON
91
+ * representation of `args`. Returns the FIRST hit. Order of `tokens` in the
92
+ * set determines which hit wins when multiple match.
93
+ *
94
+ * `args` is canonicalized to JSON before scanning so the matcher catches
95
+ * tokens regardless of nesting depth, key/value placement, or array index.
96
+ */
97
+ export declare function matchHoneytokenInArgs(set: HoneytokenSet, args: unknown): HoneytokenHit | null;
98
+ /**
99
+ * Compose phantom-tool + value-in-args checks. Phantom-tool match wins over
100
+ * value-in-args when both would fire (phantom-tool is the stronger signal:
101
+ * a literal call to a forbidden name).
102
+ */
103
+ export declare function checkHoneytoken(set: HoneytokenSet, toolName: string, args: unknown): HoneytokenHit | null;
104
+ //# sourceMappingURL=honeytokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"honeytokens.d.ts","sourceRoot":"","sources":["../../src/runtime/honeytokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,iEAAiE;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,UAAU,EAAE,EACpB,YAAY,GAAE,MAAM,EAAO,EAC3B,WAAW,CAAC,EAAE,MAAM,GACnB,aAAa,CAiCf;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAM3F;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,OAAO,GACZ,aAAa,GAAG,IAAI,CAetB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,GACZ,aAAa,GAAG,IAAI,CAEtB"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Honeytoken detection. SPEC §11 (v0.3.0+).
3
+ *
4
+ * The library exposes two zero-false-positive-by-construction defenses:
5
+ *
6
+ * 1. **Value match.** Consumer registers a set of decoy strings or regexes
7
+ * that should NEVER legitimately appear in a tool call's arguments
8
+ * (e.g., a fake `AKIA...` key stashed in a never-read env var, a
9
+ * phantom file path `~/.flowdot/keys/recovery.pem` that exists but
10
+ * contains only a marker). The matcher walks the canonical-JSON
11
+ * representation of every tool call's args; any hit means an agent has
12
+ * been probing.
13
+ *
14
+ * 2. **Phantom tool.** Consumer registers a tool name that is documented
15
+ * internally as "do not call" — never advertised to legitimate agents.
16
+ * Any dispatch to that tool name → immediate fire.
17
+ *
18
+ * Both modes are deterministic predicates over input. Zero false positives
19
+ * by construction: if a legitimate workflow ever references a honeytoken,
20
+ * the operator either misconfigured the set or the workflow actually does
21
+ * touch sensitive data and the supervisor's catch is correct.
22
+ *
23
+ * The library SHIPS NO DEFAULT TOKENS. Anti-pattern: a library shipping
24
+ * fake-looking AWS keys gets picked up by secret scanners + creates support
25
+ * load. Consumers register their own tokens via `HoneytokenSet`.
26
+ */
27
+ import { canonicalJsonStringify } from '../audit/chain.js';
28
+ /**
29
+ * Build a honeytoken set with id-uniqueness + xor-validation enforced.
30
+ *
31
+ * Throws on:
32
+ * - empty `tokens` AND empty `phantomTools` (the set would never fire)
33
+ * - duplicate token ids
34
+ * - duplicate phantom tool names
35
+ * - a token with neither `value` nor `pattern`
36
+ * - a token with BOTH `value` and `pattern`
37
+ */
38
+ export function defineHoneytokenSet(id, tokens, phantomTools = [], description) {
39
+ if (tokens.length === 0 && phantomTools.length === 0) {
40
+ throw new Error('defineHoneytokenSet: provide at least one token or phantom tool');
41
+ }
42
+ const tokenIds = new Set();
43
+ for (const t of tokens) {
44
+ if (tokenIds.has(t.id)) {
45
+ throw new Error(`defineHoneytokenSet: duplicate token id ${JSON.stringify(t.id)}`);
46
+ }
47
+ tokenIds.add(t.id);
48
+ const hasValue = t.value !== undefined;
49
+ const hasPattern = t.pattern !== undefined;
50
+ if (!hasValue && !hasPattern) {
51
+ throw new Error(`defineHoneytokenSet: token ${JSON.stringify(t.id)} must set either value or pattern`);
52
+ }
53
+ if (hasValue && hasPattern) {
54
+ throw new Error(`defineHoneytokenSet: token ${JSON.stringify(t.id)} sets both value and pattern; choose one`);
55
+ }
56
+ }
57
+ const phantomSet = new Set();
58
+ for (const name of phantomTools) {
59
+ if (phantomSet.has(name)) {
60
+ throw new Error(`defineHoneytokenSet: duplicate phantom tool ${JSON.stringify(name)}`);
61
+ }
62
+ phantomSet.add(name);
63
+ }
64
+ const out = { id, tokens, phantomTools };
65
+ if (description !== undefined)
66
+ out.description = description;
67
+ return out;
68
+ }
69
+ /**
70
+ * Test whether a tool name is a phantom tool in the set. Returns the hit
71
+ * descriptor on match, `null` otherwise.
72
+ */
73
+ export function matchPhantomTool(set, toolName) {
74
+ if (!set.phantomTools)
75
+ return null;
76
+ if (set.phantomTools.includes(toolName)) {
77
+ return { kind: 'phantom_tool', toolName };
78
+ }
79
+ return null;
80
+ }
81
+ /**
82
+ * Test whether any honeytoken value or pattern appears in the canonical-JSON
83
+ * representation of `args`. Returns the FIRST hit. Order of `tokens` in the
84
+ * set determines which hit wins when multiple match.
85
+ *
86
+ * `args` is canonicalized to JSON before scanning so the matcher catches
87
+ * tokens regardless of nesting depth, key/value placement, or array index.
88
+ */
89
+ export function matchHoneytokenInArgs(set, args) {
90
+ if (set.tokens.length === 0)
91
+ return null;
92
+ const json = canonicalJsonStringify(args);
93
+ for (const t of set.tokens) {
94
+ if (t.value !== undefined) {
95
+ if (json.includes(t.value)) {
96
+ return { kind: 'value_in_args', tokenId: t.id };
97
+ }
98
+ }
99
+ else if (t.pattern !== undefined) {
100
+ if (t.pattern.test(json)) {
101
+ return { kind: 'value_in_args', tokenId: t.id };
102
+ }
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+ /**
108
+ * Compose phantom-tool + value-in-args checks. Phantom-tool match wins over
109
+ * value-in-args when both would fire (phantom-tool is the stronger signal:
110
+ * a literal call to a forbidden name).
111
+ */
112
+ export function checkHoneytoken(set, toolName, args) {
113
+ return matchPhantomTool(set, toolName) ?? matchHoneytokenInArgs(set, args);
114
+ }
115
+ //# sourceMappingURL=honeytokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"honeytokens.js","sourceRoot":"","sources":["../../src/runtime/honeytokens.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Per-capability token-bucket rate limiter. SPEC §5 extension (v0.3.0+).
3
+ *
4
+ * v0.7 of the surface glues used a single global bucket. v0.8 splits the
5
+ * bucket per {@link CapabilityClass}: read-heavy work doesn't get blocked
6
+ * by a writes burst, and an exfil pattern (lots of credential reads +
7
+ * network-egress writes) hits the narrow buckets long before the global
8
+ * rate.
9
+ *
10
+ * A normal-workload session sees zero impact at the conservative defaults
11
+ * documented below. The buckets only bite on bursts that match the exfil
12
+ * shape.
13
+ *
14
+ * Pure mechanism: N token buckets, one per class. Same arithmetic, more
15
+ * dimensions.
16
+ */
17
+ import type { CapabilityClass } from './capability.js';
18
+ /**
19
+ * Bucket configuration for one capability class.
20
+ */
21
+ export interface BucketConfig {
22
+ /** Sustained calls per second. */
23
+ maxCallsPerSecond: number;
24
+ /** Burst capacity. Default = maxCallsPerSecond. */
25
+ bucketCapacity?: number;
26
+ }
27
+ export interface MultiRateLimiterOptions {
28
+ /**
29
+ * Per-class buckets. Classes not present in this map get the default
30
+ * fallback bucket. Pass `unknown` explicitly if you want untagged tools
31
+ * to share a specific bucket (vs. the default).
32
+ */
33
+ buckets: Partial<Record<CapabilityClass, BucketConfig>>;
34
+ /**
35
+ * Fallback for classes not in `buckets`. When omitted, classes without
36
+ * an explicit config are NOT rate-limited (every call allowed).
37
+ */
38
+ defaultBucket?: BucketConfig;
39
+ /** Time source (testing). */
40
+ now?: () => number;
41
+ }
42
+ export interface ConsumeAllowed {
43
+ allowed: true;
44
+ }
45
+ export interface ConsumeDenied {
46
+ allowed: false;
47
+ /** Which class's bucket denied first. */
48
+ class: CapabilityClass;
49
+ /** Estimated ms until the denied bucket has a token again. */
50
+ retryAfterMs: number;
51
+ }
52
+ export type ConsumeResult = ConsumeAllowed | ConsumeDenied;
53
+ /**
54
+ * Library-recommended defaults. Tuned to never trip normal CLI workloads
55
+ * (read/write at human-edit speed, occasional outbound calls) while
56
+ * catching exfil-shaped bursts in the seconds-window.
57
+ */
58
+ export declare const DEFAULT_BUCKETS: Partial<Record<CapabilityClass, BucketConfig>>;
59
+ /**
60
+ * Per-capability rate limiter. One {@link Bucket} per class; multi-class
61
+ * tools consume from every relevant bucket atomically (first denial wins;
62
+ * earlier-class buckets already consumed are NOT refunded).
63
+ *
64
+ * "First denial wins, no refund" matches the safety-conservative
65
+ * interpretation: a multi-class tool that's blocked on its rarest
66
+ * capability is fully blocked. The slightly-tighter accounting on
67
+ * already-consumed buckets is acceptable because it errs on the side of
68
+ * slowing the caller (not letting more through).
69
+ */
70
+ export declare class MultiRateLimiter {
71
+ private readonly buckets;
72
+ private readonly defaultBucket;
73
+ private readonly now;
74
+ private readonly configMap;
75
+ private readonly defaultConfig;
76
+ constructor(options: MultiRateLimiterOptions);
77
+ /**
78
+ * Attempt to consume one token from every relevant bucket. If ANY bucket
79
+ * is empty, return the FIRST class to deny (in iteration order of
80
+ * `classes`). Tokens already consumed from earlier classes in this call
81
+ * are not refunded — see class JSDoc.
82
+ *
83
+ * `unknown` (or any class not in `buckets` and no `defaultBucket`)
84
+ * passes through allowed.
85
+ */
86
+ tryConsume(classes: readonly CapabilityClass[]): ConsumeResult;
87
+ /** Current token count per class (tests + introspection). */
88
+ snapshot(): Record<string, number>;
89
+ }
90
+ //# sourceMappingURL=multi-rate-limiter.d.ts.map