instar 0.28.107 → 0.28.108
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/BurnDetector.d.ts +84 -0
- package/dist/monitoring/BurnDetector.d.ts.map +1 -0
- package/dist/monitoring/BurnDetector.js +169 -0
- package/dist/monitoring/BurnDetector.js.map +1 -0
- package/dist/monitoring/BurnThrottleRunbook.d.ts +97 -0
- package/dist/monitoring/BurnThrottleRunbook.d.ts.map +1 -0
- package/dist/monitoring/BurnThrottleRunbook.js +202 -0
- package/dist/monitoring/BurnThrottleRunbook.js.map +1 -0
- package/dist/monitoring/LlmRateGate.d.ts +133 -33
- package/dist/monitoring/LlmRateGate.d.ts.map +1 -1
- package/dist/monitoring/LlmRateGate.js +179 -46
- package/dist/monitoring/LlmRateGate.js.map +1 -1
- package/dist/monitoring/TokenLedger.d.ts +16 -0
- package/dist/monitoring/TokenLedger.d.ts.map +1 -1
- package/dist/monitoring/TokenLedger.js +28 -0
- package/dist/monitoring/TokenLedger.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/0.28.107.md +17 -12
- package/upgrades/0.28.108.md +50 -0
- package/upgrades/side-effects/token-burn-detection-phase-3.md +59 -0
- package/upgrades/side-effects/token-burn-detection-phase-4.md +78 -0
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LlmRateGate — actuator primitive for the burn-detection auto-heal system.
|
|
3
3
|
*
|
|
4
|
-
* Phase 1
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
4
|
+
* Phase 1 shipped the no-op shape. Phase 4 upgrades the gate into a stateful
|
|
5
|
+
* actuator: the burn-throttle runbook installs throttles via `installThrottle`,
|
|
6
|
+
* the LLM callers consult `shouldFire` / `decide`, and a throttle auto-expires
|
|
7
|
+
* after its TTL elapses. Per the umbrella spec §"Signal-vs-Authority
|
|
8
|
+
* Decomposition", the gate enforces decisions made elsewhere — it never
|
|
9
|
+
* decides anything on its own.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* State: in-memory only. The throttle store is process-local. If the agent
|
|
12
|
+
* restarts, any in-memory throttle is dropped — which is the right default
|
|
13
|
+
* (a restart is itself a reset, and the burn-detector will re-emit any
|
|
14
|
+
* surviving signal within 60s). Persistent throttles for scheduled-job cron
|
|
15
|
+
* entries live in a separate file (`.instar/jobs.json.throttle-overrides`,
|
|
16
|
+
* HMAC-signed) handled by the scheduler — out of scope for this primitive.
|
|
15
17
|
*
|
|
16
|
-
* Authority
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* attribution key. The class exposes a static `instance()` accessor; tests
|
|
25
|
-
* use the public constructor + `reset()` to isolate from the singleton.
|
|
18
|
+
* Authority: the gate accepts throttles only via `installThrottle`, which the
|
|
19
|
+
* runbook calls with a capability token. The token is verified by the gate
|
|
20
|
+
* against the agent's HMAC key (passed at constructor time when the gate
|
|
21
|
+
* needs to enforce, or skipped for tests). When no key is configured, the
|
|
22
|
+
* gate accepts throttles without verification — that's the "trusted local
|
|
23
|
+
* caller" mode used by the runbook before the F-8 capability surface fully
|
|
24
|
+
* lands. The verification path is in place so the runbook can switch to
|
|
25
|
+
* signed tokens without API churn.
|
|
26
26
|
*/
|
|
27
27
|
export interface LlmRateGateDecision {
|
|
28
28
|
/** Whether the gate currently allows the next LLM call for this key. */
|
|
@@ -30,34 +30,134 @@ export interface LlmRateGateDecision {
|
|
|
30
30
|
/** ISO timestamp the decision was made (debug / log). */
|
|
31
31
|
decidedAt: string;
|
|
32
32
|
/** Why — for log + verification trace. */
|
|
33
|
-
reason: '
|
|
33
|
+
reason: 'no-throttle-installed' | 'throttle-active' | 'throttle-expired' | 'runbook-self-exempt';
|
|
34
|
+
/** When the active throttle (if any) expires. Phase 4: ISO timestamp. */
|
|
35
|
+
throttleExpiresAt?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface InstalledThrottle {
|
|
38
|
+
attributionKey: string;
|
|
39
|
+
/** ISO timestamp when the throttle was installed. */
|
|
40
|
+
installedAt: string;
|
|
41
|
+
/** ISO timestamp when the throttle auto-reverts. */
|
|
42
|
+
expiresAt: string;
|
|
43
|
+
/** Free-text reason the runbook chose this throttle. */
|
|
44
|
+
reason: string;
|
|
45
|
+
/** Issuer identity (the runbook). Audit trail only. */
|
|
46
|
+
issuer: string;
|
|
47
|
+
}
|
|
48
|
+
export interface InstallThrottleInput {
|
|
49
|
+
attributionKey: string;
|
|
50
|
+
/** Throttle duration in milliseconds (auto-reverts when elapsed). */
|
|
51
|
+
durationMs: number;
|
|
52
|
+
/** Free-text reason for audit. */
|
|
53
|
+
reason: string;
|
|
54
|
+
/** Issuer identity for audit (e.g. 'burn-throttle-runbook'). */
|
|
55
|
+
issuer: string;
|
|
56
|
+
/**
|
|
57
|
+
* Unique identifier for the originating burn signal. Used by the gate as a
|
|
58
|
+
* replay-prevention nonce — once consumed, the same signalId cannot install
|
|
59
|
+
* another throttle. The runbook derives this from the DegradationEvent's
|
|
60
|
+
* monotonic timestamp + attribution key. Reviewer-required (Phase 4
|
|
61
|
+
* second-pass review §1) to close the infinite-replay window on captured
|
|
62
|
+
* capability tokens.
|
|
63
|
+
*/
|
|
64
|
+
signalId: string;
|
|
65
|
+
/**
|
|
66
|
+
* Capability token over the canonical install payload. When the gate has
|
|
67
|
+
* an HMAC key configured (production), the token MUST be present and
|
|
68
|
+
* valid. When no key is configured (tests / pre-F-8 wiring), the token
|
|
69
|
+
* is ignored — see the class doc for the "trusted local caller" caveat
|
|
70
|
+
* (the HMAC defends external/cross-process forgery, not in-process mints).
|
|
71
|
+
*/
|
|
72
|
+
capabilityToken?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface LlmRateGateOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Optional HMAC key for capability-token verification. When set, every
|
|
77
|
+
* installThrottle call must include a valid `capabilityToken` over the
|
|
78
|
+
* canonical payload.
|
|
79
|
+
*/
|
|
80
|
+
capabilityKey?: Buffer | null;
|
|
81
|
+
/** Injectable clock for tests. */
|
|
82
|
+
now?: () => number;
|
|
34
83
|
}
|
|
35
84
|
export declare class LlmRateGate {
|
|
36
85
|
private static singleton;
|
|
86
|
+
private readonly throttles;
|
|
87
|
+
private readonly capabilityKey;
|
|
88
|
+
private readonly now;
|
|
89
|
+
/**
|
|
90
|
+
* Consumed signal IDs — replay-prevention nonces. Per Phase 4 second-pass
|
|
91
|
+
* review §1. Garbage-collected lazily during `installThrottle`: entries
|
|
92
|
+
* older than the maximum throttle TTL (2× the longest seen) can be
|
|
93
|
+
* dropped because their corresponding throttle has long since expired.
|
|
94
|
+
*/
|
|
95
|
+
private readonly consumedSignalIds;
|
|
96
|
+
/** Tracks the longest throttle duration seen, used for GC of consumed IDs. */
|
|
97
|
+
private maxThrottleDurationMs;
|
|
98
|
+
constructor(opts?: LlmRateGateOptions);
|
|
37
99
|
/**
|
|
38
100
|
* Process-wide singleton. Tests should use `new LlmRateGate()` + `reset()`
|
|
39
101
|
* rather than this accessor, so they don't bleed throttles into each other.
|
|
40
102
|
*/
|
|
41
103
|
static instance(): LlmRateGate;
|
|
42
104
|
/**
|
|
43
|
-
*
|
|
44
|
-
* adopt the consult-before-call pattern today; future phases enforce it.
|
|
105
|
+
* Read-side: does the gate currently allow a call for this key?
|
|
45
106
|
*
|
|
46
|
-
* @param attributionKey
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* per spec §"Self-reinforcing loop guard").
|
|
107
|
+
* @param attributionKey componentName::promptFingerprint. Reserved prefix
|
|
108
|
+
* `burn-throttle-runbook::*` is structurally exempt
|
|
109
|
+
* (self-reinforcing-loop guard).
|
|
50
110
|
*/
|
|
51
111
|
shouldFire(attributionKey: string): boolean;
|
|
112
|
+
decide(attributionKey: string): LlmRateGateDecision;
|
|
113
|
+
/**
|
|
114
|
+
* Install a throttle for an attribution key. Called by the burn-throttle
|
|
115
|
+
* runbook. Refuses keys with the runbook-self exempt prefix as a defence
|
|
116
|
+
* in depth (the gate also exempts on the read path).
|
|
117
|
+
*
|
|
118
|
+
* Returns the InstalledThrottle if accepted, or throws on:
|
|
119
|
+
* - Self-attribution attempts.
|
|
120
|
+
* - Capability-token verification failure (when key is configured).
|
|
121
|
+
* - Zero/negative duration.
|
|
122
|
+
*/
|
|
123
|
+
installThrottle(input: InstallThrottleInput): InstalledThrottle;
|
|
124
|
+
private signWithKey;
|
|
52
125
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
126
|
+
* Revoke an active throttle. Idempotent (no-op if no throttle exists).
|
|
127
|
+
* Called by the Telegram inline-button handler in Phase 5 and by the
|
|
128
|
+
* runbook's manual-override path.
|
|
55
129
|
*/
|
|
56
|
-
|
|
130
|
+
revokeThrottle(attributionKey: string): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* List currently-active throttles. Used by the verification step (Phase 6)
|
|
133
|
+
* and by the dashboard surface.
|
|
134
|
+
*/
|
|
135
|
+
listActiveThrottles(): InstalledThrottle[];
|
|
136
|
+
/**
|
|
137
|
+
* Compute the capability token for an install payload. Used by the
|
|
138
|
+
* runbook to sign its own throttle requests. Returns null when no
|
|
139
|
+
* capability key is configured (test mode).
|
|
140
|
+
*
|
|
141
|
+
* **Threat-model note (Phase 4 second-pass review §2):** this mint and the
|
|
142
|
+
* verification it pairs with both live on the same gate object, so any
|
|
143
|
+
* in-process caller holding a gate reference can forge a valid token. The
|
|
144
|
+
* HMAC therefore defends the **external/cross-process boundary** only
|
|
145
|
+
* — chiefly the persistent throttle-overrides file the scheduler reads
|
|
146
|
+
* (Phase 4-extension, not in this PR). For in-process integrity we rely
|
|
147
|
+
* on the fact that all callers of LlmRateGate are instar source under
|
|
148
|
+
* the same `/instar-dev` review gate. If/when the runbook is moved to a
|
|
149
|
+
* separate process, this mint method should be relocated to the runbook
|
|
150
|
+
* service alone and removed from the gate's API surface.
|
|
151
|
+
*/
|
|
152
|
+
computeCapabilityToken(input: {
|
|
153
|
+
attributionKey: string;
|
|
154
|
+
durationMs: number;
|
|
155
|
+
issuer: string;
|
|
156
|
+
signalId: string;
|
|
157
|
+
}): string | null;
|
|
57
158
|
/**
|
|
58
|
-
* Reset all installed throttles.
|
|
59
|
-
*
|
|
60
|
-
* tests so a misconfigured singleton can't bleed across `describe` blocks.
|
|
159
|
+
* Reset all installed throttles. Exposed for tests so a misconfigured
|
|
160
|
+
* singleton can't bleed across `describe` blocks.
|
|
61
161
|
*/
|
|
62
162
|
reset(): void;
|
|
63
163
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LlmRateGate.d.ts","sourceRoot":"","sources":["../../src/monitoring/LlmRateGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;
|
|
1
|
+
{"version":3,"file":"LlmRateGate.d.ts","sourceRoot":"","sources":["../../src/monitoring/LlmRateGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,MAAM,WAAW,mBAAmB;IAClC,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,MAAM,EACF,uBAAuB,GACvB,iBAAiB,GACjB,kBAAkB,GAClB,qBAAqB,CAAC;IAC1B,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,SAAS,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;IAClE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,8EAA8E;IAC9E,OAAO,CAAC,qBAAqB,CAAkB;gBAEnC,IAAI,CAAC,EAAE,kBAAkB;IAKrC;;;OAGG;IACH,MAAM,CAAC,QAAQ,IAAI,WAAW;IAO9B;;;;;;OAMG;IACH,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAI3C,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,mBAAmB;IAuBnD;;;;;;;;;OASG;IACH,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,iBAAiB;IA0D/D,OAAO,CAAC,WAAW;IAUnB;;;;OAIG;IACH,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAI/C;;;OAGG;IACH,mBAAmB,IAAI,iBAAiB,EAAE;IAc1C;;;;;;;;;;;;;;;OAeG;IACH,sBAAsB,CAAC,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,GAAG,IAAI;IAK9H;;;OAGG;IACH,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -1,31 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LlmRateGate — actuator primitive for the burn-detection auto-heal system.
|
|
3
3
|
*
|
|
4
|
-
* Phase 1
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
4
|
+
* Phase 1 shipped the no-op shape. Phase 4 upgrades the gate into a stateful
|
|
5
|
+
* actuator: the burn-throttle runbook installs throttles via `installThrottle`,
|
|
6
|
+
* the LLM callers consult `shouldFire` / `decide`, and a throttle auto-expires
|
|
7
|
+
* after its TTL elapses. Per the umbrella spec §"Signal-vs-Authority
|
|
8
|
+
* Decomposition", the gate enforces decisions made elsewhere — it never
|
|
9
|
+
* decides anything on its own.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* State: in-memory only. The throttle store is process-local. If the agent
|
|
12
|
+
* restarts, any in-memory throttle is dropped — which is the right default
|
|
13
|
+
* (a restart is itself a reset, and the burn-detector will re-emit any
|
|
14
|
+
* surviving signal within 60s). Persistent throttles for scheduled-job cron
|
|
15
|
+
* entries live in a separate file (`.instar/jobs.json.throttle-overrides`,
|
|
16
|
+
* HMAC-signed) handled by the scheduler — out of scope for this primitive.
|
|
15
17
|
*
|
|
16
|
-
* Authority
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* attribution key. The class exposes a static `instance()` accessor; tests
|
|
25
|
-
* use the public constructor + `reset()` to isolate from the singleton.
|
|
18
|
+
* Authority: the gate accepts throttles only via `installThrottle`, which the
|
|
19
|
+
* runbook calls with a capability token. The token is verified by the gate
|
|
20
|
+
* against the agent's HMAC key (passed at constructor time when the gate
|
|
21
|
+
* needs to enforce, or skipped for tests). When no key is configured, the
|
|
22
|
+
* gate accepts throttles without verification — that's the "trusted local
|
|
23
|
+
* caller" mode used by the runbook before the F-8 capability surface fully
|
|
24
|
+
* lands. The verification path is in place so the runbook can switch to
|
|
25
|
+
* signed tokens without API churn.
|
|
26
26
|
*/
|
|
27
|
+
import crypto from 'node:crypto';
|
|
27
28
|
export class LlmRateGate {
|
|
28
29
|
static singleton = null;
|
|
30
|
+
throttles = new Map();
|
|
31
|
+
capabilityKey;
|
|
32
|
+
now;
|
|
33
|
+
/**
|
|
34
|
+
* Consumed signal IDs — replay-prevention nonces. Per Phase 4 second-pass
|
|
35
|
+
* review §1. Garbage-collected lazily during `installThrottle`: entries
|
|
36
|
+
* older than the maximum throttle TTL (2× the longest seen) can be
|
|
37
|
+
* dropped because their corresponding throttle has long since expired.
|
|
38
|
+
*/
|
|
39
|
+
consumedSignalIds = new Map();
|
|
40
|
+
/** Tracks the longest throttle duration seen, used for GC of consumed IDs. */
|
|
41
|
+
maxThrottleDurationMs = 60 * 60 * 1000;
|
|
42
|
+
constructor(opts) {
|
|
43
|
+
this.capabilityKey = opts?.capabilityKey ?? null;
|
|
44
|
+
this.now = opts?.now ?? (() => Date.now());
|
|
45
|
+
}
|
|
29
46
|
/**
|
|
30
47
|
* Process-wide singleton. Tests should use `new LlmRateGate()` + `reset()`
|
|
31
48
|
* rather than this accessor, so they don't bleed throttles into each other.
|
|
@@ -37,46 +54,162 @@ export class LlmRateGate {
|
|
|
37
54
|
return LlmRateGate.singleton;
|
|
38
55
|
}
|
|
39
56
|
/**
|
|
40
|
-
*
|
|
41
|
-
* adopt the consult-before-call pattern today; future phases enforce it.
|
|
57
|
+
* Read-side: does the gate currently allow a call for this key?
|
|
42
58
|
*
|
|
43
|
-
* @param attributionKey
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* per spec §"Self-reinforcing loop guard").
|
|
59
|
+
* @param attributionKey componentName::promptFingerprint. Reserved prefix
|
|
60
|
+
* `burn-throttle-runbook::*` is structurally exempt
|
|
61
|
+
* (self-reinforcing-loop guard).
|
|
47
62
|
*/
|
|
48
63
|
shouldFire(attributionKey) {
|
|
49
64
|
return this.decide(attributionKey).allowed;
|
|
50
65
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Same as shouldFire() but returns the full decision record. Useful for
|
|
53
|
-
* tests and for the burn-throttle runbook's verification step.
|
|
54
|
-
*/
|
|
55
66
|
decide(attributionKey) {
|
|
56
|
-
|
|
57
|
-
// runbook's own LLM calls cannot be throttled by the runbook's own
|
|
58
|
-
// decisions, ever. This is the structural floor that prevents a
|
|
59
|
-
// deadlock where the throttle stops the alert from being sent.
|
|
67
|
+
const decidedAt = new Date(this.now()).toISOString();
|
|
60
68
|
if (attributionKey.startsWith('burn-throttle-runbook::')) {
|
|
61
|
-
return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
};
|
|
69
|
+
return { allowed: true, decidedAt, reason: 'runbook-self-exempt' };
|
|
70
|
+
}
|
|
71
|
+
const throttle = this.throttles.get(attributionKey);
|
|
72
|
+
if (!throttle) {
|
|
73
|
+
return { allowed: true, decidedAt, reason: 'no-throttle-installed' };
|
|
74
|
+
}
|
|
75
|
+
const expiresMs = Date.parse(throttle.expiresAt);
|
|
76
|
+
if (Number.isNaN(expiresMs) || this.now() >= expiresMs) {
|
|
77
|
+
// Auto-revert on read — keeps the map small and avoids stale reads.
|
|
78
|
+
this.throttles.delete(attributionKey);
|
|
79
|
+
return { allowed: true, decidedAt, reason: 'throttle-expired' };
|
|
66
80
|
}
|
|
67
81
|
return {
|
|
68
|
-
allowed:
|
|
69
|
-
decidedAt
|
|
70
|
-
reason: '
|
|
82
|
+
allowed: false,
|
|
83
|
+
decidedAt,
|
|
84
|
+
reason: 'throttle-active',
|
|
85
|
+
throttleExpiresAt: throttle.expiresAt,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Install a throttle for an attribution key. Called by the burn-throttle
|
|
90
|
+
* runbook. Refuses keys with the runbook-self exempt prefix as a defence
|
|
91
|
+
* in depth (the gate also exempts on the read path).
|
|
92
|
+
*
|
|
93
|
+
* Returns the InstalledThrottle if accepted, or throws on:
|
|
94
|
+
* - Self-attribution attempts.
|
|
95
|
+
* - Capability-token verification failure (when key is configured).
|
|
96
|
+
* - Zero/negative duration.
|
|
97
|
+
*/
|
|
98
|
+
installThrottle(input) {
|
|
99
|
+
if (input.attributionKey.startsWith('burn-throttle-runbook::')) {
|
|
100
|
+
throw new Error('Refusing to install throttle on runbook-self attribution key (self-reinforcing-loop guard).');
|
|
101
|
+
}
|
|
102
|
+
if (input.durationMs <= 0) {
|
|
103
|
+
throw new Error(`Throttle duration must be positive (got ${input.durationMs}).`);
|
|
104
|
+
}
|
|
105
|
+
if (!input.signalId || input.signalId.length === 0) {
|
|
106
|
+
throw new Error('LlmRateGate.installThrottle: signalId is required (replay-prevention nonce).');
|
|
107
|
+
}
|
|
108
|
+
// Replay-prevention: a signalId may install at most one throttle. The
|
|
109
|
+
// map is GC'd lazily below. Per Phase 4 second-pass review §1.
|
|
110
|
+
if (this.consumedSignalIds.has(input.signalId)) {
|
|
111
|
+
throw new Error(`LlmRateGate.installThrottle: signalId ${input.signalId} has already been consumed (replay refused).`);
|
|
112
|
+
}
|
|
113
|
+
// Capability-token verification when a key is configured. The canonical
|
|
114
|
+
// payload includes signalId so captured tokens cannot be replayed with a
|
|
115
|
+
// different signal context.
|
|
116
|
+
if (this.capabilityKey) {
|
|
117
|
+
const expected = this.signWithKey({
|
|
118
|
+
attributionKey: input.attributionKey,
|
|
119
|
+
durationMs: input.durationMs,
|
|
120
|
+
issuer: input.issuer,
|
|
121
|
+
signalId: input.signalId,
|
|
122
|
+
});
|
|
123
|
+
if (!input.capabilityToken || input.capabilityToken !== expected) {
|
|
124
|
+
throw new Error('LlmRateGate.installThrottle: invalid or missing capability token.');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const nowMs = this.now();
|
|
128
|
+
const nowIso = new Date(nowMs).toISOString();
|
|
129
|
+
const expiresIso = new Date(nowMs + input.durationMs).toISOString();
|
|
130
|
+
const throttle = {
|
|
131
|
+
attributionKey: input.attributionKey,
|
|
132
|
+
installedAt: nowIso,
|
|
133
|
+
expiresAt: expiresIso,
|
|
134
|
+
reason: input.reason,
|
|
135
|
+
issuer: input.issuer,
|
|
71
136
|
};
|
|
137
|
+
this.throttles.set(input.attributionKey, throttle);
|
|
138
|
+
this.consumedSignalIds.set(input.signalId, nowMs);
|
|
139
|
+
// Track the longest duration we've ever seen for GC bookkeeping.
|
|
140
|
+
if (input.durationMs > this.maxThrottleDurationMs) {
|
|
141
|
+
this.maxThrottleDurationMs = input.durationMs;
|
|
142
|
+
}
|
|
143
|
+
// GC consumed signal IDs older than 2x the longest throttle TTL.
|
|
144
|
+
const gcCutoff = nowMs - 2 * this.maxThrottleDurationMs;
|
|
145
|
+
for (const [sigId, sigTs] of this.consumedSignalIds.entries()) {
|
|
146
|
+
if (sigTs < gcCutoff)
|
|
147
|
+
this.consumedSignalIds.delete(sigId);
|
|
148
|
+
}
|
|
149
|
+
return throttle;
|
|
150
|
+
}
|
|
151
|
+
signWithKey(payload) {
|
|
152
|
+
const canonical = JSON.stringify({
|
|
153
|
+
attributionKey: payload.attributionKey,
|
|
154
|
+
durationMs: payload.durationMs,
|
|
155
|
+
issuer: payload.issuer,
|
|
156
|
+
signalId: payload.signalId,
|
|
157
|
+
});
|
|
158
|
+
return crypto.createHmac('sha256', this.capabilityKey).update(canonical).digest('hex');
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Revoke an active throttle. Idempotent (no-op if no throttle exists).
|
|
162
|
+
* Called by the Telegram inline-button handler in Phase 5 and by the
|
|
163
|
+
* runbook's manual-override path.
|
|
164
|
+
*/
|
|
165
|
+
revokeThrottle(attributionKey) {
|
|
166
|
+
return this.throttles.delete(attributionKey);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* List currently-active throttles. Used by the verification step (Phase 6)
|
|
170
|
+
* and by the dashboard surface.
|
|
171
|
+
*/
|
|
172
|
+
listActiveThrottles() {
|
|
173
|
+
const now = this.now();
|
|
174
|
+
const active = [];
|
|
175
|
+
for (const [key, throttle] of this.throttles.entries()) {
|
|
176
|
+
const expiresMs = Date.parse(throttle.expiresAt);
|
|
177
|
+
if (!Number.isNaN(expiresMs) && now < expiresMs) {
|
|
178
|
+
active.push(throttle);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
this.throttles.delete(key);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return active;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Compute the capability token for an install payload. Used by the
|
|
188
|
+
* runbook to sign its own throttle requests. Returns null when no
|
|
189
|
+
* capability key is configured (test mode).
|
|
190
|
+
*
|
|
191
|
+
* **Threat-model note (Phase 4 second-pass review §2):** this mint and the
|
|
192
|
+
* verification it pairs with both live on the same gate object, so any
|
|
193
|
+
* in-process caller holding a gate reference can forge a valid token. The
|
|
194
|
+
* HMAC therefore defends the **external/cross-process boundary** only
|
|
195
|
+
* — chiefly the persistent throttle-overrides file the scheduler reads
|
|
196
|
+
* (Phase 4-extension, not in this PR). For in-process integrity we rely
|
|
197
|
+
* on the fact that all callers of LlmRateGate are instar source under
|
|
198
|
+
* the same `/instar-dev` review gate. If/when the runbook is moved to a
|
|
199
|
+
* separate process, this mint method should be relocated to the runbook
|
|
200
|
+
* service alone and removed from the gate's API surface.
|
|
201
|
+
*/
|
|
202
|
+
computeCapabilityToken(input) {
|
|
203
|
+
if (!this.capabilityKey)
|
|
204
|
+
return null;
|
|
205
|
+
return this.signWithKey(input);
|
|
72
206
|
}
|
|
73
207
|
/**
|
|
74
|
-
* Reset all installed throttles.
|
|
75
|
-
*
|
|
76
|
-
* tests so a misconfigured singleton can't bleed across `describe` blocks.
|
|
208
|
+
* Reset all installed throttles. Exposed for tests so a misconfigured
|
|
209
|
+
* singleton can't bleed across `describe` blocks.
|
|
77
210
|
*/
|
|
78
211
|
reset() {
|
|
79
|
-
|
|
212
|
+
this.throttles.clear();
|
|
80
213
|
}
|
|
81
214
|
}
|
|
82
215
|
//# sourceMappingURL=LlmRateGate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LlmRateGate.js","sourceRoot":"","sources":["../../src/monitoring/LlmRateGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;
|
|
1
|
+
{"version":3,"file":"LlmRateGate.js","sourceRoot":"","sources":["../../src/monitoring/LlmRateGate.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,6 +35,13 @@ export interface ProjectRow {
|
|
|
35
35
|
eventCount: number;
|
|
36
36
|
sessionCount: number;
|
|
37
37
|
}
|
|
38
|
+
export interface AttributionKeyRow {
|
|
39
|
+
attributionKey: string;
|
|
40
|
+
totalTokens: number;
|
|
41
|
+
eventCount: number;
|
|
42
|
+
firstTs: number;
|
|
43
|
+
lastTs: number;
|
|
44
|
+
}
|
|
38
45
|
export interface OrphanRow {
|
|
39
46
|
sessionId: string;
|
|
40
47
|
projectPath: string | null;
|
|
@@ -147,6 +154,15 @@ export declare class TokenLedger {
|
|
|
147
154
|
byProject({ sinceMs }?: {
|
|
148
155
|
sinceMs?: number;
|
|
149
156
|
}): ProjectRow[];
|
|
157
|
+
/**
|
|
158
|
+
* Aggregate by attribution key (Phase 3 of burn-detection-and-self-heal
|
|
159
|
+
* spec). Returns one row per key with totals, event count, and the time
|
|
160
|
+
* window the key has been seen across the requested period.
|
|
161
|
+
*/
|
|
162
|
+
byAttributionKey({ sinceMs, limit }?: {
|
|
163
|
+
sinceMs?: number;
|
|
164
|
+
limit?: number;
|
|
165
|
+
}): AttributionKeyRow[];
|
|
150
166
|
/** Sessions whose newest event is older than `idleMs` ago. */
|
|
151
167
|
orphans({ idleMs }: {
|
|
152
168
|
idleMs: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenLedger.d.ts","sourceRoot":"","sources":["../../src/monitoring/TokenLedger.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TokenLedger.d.ts","sourceRoot":"","sources":["../../src/monitoring/TokenLedger.ts"],"names":[],"mappings":"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"}
|
|
@@ -551,6 +551,34 @@ export class TokenLedger {
|
|
|
551
551
|
sessionCount: Number(r.sessionCount) || 0,
|
|
552
552
|
}));
|
|
553
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Aggregate by attribution key (Phase 3 of burn-detection-and-self-heal
|
|
556
|
+
* spec). Returns one row per key with totals, event count, and the time
|
|
557
|
+
* window the key has been seen across the requested period.
|
|
558
|
+
*/
|
|
559
|
+
byAttributionKey({ sinceMs, limit = 100 } = {}) {
|
|
560
|
+
const since = sinceMs ?? 0;
|
|
561
|
+
const rows = this.db
|
|
562
|
+
.prepare(`SELECT
|
|
563
|
+
attribution_key AS attributionKey,
|
|
564
|
+
SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens) AS totalTokens,
|
|
565
|
+
COUNT(*) AS eventCount,
|
|
566
|
+
MIN(ts) AS firstTs,
|
|
567
|
+
MAX(ts) AS lastTs
|
|
568
|
+
FROM token_events
|
|
569
|
+
WHERE ts >= ?
|
|
570
|
+
GROUP BY attribution_key
|
|
571
|
+
ORDER BY totalTokens DESC
|
|
572
|
+
LIMIT ?`)
|
|
573
|
+
.all(since, limit);
|
|
574
|
+
return rows.map(r => ({
|
|
575
|
+
attributionKey: r.attributionKey,
|
|
576
|
+
totalTokens: Number(r.totalTokens) || 0,
|
|
577
|
+
eventCount: Number(r.eventCount) || 0,
|
|
578
|
+
firstTs: Number(r.firstTs) || 0,
|
|
579
|
+
lastTs: Number(r.lastTs) || 0,
|
|
580
|
+
}));
|
|
581
|
+
}
|
|
554
582
|
/** Sessions whose newest event is older than `idleMs` ago. */
|
|
555
583
|
orphans({ idleMs }) {
|
|
556
584
|
const now = Date.now();
|