usertrust 1.4.0 → 2.0.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 +17 -1
- package/dist/anomaly/detector.d.ts.map +1 -1
- package/dist/anomaly/detector.js +36 -11
- package/dist/anomaly/detector.js.map +1 -1
- package/dist/anomaly/index.d.ts +2 -2
- package/dist/anomaly/index.d.ts.map +1 -1
- package/dist/anomaly/index.js +1 -1
- package/dist/anomaly/index.js.map +1 -1
- package/dist/anomaly/signals/spend-velocity.d.ts +39 -12
- package/dist/anomaly/signals/spend-velocity.d.ts.map +1 -1
- package/dist/anomaly/signals/spend-velocity.js +66 -43
- package/dist/anomaly/signals/spend-velocity.js.map +1 -1
- package/dist/anomaly/signals/token-rate.d.ts +33 -13
- package/dist/anomaly/signals/token-rate.d.ts.map +1 -1
- package/dist/anomaly/signals/token-rate.js +124 -38
- package/dist/anomaly/signals/token-rate.js.map +1 -1
- package/dist/anomaly/types.d.ts +28 -6
- package/dist/anomaly/types.d.ts.map +1 -1
- package/dist/audit/anchor-doctor.d.ts +42 -0
- package/dist/audit/anchor-doctor.d.ts.map +1 -0
- package/dist/audit/anchor-doctor.js +285 -0
- package/dist/audit/anchor-doctor.js.map +1 -0
- package/dist/audit/anchor-verify.d.ts +229 -0
- package/dist/audit/anchor-verify.d.ts.map +1 -0
- package/dist/audit/anchor-verify.js +809 -0
- package/dist/audit/anchor-verify.js.map +1 -0
- package/dist/audit/anchor.d.ts +177 -0
- package/dist/audit/anchor.d.ts.map +1 -0
- package/dist/audit/anchor.js +922 -0
- package/dist/audit/anchor.js.map +1 -0
- package/dist/audit/canonical.js +1 -1
- package/dist/audit/canonical.js.map +1 -1
- package/dist/audit/chain.d.ts.map +1 -1
- package/dist/audit/chain.js +21 -7
- package/dist/audit/chain.js.map +1 -1
- package/dist/audit/read.d.ts +19 -0
- package/dist/audit/read.d.ts.map +1 -0
- package/dist/audit/read.js +94 -0
- package/dist/audit/read.js.map +1 -0
- package/dist/audit/rekor-verify.d.ts +116 -0
- package/dist/audit/rekor-verify.d.ts.map +1 -0
- package/dist/audit/rekor-verify.js +533 -0
- package/dist/audit/rekor-verify.js.map +1 -0
- package/dist/audit/rekor.d.ts +71 -0
- package/dist/audit/rekor.d.ts.map +1 -0
- package/dist/audit/rekor.js +556 -0
- package/dist/audit/rekor.js.map +1 -0
- package/dist/audit/rotation.js +1 -1
- package/dist/audit/rotation.js.map +1 -1
- package/dist/audit/sigv4.d.ts +44 -0
- package/dist/audit/sigv4.d.ts.map +1 -0
- package/dist/audit/sigv4.js +134 -0
- package/dist/audit/sigv4.js.map +1 -0
- package/dist/audit/verify.d.ts +78 -0
- package/dist/audit/verify.d.ts.map +1 -1
- package/dist/audit/verify.js +195 -1
- package/dist/audit/verify.js.map +1 -1
- package/dist/cli/anchor.d.ts +3 -0
- package/dist/cli/anchor.d.ts.map +1 -0
- package/dist/cli/anchor.js +626 -0
- package/dist/cli/anchor.js.map +1 -0
- package/dist/cli/completions.js +1 -1
- package/dist/cli/export.d.ts +3 -0
- package/dist/cli/export.d.ts.map +1 -0
- package/dist/cli/export.js +58 -0
- package/dist/cli/export.js.map +1 -0
- package/dist/cli/health.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +99 -2
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/inspect.d.ts.map +1 -1
- package/dist/cli/inspect.js +4 -34
- package/dist/cli/inspect.js.map +1 -1
- package/dist/cli/main.d.ts +2 -2
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +20 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/pricing.js +1 -1
- package/dist/cli/pricing.js.map +1 -1
- package/dist/cli/ui.d.ts +3 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +35 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli/verify.d.ts.map +1 -1
- package/dist/cli/verify.js +342 -3
- package/dist/cli/verify.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/detect.d.ts +71 -16
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +141 -6
- package/dist/detect.js.map +1 -1
- package/dist/export/markdown.d.ts +12 -0
- package/dist/export/markdown.d.ts.map +1 -0
- package/dist/export/markdown.js +142 -0
- package/dist/export/markdown.js.map +1 -0
- package/dist/govern.d.ts +67 -3
- package/dist/govern.d.ts.map +1 -1
- package/dist/govern.js +1011 -219
- package/dist/govern.js.map +1 -1
- package/dist/headless.d.ts +41 -2
- package/dist/headless.d.ts.map +1 -1
- package/dist/headless.js +58 -7
- package/dist/headless.js.map +1 -1
- package/dist/index.d.ts +33 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -23
- package/dist/index.js.map +1 -1
- package/dist/ledger/client.d.ts.map +1 -1
- package/dist/ledger/client.js +57 -46
- package/dist/ledger/client.js.map +1 -1
- package/dist/ledger/engine.d.ts.map +1 -1
- package/dist/ledger/engine.js +22 -13
- package/dist/ledger/engine.js.map +1 -1
- package/dist/ledger/pricing.d.ts +54 -0
- package/dist/ledger/pricing.d.ts.map +1 -1
- package/dist/ledger/pricing.js +128 -8
- package/dist/ledger/pricing.js.map +1 -1
- package/dist/shared/types.d.ts +154 -340
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +67 -17
- package/dist/shared/types.js.map +1 -1
- package/dist/snapshot/checkpoint.d.ts.map +1 -1
- package/dist/snapshot/checkpoint.js +20 -2
- package/dist/snapshot/checkpoint.js.map +1 -1
- package/dist/streaming.d.ts +13 -3
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +88 -30
- package/dist/streaming.js.map +1 -1
- package/dist/supply-chain/sign.d.ts.map +1 -1
- package/dist/supply-chain/sign.js +3 -0
- package/dist/supply-chain/sign.js.map +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -112,7 +112,8 @@ All commands support `--json` for machine-readable output.
|
|
|
112
112
|
usertrust init # Create .usertrust/ vault
|
|
113
113
|
usertrust inspect # Vault bank statement
|
|
114
114
|
usertrust health # Entropy diagnostics (6 signals, 0-100 score)
|
|
115
|
-
usertrust verify # Verify audit chain integrity
|
|
115
|
+
usertrust verify # Verify audit chain integrity (+ external anchors)
|
|
116
|
+
usertrust anchor # External anchoring: init|now|status|doctor|export|export-bundle|rotate|resume
|
|
116
117
|
usertrust snapshot # Checkpoint/restore vault state
|
|
117
118
|
usertrust tb # TigerBeetle process management
|
|
118
119
|
usertrust completions # Shell completions (bash, zsh, fish)
|
|
@@ -189,6 +190,8 @@ const config = defineConfig({
|
|
|
189
190
|
|
|
190
191
|
**Merkle proofs (RFC 6962)** — Inclusion and consistency proofs for public verifiability. Any third party can verify a specific event existed in the chain.
|
|
191
192
|
|
|
193
|
+
**External anchoring** — Ed25519-signed chain-head checkpoints pushed to an append-only store the vault operator cannot rewrite (S3 Object Lock, protected git branch, SIEM). Even a fully re-hashed, internally-consistent rewrite fails verification against the externally-held root. Ships a native SigV4 S3 sink (no AWS SDK), an experimental [Rekor](https://docs.sigstore.dev/logging/overview/) transparency-log witness that publishes digests only, `anchor doctor` to probe whether the writer identity can actually delete or overwrite in the store, and `anchor export-bundle` to hand an auditor the records and receipts in one file. See the [anchoring guide](https://github.com/usertools-ai/usertrust/blob/master/docs/anchoring.md).
|
|
194
|
+
|
|
192
195
|
**Policy engine** — 12 field operators (`eq`, `gt`, `in`, `regex`, etc.) with soft/hard enforcement. Block specific models, cap costs, require approvals.
|
|
193
196
|
|
|
194
197
|
**PII detection** — Luhn-validated credit card numbers, SSN patterns, email addresses, phone numbers, IPv4 addresses. Block or warn before data leaves your network.
|
|
@@ -255,6 +258,19 @@ All hashes: valid (847/847)
|
|
|
255
258
|
|
|
256
259
|
The verify package has zero runtime dependencies. It reads JSONL, recomputes SHA-256 hashes, and checks the chain. Anyone can verify a vault without trusting the usertrust SDK.
|
|
257
260
|
|
|
261
|
+
With [external anchoring](https://github.com/usertools-ai/usertrust/blob/master/docs/anchoring.md) enabled, verification goes further: the auditor fetches signed checkpoints from an append-only store the operator cannot rewrite and pins the public key out-of-band — so even a fully re-hashed rewrite of the vault fails:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
usertrust anchor init # once: mint identity, pin the key
|
|
265
|
+
usertrust anchor now --sink-file /mnt/worm/anchors.jsonl # from cron/CI, or in-process
|
|
266
|
+
|
|
267
|
+
# or publish straight to S3, after checking the store really refuses deletes
|
|
268
|
+
usertrust anchor doctor --sink-s3 bucket=audit-anchors,region=us-east-1
|
|
269
|
+
usertrust anchor now --sink-s3 bucket=audit-anchors,region=us-east-1
|
|
270
|
+
|
|
271
|
+
npx usertrust-verify .usertrust --anchors anchors.jsonl --pubkey root.pem --require-external-anchor
|
|
272
|
+
```
|
|
273
|
+
|
|
258
274
|
## License
|
|
259
275
|
|
|
260
276
|
Apache 2.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/anomaly/detector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/anomaly/detector.ts"],"names":[],"mappings":"AAuCA,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;AA+CD,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,CAoKjB"}
|
package/dist/anomaly/detector.js
CHANGED
|
@@ -14,9 +14,12 @@
|
|
|
14
14
|
* - After cooldownMs the detector auto-resets; reset() can be called manually.
|
|
15
15
|
*
|
|
16
16
|
* Defaults are tuned conservatively (500 tok/s sustained, $1/min, 3 injections in 60s).
|
|
17
|
-
*
|
|
17
|
+
* Local-scope events (event.endpointClass === "local", stamped by govern.ts) use
|
|
18
|
+
* local-calibrated thresholds: 5000 tok/s (perModel glob overrides first) and
|
|
19
|
+
* 10_000 nominal usertokens/min. Detection is opt-in (anomaly.enabled=true) —
|
|
20
|
+
* when off, observe/check are no-ops.
|
|
18
21
|
*/
|
|
19
|
-
import { estimateCost } from "../ledger/pricing.js";
|
|
22
|
+
import { costFromRates, estimateCost } from "../ledger/pricing.js";
|
|
20
23
|
import { createInjectionCascadeSignal, resolveInjectionCascadeConfig, } from "./signals/injection-cascade.js";
|
|
21
24
|
import { createSpendVelocitySignal, resolveSpendVelocityConfig, } from "./signals/spend-velocity.js";
|
|
22
25
|
import { createTokenRateSignal, resolveTokenRateConfig, } from "./signals/token-rate.js";
|
|
@@ -33,11 +36,31 @@ export function resolveAnomalyConfig(cfg) {
|
|
|
33
36
|
}
|
|
34
37
|
/** USD value of one usertoken. 1 usertoken = $0.0001 (one basis point of a cent). */
|
|
35
38
|
const USERTOKENS_PER_DOLLAR = 10_000;
|
|
36
|
-
function defaultCostCalculator(model, inputTokens, outputTokens) {
|
|
37
|
-
|
|
39
|
+
function defaultCostCalculator(model, inputTokens, outputTokens, event) {
|
|
40
|
+
if (event?.endpointClass === "local") {
|
|
41
|
+
// Local scope without an injected calculator: price in nominal usertokens at
|
|
42
|
+
// the shipped default local rate {0,0}. costFromRates floors at 1, so the
|
|
43
|
+
// cumulative cost is a constant and spend-velocity stays flat — token_rate is
|
|
44
|
+
// the primary local signal. govern.ts injects a config-aware calculator that
|
|
45
|
+
// prices via resolveRates (operator-set local rates enable velocity showback).
|
|
46
|
+
return costFromRates({ inputPer1k: 0, outputPer1k: 0 }, inputTokens, outputTokens);
|
|
47
|
+
}
|
|
48
|
+
// estimateCost returns usertokens; convert to dollars (cloud usd-proxy scope).
|
|
38
49
|
const usertokens = estimateCost(model, inputTokens, outputTokens);
|
|
39
50
|
return usertokens / USERTOKENS_PER_DOLLAR;
|
|
40
51
|
}
|
|
52
|
+
/** Verdict text for a token-rate result; names non-legacy thresholds (local/perModel). */
|
|
53
|
+
function tokenRateMessage(r) {
|
|
54
|
+
const base = `tokens/s ${r.metric.toFixed(1)} >= ${r.threshold}`;
|
|
55
|
+
return r.thresholdLabel === "thresholdTokPerSec" ? base : `${base} (${r.thresholdLabel})`;
|
|
56
|
+
}
|
|
57
|
+
/** Verdict text for a spend-velocity result; never fakes dollars for nominal spend. */
|
|
58
|
+
function spendVelocityMessage(r) {
|
|
59
|
+
if (r.unit === "usertokens") {
|
|
60
|
+
return `usertokens/min ${r.metric.toFixed(1)} >= ${r.threshold} (${r.thresholdLabel})`;
|
|
61
|
+
}
|
|
62
|
+
return `$/min ${r.metric.toFixed(4)} >= ${r.threshold}`;
|
|
63
|
+
}
|
|
41
64
|
export function createAnomalyDetector(cfg, options) {
|
|
42
65
|
const config = resolveAnomalyConfig(cfg);
|
|
43
66
|
const now = options?.now ?? (() => Date.now());
|
|
@@ -60,9 +83,11 @@ export function createAnomalyDetector(cfg, options) {
|
|
|
60
83
|
}
|
|
61
84
|
function observeChunk(ev) {
|
|
62
85
|
const t = ev.at ?? now();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
// Per-event scope: the event's model/endpointClass (stamped by govern.ts)
|
|
87
|
+
// override options.model / the default cloud scope when present (M2).
|
|
88
|
+
tokenRate.observe(ev.deltaTokens, t, ev);
|
|
89
|
+
const cost = costCalculator(ev.model ?? model, ev.cumulativeInputTokens, ev.cumulativeOutputTokens, ev);
|
|
90
|
+
spendVelocity.observe(cost, t, ev.endpointClass ?? "cloud");
|
|
66
91
|
}
|
|
67
92
|
function observeInjection(ev) {
|
|
68
93
|
injectionCascade.observe(ev.at ?? now());
|
|
@@ -91,11 +116,11 @@ export function createAnomalyDetector(cfg, options) {
|
|
|
91
116
|
// Check signals in priority order: token-rate first (cheapest), spend-velocity, then injection.
|
|
92
117
|
const tr = tokenRate.check(t);
|
|
93
118
|
if (tr.tripped) {
|
|
94
|
-
return markTripped("token_rate",
|
|
119
|
+
return markTripped("token_rate", tokenRateMessage(tr), tr.metric, tr.threshold);
|
|
95
120
|
}
|
|
96
121
|
const sv = spendVelocity.check(t);
|
|
97
122
|
if (sv.tripped) {
|
|
98
|
-
return markTripped("spend_velocity",
|
|
123
|
+
return markTripped("spend_velocity", spendVelocityMessage(sv), sv.metric, sv.threshold);
|
|
99
124
|
}
|
|
100
125
|
const ic = injectionCascade.check(t);
|
|
101
126
|
if (ic.tripped) {
|
|
@@ -112,7 +137,7 @@ export function createAnomalyDetector(cfg, options) {
|
|
|
112
137
|
return {
|
|
113
138
|
tripped: true,
|
|
114
139
|
kind,
|
|
115
|
-
message:
|
|
140
|
+
message: tokenRateMessage(r),
|
|
116
141
|
metric: r.metric,
|
|
117
142
|
threshold: r.threshold,
|
|
118
143
|
};
|
|
@@ -122,7 +147,7 @@ export function createAnomalyDetector(cfg, options) {
|
|
|
122
147
|
return {
|
|
123
148
|
tripped: true,
|
|
124
149
|
kind,
|
|
125
|
-
message:
|
|
150
|
+
message: spendVelocityMessage(r),
|
|
126
151
|
metric: r.metric,
|
|
127
152
|
threshold: r.threshold,
|
|
128
153
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/anomaly/detector.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/anomaly/detector.ts"],"names":[],"mappings":"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"}
|
package/dist/anomaly/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* anomaly/index.ts — Public exports for streaming anomaly governance.
|
|
3
3
|
*/
|
|
4
|
-
export {
|
|
4
|
+
export { AnomalyError } from "../shared/errors.js";
|
|
5
5
|
export type { AnomalyDetector } from "./detector.js";
|
|
6
|
+
export { createAnomalyDetector, resolveAnomalyConfig } from "./detector.js";
|
|
6
7
|
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
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/anomaly/index.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/anomaly/index.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC5E,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"}
|
package/dist/anomaly/index.js
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* anomaly/index.ts — Public exports for streaming anomaly governance.
|
|
5
5
|
*/
|
|
6
|
-
export { createAnomalyDetector, resolveAnomalyConfig } from "./detector.js";
|
|
7
6
|
export { AnomalyError } from "../shared/errors.js";
|
|
7
|
+
export { createAnomalyDetector, resolveAnomalyConfig } from "./detector.js";
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/anomaly/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC;;GAEG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/anomaly/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -1,37 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* spend-velocity.ts — Spend-velocity anomaly signal.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Dual denomination (M2): cloud-scope observations accumulate usd-proxy cost
|
|
5
|
+
* (dollars) against `thresholdDollarsPerMin`; local-scope observations
|
|
6
|
+
* accumulate NOMINAL USERTOKENS against `localThresholdUsertokensPerMin`
|
|
7
|
+
* (default 10_000). The two series are independent rolling windows; check()
|
|
8
|
+
* reports which denomination (and threshold) applied via `unit`/`thresholdLabel`
|
|
9
|
+
* so verdict text never fakes dollars for nominal spend.
|
|
7
10
|
*
|
|
8
|
-
*
|
|
11
|
+
* Explicit documented behavior: with the default local rate {0,0}, per-chunk
|
|
12
|
+
* cumulative nominal cost is a constant floor of 1 usertoken, so spend_velocity
|
|
13
|
+
* CANNOT trip on default local config — token_rate is the primary local signal.
|
|
14
|
+
* Velocity becomes meaningful when the operator sets nonzero local rates
|
|
15
|
+
* (amortized-usd showback).
|
|
16
|
+
*
|
|
17
|
+
* Default: $1.00/min (cloud) / 10_000 ut/min (local) over a 10s rolling window.
|
|
9
18
|
*
|
|
10
19
|
* Cost is computed via a `costCalculator` callback so the signal stays
|
|
11
20
|
* decoupled from the SDK's pricing module.
|
|
12
21
|
*/
|
|
22
|
+
import type { EndpointClass } from "../../shared/types.js";
|
|
13
23
|
import type { SpendVelocityConfig } from "../types.js";
|
|
24
|
+
/** Denomination of a velocity measurement. */
|
|
25
|
+
export type SpendUnit = "dollars" | "usertokens";
|
|
14
26
|
interface CostSample {
|
|
15
27
|
tMs: number;
|
|
16
|
-
|
|
28
|
+
/** Cumulative cost in the series' denomination (dollars or nominal usertokens). */
|
|
29
|
+
cumulative: number;
|
|
17
30
|
}
|
|
18
31
|
export interface SpendVelocitySignalState {
|
|
32
|
+
/** Cloud-scope samples (cumulative dollars). */
|
|
19
33
|
samples: CostSample[];
|
|
34
|
+
/** Local-scope samples (cumulative nominal usertokens). */
|
|
35
|
+
localSamples: CostSample[];
|
|
20
36
|
lastDollarsPerMin: number;
|
|
37
|
+
lastUsertokensPerMin: number;
|
|
38
|
+
}
|
|
39
|
+
export interface SpendVelocityCheckResult {
|
|
40
|
+
tripped: boolean;
|
|
41
|
+
metric: number;
|
|
42
|
+
threshold: number;
|
|
43
|
+
/** Denomination of metric/threshold. */
|
|
44
|
+
unit: SpendUnit;
|
|
45
|
+
/** Which config threshold applied: "thresholdDollarsPerMin" | "localThresholdUsertokensPerMin". */
|
|
46
|
+
thresholdLabel: string;
|
|
21
47
|
}
|
|
22
48
|
export interface SpendVelocitySignal {
|
|
23
49
|
state: Readonly<SpendVelocitySignalState>;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
50
|
+
/**
|
|
51
|
+
* Record cumulative cost at time `nowMs`. `scope` selects the series:
|
|
52
|
+
* "local" accumulates nominal usertokens, "cloud" (default) dollars.
|
|
53
|
+
*/
|
|
54
|
+
observe(cumulativeCost: number, nowMs: number, scope?: EndpointClass): void;
|
|
55
|
+
check(nowMs: number): SpendVelocityCheckResult;
|
|
31
56
|
reset(): void;
|
|
32
57
|
}
|
|
33
58
|
export interface ResolvedSpendVelocityConfig {
|
|
34
59
|
thresholdDollarsPerMin: number;
|
|
60
|
+
/** Local-scope threshold; defaulted to 10_000 at consumption when absent. */
|
|
61
|
+
localThresholdUsertokensPerMin?: number | undefined;
|
|
35
62
|
windowMs: number;
|
|
36
63
|
}
|
|
37
64
|
export declare const SPEND_VELOCITY_DEFAULTS: ResolvedSpendVelocityConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spend-velocity.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/spend-velocity.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"spend-velocity.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/spend-velocity.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAKvD,8CAA8C;AAC9C,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AAEjD,UAAU,UAAU;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACxC,gDAAgD;IAChD,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,2DAA2D;IAC3D,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,IAAI,EAAE,SAAS,CAAC;IAChB,mGAAmG;IACnG,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,QAAQ,CAAC,wBAAwB,CAAC,CAAC;IAC1C;;;OAGG;IACH,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5E,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,wBAAwB,CAAC;IAC/C,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,2BAA2B;IAC3C,sBAAsB,EAAE,MAAM,CAAC;IAC/B,6EAA6E;IAC7E,8BAA8B,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpD,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,uBAAuB,EAAE,2BAGrC,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,GAAG,CAAC,EAAE,mBAAmB,GAAG,2BAA2B,CAQjG;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,2BAA2B,GAAG,mBAAmB,CAiG/F"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright 2026 Usertools, Inc.
|
|
3
|
+
/** Default local-scope threshold, applied where consumed (resolved config keeps it optional). */
|
|
4
|
+
const LOCAL_THRESHOLD_USERTOKENS_PER_MIN_DEFAULT = 10_000;
|
|
3
5
|
export const SPEND_VELOCITY_DEFAULTS = {
|
|
4
6
|
thresholdDollarsPerMin: 1.0,
|
|
5
7
|
windowMs: 10_000,
|
|
@@ -7,20 +9,25 @@ export const SPEND_VELOCITY_DEFAULTS = {
|
|
|
7
9
|
export function resolveSpendVelocityConfig(cfg) {
|
|
8
10
|
return {
|
|
9
11
|
thresholdDollarsPerMin: cfg?.thresholdDollarsPerMin ?? SPEND_VELOCITY_DEFAULTS.thresholdDollarsPerMin,
|
|
12
|
+
// M2 field stays optional in resolved config — defaulted where consumed.
|
|
13
|
+
localThresholdUsertokensPerMin: cfg?.localThresholdUsertokensPerMin,
|
|
10
14
|
windowMs: cfg?.windowMs ?? SPEND_VELOCITY_DEFAULTS.windowMs,
|
|
11
15
|
};
|
|
12
16
|
}
|
|
13
17
|
export function createSpendVelocitySignal(cfg) {
|
|
14
18
|
const state = {
|
|
15
19
|
samples: [],
|
|
20
|
+
localSamples: [],
|
|
16
21
|
lastDollarsPerMin: 0,
|
|
22
|
+
lastUsertokensPerMin: 0,
|
|
17
23
|
};
|
|
18
|
-
|
|
24
|
+
const localThreshold = cfg.localThresholdUsertokensPerMin ?? LOCAL_THRESHOLD_USERTOKENS_PER_MIN_DEFAULT;
|
|
25
|
+
function pruneOlderThan(series, cutoffMs) {
|
|
19
26
|
// Keep at least one sample older than the cutoff so we can compute deltas.
|
|
20
27
|
// Find the most recent sample at or before cutoffMs and drop everything before it.
|
|
21
28
|
let lastBeforeCutoff = -1;
|
|
22
|
-
for (let i = 0; i <
|
|
23
|
-
const sample =
|
|
29
|
+
for (let i = 0; i < series.length; i++) {
|
|
30
|
+
const sample = series[i];
|
|
24
31
|
if (sample !== undefined && sample.tMs <= cutoffMs) {
|
|
25
32
|
lastBeforeCutoff = i;
|
|
26
33
|
}
|
|
@@ -29,61 +36,77 @@ export function createSpendVelocitySignal(cfg) {
|
|
|
29
36
|
}
|
|
30
37
|
}
|
|
31
38
|
if (lastBeforeCutoff > 0) {
|
|
32
|
-
|
|
39
|
+
series.splice(0, lastBeforeCutoff);
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
|
-
function observe(
|
|
42
|
+
function observe(cumulativeCost, nowMs, scope = "cloud") {
|
|
36
43
|
// Reject NaN/negative deltas — keep the cumulative monotonic non-decreasing.
|
|
37
|
-
if (!Number.isFinite(
|
|
44
|
+
if (!Number.isFinite(cumulativeCost) || cumulativeCost < 0)
|
|
38
45
|
return;
|
|
39
|
-
const
|
|
40
|
-
|
|
46
|
+
const series = scope === "local" ? state.localSamples : state.samples;
|
|
47
|
+
const last = series[series.length - 1];
|
|
48
|
+
if (last !== undefined && cumulativeCost < last.cumulative) {
|
|
41
49
|
// Provider regressed (shouldn't happen) — clamp to last
|
|
42
|
-
|
|
50
|
+
series.push({ tMs: nowMs, cumulative: last.cumulative });
|
|
43
51
|
}
|
|
44
52
|
else {
|
|
45
|
-
|
|
53
|
+
series.push({ tMs: nowMs, cumulative: cumulativeCost });
|
|
46
54
|
}
|
|
47
|
-
pruneOlderThan(nowMs - cfg.windowMs);
|
|
55
|
+
pruneOlderThan(series, nowMs - cfg.windowMs);
|
|
48
56
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
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;
|
|
57
|
+
/** Rolling per-minute rate over a series, or null when not computable. */
|
|
58
|
+
function ratePerMin(series, nowMs) {
|
|
59
|
+
pruneOlderThan(series, nowMs - cfg.windowMs);
|
|
60
|
+
if (series.length < 2)
|
|
61
|
+
return null;
|
|
62
|
+
const first = series[0];
|
|
63
|
+
const last = series[series.length - 1];
|
|
64
|
+
if (first === undefined || last === undefined)
|
|
65
|
+
return null;
|
|
66
|
+
const costDelta = last.cumulative - first.cumulative;
|
|
68
67
|
const minutesDelta = (last.tMs - first.tMs) / 60_000;
|
|
69
|
-
if (minutesDelta <= 0)
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
if (minutesDelta <= 0)
|
|
69
|
+
return null;
|
|
70
|
+
return costDelta / minutesDelta;
|
|
71
|
+
}
|
|
72
|
+
function check(nowMs) {
|
|
73
|
+
const cloudRate = ratePerMin(state.samples, nowMs);
|
|
74
|
+
const localRate = ratePerMin(state.localSamples, nowMs);
|
|
75
|
+
if (cloudRate !== null)
|
|
76
|
+
state.lastDollarsPerMin = cloudRate;
|
|
77
|
+
if (localRate !== null)
|
|
78
|
+
state.lastUsertokensPerMin = localRate;
|
|
79
|
+
const cloudResult = (tripped) => ({
|
|
80
|
+
tripped,
|
|
81
|
+
metric: cloudRate ?? 0,
|
|
81
82
|
threshold: cfg.thresholdDollarsPerMin,
|
|
82
|
-
|
|
83
|
+
unit: "dollars",
|
|
84
|
+
thresholdLabel: "thresholdDollarsPerMin",
|
|
85
|
+
});
|
|
86
|
+
const localResult = (tripped, rate) => ({
|
|
87
|
+
tripped,
|
|
88
|
+
metric: rate,
|
|
89
|
+
threshold: localThreshold,
|
|
90
|
+
unit: "usertokens",
|
|
91
|
+
thresholdLabel: "localThresholdUsertokensPerMin",
|
|
92
|
+
});
|
|
93
|
+
if (cloudRate !== null && cloudRate >= cfg.thresholdDollarsPerMin)
|
|
94
|
+
return cloudResult(true);
|
|
95
|
+
if (localRate !== null && localRate >= localThreshold)
|
|
96
|
+
return localResult(true, localRate);
|
|
97
|
+
// Neither tripped: report the denomination closer to its threshold
|
|
98
|
+
// (cloud on tie / when nothing is computable — legacy behavior).
|
|
99
|
+
if (localRate !== null &&
|
|
100
|
+
(cloudRate === null || localRate / localThreshold > cloudRate / cfg.thresholdDollarsPerMin)) {
|
|
101
|
+
return localResult(false, localRate);
|
|
102
|
+
}
|
|
103
|
+
return cloudResult(false);
|
|
83
104
|
}
|
|
84
105
|
function reset() {
|
|
85
106
|
state.samples.length = 0;
|
|
107
|
+
state.localSamples.length = 0;
|
|
86
108
|
state.lastDollarsPerMin = 0;
|
|
109
|
+
state.lastUsertokensPerMin = 0;
|
|
87
110
|
}
|
|
88
111
|
return { state, observe, check, reset };
|
|
89
112
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spend-velocity.js","sourceRoot":"","sources":["../../../src/anomaly/signals/spend-velocity.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;
|
|
1
|
+
{"version":3,"file":"spend-velocity.js","sourceRoot":"","sources":["../../../src/anomaly/signals/spend-velocity.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,14 @@
|
|
|
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
|
-
*/
|
|
1
|
+
import type { EndpointClass } from "../../shared/types.js";
|
|
11
2
|
import type { TokenRateConfig } from "../types.js";
|
|
12
|
-
|
|
3
|
+
/** Threshold-source labels: "thresholdTokPerSec", "localThresholdTokPerSec", or `perModel["<pattern>"]`. */
|
|
4
|
+
export type TokenRateThresholdLabel = string;
|
|
5
|
+
/** Per-observation scope context (an AnomalyChunkEvent satisfies this shape). */
|
|
6
|
+
export interface TokenRateObserveContext {
|
|
7
|
+
model?: string | undefined;
|
|
8
|
+
endpointClass?: EndpointClass | undefined;
|
|
9
|
+
}
|
|
10
|
+
/** Rolling-window state for a single endpoint scope (cloud or local). */
|
|
11
|
+
export interface ScopeWindowState {
|
|
13
12
|
/** Per-window tokens accumulated in the current bucket. */
|
|
14
13
|
currentWindowTokens: number;
|
|
15
14
|
/** Start time of the current window. */
|
|
@@ -18,11 +17,27 @@ export interface TokenRateSignalState {
|
|
|
18
17
|
consecutiveHotWindows: number;
|
|
19
18
|
/** Last computed peak rate (for the most recently closed window). */
|
|
20
19
|
lastWindowRateTokPerSec: number;
|
|
20
|
+
/** Whether any observation has been routed to this scope yet. */
|
|
21
|
+
initialized: boolean;
|
|
22
|
+
/** Effective threshold for this scope's windows (resolved per observation). */
|
|
23
|
+
threshold: number;
|
|
24
|
+
/** Source label of the effective threshold. */
|
|
25
|
+
thresholdLabel: TokenRateThresholdLabel;
|
|
26
|
+
}
|
|
27
|
+
export interface TokenRateSignalState {
|
|
28
|
+
/** Cloud/unscoped window state (legacy `thresholdTokPerSec`). */
|
|
29
|
+
cloud: Readonly<ScopeWindowState>;
|
|
30
|
+
/** Local window state (`perModel` glob / `localThresholdTokPerSec`). */
|
|
31
|
+
local: Readonly<ScopeWindowState>;
|
|
21
32
|
}
|
|
22
33
|
export interface TokenRateSignal {
|
|
23
34
|
state: Readonly<TokenRateSignalState>;
|
|
24
|
-
/**
|
|
25
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Observe a chunk's delta tokens at time `nowMs`. `context` (the chunk
|
|
37
|
+
* event) selects the effective threshold for subsequent window evaluation;
|
|
38
|
+
* omitted/unscoped context means the legacy cloud threshold.
|
|
39
|
+
*/
|
|
40
|
+
observe(deltaTokens: number, nowMs: number, context?: TokenRateObserveContext): void;
|
|
26
41
|
/**
|
|
27
42
|
* Check if the threshold has been crossed.
|
|
28
43
|
* Returns the metric (peak window rate) when tripped, else null.
|
|
@@ -32,11 +47,16 @@ export interface TokenRateSignal {
|
|
|
32
47
|
metric: number;
|
|
33
48
|
threshold: number;
|
|
34
49
|
hotWindows: number;
|
|
50
|
+
thresholdLabel: TokenRateThresholdLabel;
|
|
35
51
|
};
|
|
36
52
|
reset(): void;
|
|
37
53
|
}
|
|
38
54
|
export interface ResolvedTokenRateConfig {
|
|
39
55
|
thresholdTokPerSec: number;
|
|
56
|
+
/** Local-scope threshold; defaulted to 5000 at consumption when absent. */
|
|
57
|
+
localThresholdTokPerSec?: number | undefined;
|
|
58
|
+
/** Per-model overrides for local-scope events (trailing-* glob keys). */
|
|
59
|
+
perModel?: Record<string, number> | undefined;
|
|
40
60
|
windowMs: number;
|
|
41
61
|
consecutiveWindows: number;
|
|
42
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-rate.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/token-rate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token-rate.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/token-rate.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAKnD,4GAA4G;AAC5G,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAE7C,iFAAiF;AACjF,MAAM,WAAW,uBAAuB;IACvC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC1C;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAChC,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;IAChC,iEAAiE;IACjE,WAAW,EAAE,OAAO,CAAC;IACrB,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,cAAc,EAAE,uBAAuB,CAAC;CACxC;AAED,MAAM,WAAW,oBAAoB;IACpC,iEAAiE;IACjE,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAClC,wEAAwE;IACxE,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IACtC;;;;OAIG;IACH,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACrF;;;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;QACnB,cAAc,EAAE,uBAAuB,CAAC;KACxC,CAAC;IACF,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACvC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,uBAAuB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC9C,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,CASrF;AAUD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,uBAAuB,GAAG,eAAe,CAsInF"}
|