instar 0.28.108 → 0.28.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/monitoring/BurnAlertButtons.d.ts +107 -0
- package/dist/monitoring/BurnAlertButtons.d.ts.map +1 -0
- package/dist/monitoring/BurnAlertButtons.js +270 -0
- package/dist/monitoring/BurnAlertButtons.js.map +1 -0
- package/dist/monitoring/BurnDetectionSubscriber.d.ts +25 -0
- package/dist/monitoring/BurnDetectionSubscriber.d.ts.map +1 -0
- package/dist/monitoring/BurnDetectionSubscriber.js +34 -0
- package/dist/monitoring/BurnDetectionSubscriber.js.map +1 -0
- package/dist/monitoring/BurnThrottleRunbook.d.ts +9 -1
- package/dist/monitoring/BurnThrottleRunbook.d.ts.map +1 -1
- package/dist/monitoring/BurnThrottleRunbook.js +17 -2
- package/dist/monitoring/BurnThrottleRunbook.js.map +1 -1
- package/dist/monitoring/BurnVerifier.d.ts +68 -0
- package/dist/monitoring/BurnVerifier.d.ts.map +1 -0
- package/dist/monitoring/BurnVerifier.js +151 -0
- package/dist/monitoring/BurnVerifier.js.map +1 -0
- package/dist/server/AgentServer.d.ts +4 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +66 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +3 -3
- package/upgrades/0.28.109.md +32 -0
- package/upgrades/0.28.110.md +41 -0
- package/upgrades/side-effects/token-burn-detection-phase-5.md +63 -0
- package/upgrades/side-effects/token-burn-detection-phase-6.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"}
|
|
@@ -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;
|
|
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.
|
|
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
|
-
//
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoBH,MAAM,CAAC,MAAM,4BAA4B,GAAuB;IAC9D,YAAY,EAAE,IAAI;IAClB,qBAAqB,EAAE,KAAK;IAC5B,kBAAkB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;CACnC,CAAC;
|
|
1
|
+
{"version":3,"file":"BurnThrottleRunbook.js","sourceRoot":"","sources":["../../src/monitoring/BurnThrottleRunbook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BurnVerifier — post-throttle verification + follow-up alerts.
|
|
3
|
+
*
|
|
4
|
+
* Phase 6 (final phase) of docs/specs/token-burn-detection-and-self-heal.md.
|
|
5
|
+
* Five minutes after the runbook installs a throttle, this module re-samples
|
|
6
|
+
* the token-ledger telemetry for the affected attribution_key. If the
|
|
7
|
+
* post-throttle rate dropped materially, a structured "I caught it, I
|
|
8
|
+
* slowed it down, here's the before-and-after" Telegram message goes out
|
|
9
|
+
* (the umbrella spec's "fixed, here's the before/after" follow-up shape).
|
|
10
|
+
*
|
|
11
|
+
* If the post-throttle rate did NOT drop, the verifier escalates: the
|
|
12
|
+
* throttle was pointed at the wrong key, or the path doesn't honour the
|
|
13
|
+
* gate. Operator gets a "I tried but it did not work" message with the
|
|
14
|
+
* specific shape that distinguishes from "I caught it" so they know to
|
|
15
|
+
* intervene manually.
|
|
16
|
+
*
|
|
17
|
+
* Pure timer + telemetry-read; no LLM calls (the umbrella spec's
|
|
18
|
+
* §"Why it's safe — What if the watcher itself burns tokens?" guarantee).
|
|
19
|
+
*/
|
|
20
|
+
import type { TokenLedger } from './TokenLedger.js';
|
|
21
|
+
import type { RunbookOutcome } from './BurnThrottleRunbook.js';
|
|
22
|
+
import type { DegradationEvent } from './DegradationReporter.js';
|
|
23
|
+
export interface VerificationResult {
|
|
24
|
+
attributionKey: string;
|
|
25
|
+
preThrottleRate: number;
|
|
26
|
+
postThrottleRate: number;
|
|
27
|
+
ratio: number;
|
|
28
|
+
successfullyThrottled: boolean;
|
|
29
|
+
verifiedAt: string;
|
|
30
|
+
}
|
|
31
|
+
export interface BurnVerifierDeps {
|
|
32
|
+
ledger: Pick<TokenLedger, 'byAttributionKey'>;
|
|
33
|
+
sendTelegram?: (topicId: number, text: string) => void | Promise<void>;
|
|
34
|
+
alertTopicId?: number;
|
|
35
|
+
now?: () => number;
|
|
36
|
+
config?: {
|
|
37
|
+
verifyDelayMs?: number;
|
|
38
|
+
sampleWindowMs?: number;
|
|
39
|
+
successRatio?: number;
|
|
40
|
+
};
|
|
41
|
+
schedule?: (cb: () => void, delayMs: number) => void;
|
|
42
|
+
}
|
|
43
|
+
export declare class BurnVerifier {
|
|
44
|
+
private readonly ledger;
|
|
45
|
+
private readonly sendTelegram;
|
|
46
|
+
private readonly alertTopicId;
|
|
47
|
+
private readonly now;
|
|
48
|
+
private readonly verifyDelayMs;
|
|
49
|
+
private readonly sampleWindowMs;
|
|
50
|
+
private readonly successRatio;
|
|
51
|
+
private readonly schedule;
|
|
52
|
+
constructor(deps: BurnVerifierDeps);
|
|
53
|
+
/**
|
|
54
|
+
* Called by the subscriber when the runbook installs a throttle. Pulls
|
|
55
|
+
* the pre-throttle rate from the originating event, schedules a re-sample
|
|
56
|
+
* after `verifyDelayMs`, and on fire sends the structured follow-up.
|
|
57
|
+
*/
|
|
58
|
+
scheduleVerification(outcome: RunbookOutcome, event: DegradationEvent): void;
|
|
59
|
+
/**
|
|
60
|
+
* Run the verification re-sample. Exposed for tests + the manual-rerun
|
|
61
|
+
* surface (Phase 6+ dashboard control).
|
|
62
|
+
*/
|
|
63
|
+
runVerification(attributionKey: string, preThrottleRate: number): VerificationResult;
|
|
64
|
+
private fireFollowUp;
|
|
65
|
+
private fireTelegram;
|
|
66
|
+
}
|
|
67
|
+
export declare function extractTokensLast1h(event: DegradationEvent): number;
|
|
68
|
+
//# sourceMappingURL=BurnVerifier.d.ts.map
|