instar 0.28.107 → 0.28.109

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 (32) hide show
  1. package/dist/monitoring/BurnAlertButtons.d.ts +107 -0
  2. package/dist/monitoring/BurnAlertButtons.d.ts.map +1 -0
  3. package/dist/monitoring/BurnAlertButtons.js +270 -0
  4. package/dist/monitoring/BurnAlertButtons.js.map +1 -0
  5. package/dist/monitoring/BurnDetectionSubscriber.d.ts +25 -0
  6. package/dist/monitoring/BurnDetectionSubscriber.d.ts.map +1 -0
  7. package/dist/monitoring/BurnDetectionSubscriber.js +34 -0
  8. package/dist/monitoring/BurnDetectionSubscriber.js.map +1 -0
  9. package/dist/monitoring/BurnDetector.d.ts +84 -0
  10. package/dist/monitoring/BurnDetector.d.ts.map +1 -0
  11. package/dist/monitoring/BurnDetector.js +169 -0
  12. package/dist/monitoring/BurnDetector.js.map +1 -0
  13. package/dist/monitoring/BurnThrottleRunbook.d.ts +105 -0
  14. package/dist/monitoring/BurnThrottleRunbook.d.ts.map +1 -0
  15. package/dist/monitoring/BurnThrottleRunbook.js +217 -0
  16. package/dist/monitoring/BurnThrottleRunbook.js.map +1 -0
  17. package/dist/monitoring/LlmRateGate.d.ts +133 -33
  18. package/dist/monitoring/LlmRateGate.d.ts.map +1 -1
  19. package/dist/monitoring/LlmRateGate.js +179 -46
  20. package/dist/monitoring/LlmRateGate.js.map +1 -1
  21. package/dist/monitoring/TokenLedger.d.ts +16 -0
  22. package/dist/monitoring/TokenLedger.d.ts.map +1 -1
  23. package/dist/monitoring/TokenLedger.js +28 -0
  24. package/dist/monitoring/TokenLedger.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/data/builtin-manifest.json +2 -2
  27. package/upgrades/0.28.107.md +17 -12
  28. package/upgrades/0.28.108.md +50 -0
  29. package/upgrades/0.28.109.md +32 -0
  30. package/upgrades/side-effects/token-burn-detection-phase-3.md +59 -0
  31. package/upgrades/side-effects/token-burn-detection-phase-4.md +78 -0
  32. package/upgrades/side-effects/token-burn-detection-phase-5.md +63 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * BurnAlertButtons — principal-bound Telegram inline-button surface for the
