usertrust 1.2.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/anomaly/detector.d.ts +17 -0
- package/dist/anomaly/detector.d.ts.map +1 -0
- package/dist/anomaly/detector.js +174 -0
- package/dist/anomaly/detector.js.map +1 -0
- package/dist/anomaly/index.d.ts +8 -0
- package/dist/anomaly/index.d.ts.map +1 -0
- package/dist/anomaly/index.js +8 -0
- package/dist/anomaly/index.js.map +1 -0
- package/dist/anomaly/signals/injection-cascade.d.ts +31 -0
- package/dist/anomaly/signals/injection-cascade.d.ts.map +1 -0
- package/dist/anomaly/signals/injection-cascade.js +46 -0
- package/dist/anomaly/signals/injection-cascade.js.map +1 -0
- package/dist/anomaly/signals/spend-velocity.d.ts +41 -0
- package/dist/anomaly/signals/spend-velocity.d.ts.map +1 -0
- package/dist/anomaly/signals/spend-velocity.js +90 -0
- package/dist/anomaly/signals/spend-velocity.js.map +1 -0
- package/dist/anomaly/signals/token-rate.d.ts +46 -0
- package/dist/anomaly/signals/token-rate.d.ts.map +1 -0
- package/dist/anomaly/signals/token-rate.js +90 -0
- package/dist/anomaly/signals/token-rate.js.map +1 -0
- package/dist/anomaly/types.d.ts +103 -0
- package/dist/anomaly/types.d.ts.map +1 -0
- package/dist/anomaly/types.js +4 -0
- package/dist/anomaly/types.js.map +1 -0
- package/dist/audit/chain.d.ts +9 -0
- package/dist/audit/chain.d.ts.map +1 -1
- package/dist/audit/chain.js +65 -9
- package/dist/audit/chain.js.map +1 -1
- package/dist/audit/verify.d.ts +19 -0
- package/dist/audit/verify.d.ts.map +1 -1
- package/dist/audit/verify.js +216 -14
- package/dist/audit/verify.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +9 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/skill.d.ts.map +1 -1
- package/dist/cli/skill.js +35 -5
- package/dist/cli/skill.js.map +1 -1
- package/dist/cli/snapshot.d.ts.map +1 -1
- package/dist/cli/snapshot.js +20 -2
- package/dist/cli/snapshot.js.map +1 -1
- package/dist/cli/verify.d.ts.map +1 -1
- package/dist/cli/verify.js +20 -11
- package/dist/cli/verify.js.map +1 -1
- package/dist/govern.d.ts +14 -3
- package/dist/govern.d.ts.map +1 -1
- package/dist/govern.js +367 -129
- package/dist/govern.js.map +1 -1
- package/dist/headless.d.ts.map +1 -1
- package/dist/headless.js +155 -36
- package/dist/headless.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ledger/client.d.ts +14 -0
- package/dist/ledger/client.d.ts.map +1 -1
- package/dist/ledger/client.js +56 -0
- package/dist/ledger/client.js.map +1 -1
- package/dist/ledger/pricing.d.ts.map +1 -1
- package/dist/ledger/pricing.js +8 -2
- package/dist/ledger/pricing.js.map +1 -1
- package/dist/policy/default-rules.d.ts +18 -3
- package/dist/policy/default-rules.d.ts.map +1 -1
- package/dist/policy/default-rules.js +33 -15
- package/dist/policy/default-rules.js.map +1 -1
- package/dist/policy/gate.d.ts.map +1 -1
- package/dist/policy/gate.js +114 -16
- package/dist/policy/gate.js.map +1 -1
- package/dist/policy/injection.d.ts.map +1 -1
- package/dist/policy/injection.js +25 -16
- package/dist/policy/injection.js.map +1 -1
- package/dist/policy/pii.d.ts +20 -0
- package/dist/policy/pii.d.ts.map +1 -1
- package/dist/policy/pii.js +45 -2
- package/dist/policy/pii.js.map +1 -1
- package/dist/proxy.d.ts +17 -9
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +8 -36
- package/dist/proxy.js.map +1 -1
- package/dist/shared/errors.d.ts +8 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +18 -0
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/types.d.ts +119 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +37 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/snapshot/checkpoint.d.ts +18 -2
- package/dist/snapshot/checkpoint.d.ts.map +1 -1
- package/dist/snapshot/checkpoint.js +101 -17
- package/dist/snapshot/checkpoint.js.map +1 -1
- package/dist/streaming.d.ts +21 -3
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +35 -8
- package/dist/streaming.js.map +1 -1
- package/dist/supply-chain/permissions.d.ts +16 -3
- package/dist/supply-chain/permissions.d.ts.map +1 -1
- package/dist/supply-chain/permissions.js +59 -9
- package/dist/supply-chain/permissions.js.map +1 -1
- package/dist/supply-chain/sign.d.ts +5 -2
- package/dist/supply-chain/sign.d.ts.map +1 -1
- package/dist/supply-chain/sign.js +10 -2
- package/dist/supply-chain/sign.js.map +1 -1
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@ import Anthropic from "@anthropic-ai/sdk";
|
|
|
10
10
|
// Audit chain and policy engine still run.
|
|
11
11
|
const client = await trust(new Anthropic(), { dryRun: true, budget: 50_000 });
|
|
12
12
|
|
|
13
|
-
const { response,
|
|
14
|
-
model: "claude-sonnet-4
|
|
13
|
+
const { response, receipt } = await client.messages.create({
|
|
14
|
+
model: "claude-sonnet-4-6",
|
|
15
15
|
max_tokens: 1024,
|
|
16
16
|
messages: [{ role: "user", content: "Analyze this contract" }],
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
console.log(
|
|
19
|
+
console.log(receipt);
|
|
20
20
|
|
|
21
21
|
// REQUIRED — process hangs without this
|
|
22
22
|
await client.destroy();
|
|
@@ -26,7 +26,7 @@ That's it. One function wraps any supported LLM client. Every call is metered, a
|
|
|
26
26
|
|
|
27
27
|
### Expected Output
|
|
28
28
|
|
|
29
|
-
The `
|
|
29
|
+
The `receipt` returned from every call:
|
|
30
30
|
|
|
31
31
|
```
|
|
32
32
|
{
|
|
@@ -35,7 +35,7 @@ The `governance` receipt returned from every call:
|
|
|
35
35
|
transferId: "tx_m4k7p2_a1b2c3",
|
|
36
36
|
auditHash: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
37
37
|
settled: true,
|
|
38
|
-
model: "claude-sonnet-4
|
|
38
|
+
model: "claude-sonnet-4-6",
|
|
39
39
|
provider: "anthropic",
|
|
40
40
|
inputTokens: 12,
|
|
41
41
|
outputTokens: 28
|
|
@@ -61,11 +61,11 @@ import OpenAI from "openai";
|
|
|
61
61
|
|
|
62
62
|
// Anthropic
|
|
63
63
|
const anthropic = await trust(new Anthropic());
|
|
64
|
-
const { response,
|
|
64
|
+
const { response, receipt } = await anthropic.messages.create({ ... });
|
|
65
65
|
|
|
66
66
|
// OpenAI
|
|
67
67
|
const openai = await trust(new OpenAI());
|
|
68
|
-
const { response,
|
|
68
|
+
const { response, receipt } = await openai.chat.completions.create({ ... });
|
|
69
69
|
|
|
70
70
|
// With options (full mode — requires TigerBeetle)
|
|
71
71
|
const client = await trust(new Anthropic(), {
|
|
@@ -73,7 +73,7 @@ const client = await trust(new Anthropic(), {
|
|
|
73
73
|
});
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
Every call returns `{ response,
|
|
76
|
+
Every call returns `{ response, receipt }`:
|
|
77
77
|
|
|
78
78
|
```typescript
|
|
79
79
|
{
|
|
@@ -82,7 +82,7 @@ Every call returns `{ response, governance }` where `governance` is a receipt:
|
|
|
82
82
|
budgetRemaining: 49_858,
|
|
83
83
|
auditHash: "a3f8...",
|
|
84
84
|
settled: true,
|
|
85
|
-
model: "claude-sonnet-4
|
|
85
|
+
model: "claude-sonnet-4-6",
|
|
86
86
|
provider: "anthropic",
|
|
87
87
|
timestamp: "2026-03-16T12:00:00.000Z"
|
|
88
88
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AnomalyConfig, AnomalyDetectorOptions, AnomalyDetectorState, AnomalyEvent, AnomalyVerdict, ResolvedAnomalyConfig } from "./types.js";
|
|
2
|
+
/** Resolve a partial AnomalyConfig with defaults. */
|
|
3
|
+
export declare function resolveAnomalyConfig(cfg?: AnomalyConfig): ResolvedAnomalyConfig;
|
|
4
|
+
export interface AnomalyDetector {
|
|
5
|
+
readonly config: ResolvedAnomalyConfig;
|
|
6
|
+
readonly state: Readonly<AnomalyDetectorState>;
|
|
7
|
+
/** Observe a chunk or injection event. */
|
|
8
|
+
observe(event: AnomalyEvent): void;
|
|
9
|
+
/** Check current state. Returns the most-severe tripped verdict, or {tripped:false}. */
|
|
10
|
+
check(): AnomalyVerdict;
|
|
11
|
+
/** Manually reset the detector to a clean state. */
|
|
12
|
+
reset(): void;
|
|
13
|
+
/** True if currently tripped (and not yet auto-recovered via cooldown). */
|
|
14
|
+
isTripped(): boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function createAnomalyDetector(cfg?: AnomalyConfig, options?: AnomalyDetectorOptions): AnomalyDetector;
|
|
17
|
+
//# sourceMappingURL=detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/anomaly/detector.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAEX,aAAa,EACb,sBAAsB,EACtB,oBAAoB,EACpB,YAAY,EAGZ,cAAc,EACd,qBAAqB,EACrB,MAAM,YAAY,CAAC;AAIpB,qDAAqD;AACrD,wBAAgB,oBAAoB,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,qBAAqB,CAQ/E;AAWD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IAC/C,0CAA0C;IAC1C,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,wFAAwF;IACxF,KAAK,IAAI,cAAc,CAAC;IACxB,oDAAoD;IACpD,KAAK,IAAI,IAAI,CAAC;IACd,2EAA2E;IAC3E,SAAS,IAAI,OAAO,CAAC;CACrB;AAED,wBAAgB,qBAAqB,CACpC,GAAG,CAAC,EAAE,aAAa,EACnB,OAAO,CAAC,EAAE,sBAAsB,GAC9B,eAAe,CAuKjB"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Usertools, Inc.
|
|
3
|
+
/**
|
|
4
|
+
* detector.ts — AnomalyDetector
|
|
5
|
+
*
|
|
6
|
+
* Mental model:
|
|
7
|
+
* - One detector instance per intercepted streaming call (or shared across
|
|
8
|
+
* a "conversation" when injection-cascade tracking is needed).
|
|
9
|
+
* - The streaming pipeline calls observe(event) for each chunk; the detector
|
|
10
|
+
* updates per-signal state and check() returns a verdict.
|
|
11
|
+
* - On tripped verdict: caller throws AnomalyError, which propagates through
|
|
12
|
+
* the existing wrapStream onError → VOID flow → upstream caller. An audit
|
|
13
|
+
* event `anomaly_detected` is appended via the existing hash-chain.
|
|
14
|
+
* - After cooldownMs the detector auto-resets; reset() can be called manually.
|
|
15
|
+
*
|
|
16
|
+
* Defaults are tuned conservatively (500 tok/s sustained, $1/min, 3 injections in 60s).
|
|
17
|
+
* Detection is opt-in (anomaly.enabled=true) — when off, observe/check are no-ops.
|
|
18
|
+
*/
|
|
19
|
+
import { estimateCost } from "../ledger/pricing.js";
|
|
20
|
+
import { createInjectionCascadeSignal, resolveInjectionCascadeConfig, } from "./signals/injection-cascade.js";
|
|
21
|
+
import { createSpendVelocitySignal, resolveSpendVelocityConfig, } from "./signals/spend-velocity.js";
|
|
22
|
+
import { createTokenRateSignal, resolveTokenRateConfig, } from "./signals/token-rate.js";
|
|
23
|
+
const DEFAULT_COOLDOWN_MS = 30_000;
|
|
24
|
+
/** Resolve a partial AnomalyConfig with defaults. */
|
|
25
|
+
export function resolveAnomalyConfig(cfg) {
|
|
26
|
+
return {
|
|
27
|
+
enabled: cfg?.enabled ?? false,
|
|
28
|
+
tokenRate: resolveTokenRateConfig(cfg?.tokenRate),
|
|
29
|
+
spendVelocity: resolveSpendVelocityConfig(cfg?.spendVelocity),
|
|
30
|
+
injectionCascade: resolveInjectionCascadeConfig(cfg?.injectionCascade),
|
|
31
|
+
cooldownMs: cfg?.cooldownMs ?? DEFAULT_COOLDOWN_MS,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** USD value of one usertoken. 1 usertoken = $0.0001 (one basis point of a cent). */
|
|
35
|
+
const USERTOKENS_PER_DOLLAR = 10_000;
|
|
36
|
+
function defaultCostCalculator(model, inputTokens, outputTokens) {
|
|
37
|
+
// estimateCost returns usertokens; convert to dollars.
|
|
38
|
+
const usertokens = estimateCost(model, inputTokens, outputTokens);
|
|
39
|
+
return usertokens / USERTOKENS_PER_DOLLAR;
|
|
40
|
+
}
|
|
41
|
+
export function createAnomalyDetector(cfg, options) {
|
|
42
|
+
const config = resolveAnomalyConfig(cfg);
|
|
43
|
+
const now = options?.now ?? (() => Date.now());
|
|
44
|
+
const model = options?.model ?? "unknown";
|
|
45
|
+
const costCalculator = options?.costCalculator ?? defaultCostCalculator;
|
|
46
|
+
const tokenRate = createTokenRateSignal(config.tokenRate);
|
|
47
|
+
const spendVelocity = createSpendVelocitySignal(config.spendVelocity);
|
|
48
|
+
const injectionCascade = createInjectionCascadeSignal(config.injectionCascade);
|
|
49
|
+
const state = {
|
|
50
|
+
tripped: false,
|
|
51
|
+
trippedAt: null,
|
|
52
|
+
trippedKind: null,
|
|
53
|
+
};
|
|
54
|
+
function maybeAutoRecover() {
|
|
55
|
+
if (!state.tripped || state.trippedAt == null)
|
|
56
|
+
return;
|
|
57
|
+
if (now() - state.trippedAt >= config.cooldownMs) {
|
|
58
|
+
doReset();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function observeChunk(ev) {
|
|
62
|
+
const t = ev.at ?? now();
|
|
63
|
+
tokenRate.observe(ev.deltaTokens, t);
|
|
64
|
+
const dollars = costCalculator(model, ev.cumulativeInputTokens, ev.cumulativeOutputTokens);
|
|
65
|
+
spendVelocity.observe(dollars, t);
|
|
66
|
+
}
|
|
67
|
+
function observeInjection(ev) {
|
|
68
|
+
injectionCascade.observe(ev.at ?? now());
|
|
69
|
+
}
|
|
70
|
+
function observe(event) {
|
|
71
|
+
if (!config.enabled)
|
|
72
|
+
return;
|
|
73
|
+
maybeAutoRecover();
|
|
74
|
+
if (event.kind === "chunk") {
|
|
75
|
+
observeChunk(event);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
observeInjection(event);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function check() {
|
|
82
|
+
if (!config.enabled)
|
|
83
|
+
return { tripped: false };
|
|
84
|
+
maybeAutoRecover();
|
|
85
|
+
// If already tripped (still in cooldown), keep returning the trip verdict
|
|
86
|
+
// so the caller's check is idempotent during the same call.
|
|
87
|
+
if (state.tripped && state.trippedKind != null) {
|
|
88
|
+
return buildKnownTrip(state.trippedKind);
|
|
89
|
+
}
|
|
90
|
+
const t = now();
|
|
91
|
+
// Check signals in priority order: token-rate first (cheapest), spend-velocity, then injection.
|
|
92
|
+
const tr = tokenRate.check(t);
|
|
93
|
+
if (tr.tripped) {
|
|
94
|
+
return markTripped("token_rate", `tokens/s ${tr.metric.toFixed(1)} >= ${tr.threshold}`, tr.metric, tr.threshold);
|
|
95
|
+
}
|
|
96
|
+
const sv = spendVelocity.check(t);
|
|
97
|
+
if (sv.tripped) {
|
|
98
|
+
return markTripped("spend_velocity", `$/min ${sv.metric.toFixed(4)} >= ${sv.threshold}`, sv.metric, sv.threshold);
|
|
99
|
+
}
|
|
100
|
+
const ic = injectionCascade.check(t);
|
|
101
|
+
if (ic.tripped) {
|
|
102
|
+
return markTripped("injection_cascade", `injection events ${ic.metric} >= ${ic.threshold} within window`, ic.metric, ic.threshold);
|
|
103
|
+
}
|
|
104
|
+
return { tripped: false };
|
|
105
|
+
}
|
|
106
|
+
function buildKnownTrip(kind) {
|
|
107
|
+
// Re-check the tripped signal so metric reflects the most recent value.
|
|
108
|
+
const t = now();
|
|
109
|
+
switch (kind) {
|
|
110
|
+
case "token_rate": {
|
|
111
|
+
const r = tokenRate.check(t);
|
|
112
|
+
return {
|
|
113
|
+
tripped: true,
|
|
114
|
+
kind,
|
|
115
|
+
message: `tokens/s ${r.metric.toFixed(1)} >= ${r.threshold}`,
|
|
116
|
+
metric: r.metric,
|
|
117
|
+
threshold: r.threshold,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
case "spend_velocity": {
|
|
121
|
+
const r = spendVelocity.check(t);
|
|
122
|
+
return {
|
|
123
|
+
tripped: true,
|
|
124
|
+
kind,
|
|
125
|
+
message: `$/min ${r.metric.toFixed(4)} >= ${r.threshold}`,
|
|
126
|
+
metric: r.metric,
|
|
127
|
+
threshold: r.threshold,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
case "injection_cascade": {
|
|
131
|
+
const r = injectionCascade.check(t);
|
|
132
|
+
return {
|
|
133
|
+
tripped: true,
|
|
134
|
+
kind,
|
|
135
|
+
message: `injection events ${r.metric} >= ${r.threshold} within window`,
|
|
136
|
+
metric: r.metric,
|
|
137
|
+
threshold: r.threshold,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function markTripped(kind, message, metric, threshold) {
|
|
143
|
+
state.tripped = true;
|
|
144
|
+
state.trippedAt = now();
|
|
145
|
+
state.trippedKind = kind;
|
|
146
|
+
return { tripped: true, kind, message, metric, threshold };
|
|
147
|
+
}
|
|
148
|
+
function doReset() {
|
|
149
|
+
state.tripped = false;
|
|
150
|
+
state.trippedAt = null;
|
|
151
|
+
state.trippedKind = null;
|
|
152
|
+
tokenRate.reset();
|
|
153
|
+
spendVelocity.reset();
|
|
154
|
+
injectionCascade.reset();
|
|
155
|
+
}
|
|
156
|
+
function isTripped() {
|
|
157
|
+
// Run a check first so that signals which have crossed thresholds since
|
|
158
|
+
// the last observe() are reflected. Then handle auto-recovery.
|
|
159
|
+
if (!state.tripped && config.enabled) {
|
|
160
|
+
check();
|
|
161
|
+
}
|
|
162
|
+
maybeAutoRecover();
|
|
163
|
+
return state.tripped;
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
config,
|
|
167
|
+
state,
|
|
168
|
+
observe,
|
|
169
|
+
check,
|
|
170
|
+
reset: doReset,
|
|
171
|
+
isTripped,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/anomaly/detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* anomaly/index.ts — Public exports for streaming anomaly governance.
|
|
3
|
+
*/
|
|
4
|
+
export { createAnomalyDetector, resolveAnomalyConfig } from "./detector.js";
|
|
5
|
+
export type { AnomalyDetector } from "./detector.js";
|
|
6
|
+
export type { AnomalyChunkEvent, AnomalyConfig, AnomalyDetectorOptions, AnomalyDetectorState, AnomalyEvent, AnomalyInjectionEvent, AnomalyKind, AnomalyVerdict, InjectionCascadeConfig, ResolvedAnomalyConfig, SpendVelocityConfig, TokenRateConfig, } from "./types.js";
|
|
7
|
+
export { AnomalyError } from "../shared/errors.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/anomaly/index.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC5E,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EACX,iBAAiB,EACjB,aAAa,EACb,sBAAsB,EACtB,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Usertools, Inc.
|
|
3
|
+
/**
|
|
4
|
+
* anomaly/index.ts — Public exports for streaming anomaly governance.
|
|
5
|
+
*/
|
|
6
|
+
export { createAnomalyDetector, resolveAnomalyConfig } from "./detector.js";
|
|
7
|
+
export { AnomalyError } from "../shared/errors.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/anomaly/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAgB5E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* injection-cascade.ts — Injection-cascade anomaly signal.
|
|
3
|
+
*
|
|
4
|
+
* Detects when N injection signals fire within an M-second rolling window.
|
|
5
|
+
* Useful for catching agents under sustained adversarial pressure.
|
|
6
|
+
*
|
|
7
|
+
* Default: 3 events in 60s.
|
|
8
|
+
*/
|
|
9
|
+
import type { InjectionCascadeConfig } from "../types.js";
|
|
10
|
+
export interface InjectionCascadeSignalState {
|
|
11
|
+
/** Timestamps (ms since epoch) of injection events within the active window. */
|
|
12
|
+
eventTimes: number[];
|
|
13
|
+
}
|
|
14
|
+
export interface InjectionCascadeSignal {
|
|
15
|
+
state: Readonly<InjectionCascadeSignalState>;
|
|
16
|
+
observe(nowMs: number): void;
|
|
17
|
+
check(nowMs: number): {
|
|
18
|
+
tripped: boolean;
|
|
19
|
+
metric: number;
|
|
20
|
+
threshold: number;
|
|
21
|
+
};
|
|
22
|
+
reset(): void;
|
|
23
|
+
}
|
|
24
|
+
export interface ResolvedInjectionCascadeConfig {
|
|
25
|
+
eventCount: number;
|
|
26
|
+
windowMs: number;
|
|
27
|
+
}
|
|
28
|
+
export declare const INJECTION_CASCADE_DEFAULTS: ResolvedInjectionCascadeConfig;
|
|
29
|
+
export declare function resolveInjectionCascadeConfig(cfg?: InjectionCascadeConfig): ResolvedInjectionCascadeConfig;
|
|
30
|
+
export declare function createInjectionCascadeSignal(cfg: ResolvedInjectionCascadeConfig): InjectionCascadeSignal;
|
|
31
|
+
//# sourceMappingURL=injection-cascade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injection-cascade.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/injection-cascade.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,WAAW,2BAA2B;IAC3C,gFAAgF;IAChF,UAAU,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACtC,KAAK,EAAE,QAAQ,CAAC,2BAA2B,CAAC,CAAC;IAC7C,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG;QACrB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,8BAA8B;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,0BAA0B,EAAE,8BAGxC,CAAC;AAEF,wBAAgB,6BAA6B,CAC5C,GAAG,CAAC,EAAE,sBAAsB,GAC1B,8BAA8B,CAKhC;AAED,wBAAgB,4BAA4B,CAC3C,GAAG,EAAE,8BAA8B,GACjC,sBAAsB,CAwCxB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Usertools, Inc.
|
|
3
|
+
export const INJECTION_CASCADE_DEFAULTS = {
|
|
4
|
+
eventCount: 3,
|
|
5
|
+
windowMs: 60_000,
|
|
6
|
+
};
|
|
7
|
+
export function resolveInjectionCascadeConfig(cfg) {
|
|
8
|
+
return {
|
|
9
|
+
eventCount: cfg?.eventCount ?? INJECTION_CASCADE_DEFAULTS.eventCount,
|
|
10
|
+
windowMs: cfg?.windowMs ?? INJECTION_CASCADE_DEFAULTS.windowMs,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function createInjectionCascadeSignal(cfg) {
|
|
14
|
+
const state = {
|
|
15
|
+
eventTimes: [],
|
|
16
|
+
};
|
|
17
|
+
function pruneOlderThan(cutoffMs) {
|
|
18
|
+
while (state.eventTimes.length > 0) {
|
|
19
|
+
const head = state.eventTimes[0];
|
|
20
|
+
if (head !== undefined && head < cutoffMs) {
|
|
21
|
+
state.eventTimes.shift();
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function observe(nowMs) {
|
|
29
|
+
pruneOlderThan(nowMs - cfg.windowMs);
|
|
30
|
+
state.eventTimes.push(nowMs);
|
|
31
|
+
}
|
|
32
|
+
function check(nowMs) {
|
|
33
|
+
pruneOlderThan(nowMs - cfg.windowMs);
|
|
34
|
+
const count = state.eventTimes.length;
|
|
35
|
+
return {
|
|
36
|
+
tripped: count >= cfg.eventCount,
|
|
37
|
+
metric: count,
|
|
38
|
+
threshold: cfg.eventCount,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function reset() {
|
|
42
|
+
state.eventTimes.length = 0;
|
|
43
|
+
}
|
|
44
|
+
return { state, observe, check, reset };
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=injection-cascade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injection-cascade.js","sourceRoot":"","sources":["../../../src/anomaly/signals/injection-cascade.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAkCjC,MAAM,CAAC,MAAM,0BAA0B,GAAmC;IACzE,UAAU,EAAE,CAAC;IACb,QAAQ,EAAE,MAAM;CAChB,CAAC;AAEF,MAAM,UAAU,6BAA6B,CAC5C,GAA4B;IAE5B,OAAO;QACN,UAAU,EAAE,GAAG,EAAE,UAAU,IAAI,0BAA0B,CAAC,UAAU;QACpE,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,0BAA0B,CAAC,QAAQ;KAC9D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC3C,GAAmC;IAEnC,MAAM,KAAK,GAAgC;QAC1C,UAAU,EAAE,EAAE;KACd,CAAC;IAEF,SAAS,cAAc,CAAC,QAAgB;QACvC,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YACjC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,GAAG,QAAQ,EAAE,CAAC;gBAC3C,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YAC1B,CAAC;iBAAM,CAAC;gBACP,MAAM;YACP,CAAC;QACF,CAAC;IACF,CAAC;IAED,SAAS,OAAO,CAAC,KAAa;QAC7B,cAAc,CAAC,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,SAAS,KAAK,CAAC,KAAa;QAK3B,cAAc,CAAC,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;QACtC,OAAO;YACN,OAAO,EAAE,KAAK,IAAI,GAAG,CAAC,UAAU;YAChC,MAAM,EAAE,KAAK;YACb,SAAS,EAAE,GAAG,CAAC,UAAU;SACzB,CAAC;IACH,CAAC;IAED,SAAS,KAAK;QACb,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* spend-velocity.ts — Spend-velocity anomaly signal.
|
|
3
|
+
*
|
|
4
|
+
* Tracks $/min over a rolling window. Each observation records the
|
|
5
|
+
* cumulative cost (in dollars). The signal trips when the rolling
|
|
6
|
+
* dollars-per-minute exceeds the configured ceiling.
|
|
7
|
+
*
|
|
8
|
+
* Default: $1.00/min over a 10s rolling window.
|
|
9
|
+
*
|
|
10
|
+
* Cost is computed via a `costCalculator` callback so the signal stays
|
|
11
|
+
* decoupled from the SDK's pricing module.
|
|
12
|
+
*/
|
|
13
|
+
import type { SpendVelocityConfig } from "../types.js";
|
|
14
|
+
interface CostSample {
|
|
15
|
+
tMs: number;
|
|
16
|
+
cumulativeDollars: number;
|
|
17
|
+
}
|
|
18
|
+
export interface SpendVelocitySignalState {
|
|
19
|
+
samples: CostSample[];
|
|
20
|
+
lastDollarsPerMin: number;
|
|
21
|
+
}
|
|
22
|
+
export interface SpendVelocitySignal {
|
|
23
|
+
state: Readonly<SpendVelocitySignalState>;
|
|
24
|
+
/** Record cumulative dollars at time `nowMs`. */
|
|
25
|
+
observe(cumulativeDollars: number, nowMs: number): void;
|
|
26
|
+
check(nowMs: number): {
|
|
27
|
+
tripped: boolean;
|
|
28
|
+
metric: number;
|
|
29
|
+
threshold: number;
|
|
30
|
+
};
|
|
31
|
+
reset(): void;
|
|
32
|
+
}
|
|
33
|
+
export interface ResolvedSpendVelocityConfig {
|
|
34
|
+
thresholdDollarsPerMin: number;
|
|
35
|
+
windowMs: number;
|
|
36
|
+
}
|
|
37
|
+
export declare const SPEND_VELOCITY_DEFAULTS: ResolvedSpendVelocityConfig;
|
|
38
|
+
export declare function resolveSpendVelocityConfig(cfg?: SpendVelocityConfig): ResolvedSpendVelocityConfig;
|
|
39
|
+
export declare function createSpendVelocitySignal(cfg: ResolvedSpendVelocityConfig): SpendVelocitySignal;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=spend-velocity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spend-velocity.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/spend-velocity.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,UAAU,UAAU;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,iBAAiB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACxC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,QAAQ,CAAC,wBAAwB,CAAC,CAAC;IAC1C,iDAAiD;IACjD,OAAO,CAAC,iBAAiB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACxD,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG;QACrB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,2BAA2B;IAC3C,sBAAsB,EAAE,MAAM,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,uBAAuB,EAAE,2BAGrC,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,GAAG,CAAC,EAAE,mBAAmB,GAAG,2BAA2B,CAMjG;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,2BAA2B,GAAG,mBAAmB,CAkF/F"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Usertools, Inc.
|
|
3
|
+
export const SPEND_VELOCITY_DEFAULTS = {
|
|
4
|
+
thresholdDollarsPerMin: 1.0,
|
|
5
|
+
windowMs: 10_000,
|
|
6
|
+
};
|
|
7
|
+
export function resolveSpendVelocityConfig(cfg) {
|
|
8
|
+
return {
|
|
9
|
+
thresholdDollarsPerMin: cfg?.thresholdDollarsPerMin ?? SPEND_VELOCITY_DEFAULTS.thresholdDollarsPerMin,
|
|
10
|
+
windowMs: cfg?.windowMs ?? SPEND_VELOCITY_DEFAULTS.windowMs,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function createSpendVelocitySignal(cfg) {
|
|
14
|
+
const state = {
|
|
15
|
+
samples: [],
|
|
16
|
+
lastDollarsPerMin: 0,
|
|
17
|
+
};
|
|
18
|
+
function pruneOlderThan(cutoffMs) {
|
|
19
|
+
// Keep at least one sample older than the cutoff so we can compute deltas.
|
|
20
|
+
// Find the most recent sample at or before cutoffMs and drop everything before it.
|
|
21
|
+
let lastBeforeCutoff = -1;
|
|
22
|
+
for (let i = 0; i < state.samples.length; i++) {
|
|
23
|
+
const sample = state.samples[i];
|
|
24
|
+
if (sample !== undefined && sample.tMs <= cutoffMs) {
|
|
25
|
+
lastBeforeCutoff = i;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (lastBeforeCutoff > 0) {
|
|
32
|
+
state.samples.splice(0, lastBeforeCutoff);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function observe(cumulativeDollars, nowMs) {
|
|
36
|
+
// Reject NaN/negative deltas — keep the cumulative monotonic non-decreasing.
|
|
37
|
+
if (!Number.isFinite(cumulativeDollars) || cumulativeDollars < 0)
|
|
38
|
+
return;
|
|
39
|
+
const last = state.samples[state.samples.length - 1];
|
|
40
|
+
if (last !== undefined && cumulativeDollars < last.cumulativeDollars) {
|
|
41
|
+
// Provider regressed (shouldn't happen) — clamp to last
|
|
42
|
+
state.samples.push({ tMs: nowMs, cumulativeDollars: last.cumulativeDollars });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
state.samples.push({ tMs: nowMs, cumulativeDollars });
|
|
46
|
+
}
|
|
47
|
+
pruneOlderThan(nowMs - cfg.windowMs);
|
|
48
|
+
}
|
|
49
|
+
function check(nowMs) {
|
|
50
|
+
pruneOlderThan(nowMs - cfg.windowMs);
|
|
51
|
+
if (state.samples.length < 2) {
|
|
52
|
+
return {
|
|
53
|
+
tripped: false,
|
|
54
|
+
metric: 0,
|
|
55
|
+
threshold: cfg.thresholdDollarsPerMin,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const first = state.samples[0];
|
|
59
|
+
const last = state.samples[state.samples.length - 1];
|
|
60
|
+
if (first === undefined || last === undefined) {
|
|
61
|
+
return {
|
|
62
|
+
tripped: false,
|
|
63
|
+
metric: 0,
|
|
64
|
+
threshold: cfg.thresholdDollarsPerMin,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const dollarsDelta = last.cumulativeDollars - first.cumulativeDollars;
|
|
68
|
+
const minutesDelta = (last.tMs - first.tMs) / 60_000;
|
|
69
|
+
if (minutesDelta <= 0) {
|
|
70
|
+
return {
|
|
71
|
+
tripped: false,
|
|
72
|
+
metric: 0,
|
|
73
|
+
threshold: cfg.thresholdDollarsPerMin,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const dollarsPerMin = dollarsDelta / minutesDelta;
|
|
77
|
+
state.lastDollarsPerMin = dollarsPerMin;
|
|
78
|
+
return {
|
|
79
|
+
tripped: dollarsPerMin >= cfg.thresholdDollarsPerMin,
|
|
80
|
+
metric: dollarsPerMin,
|
|
81
|
+
threshold: cfg.thresholdDollarsPerMin,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function reset() {
|
|
85
|
+
state.samples.length = 0;
|
|
86
|
+
state.lastDollarsPerMin = 0;
|
|
87
|
+
}
|
|
88
|
+
return { state, observe, check, reset };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=spend-velocity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spend-velocity.js","sourceRoot":"","sources":["../../../src/anomaly/signals/spend-velocity.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AA4CjC,MAAM,CAAC,MAAM,uBAAuB,GAAgC;IACnE,sBAAsB,EAAE,GAAG;IAC3B,QAAQ,EAAE,MAAM;CAChB,CAAC;AAEF,MAAM,UAAU,0BAA0B,CAAC,GAAyB;IACnE,OAAO;QACN,sBAAsB,EACrB,GAAG,EAAE,sBAAsB,IAAI,uBAAuB,CAAC,sBAAsB;QAC9E,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,uBAAuB,CAAC,QAAQ;KAC3D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,GAAgC;IACzE,MAAM,KAAK,GAA6B;QACvC,OAAO,EAAE,EAAE;QACX,iBAAiB,EAAE,CAAC;KACpB,CAAC;IAEF,SAAS,cAAc,CAAC,QAAgB;QACvC,2EAA2E;QAC3E,mFAAmF;QACnF,IAAI,gBAAgB,GAAG,CAAC,CAAC,CAAC;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAChC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,IAAI,QAAQ,EAAE,CAAC;gBACpD,gBAAgB,GAAG,CAAC,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACP,MAAM;YACP,CAAC;QACF,CAAC;QACD,IAAI,gBAAgB,GAAG,CAAC,EAAE,CAAC;YAC1B,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;QAC3C,CAAC;IACF,CAAC;IAED,SAAS,OAAO,CAAC,iBAAyB,EAAE,KAAa;QACxD,6EAA6E;QAC7E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,iBAAiB,GAAG,CAAC;YAAE,OAAO;QACzE,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACrD,IAAI,IAAI,KAAK,SAAS,IAAI,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACtE,wDAAwD;YACxD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,iBAAiB,EAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;QAC/E,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;QACvD,CAAC;QACD,cAAc,CAAC,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED,SAAS,KAAK,CAAC,KAAa;QAK3B,cAAc,CAAC,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,CAAC;gBACT,SAAS,EAAE,GAAG,CAAC,sBAAsB;aACrC,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACrD,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/C,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,CAAC;gBACT,SAAS,EAAE,GAAG,CAAC,sBAAsB;aACrC,CAAC;QACH,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC,iBAAiB,CAAC;QACtE,MAAM,YAAY,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;QACrD,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;YACvB,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,CAAC;gBACT,SAAS,EAAE,GAAG,CAAC,sBAAsB;aACrC,CAAC;QACH,CAAC;QACD,MAAM,aAAa,GAAG,YAAY,GAAG,YAAY,CAAC;QAClD,KAAK,CAAC,iBAAiB,GAAG,aAAa,CAAC;QACxC,OAAO;YACN,OAAO,EAAE,aAAa,IAAI,GAAG,CAAC,sBAAsB;YACpD,MAAM,EAAE,aAAa;YACrB,SAAS,EAAE,GAAG,CAAC,sBAAsB;SACrC,CAAC;IACH,CAAC;IAED,SAAS,KAAK;QACb,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QACzB,KAAK,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* token-rate.ts — Token-rate anomaly signal.
|
|
3
|
+
*
|
|
4
|
+
* Buckets observed chunks into fixed-width windows (default 2 s) and counts
|
|
5
|
+
* tokens-per-second within each window. Trips when N consecutive windows
|
|
6
|
+
* exceed the configured threshold (default 500 tok/s, 3 windows).
|
|
7
|
+
*
|
|
8
|
+
* Brief spikes (1-2 hot windows) are ignored — only sustained runaway
|
|
9
|
+
* behavior trips the circuit.
|
|
10
|
+
*/
|
|
11
|
+
import type { TokenRateConfig } from "../types.js";
|
|
12
|
+
export interface TokenRateSignalState {
|
|
13
|
+
/** Per-window tokens accumulated in the current bucket. */
|
|
14
|
+
currentWindowTokens: number;
|
|
15
|
+
/** Start time of the current window. */
|
|
16
|
+
currentWindowStartMs: number;
|
|
17
|
+
/** Number of consecutive completed windows that exceeded threshold. */
|
|
18
|
+
consecutiveHotWindows: number;
|
|
19
|
+
/** Last computed peak rate (for the most recently closed window). */
|
|
20
|
+
lastWindowRateTokPerSec: number;
|
|
21
|
+
}
|
|
22
|
+
export interface TokenRateSignal {
|
|
23
|
+
state: Readonly<TokenRateSignalState>;
|
|
24
|
+
/** Observe a chunk's delta tokens at time `nowMs`. */
|
|
25
|
+
observe(deltaTokens: number, nowMs: number): void;
|
|
26
|
+
/**
|
|
27
|
+
* Check if the threshold has been crossed.
|
|
28
|
+
* Returns the metric (peak window rate) when tripped, else null.
|
|
29
|
+
*/
|
|
30
|
+
check(nowMs: number): {
|
|
31
|
+
tripped: boolean;
|
|
32
|
+
metric: number;
|
|
33
|
+
threshold: number;
|
|
34
|
+
hotWindows: number;
|
|
35
|
+
};
|
|
36
|
+
reset(): void;
|
|
37
|
+
}
|
|
38
|
+
export interface ResolvedTokenRateConfig {
|
|
39
|
+
thresholdTokPerSec: number;
|
|
40
|
+
windowMs: number;
|
|
41
|
+
consecutiveWindows: number;
|
|
42
|
+
}
|
|
43
|
+
export declare const TOKEN_RATE_DEFAULTS: ResolvedTokenRateConfig;
|
|
44
|
+
export declare function resolveTokenRateConfig(cfg?: TokenRateConfig): ResolvedTokenRateConfig;
|
|
45
|
+
export declare function createTokenRateSignal(cfg: ResolvedTokenRateConfig): TokenRateSignal;
|
|
46
|
+
//# sourceMappingURL=token-rate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-rate.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/token-rate.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACpC,2DAA2D;IAC3D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,wCAAwC;IACxC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uEAAuE;IACvE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,qEAAqE;IACrE,uBAAuB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IACtC,sDAAsD;IACtD,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAClD;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG;QACrB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACvC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,mBAAmB,EAAE,uBAIjC,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,GAAG,CAAC,EAAE,eAAe,GAAG,uBAAuB,CAMrF;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,uBAAuB,GAAG,eAAe,CAmFnF"}
|