instar 0.28.108 → 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.
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AAgDjC,MAAM,iBAAiB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9C,MAAM,OAAO,gBAAgB;IACV,aAAa,CAAS;IACtB,iBAAiB,CAAc;IAC/B,IAAI,CAAc;IAClB,GAAG,CAAe;IAClB,gBAAgB,CAAS;IAC1C,8DAA8D;IAC7C,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtD,qGAAqG;IACpF,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAErD,YAAY,IAA0B;QACpC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC;QACxC,IAAI,CAAC,iBAAiB,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,iBAAiB,CAAC;IACrE,CAAC;IAED;;;;;OAKG;IACH,kBAAkB,CAAC,OAA0B;QAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,MAAM;aACf,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,CAAC;aACxC,MAAM,CAAC,SAAS,CAAC;aACjB,MAAM,CAAC,KAAK,CAAC;aACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChB,uEAAuE;QACvE,6CAA6C;QAC7C,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,QAAQ,IAAI,GAAG,EAAE,CAAC;IAClF,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,cAAsB,EAAE,QAAgB;QACpD,OAAO;YACL,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,EAAE;YAC5G,EAAE,IAAI,EAAE,YAAY,EAAE,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,EAAE;YAClH,EAAE,IAAI,EAAE,YAAY,EAAE,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,EAAE;YAC9G,EAAE,IAAI,EAAE,aAAa,EAAE,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,EAAE;SACrH,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAmD;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC1D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,aAAa;gBACrB,cAAc,EAAE,EAAE;gBAClB,MAAM,EAAE,mBAAmB;gBAC3B,WAAW,EAAE,gCAAgC;aAC9C,CAAC;QACJ,CAAC;QAED,sBAAsB;QACtB,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;YAClD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,cAAc,EAAE,MAAM,CAAC,cAAc;gBACrC,MAAM,EAAE,0BAA0B;gBAClC,WAAW,EAAE,8CAA8C;aAC5D,CAAC;QACJ,CAAC;QAED,sBAAsB;QACtB,MAAM,WAAW,GAAG,MAAM;aACvB,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,CAAC;aACxC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;aACjC,MAAM,CAAC,KAAK,CAAC;aACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChB,IAAI,MAAM,CAAC,GAAG,KAAK,WAAW,EAAE,CAAC;YAC/B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,cAAc,EAAE,MAAM,CAAC,cAAc;gBACrC,MAAM,EAAE,mBAAmB;gBAC3B,WAAW,EAAE,gDAAgD;aAC9D,CAAC;QACJ,CAAC;QAED,4EAA4E;QAC5E,MAAM,cAAc,GAAG,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC7D,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;YACtC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,cAAc,EAAE,MAAM,CAAC,cAAc;gBACrC,MAAM,EAAE,oBAAoB;gBAC5B,WAAW,EAAE,oCAAoC;aAClD,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,EAAE,CAAC;QAElB,uBAAuB;QACvB,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;YACtB,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;gBACjE,OAAO;oBACL,EAAE,EAAE,IAAI;oBACR,MAAM,EAAE,SAAS;oBACjB,cAAc,EAAE,MAAM,CAAC,cAAc;oBACrC,MAAM,EAAE,UAAU;oBAClB,WAAW,EAAE,QAAQ;wBACnB,CAAC,CAAC,4BAA4B,MAAM,CAAC,cAAc,GAAG;wBACtD,CAAC,CAAC,8BAA8B,MAAM,CAAC,cAAc,wBAAwB;iBAChF,CAAC;YACJ,CAAC;YACD,KAAK,YAAY,CAAC,CAAC,CAAC;gBAClB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC;gBAC5E,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;gBAChD,OAAO;oBACL,EAAE,EAAE,IAAI;oBACR,MAAM,EAAE,YAAY;oBACpB,cAAc,EAAE,MAAM,CAAC,cAAc;oBACrC,MAAM,EAAE,iBAAiB;oBACzB,WAAW,EAAE,WAAW,MAAM,CAAC,cAAc,2EAA2E;iBACzH,CAAC;YACJ,CAAC;YACD,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,sEAAsE;gBACtE,sEAAsE;gBACtE,gCAAgC;gBAChC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,MAAM,CAAC,cAAc,CAAC,CAAC;gBACvG,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,OAAO;wBACL,EAAE,EAAE,KAAK;wBACT,MAAM,EAAE,QAAQ;wBAChB,cAAc,EAAE,MAAM,CAAC,cAAc;wBACrC,MAAM,EAAE,6BAA6B;wBACrC,WAAW,EAAE,yCAAyC,MAAM,CAAC,cAAc,GAAG;qBAC/E,CAAC;gBACJ,CAAC;gBACD,MAAM,cAAc,GAAG,UAAU,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBACjE,MAAM,gBAAgB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;gBACxC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC;oBAC3C,cAAc,EAAE,MAAM,CAAC,cAAc;oBACrC,UAAU,EAAE,gBAAgB;oBAC5B,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,QAAQ,EAAE,cAAc;iBACzB,CAAC,IAAI,SAAS,CAAC;gBAChB,IAAI,CAAC;oBACH,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC;wBACxB,cAAc,EAAE,MAAM,CAAC,cAAc;wBACrC,UAAU,EAAE,gBAAgB;wBAC5B,MAAM,EAAE,+CAA+C;wBACvD,MAAM,EAAE,MAAM,CAAC,MAAM;wBACrB,QAAQ,EAAE,cAAc;wBACxB,eAAe,EAAE,GAAG;qBACrB,CAAC,CAAC;gBACL,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO;wBACL,EAAE,EAAE,KAAK;wBACT,MAAM,EAAE,QAAQ;wBAChB,cAAc,EAAE,MAAM,CAAC,cAAc;wBACrC,MAAM,EAAE,6BAA6B;wBACrC,WAAW,EAAE,qBAAsB,GAAa,CAAC,OAAO,EAAE;qBAC3D,CAAC;gBACJ,CAAC;gBACD,OAAO;oBACL,EAAE,EAAE,IAAI;oBACR,MAAM,EAAE,QAAQ;oBAChB,cAAc,EAAE,MAAM,CAAC,cAAc;oBACrC,MAAM,EAAE,UAAU;oBAClB,WAAW,EAAE,4BAA4B,MAAM,CAAC,cAAc,oBAAoB;iBACnF,CAAC;YACJ,CAAC;YACD,KAAK,aAAa,CAAC,CAAC,CAAC;gBACnB,qEAAqE;gBACrE,sEAAsE;gBACtE,OAAO;oBACL,EAAE,EAAE,IAAI;oBACR,MAAM,EAAE,aAAa;oBACrB,cAAc,EAAE,MAAM,CAAC,cAAc;oBACrC,MAAM,EAAE,UAAU;oBAClB,WAAW,EAAE,WAAW,MAAM,CAAC,cAAc,wBAAwB;iBACtE,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,cAAsB;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACjD,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;YACpC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6BAA6B;IAC7B,WAAW;QACT,MAAM,GAAG,GAAyD,EAAE,CAAC;QACrE,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YACpD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;gBACzB,GAAG,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAChF,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,YAAY,CAAC,OAA0B;QAC7C,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC3E,CAAC;IAEO,iBAAiB,CAAC,CAAS;QACjC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACpC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAoB,CAAC;QAC3C,IAAI,CAAC,CAAC,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QACtF,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACrD,OAAO;YACL,MAAM;YACN,cAAc,EAAE,KAAK,CAAC,CAAC,CAAC;YACxB,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;YAClB,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;SACd,CAAC;IACJ,CAAC;IAED,yDAAyD;IACjD,UAAU;QAChB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAChD,KAAK,MAAM,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;YAChD,IAAI,EAAE,GAAG,MAAM;gBAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;CACF"}
@@ -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"}
@@ -35,7 +35,7 @@ export interface BurnThrottleConfig {
35
35
  throttleDurationMs: number;
36
36
  }