3
+ * burn-detection auto-heal system.
4
+ *
5
+ * Phase 5 of docs/specs/token-burn-detection-and-self-heal.md.
6
+ *
7
+ * The convergence audit identified that the original spec's "tap to throttle"
8
+ * button could be tapped by anyone in any Telegram chat the bot was in. The
9
+ * Phase 5 callback handler closes that vector with three layers:
10
+ *
11
+ * 1. **Principal verification**: every callback's `from.id` must be in the
12
+ * agent's `authorizedUserIds` list (the same list `MessagingToneGate`
13
+ * and other principal-aware code paths consult).
14
+ * 2. **HMAC signature**: every `callback_data` is signed by the agent's
15
+ * capability key over the canonical action payload. Forged callback
16
+ * data fails verification.
17
+ * 3. **Signal-id freshness**: each button carries the signal-id that
18
+ * produced it; once consumed, the same signal-id cannot be tapped
19
+ * twice (the spec's "replay reject" requirement).
20
+ *
21
+ * This module is pure logic over a tiny callback_data string. The actual
22
+ * Telegram-wire integration (sending the inline_keyboard with reply_markup,
23
+ * receiving the callback from TelegramAdapter, dispatching to handle())
24
+ * is wired in a follow-up alongside the existing CallbackRegistry pattern
25
+ * in TelegramAdapter.ts.
26
+ */
27
+ import type { LlmRateGate } from './LlmRateGate.js';
28
+ /** The four actions a user can take from a burn alert. */
29
+ export type BurnAlertAction = 'release' | 'snooze-24h' | 'extend' | 'investigate';
30
+ export interface BurnAlertCallback {
31
+ /** What the user tapped. */
32
+ action: BurnAlertAction;
33
+ /** Which attribution key this button refers to. */
34
+ attributionKey: string;
35
+ /** Signal-id from the originating burn signal (replay prevention). */
36
+ signalId: string;
37
+ }
38
+ export interface BurnAlertCallbackResult {
39
+ ok: boolean;
40
+ action: BurnAlertAction;
41
+ attributionKey: string;
42
+ reason: 'accepted' | 'principal-not-authorized' | 'invalid-signature' | 'signal-id-replayed' | 'malformed-payload' | 'snooze-recorded' | 'extend-failed-not-throttled';
43
+ /** Human-readable status to feed back to the Telegram user. */
44
+ userMessage: string;
45
+ }
46
+ export interface BurnAlertButtonsDeps {
47
+ /** HMAC key to sign + verify callback_data. */
48
+ capabilityKey: Buffer;
49
+ /** Authorized user IDs — same list MessagingToneGate consults. */
50
+ authorizedUserIds: number[];
51
+ /** The LlmRateGate; release/extend mutate throttles. */
52
+ gate: LlmRateGate;
53
+ /** Injectable clock for tests. */
54
+ now?: () => number;
55
+ /** Snooze TTL (default 24h). */
56
+ snoozeDurationMs?: number;
57
+ }
58
+ export declare class BurnAlertButtons {
59
+ private readonly capabilityKey;
60
+ private readonly authorizedUserIds;
61
+ private readonly gate;
62
+ private readonly now;
63
+ private readonly snoozeDurationMs;
64
+ /** Consumed signal-ids per button — guards against replay. */
65
+ private readonly consumed;
66
+ /** Snoozed attribution keys → snooze-expires-at (ms). Inspected by the runbook before throttling. */
67
+ private readonly snoozed;
68
+ constructor(deps: BurnAlertButtonsDeps);
69
+ /**
70
+ * Generate the signed payload for one button. The opaque callback_data
71
+ * string is what the Telegram inline button carries. The button shape
72
+ * itself (text label, button row layout) is the caller's job — they get
73
+ * the payload, they pass it to Telegram's reply_markup.
74
+ */
75
+ encodeCallbackData(payload: BurnAlertCallback): string;
76
+ /**
77
+ * Build a four-button keyboard row for a burn alert. Returns the inline
78
+ * objects the caller can pass to Telegram's reply_markup.inline_keyboard.
79
+ */
80
+ buildKeyboard(attributionKey: string, signalId: string): Array<{
81
+ text: string;
82
+ callback_data: string;
83
+ }>;
84
+ /**
85
+ * Handle a Telegram callback. Returns a structured result the caller can
86
+ * use to log the audit trail and answer the Telegram callback query.
87
+ */
88
+ handle(input: {
89
+ callbackData: string;
90
+ fromUserId: number;
91
+ }): BurnAlertCallbackResult;
92
+ /**
93
+ * Read-side: is this attribution key currently snoozed? Called by the
94
+ * runbook before installing a throttle. Auto-expires entries.
95
+ */
96
+ isSnoozed(attributionKey: string): boolean;
97
+ /** For tests + dashboard. */
98
+ listSnoozes(): Array<{
99
+ attributionKey: string;
100
+ expiresAt: string;
101
+ }>;
102
+ private canonicalize;
103
+ private parseCallbackData;
104
+ /** GC consumed signal-id-action pairs older than 24h. */
105
+ private gcConsumed;
106
+ }
107
+ //# sourceMappingURL=BurnAlertButtons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BurnAlertButtons.d.ts","sourceRoot":"","sources":["../../src/monitoring/BurnAlertButtons.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,0DAA0D;AAC1D,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,YAAY,GACZ,QAAQ,GACR,aAAa,CAAC;AAElB,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,MAAM,EAAE,eAAe,CAAC;IACxB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,eAAe,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EACF,UAAU,GACV,0BAA0B,GAC1B,mBAAmB,GACnB,oBAAoB,GACpB,mBAAmB,GACnB,iBAAiB,GACjB,6BAA6B,CAAC;IAClC,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,wDAAwD;IACxD,IAAI,EAAE,WAAW,CAAC;IAClB,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,gCAAgC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAc;IAChD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,qGAAqG;IACrG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;gBAEzC,IAAI,EAAE,oBAAoB;IAQtC;;;;;OAKG;IACH,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM;IAYtD;;;OAGG;IACH,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IASvG;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,uBAAuB;IA4IpF;;;OAGG;IACH,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAU1C,6BAA6B;IAC7B,WAAW,IAAI,KAAK,CAAC;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAYnE,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,iBAAiB;IAczB,yDAAyD;IACzD,OAAO,CAAC,UAAU;CAMnB"}
@@ -0,0 +1,270 @@
1
+ /**
2
+ * BurnAlertButtons — principal-bound Telegram inline-button surface for the
3
+ * burn-detection auto-heal system.
4
+ *
5
+ * Phase 5 of docs/specs/token-burn-detection-and-self-heal.md.
6
+ *
7
+ * The convergence audit identified that the original spec's "tap to throttle"
8
+ * button could be tapped by anyone in any Telegram chat the bot was in. The
9
+ * Phase 5 callback handler closes that vector with three layers:
10
+ *
11
+ * 1. **Principal verification**: every callback's `from.id` must be in the
12
+ * agent's `authorizedUserIds` list (the same list `MessagingToneGate`
13
+ * and other principal-aware code paths consult).
14
+ * 2. **HMAC signature**: every `callback_data` is signed by the agent's
15
+ * capability key over the canonical action payload. Forged callback
16
+ * data fails verification.
17
+ * 3. **Signal-id freshness**: each button carries the signal-id that
18
+ * produced it; once consumed, the same signal-id cannot be tapped
19
+ * twice (the spec's "replay reject" requirement).
20
+ *
21
+ * This module is pure logic over a tiny callback_data string. The actual
22
+ * Telegram-wire integration (sending the inline_keyboard with reply_markup,
23
+ * receiving the callback from TelegramAdapter, dispatching to handle())
24
+ * is wired in a follow-up alongside the existing CallbackRegistry pattern
25
+ * in TelegramAdapter.ts.
26
+ */
27
+ import crypto from 'node:crypto';
28
+ const DEFAULT_SNOOZE_MS = 24 * 60 * 60 * 1000;
29
+ export class BurnAlertButtons {
30
+ capabilityKey;
31
+ authorizedUserIds;
32
+ gate;
33
+ now;
34
+ snoozeDurationMs;
35
+ /** Consumed signal-ids per button — guards against replay. */
36
+ consumed = new Map();
37
+ /** Snoozed attribution keys → snooze-expires-at (ms). Inspected by the runbook before throttling. */
38
+ snoozed = new Map();
39
+ constructor(deps) {
40
+ this.capabilityKey = deps.capabilityKey;
41
+ this.authorizedUserIds = new Set(deps.authorizedUserIds);
42
+ this.gate = deps.gate;
43
+ this.now = deps.now ?? (() => Date.now());
44
+ this.snoozeDurationMs = deps.snoozeDurationMs ?? DEFAULT_SNOOZE_MS;
45
+ }
46
+ /**
47
+ * Generate the signed payload for one button. The opaque callback_data
48
+ * string is what the Telegram inline button carries. The button shape
49
+ * itself (text label, button row layout) is the caller's job — they get
50
+ * the payload, they pass it to Telegram's reply_markup.
51
+ */
52
+ encodeCallbackData(payload) {
53
+ const canonical = this.canonicalize(payload);
54
+ const sig = crypto
55
+ .createHmac('sha256', this.capabilityKey)
56
+ .update(canonical)
57
+ .digest('hex')
58
+ .slice(0, 16);
59
+ // Compact encoding for Telegram's 64-byte callback_data limit. Format:
60
+ // <action>|<attributionKey>|<signalId>|<sig>
61
+ return `${payload.action}|${payload.attributionKey}|${payload.signalId}|${sig}`;
62
+ }
63
+ /**
64
+ * Build a four-button keyboard row for a burn alert. Returns the inline
65
+ * objects the caller can pass to Telegram's reply_markup.inline_keyboard.
66
+ */
67
+ buildKeyboard(attributionKey, signalId) {
68
+ return [
69
+ { text: 'Release', callback_data: this.encodeCallbackData({ action: 'release', attributionKey, signalId }) },
70
+ { text: 'Snooze 24h', callback_data: this.encodeCallbackData({ action: 'snooze-24h', attributionKey, signalId }) },
71
+ { text: 'Extend +1h', callback_data: this.encodeCallbackData({ action: 'extend', attributionKey, signalId }) },
72
+ { text: 'Investigate', callback_data: this.encodeCallbackData({ action: 'investigate', attributionKey, signalId }) },
73
+ ];
74
+ }
75
+ /**
76
+ * Handle a Telegram callback. Returns a structured result the caller can
77
+ * use to log the audit trail and answer the Telegram callback query.
78
+ */
79
+ handle(input) {
80
+ const parsed = this.parseCallbackData(input.callbackData);
81
+ if (!parsed) {
82
+ return {
83
+ ok: false,
84
+ action: 'investigate',
85
+ attributionKey: '',
86
+ reason: 'malformed-payload',
87
+ userMessage: 'I could not parse that button.',
88
+ };
89
+ }
90
+ // 1. Principal check.
91
+ if (!this.authorizedUserIds.has(input.fromUserId)) {
92
+ return {
93
+ ok: false,
94
+ action: parsed.action,
95
+ attributionKey: parsed.attributionKey,
96
+ reason: 'principal-not-authorized',
97
+ userMessage: 'You are not authorized to act on this agent.',
98
+ };
99
+ }
100
+ // 2. Signature check.
101
+ const expectedSig = crypto
102
+ .createHmac('sha256', this.capabilityKey)
103
+ .update(this.canonicalize(parsed))
104
+ .digest('hex')
105
+ .slice(0, 16);
106
+ if (parsed.sig !== expectedSig) {
107
+ return {
108
+ ok: false,
109
+ action: parsed.action,
110
+ attributionKey: parsed.attributionKey,
111
+ reason: 'invalid-signature',
112
+ userMessage: 'That button signature did not match. Refusing.',
113
+ };
114
+ }
115
+ // 3. Freshness check. The same signal-id-action pair can fire at most once.
116
+ const consumptionKey = `${parsed.action}|${parsed.signalId}`;
117
+ if (this.consumed.has(consumptionKey)) {
118
+ return {
119
+ ok: false,
120
+ action: parsed.action,
121
+ attributionKey: parsed.attributionKey,
122
+ reason: 'signal-id-replayed',
123
+ userMessage: 'That button has already been used.',
124
+ };
125
+ }
126
+ this.consumed.set(consumptionKey, this.now());
127
+ this.gcConsumed();
128
+ // 4. Apply the action.
129
+ switch (parsed.action) {
130
+ case 'release': {
131
+ const released = this.gate.revokeThrottle(parsed.attributionKey);
132
+ return {
133
+ ok: true,
134
+ action: 'release',
135
+ attributionKey: parsed.attributionKey,
136
+ reason: 'accepted',
137
+ userMessage: released
138
+ ? `Released the throttle on ${parsed.attributionKey}.`
139
+ : `No throttle was active for ${parsed.attributionKey} — nothing to release.`,
140
+ };
141
+ }
142
+ case 'snooze-24h': {
143
+ this.snoozed.set(parsed.attributionKey, this.now() + this.snoozeDurationMs);
144
+ this.gate.revokeThrottle(parsed.attributionKey);
145
+ return {
146
+ ok: true,
147
+ action: 'snooze-24h',
148
+ attributionKey: parsed.attributionKey,
149
+ reason: 'snooze-recorded',
150
+ userMessage: `Snoozed ${parsed.attributionKey} for 24 hours. The runbook will not throttle this key during that window.`,
151
+ };
152
+ }
153
+ case 'extend': {
154
+ // Re-install throttle for another hour using a fresh signalId derived
155
+ // from "extend|<existing signalId>". Returns a structured error if no
156
+ // throttle is currently active.
157
+ const active = this.gate.listActiveThrottles().find((t) => t.attributionKey === parsed.attributionKey);
158
+ if (!active) {
159
+ return {
160
+ ok: false,
161
+ action: 'extend',
162
+ attributionKey: parsed.attributionKey,
163
+ reason: 'extend-failed-not-throttled',
164
+ userMessage: `Cannot extend — no active throttle on ${parsed.attributionKey}.`,
165
+ };
166
+ }
167
+ const extendSignalId = `extend:${parsed.signalId}:${this.now()}`;
168
+ const extendDurationMs = 60 * 60 * 1000;
169
+ const tok = this.gate.computeCapabilityToken({
170
+ attributionKey: parsed.attributionKey,
171
+ durationMs: extendDurationMs,
172
+ issuer: active.issuer,
173
+ signalId: extendSignalId,
174
+ }) ?? undefined;
175
+ try {
176
+ this.gate.installThrottle({
177
+ attributionKey: parsed.attributionKey,
178
+ durationMs: extendDurationMs,
179
+ reason: `User-initiated extension via Telegram button.`,
180
+ issuer: active.issuer,
181
+ signalId: extendSignalId,
182
+ capabilityToken: tok,
183
+ });
184
+ }
185
+ catch (err) {
186
+ return {
187
+ ok: false,
188
+ action: 'extend',
189
+ attributionKey: parsed.attributionKey,
190
+ reason: 'extend-failed-not-throttled',
191
+ userMessage: `Could not extend: ${err.message}`,
192
+ };
193
+ }
194
+ return {
195
+ ok: true,
196
+ action: 'extend',
197
+ attributionKey: parsed.attributionKey,
198
+ reason: 'accepted',
199
+ userMessage: `Extended the throttle on ${parsed.attributionKey} for another hour.`,
200
+ };
201
+ }
202
+ case 'investigate': {
203
+ // No state change; this is purely for audit. The user is saying "I'm
204
+ // looking at this one". The agent records it but otherwise stands by.
205
+ return {
206
+ ok: true,
207
+ action: 'investigate',
208
+ attributionKey: parsed.attributionKey,
209
+ reason: 'accepted',
210
+ userMessage: `Logged. ${parsed.attributionKey} marked for follow-up.`,
211
+ };
212
+ }
213
+ }
214
+ }
215
+ /**
216
+ * Read-side: is this attribution key currently snoozed? Called by the
217
+ * runbook before installing a throttle. Auto-expires entries.
218
+ */
219
+ isSnoozed(attributionKey) {
220
+ const expires = this.snoozed.get(attributionKey);
221
+ if (!expires)
222
+ return false;
223
+ if (this.now() >= expires) {
224
+ this.snoozed.delete(attributionKey);
225
+ return false;
226
+ }
227
+ return true;
228
+ }
229
+ /** For tests + dashboard. */
230
+ listSnoozes() {
231
+ const out = [];
232
+ for (const [key, expires] of this.snoozed.entries()) {
233
+ if (this.now() < expires) {
234
+ out.push({ attributionKey: key, expiresAt: new Date(expires).toISOString() });
235
+ }
236
+ else {
237
+ this.snoozed.delete(key);
238
+ }
239
+ }
240
+ return out;
241
+ }
242
+ canonicalize(payload) {
243
+ return `${payload.action}|${payload.attributionKey}|${payload.signalId}`;
244
+ }
245
+ parseCallbackData(s) {
246
+ const parts = s.split('|');
247
+ if (parts.length !== 4)
248
+ return null;
249
+ const action = parts[0];
250
+ if (!['release', 'snooze-24h', 'extend', 'investigate'].includes(action))
251
+ return null;
252
+ if (!parts[1] || !parts[2] || !parts[3])
253
+ return null;
254
+ return {
255
+ action,
256
+ attributionKey: parts[1],
257
+ signalId: parts[2],
258
+ sig: parts[3],
259
+ };
260
+ }
261
+ /** GC consumed signal-id-action pairs older than 24h. */
262
+ gcConsumed() {
263
+ const cutoff = this.now() - 24 * 60 * 60 * 1000;
264
+ for (const [key, ts] of this.consumed.entries()) {
265
+ if (ts < cutoff)
266
+ this.consumed.delete(key);
267
+ }
268
+ }
269
+ }
270
+ //# sourceMappingURL=BurnAlertButtons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BurnAlertButtons.js","sourceRoot":"","sources":["../../src/monitoring/BurnAlertButtons.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * BurnDetectionSubscriber — wires the BurnThrottleRunbook into the
3
+ * DegradationReporter as a self-healer for feature='token-burn-detection'.
4
+ *
5
+ * Phase 5 of docs/specs/token-burn-detection-and-self-heal.md. The
6
+ * DegradationReporter already supports a `registerHealer(feature, healer)`
7
+ * surface (per Remediator V2 spec). The runbook's `handle()` is sync and
8
+ * returns RunbookOutcome; the healer signature is async + returns boolean.
9
+ * This module is the small adapter between them.
10
+ *
11
+ * Returns true to DegradationReporter when the runbook installed a
12
+ * throttle (the healer "succeeded" by acting on the signal); returns false
13
+ * when only an alert was sent (the runbook chose alert-only) so the
14
+ * reporter's own audit log records the partial-action correctly.
15
+ */
16
+ import type { DegradationEvent, DegradationReporter } from './DegradationReporter.js';
17
+ import type { BurnThrottleRunbook, RunbookOutcome } from './BurnThrottleRunbook.js';
18
+ export declare const BURN_DETECTION_FEATURE = "token-burn-detection";
19
+ export declare function registerBurnDetectionSubscriber(reporter: Pick<DegradationReporter, 'registerHealer'>, runbook: BurnThrottleRunbook,
20
+ /**
21
+ * Optional sink for outcomes — the verification step (Phase 6) will
22
+ * subscribe here to re-sample telemetry post-throttle and emit a follow-up.
23
+ */
24
+ onOutcome?: (outcome: RunbookOutcome, event: DegradationEvent) => void): void;
25
+ //# sourceMappingURL=BurnDetectionSubscriber.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BurnDetectionSubscriber.d.ts","sourceRoot":"","sources":["../../src/monitoring/BurnDetectionSubscriber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACtF,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAEpF,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,EACrD,OAAO,EAAE,mBAAmB;AAC5B;;;GAGG;AACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,gBAAgB,KAAK,IAAI,GACrE,IAAI,CAUN"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * BurnDetectionSubscriber — wires the BurnThrottleRunbook into the
3
+ * DegradationReporter as a self-healer for feature='token-burn-detection'.
4
+ *
5
+ * Phase 5 of docs/specs/token-burn-detection-and-self-heal.md. The
6
+ * DegradationReporter already supports a `registerHealer(feature, healer)`
7
+ * surface (per Remediator V2 spec). The runbook's `handle()` is sync and
8
+ * returns RunbookOutcome; the healer signature is async + returns boolean.
9
+ * This module is the small adapter between them.
10
+ *
11
+ * Returns true to DegradationReporter when the runbook installed a
12
+ * throttle (the healer "succeeded" by acting on the signal); returns false
13
+ * when only an alert was sent (the runbook chose alert-only) so the
14
+ * reporter's own audit log records the partial-action correctly.
15
+ */
16
+ export const BURN_DETECTION_FEATURE = 'token-burn-detection';
17
+ export function registerBurnDetectionSubscriber(reporter, runbook,
18
+ /**
19
+ * Optional sink for outcomes — the verification step (Phase 6) will
20
+ * subscribe here to re-sample telemetry post-throttle and emit a follow-up.
21
+ */
22
+ onOutcome) {
23
+ reporter.registerHealer(BURN_DETECTION_FEATURE, async (event) => {
24
+ const outcome = runbook.handle(event);
25
+ try {
26
+ onOutcome?.(outcome, event);
27
+ }
28
+ catch (err) {
29
+ console.warn(`[burn-detection-subscriber] onOutcome threw: ${err.message}`);
30
+ }
31
+ return outcome.kind === 'throttle-installed';
32
+ });
33
+ }
34
+ //# sourceMappingURL=BurnDetectionSubscriber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BurnDetectionSubscriber.js","sourceRoot":"","sources":["../../src/monitoring/BurnDetectionSubscriber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,MAAM,CAAC,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,UAAU,+BAA+B,CAC7C,QAAqD,EACrD,OAA4B;AAC5B;;;GAGG;AACH,SAAsE;IAEtE,QAAQ,CAAC,cAAc,CAAC,sBAAsB,EAAE,KAAK,EAAE,KAAuB,EAAE,EAAE;QAChF,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,CAAC;YACH,SAAS,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,gDAAiD,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACzF,CAAC;QACD,OAAO,OAAO,CAAC,IAAI,KAAK,oBAAoB,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * BurnDetector — emits a structured signal when a single attribution_key
3
+ * crosses configured spend thresholds.
4
+ *
5
+ * Phase 3 of docs/specs/token-burn-detection-and-self-heal.md.
6
+ *
7
+ * Signal-only (per umbrella spec §"Signal-vs-Authority Decomposition"): the
8
+ * detector emits to the existing DegradationReporter. The Phase 4 runbook
9
+ * (Tier-2 Remediator surface) is the only authority that decides whether
10
+ * to alert / throttle / both. The BurnDetector cannot decide anything.
11
+ *
12
+ * Triggers (OR'd):
13
+ * 1. Absolute share — a single attribution_key consumed > absoluteShareThreshold
14
+ * of total 24h spend.
15
+ * 2. Baseline divergence — the key's last-1h rate is > rollingBaselineMultiplier ×
16
+ * its trailing-7-day median rate, AND the 1h rate exceeds rollingBaselineFloor
17
+ * tokens/h.
18
+ *
19
+ * Cold-start (per umbrella spec §"Threshold logic"): the baseline-divergence
20
+ * trigger only fires after a key has been observed for 7 days. Until then,
21
+ * absolute-share is the only signal — that's the trigger that would have
22
+ * caught the 2026-05-15 incident.
23
+ *
24
+ * Polling cadence: 60s, decoupled from the ledger writer. The detector does
25
+ * its own clock (injected `now`) so tests don't need to fake timers globally.
26
+ */
27
+ import type { TokenLedger } from './TokenLedger.js';
28
+ import type { DegradationReporter } from './DegradationReporter.js';
29
+ export interface BurnDetectionConfig {
30
+ enabled: boolean;
31
+ /** Default 0.25 (25%). */
32
+ absoluteShareThreshold: number;
33
+ /** Default 2 (2x). */
34
+ rollingBaselineMultiplier: number;
35
+ /** Default 10_000_000 tokens/hour. */
36
+ rollingBaselineFloor: number;
37
+ /** Per-key alert cooldown (default 3_600_000ms = 1h). */
38
+ perKeyAlertCooldownMs: number;
39
+ /** Poll interval (default 60_000ms = 60s). */
40
+ pollIntervalMs: number;
41
+ /** Cold-start duration (default 7d). Baseline-divergence trigger disabled until elapsed. */
42
+ coldStartMs: number;
43
+ }
44
+ export declare const DEFAULT_BURN_DETECTION_CONFIG: BurnDetectionConfig;
45
+ export interface BurnSignal {
46
+ attributionKey: string;
47
+ trigger: 'absolute-share' | 'baseline-divergence';
48
+ emittedAt: string;
49
+ observed: {
50
+ tokens24h: number;
51
+ share24h: number;
52
+ tokensLast1h: number;
53
+ projectedDaily: number;
54
+ };
55
+ /** Trailing-7d median tokens/h. Only populated for `baseline-divergence`. */
56
+ baselineMedian7d?: number;
57
+ }
58
+ export interface BurnDetectorDeps {
59
+ ledger: Pick<TokenLedger, 'byAttributionKey' | 'summary'>;
60
+ reporter: Pick<DegradationReporter, 'report'>;
61
+ config?: Partial<BurnDetectionConfig>;
62
+ /** Injectable clock for tests. Defaults to `Date.now`. */
63
+ now?: () => number;
64
+ }
65
+ export declare class BurnDetector {
66
+ private readonly ledger;
67
+ private readonly reporter;
68
+ private readonly config;
69
+ private readonly now;
70
+ /** First time a given attribution_key was seen. Used for cold-start cutoff. */
71
+ private readonly firstSeen;
72
+ /** Last alert emit time per key — gates per-key alert cooldown. */
73
+ private readonly lastAlertAt;
74
+ private timer;
75
+ constructor(deps: BurnDetectorDeps);
76
+ start(): void;
77
+ stop(): void;
78
+ /**
79
+ * One detector cycle. Returns the signals emitted on this tick — useful
80
+ * for tests, and the production loop ignores the return.
81
+ */
82
+ tick(): BurnSignal[];
83
+ }
84
+ //# sourceMappingURL=BurnDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BurnDetector.d.ts","sourceRoot":"","sources":["../../src/monitoring/BurnDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,0BAA0B;IAC1B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,sBAAsB;IACtB,yBAAyB,EAAE,MAAM,CAAC;IAClC,sCAAsC;IACtC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,yDAAyD;IACzD,qBAAqB,EAAE,MAAM,CAAC;IAC9B,8CAA8C;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB,4FAA4F;IAC5F,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,6BAA6B,EAAE,mBAQ3C,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,gBAAgB,GAAG,qBAAqB,CAAC;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAC1D,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACtC,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+B;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,KAAK,CAA+C;gBAEhD,IAAI,EAAE,gBAAgB;IAOlC,KAAK,IAAI,IAAI;IAgBb,IAAI,IAAI,IAAI;IAOZ;;;OAGG;IACH,IAAI,IAAI,UAAU,EAAE;CAkGrB"}