37
37
  export declare const DEFAULT_BURN_THROTTLE_CONFIG: BurnThrottleConfig;
38
- export type RunbookOutcomeKind = 'alert-only-unknown' | 'alert-only-config-disabled' | 'alert-only-self-attribution' | 'throttle-installed' | 'throttle-failed';
38
+ export type RunbookOutcomeKind = 'alert-only-unknown' | 'alert-only-config-disabled' | 'alert-only-self-attribution' | 'alert-only-snoozed' | 'throttle-installed' | 'throttle-failed';
39
39
  export interface RunbookOutcome {
40
40
  kind: RunbookOutcomeKind;
41
41
  attributionKey: string;
@@ -57,6 +57,13 @@ export interface BurnThrottleRunbookDeps {
57
57
  alertTopicId?: number;
58
58
  /** Injectable clock for tests. */
59
59
  now?: () => number;
60
+ /**
61
+ * Phase 5 — snooze check. If provided, the runbook calls isSnoozed(key)
62
+ * before throttling. A snoozed key skips auto-throttle (the user tapped
63
+ * "Snooze 24h" on a previous alert). The alert still fires so the user
64
+ * knows the burn is recurring while snoozed.
65
+ */
66
+ isSnoozed?: (attributionKey: string) => boolean;
60
67
  }
61
68
  /** Identity tag the runbook uses when installing throttles. */
62
69
  export declare const RUNBOOK_ISSUER = "burn-throttle-runbook";
@@ -66,6 +73,7 @@ export declare class BurnThrottleRunbook {
66
73
  private readonly sendTelegram;
67
74
  private readonly alertTopicId;
68
75
  private readonly now;
76
+ private readonly isSnoozed;
69
77
  constructor(deps: BurnThrottleRunbookDeps);
70
78
  /**
71
79
  * Handle a burn-detection signal. Called from a subscriber hook on
@@ -1 +1 @@
1
- {"version":3,"file":"BurnThrottleRunbook.d.ts","sourceRoot":"","sources":["../../src/monitoring/BurnThrottleRunbook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,YAAY,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,qBAAqB,EAAE,OAAO,CAAC;IAC/B,yCAAyC;IACzC,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,4BAA4B,EAAE,kBAI1C,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,oBAAoB,GACpB,4BAA4B,GAC5B,6BAA6B,GAC7B,oBAAoB,GACpB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,2EAA2E;AAC3E,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1F,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrC,qFAAqF;IACrF,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,+DAA+D;AAC/D,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAEtD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,EAAE,uBAAuB;IAQzC;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,cAAc;IA8F/C,OAAO,CAAC,YAAY;CAYrB;AAID;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAGrE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAI9D;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,yGAAyG;AACzG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAazG"}
1
+ {"version":3,"file":"BurnThrottleRunbook.d.ts","sourceRoot":"","sources":["../../src/monitoring/BurnThrottleRunbook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,YAAY,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,qBAAqB,EAAE,OAAO,CAAC;IAC/B,yCAAyC;IACzC,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,4BAA4B,EAAE,kBAI1C,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,oBAAoB,GACpB,4BAA4B,GAC5B,6BAA6B,GAC7B,oBAAoB,GACpB,oBAAoB,GACpB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,2EAA2E;AAC3E,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1F,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrC,qFAAqF;IACrF,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC;CACjD;AAED,+DAA+D;AAC/D,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAEtD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2B;gBAEzC,IAAI,EAAE,uBAAuB;IASzC;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,cAAc;IA4G/C,OAAO,CAAC,YAAY;CAYrB;AAID;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAGrE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAI9D;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,yGAAyG;AACzG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAazG"}
@@ -31,12 +31,14 @@ export class BurnThrottleRunbook {
31
31
  sendTelegram;
32
32
  alertTopicId;
33
33
  now;
34
+ isSnoozed;
34
35
  constructor(deps) {
35
36
  this.gate = deps.gate;
36
37
  this.config = { ...DEFAULT_BURN_THROTTLE_CONFIG, ...(deps.config ?? {}) };
37
38
  this.sendTelegram = deps.sendTelegram;
38
39
  this.alertTopicId = deps.alertTopicId ?? 8615;
39
40
  this.now = deps.now ?? (() => Date.now());
41
+ this.isSnoozed = deps.isSnoozed ?? (() => false);
40
42
  }
41
43
  /**
42
44
  * Handle a burn-detection signal. Called from a subscriber hook on
@@ -83,7 +85,20 @@ export class BurnThrottleRunbook {
83
85
  reason: 'autoThrottle is disabled in config; alert sent but no throttle installed.',
84
86
  };
85
87
  }
86
- // 3. Unknown attribution + caller has not opted in to auto-throttle on unknown.
88
+ // 3. Snoozed key (Phase 5): user already said "this is fine, leave it
89
+ // alone for 24h." We still alert because the burn is recurring — the
90
+ // user should know — but we don't throttle.
91
+ if (this.isSnoozed(attributionKey)) {
92
+ this.fireTelegram(`${alertText}\n\nThis key is currently snoozed; I will not slow it down. The snooze auto-expires at the configured time.`);
93
+ return {
94
+ kind: 'alert-only-snoozed',
95
+ attributionKey,
96
+ decidedAt,
97
+ trigger,
98
+ reason: 'Attribution key is currently snoozed; alert sent but no throttle installed.',
99
+ };
100
+ }
101
+ // 4. Unknown attribution + caller has not opted in to auto-throttle on unknown.
87
102
  if (isUnknownKey(attributionKey) && !this.config.autoThrottleOnUnknown) {
88
103
  this.fireTelegram(alertText);
89
104
  return {
@@ -94,7 +109,7 @@ export class BurnThrottleRunbook {
94
109
  reason: 'Attribution key has no known component; operator must opt in to auto-throttle on unknown.',
95
110
  };
96
111
  }
97
- // 4. Install throttle, send alert.
112
+ // 5. Install throttle, send alert.
98
113
  const token = this.gate.computeCapabilityToken({
99
114
  attributionKey,
100
115
  durationMs: this.config.throttleDurationMs,
@@ -1 +1 @@
1
- {"version":3,"file":"BurnThrottleRunbook.js","sourceRoot":"","sources":["../../src/monitoring/BurnThrottleRunbook.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BurnThrottleRunbook.js","sourceRoot":"","sources":["../../src/monitoring/BurnThrottleRunbook.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.28.108",
3
+ "version": "0.28.109",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-05-15T22:39:15.698Z",
5
- "instarVersion": "0.28.108",
4
+ "generatedAt": "2026-05-15T22:54:11.728Z",
5
+ "instarVersion": "0.28.109",
6
6
  "entryCount": 188,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,32 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: minor -->
4
+
5
+ ## What Changed
6
+
7
+ Phase five of the token-burn-detection-and-self-heal system. The two pieces that land here:
8
+
9
+ - Four buttons that go on every burn alert: Release, Snooze 24h, Extend 1h, Investigate. They are cryptographically signed and bound to your Telegram user ID, so only you can tap them and only on the original alert.
10
+ - A small adapter that connects the phase three detector and the phase four runbook into the live degradation chain.
11
+
12
+ The actual emit of the four-button inline keyboard on a real Telegram message, and the receipt of the tap from the Telegram webhook, is a small follow-up change to the Telegram adapter that lands in phase six. This release ships the pure pieces fully tested, so phase six can wire them with confidence.
13
+
14
+ ## What to Tell Your User
15
+
16
+ The fifth of six pieces. From this release on, the system catches burns AND has the user-facing controls ready to land — the four buttons that will appear under each burn alert once phase six wires them.
17
+
18
+ ## Summary of New Capabilities
19
+
20
+ | Capability | How to Use |
21
+ |-----------|-----------|
22
+ | Principal-bound burn alert buttons (pure module) | Automatic once phase six wires it into the Telegram adapter. |
23
+ | Snooze 24h state honored by the runbook | Automatic after wiring. |
24
+ | Subscriber wiring (runbook as degradation healer) | Automatic once phase six adds the startup hook. |
25
+
26
+ ## Evidence
27
+
28
+ Fifteen new tests in `tests/unit/burn-detection-phase-5.test.ts` all pass. Tests cover: each of the four buttons doing what it says, the principal-bind accepting the authorized user and rejecting strangers, the signature check rejecting tampered callback data, the freshness check rejecting replay, the snooze TTL auto-expiring, the runbook honoring a snoozed key (alert-only-snoozed outcome), and the subscriber adapter registering correctly.
29
+
30
+ The phase four test suite (twenty-two tests) is untouched and passes — no regression.
31
+
32
+ Side-effects review for this phase is in `upgrades/side-effects/token-burn-detection-phase-5.md`. The reviewer concurred with no new concerns above the documented out-of-scope items.
@@ -0,0 +1,63 @@
1
+ # Side-Effects Review — Token-Burn Detection Phase 5
2
+
3
+ **Spec**: `docs/specs/token-burn-detection-phase-5.md` (parent: `docs/specs/token-burn-detection-and-self-heal.md`, approved by Justin 2026-05-15).
4
+
5
+ ## 1. Over-block
6
+
7
+ The Phase 5 callback handler can reject legitimate user actions in two cases:
8
+ - **Misconfigured authorizedUserIds**: if the operator's Telegram user_id is missing from the list, every tap is rejected as principal-not-authorized. Mitigation: the same misconfig already breaks `MessagingToneGate` and inbound-message handling, so the operator would notice quickly via existing flows.
9
+ - **Signature drift across deploy**: if the agent's capability key rotates between when a button was sent and when it's tapped, all callback signatures will fail. Mitigation: buttons are short-lived (the operator typically taps within minutes), and a failed signature surfaces an explicit error rather than a silent ignore.
10
+
11
+ ## 2. Under-block
12
+
13
+ - **A legitimate Telegram callback whose payload is missing one of four pipe-delimited fields** falls through to `malformed-payload` and is rejected. Mitigation: the only producer of these payloads is `BurnAlertButtons.encodeCallbackData` itself, so a malformed payload in production strongly indicates corruption or tampering — both cases the rejection correctly handles.
14
+ - **The runbook's `isSnoozed` is a process-local in-memory map.** A restart drops snoozes. Mitigation: the runbook still alerts on next burn (alert-only-snoozed) — the user can re-snooze if needed. Persistent snooze state is out of scope for this phase (would land alongside the persistent throttle-overrides file in a separate PR).
15
+
16
+ ## 3. Level-of-abstraction fit
17
+
18
+ - `BurnAlertButtons` is in `src/monitoring/`. Correct layer — operates over an in-memory state that mirrors `LlmRateGate` state.
19
+ - `BurnDetectionSubscriber` is the thin wiring adapter, also in `src/monitoring/`. Correct layer.
20
+ - The pure module / wire boundary is sharp: `BurnAlertButtons` knows nothing about Telegram protocol details; `TelegramAdapter` integration is a future small change that imports `BurnAlertButtons.buildKeyboard` + `BurnAlertButtons.handle`.
21
+
22
+ ## 4. Signal-vs-authority compliance
23
+
24
+ The buttons are authority surfaces — tapping Release or Extend mutates the gate's throttle state, and tapping Snooze 24h mutates the runbook's snooze state. The authority is:
25
+ - Held by the agent's authorized principal(s) — `authorizedUserIds`, the same trust surface that admits inbound messages.
26
+ - Bound to that principal via HMAC over the canonical action.
27
+ - Subject to freshness check so a stolen authority token cannot replay.
28
+
29
+ The Phase 4 second-pass review § 1 (replay prevention) is fully realised in this phase's button surface — every signalId-action pair is single-use.
30
+
31
+ **Compliant.**
32
+
33
+ ## 5. Interactions
34
+
35
+ - **`MessagingToneGate`**: the buttons share the same `authorizedUserIds` list. No new authorized-user list is introduced; we reuse the existing one.
36
+ - **`LlmRateGate`**: BurnAlertButtons mutates it via the existing `installThrottle` + `revokeThrottle` API; no new surface on the gate.
37
+ - **`BurnThrottleRunbook`**: gained one optional dep (`isSnoozed`). Existing callers (Phase 4 tests) keep working — `isSnoozed` defaults to "always false."
38
+ - **`DegradationReporter`**: the subscriber registers via the existing `registerHealer(feature, healer)` API (Remediator V2 wire). No new surface on the reporter.
39
+ - **`TelegramAdapter`**: not touched in this phase; pure-module boundary preserved.
40
+
41
+ No double-fire, no race.
42
+
43
+ ## 6. External surfaces
44
+
45
+ - **No new endpoints, no new CLI.**
46
+ - **Future Telegram-wire integration** will add `inline_keyboard` to outgoing burn-alert messages and an inbound callback handler. That's a documented Phase 6 / follow-up change; this PR ships the pure pieces.
47
+
48
+ ## 7. Rollback cost
49
+
50
+ Three new modules and one runbook constructor field addition. Backout = delete the three new files + revert the constructor + outcome enum changes. No persistent state, no schema migration.
51
+
52
+ ## Second-pass review
53
+
54
+ **Conducted** (required for this phase per `/instar-dev` Phase 5 criteria — touches inbound-messaging authority).
55
+
56
+ The reviewer was given the umbrella spec, the convergence report's principal-binding finding, and the three new files. Review focused on:
57
+
58
+ - Principal verification correctness: `authorizedUserIds.has(input.fromUserId)` — the same shape `MessagingToneGate` uses, no novel authorization surface.
59
+ - HMAC scope: signs `action | attributionKey | signalId`. Excludes timing fields so a button's lifetime is bounded by the freshness map's GC window (24h) rather than the HMAC.
60
+ - Replay map size: GC'd at 24h cutoff in `handle()`. Bounded by burst rate × 24h.
61
+ - Snooze map persistence: documented as in-memory; restart drops snoozes; alert-only-snoozed semantics keep the user informed even if the state is lost.
62
+
63
+ **Concur.** No new concerns above the documented out-of-scope items (persistent snooze state, actual TelegramAdapter wire-up).