@agentguard-run/spend 0.15.2 → 0.15.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatLineage = void 0;
4
+ exports.workloadProvenance = workloadProvenance;
5
+ const aggregate_1 = require("../dashboard/aggregate");
6
+ const defaultKeyOf = (d) => d.triggeredScopeKey ?? 'default';
7
+ /**
8
+ * Build the portable per-workload provenance manifest from signed ledger
9
+ * entries. Pass the async `verifyChain` result as `continuity` so this stays a
10
+ * pure, synchronous function (never null-continuity in production — a manifest
11
+ * without tamper evidence is not the product).
12
+ */
13
+ function workloadProvenance(entries, continuity = { verified: false, entries: 0, reason: 'NOT_CHECKED' }, keyOf = defaultKeyOf) {
14
+ const groups = new Map();
15
+ for (const entry of entries) {
16
+ const d = entry.decision;
17
+ const kind = d.entryType ?? 'decision';
18
+ if (kind !== 'decision')
19
+ continue; // settlements/outcomes don't add model steps
20
+ const key = keyOf(d);
21
+ const grp = groups.get(key) ?? {
22
+ calls: 0, spent: 0, frontier: 0, dark: 0, first: null, last: null,
23
+ models: new Map(), ordered: [],
24
+ };
25
+ const model = d.modelResolved || d.modelRequested || 'unknown';
26
+ const { origin } = (0, aggregate_1.classifyOrigin)(d);
27
+ const cents = (0, aggregate_1.decisionCents)(d);
28
+ grp.calls += 1;
29
+ grp.spent += cents;
30
+ if (origin === 'frontier')
31
+ grp.frontier += cents;
32
+ else if (origin === 'dark')
33
+ grp.dark += cents;
34
+ if (d.timestamp) {
35
+ if (!grp.first || d.timestamp < grp.first)
36
+ grp.first = d.timestamp;
37
+ if (!grp.last || d.timestamp > grp.last)
38
+ grp.last = d.timestamp;
39
+ }
40
+ const mu = grp.models.get(model) ?? { model, origin, provider: d.provider ?? 'unknown', calls: 0, cents: 0 };
41
+ mu.calls += 1;
42
+ mu.cents += cents;
43
+ grp.models.set(model, mu);
44
+ grp.ordered.push({ seq: entry.sequence, model, origin });
45
+ groups.set(key, grp);
46
+ }
47
+ const workloads = [];
48
+ for (const [key, grp] of groups) {
49
+ grp.ordered.sort((a, b) => a.seq - b.seq);
50
+ const swaps = [];
51
+ for (let i = 1; i < grp.ordered.length; i++) {
52
+ const prev = grp.ordered[i - 1];
53
+ const cur = grp.ordered[i];
54
+ if (cur.model !== prev.model) {
55
+ swaps.push({
56
+ atSequence: cur.seq,
57
+ fromModel: prev.model, toModel: cur.model,
58
+ fromOrigin: prev.origin, toOrigin: cur.origin,
59
+ crossesBoundary: prev.origin !== cur.origin
60
+ && prev.origin !== 'unknown' && cur.origin !== 'unknown',
61
+ });
62
+ }
63
+ }
64
+ // lineage = models in first-appearance order (collapse consecutive repeats)
65
+ const lineage = [];
66
+ for (const step of grp.ordered)
67
+ if (lineage[lineage.length - 1] !== step.model)
68
+ lineage.push(step.model);
69
+ workloads.push({
70
+ key,
71
+ calls: grp.calls,
72
+ spentCents: grp.spent,
73
+ frontierCents: grp.frontier,
74
+ darkCents: grp.dark,
75
+ firstAt: grp.first,
76
+ lastAt: grp.last,
77
+ models: [...grp.models.values()].sort((a, b) => b.cents - a.cents),
78
+ lineage,
79
+ swaps,
80
+ });
81
+ }
82
+ workloads.sort((a, b) => b.spentCents - a.spentCents);
83
+ return { workloads, continuity };
84
+ }
85
+ /** Human-readable lineage, e.g. "claude-5 → gpt-5-mini → glm-5.2". */
86
+ const formatLineage = (w) => w.lineage.join(' → ');
87
+ exports.formatLineage = formatLineage;
88
+ //# sourceMappingURL=portability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portability.js","sourceRoot":"","sources":["../../src/receipts/portability.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -16,6 +16,36 @@ export type SpendWindow = 'per_call' | 'per_minute' | 'per_hour' | 'per_day' | '
16
16
  export type Provider = 'openai' | 'anthropic' | 'bedrock' | 'gemini' | 'unknown';
17
17
  export type EnforcementMode = 'shadow' | 'canary' | 'enforce';
18
18
  export type CapabilityTier = 'read_only' | 'data_write' | 'payment_initiate' | 'payment_execute';
19
+ /**
20
+ * DAG-TAT attestation requirement attached to a SpendPolicy.
21
+ *
22
+ * Claims listed in `requireFor` are no longer trusted as self-declared
23
+ * strings: the call must carry an attestation chain (a signed receipt DAG)
24
+ * that verifies under `signerKeys` and clears the trust/depth gate for the
25
+ * claimed tier. Node/envelope shapes are defined in receipts/dag.ts; they are
26
+ * typed structurally here to keep core types free of that dependency.
27
+ */
28
+ export interface AttestationRequirement {
29
+ /** Capability claims that must be backed by a verified attestation chain. */
30
+ requireFor: CapabilityTier[];
31
+ /**
32
+ * Trusted Ed25519 signer public keys: a single hex/raw key, or a map of
33
+ * signer fingerprint -> key for multi-signer chains.
34
+ */
35
+ signerKeys: Record<string, string> | string;
36
+ /**
37
+ * Optional per-level overrides of the trust/depth gate thresholds
38
+ * (levels: READ_ONLY | TRANSACT | ADMIN | ORCHESTRATE).
39
+ */
40
+ thresholds?: Partial<Record<'READ_ONLY' | 'TRANSACT' | 'ADMIN' | 'ORCHESTRATE', {
41
+ minTrust?: number;
42
+ minDepth?: number;
43
+ }>>;
44
+ }
45
+ /** Structural shape for an attestation chain carried on a call. */
46
+ export type AttestationChainInput = ReadonlyArray<Record<string, unknown>> | {
47
+ nodes: ReadonlyArray<Record<string, unknown>>;
48
+ };
19
49
  /**
20
50
  * A spend cap with one or more thresholds across configurable windows.
21
51
  * All amounts are USD cents (integer math, no floating-point cost drift).
@@ -97,12 +127,22 @@ export interface SpendPolicy {
97
127
  *
98
128
  * The tier set is informed by the capability-tier framework of Patent D
99
129
  * §7.3 (App. No. 63/984,626). The SDK enforces a flat ordered comparison
100
- * between the call's capability claim and this requirement. Sourcing the
101
- * capability claim from a DAG-TAT verifier, an enterprise identity
102
- * provider, an ERC-8004 credential, or any other attestation mechanism is
103
- * out of scope of this package and is performed by the integrating code.
130
+ * between the call's capability claim and this requirement. To require the
131
+ * claim itself to be PROVEN rather than self-declared, also set
132
+ * `attestation`: listed claims must be backed by a verified receipt-DAG
133
+ * attestation chain (DAG-TAT) or the call is blocked.
104
134
  */
105
135
  requiredCapability?: CapabilityTier;
136
+ /** Optional DAG-TAT attestation requirement for capability claims.
137
+ *
138
+ * When present, any call whose `capabilityClaim` is listed in
139
+ * `attestation.requireFor` must carry a `CallContext.attestation` chain
140
+ * that (a) verifies as a signed, acyclic receipt DAG under `signerKeys`
141
+ * and (b) clears the trust/depth gate for the claimed tier. Verification
142
+ * is local (Ed25519, no network). Fails closed: a listed claim without a
143
+ * valid chain blocks in every enforcement mode.
144
+ */
145
+ attestation?: AttestationRequirement;
106
146
  /** Optional governance posture used by Advisor and provenance policy. */
107
147
  posture?: GovernancePosture;
108
148
  /** Optional outcome definitions for Reviewer Cascade alpha policies. */
@@ -150,6 +190,12 @@ export interface CallContext {
150
190
  builderCode?: string;
151
191
  /** Optional capability tier the caller claims */
152
192
  capabilityClaim?: CapabilityTier;
193
+ /**
194
+ * Optional DAG-TAT attestation chain backing the capability claim.
195
+ * Required when the policy's `attestation.requireFor` lists the claimed
196
+ * tier. An array of signed receipt-DAG nodes or an envelope of them.
197
+ */
198
+ attestation?: AttestationChainInput;
153
199
  /** Optional consent receipt for foreign origin model weights. */
154
200
  foreignOriginConsentReceiptId?: string;
155
201
  /** Captured provider provenance for the call. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentguard-run/spend",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
4
4
  "description": "All terminology and labels used in AgentGuard materials are descriptive of software functionality only, not legal definitions or guarantees of compliance. Terms like receipt, audit log, evidence, audit trail, and attestation refer solely to cryptographically-signed records produced by the software. Full functional-use disclaimer in README.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -148,14 +148,18 @@
148
148
  "src/cli/byo.ts",
149
149
  "docs/patents/CLAIM_MAPPING.md",
150
150
  "src/frameworks",
151
- "src/operating-pack.ts"
151
+ "src/operating-pack.ts",
152
+ "src/dashboard"
152
153
  ],
153
154
  "scripts": {
154
155
  "build": "tsc",
155
156
  "prepublishOnly": "npm run build",
156
157
  "clean": "rm -rf dist dist-test",
157
- "test": "AGENTGUARD_HOME=./dist-test/home tsc -p tsconfig.test.json && rm -rf dist-test/home && AGENTGUARD_HOME=./dist-test/home node --test dist-test/**/*.test.js",
158
- "verify": "node dist/verify-test-vector.js"
158
+ "demo:attestation": "npx --yes tsx examples/dag-attestation-demo.ts",
159
+ "test": "AGENTGUARD_HOME=./dist-test/home tsc -p tsconfig.test.json && rm -rf dist-test/home && AGENTGUARD_HOME=./dist-test/home AGENTGUARD_LICENSE_ENDPOINT=http://127.0.0.1:9 node --test dist-test/**/*.test.js",
160
+ "verify": "node dist/verify-test-vector.js",
161
+ "demo:dashboard": "npx --yes tsx examples/cfo-dashboard-demo.ts",
162
+ "demo:hotswap": "npx --yes tsx examples/hot-swap-provenance-demo.ts"
159
163
  },
160
164
  "keywords": [
161
165
  "ai-agent-security",
@@ -0,0 +1,103 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { aggregateLedger, classifyOrigin, decisionCents } from './aggregate';
4
+ import type { SignedDecisionLogEntry, SpendDecision, Provider } from '../types';
5
+ import type { ProvenanceProvider } from '../receipts/schema';
6
+
7
+ let seq = 0;
8
+ function entry(d: Partial<SpendDecision>): SignedDecisionLogEntry {
9
+ const decision: SpendDecision = {
10
+ decisionId: `d${seq}`,
11
+ timestamp: '2026-07-10T14:00:00.000Z',
12
+ action: 'allow',
13
+ triggeredCap: null,
14
+ triggeredScopeKey: null,
15
+ projectedCents: 0,
16
+ windowSpendBefore: 0,
17
+ windowSpendAfter: 0,
18
+ provider: 'openai',
19
+ modelRequested: 'gpt-5',
20
+ modelResolved: 'gpt-5',
21
+ policyId: 'p',
22
+ policyVersion: 1,
23
+ enforcementMode: 'enforce',
24
+ reasons: [],
25
+ ...d,
26
+ };
27
+ return {
28
+ sequence: seq++, decision, previousHash: '0'.repeat(64),
29
+ entryHash: 'x', signature: 'y', signerFingerprint: 'f',
30
+ };
31
+ }
32
+ function withProvenance(fam: ProvenanceProvider, route = 'api'): SpendDecision['provenance'] {
33
+ return {
34
+ model_identity: { provider: fam, model_id: 'm', model_version: '1', model_family: 'x', weights_origin_country: 'US' },
35
+ hosting: { provider_route: route, jurisdiction_country: 'US', jurisdiction_region: 'us' },
36
+ compliance: {
37
+ baa_in_force: false, baa_vendor: null, hipaa_eligible: false, data_retention_days: null,
38
+ data_residency_attested: false, foreign_origin_weight_flag: false,
39
+ foreign_origin_consent_receipt_id: null, inference_billing: 'customer_managed',
40
+ },
41
+ captured_at: '2026-07-10T14:00:00.000Z',
42
+ };
43
+ }
44
+
45
+ test('classifyOrigin: self-hosted provenance = dark, not inferred', () => {
46
+ const d = entry({ provenance: withProvenance('self_hosted') }).decision;
47
+ assert.deepEqual(classifyOrigin(d), { origin: 'dark', inferred: false });
48
+ });
49
+
50
+ test('classifyOrigin: open-weight family = dark; frontier family = frontier', () => {
51
+ assert.equal(classifyOrigin(entry({ provenance: withProvenance('deepseek') }).decision).origin, 'dark');
52
+ assert.equal(classifyOrigin(entry({ provenance: withProvenance('anthropic') }).decision).origin, 'frontier');
53
+ });
54
+
55
+ test('classifyOrigin: no provenance falls back to provider and is flagged inferred', () => {
56
+ assert.deepEqual(classifyOrigin(entry({ provider: 'anthropic' }).decision), { origin: 'frontier', inferred: true });
57
+ assert.deepEqual(classifyOrigin(entry({ provider: 'unknown' as Provider }).decision), { origin: 'dark', inferred: true });
58
+ });
59
+
60
+ test('decisionCents: blocked = 0, settled actual beats projection', () => {
61
+ assert.equal(decisionCents(entry({ action: 'block', projectedCents: 500 }).decision), 0);
62
+ assert.equal(decisionCents(entry({ projectedCents: 500, actualCents: 420 }).decision), 420);
63
+ assert.equal(decisionCents(entry({ projectedCents: 500 }).decision), 500);
64
+ });
65
+
66
+ test('aggregate: dark share, savings, true-up, and buckets', () => {
67
+ seq = 0;
68
+ const entries = [
69
+ entry({ action: 'allow', provider: 'anthropic', modelResolved: 'claude-5', projectedCents: 100, actualCents: 100, provenance: withProvenance('anthropic') }),
70
+ entry({ action: 'allow', modelResolved: 'glm-5.2', projectedCents: 4, actualCents: 4, provider: 'unknown' as Provider, provenance: withProvenance('self_hosted', 'self-hosted-gpu') }),
71
+ entry({ action: 'downgrade', provider: 'openai', modelResolved: 'gpt-5-mini', projectedCents: 200, actualCents: 20 }),
72
+ entry({ action: 'block', provider: 'openai', modelResolved: 'gpt-5', projectedCents: 300 }),
73
+ entry({ entryType: 'settlement', actualCents: 98, projectedCents: 0 }),
74
+ ];
75
+ const dash = aggregateLedger(entries, { verified: true, entries: entries.length });
76
+
77
+ // 4 enforcement decisions (settlement not counted as a call)
78
+ assert.equal(dash.totals.calls, 4);
79
+ // spend = 100 + 4 + 20 + 0(blocked) = 124
80
+ assert.equal(dash.totals.spentCents, 124);
81
+ assert.equal(dash.totals.darkCents, 4);
82
+ assert.equal(dash.totals.frontierCents, 120); // 100 anthropic + 20 downgraded openai(inferred frontier)
83
+ assert.equal(dash.totals.darkSharePct, Math.round((4 / 124) * 1000) / 10);
84
+ // savings = downgrade delta (200-20=180) + blocked projection (300) = 480
85
+ assert.equal(dash.enforcement.estimatedSavedCents, 480);
86
+ assert.equal(dash.enforcement.blocked, 1);
87
+ assert.equal(dash.enforcement.downgraded, 1);
88
+ // true-up: settled actual 98 vs reserved (100+4+200+300=604)
89
+ assert.equal(dash.trueUp.settledCents, 98);
90
+ assert.equal(dash.trueUp.reservedCents, 604);
91
+ assert.equal(dash.trueUp.driftCents, 98 - 604);
92
+ // integrity passthrough
93
+ assert.equal(dash.integrity.verified, true);
94
+ // model bucket present
95
+ assert.ok(dash.byModel.find(b => b.key === 'claude-5' && b.cents === 100));
96
+ });
97
+
98
+ test('aggregate: empty ledger is safe', () => {
99
+ const dash = aggregateLedger([]);
100
+ assert.equal(dash.totals.calls, 0);
101
+ assert.equal(dash.totals.darkSharePct, 0);
102
+ assert.equal(dash.integrity.verified, false);
103
+ });
@@ -0,0 +1,201 @@
1
+ /**
2
+ * AgentGuard® — CFO cost dashboard, read side.
3
+ *
4
+ * Aggregates the signed, hash-chained decision log into a CFO-legible view:
5
+ * spend over time, by model, by provider, frontier-vs-"dark" (local /
6
+ * open-weight / self-hosted) tokens, reserved-vs-settled true-up, an
7
+ * enforcement/savings summary, and a tamper-evidence check. Everything is
8
+ * derived IN-PROCESS from the customer-held ledger — no proxy, no data plane,
9
+ * no provider billing API required.
10
+ *
11
+ * The "dark token" view is the differentiator: open-source / self-hosted usage
12
+ * never appears on any provider's bill, so a provider console structurally
13
+ * cannot show it. The signed ledger can, because the receipt is written where
14
+ * the call is made.
15
+ *
16
+ * Honest scope note: the signed SpendDecision carries time, provider, model,
17
+ * cents, action, builderCode, entryType and a provenance block — but NOT full
18
+ * tenant/agent identity (only the scope key a cap matched on). Per-agent /
19
+ * per-tenant breakdown therefore requires recording a metadata-only scope
20
+ * digest on the decision; see `scopeKeyBreakdown` (best-effort from
21
+ * triggeredScopeKey) and DASHBOARD.md. We do not invent attribution the ledger
22
+ * does not carry.
23
+ *
24
+ * Patent notice: signed hash-chained decision log (U.S. application filed May
25
+ * 2026); composes with DAG Trust Attestation, Patent D §7.3 (App. No.
26
+ * 63/984,626). AgentGuard® is a U.S. registered trademark (Reg. No. 8281464)
27
+ * of Dunecrest Ventures Inc.
28
+ */
29
+ import type { Provider, SignedDecisionLogEntry, SpendDecision } from '../types';
30
+ import type { ProvenanceProvider } from '../receipts/schema';
31
+
32
+ /** Model families that are open-weight / self-hostable → "dark" when self-hosted. */
33
+ const OPEN_WEIGHT_FAMILIES = new Set<ProvenanceProvider>([
34
+ 'meta', 'mistral', 'cohere', 'fireworks', 'baseten', 'together',
35
+ 'deepseek', 'moonshot', 'alibaba', 'self_hosted',
36
+ ]);
37
+ const FRONTIER_PROVIDERS = new Set<Provider>(['openai', 'anthropic', 'gemini', 'bedrock']);
38
+
39
+ export type TokenOrigin = 'frontier' | 'dark' | 'unknown';
40
+
41
+ /** Classify a decision as frontier vs dark (local/open-weight) spend.
42
+ * Prefers the signed provenance block; falls back to the coarse provider
43
+ * field and labels the result `inferred` so the UI never overstates. */
44
+ export function classifyOrigin(decision: SpendDecision): { origin: TokenOrigin; inferred: boolean } {
45
+ const prov = decision.provenance;
46
+ if (prov?.model_identity) {
47
+ const fam = prov.model_identity.provider;
48
+ if (fam === 'self_hosted') return { origin: 'dark', inferred: false };
49
+ const selfHosted = /self|local|on[-_]?prem|in[-_]?house/i.test(prov.hosting?.provider_route ?? '');
50
+ if (selfHosted) return { origin: 'dark', inferred: false };
51
+ if (OPEN_WEIGHT_FAMILIES.has(fam)) return { origin: 'dark', inferred: false };
52
+ if (fam === 'anthropic' || fam === 'openai' || fam === 'google') return { origin: 'frontier', inferred: false };
53
+ }
54
+ if (FRONTIER_PROVIDERS.has(decision.provider)) return { origin: 'frontier', inferred: true };
55
+ if (decision.provider === 'unknown') return { origin: 'dark', inferred: true };
56
+ return { origin: 'unknown', inferred: true };
57
+ }
58
+
59
+ /** Cents this decision actually cost the org: settled actuals win over the
60
+ * pre-dispatch projection; blocked calls cost zero. */
61
+ export function decisionCents(decision: SpendDecision): number {
62
+ if (decision.action === 'block') return 0;
63
+ if (typeof decision.actualCents === 'number') return decision.actualCents;
64
+ return decision.projectedCents ?? 0;
65
+ }
66
+
67
+ export interface BucketSpend {
68
+ key: string;
69
+ cents: number;
70
+ calls: number;
71
+ }
72
+ export interface CfoDashboard {
73
+ generatedAt: string;
74
+ windowStart: string | null;
75
+ windowEnd: string | null;
76
+ totals: {
77
+ calls: number;
78
+ spentCents: number;
79
+ frontierCents: number;
80
+ darkCents: number;
81
+ darkSharePct: number; // dark / total spend, 0..100
82
+ inferredCents: number; // spend whose origin was inferred, not from provenance
83
+ };
84
+ enforcement: {
85
+ allowed: number;
86
+ downgraded: number;
87
+ blocked: number;
88
+ shadow: number;
89
+ /** Cents NOT spent because a call was blocked or downgraded — the ROI line. */
90
+ estimatedSavedCents: number;
91
+ };
92
+ trueUp: {
93
+ reservedCents: number; // sum of projections on decision entries
94
+ settledCents: number; // sum of actuals on settlement entries
95
+ driftCents: number; // settled - reserved (over/under-estimate)
96
+ };
97
+ byHour: BucketSpend[]; // ISO hour → spend
98
+ byModel: BucketSpend[]; // modelResolved → spend, desc
99
+ byProvider: BucketSpend[];
100
+ byBuilderCode: BucketSpend[];
101
+ scopeKeyBreakdown: BucketSpend[]; // best-effort from triggeredScopeKey (may be sparse)
102
+ integrity: { verified: boolean; entries: number; reason?: string; sequence?: number };
103
+ }
104
+
105
+ function topBuckets(map: Map<string, BucketSpend>, limit = 50): BucketSpend[] {
106
+ return [...map.values()].sort((a, b) => b.cents - a.cents).slice(0, limit);
107
+ }
108
+
109
+ function bump(map: Map<string, BucketSpend>, key: string, cents: number) {
110
+ const cur = map.get(key) ?? { key, cents: 0, calls: 0 };
111
+ cur.cents += cents;
112
+ cur.calls += 1;
113
+ map.set(key, cur);
114
+ }
115
+
116
+ /**
117
+ * Build the CFO dashboard from signed ledger entries. Pure + synchronous.
118
+ * `integrity` is passed in (compute via verifyChain, which is async) so this
119
+ * stays a pure function; pass null to skip.
120
+ */
121
+ export function aggregateLedger(
122
+ entries: SignedDecisionLogEntry[],
123
+ integrity: CfoDashboard['integrity'] | null = null,
124
+ now: string = new Date().toISOString(),
125
+ ): CfoDashboard {
126
+ const byHour = new Map<string, BucketSpend>();
127
+ const byModel = new Map<string, BucketSpend>();
128
+ const byProvider = new Map<string, BucketSpend>();
129
+ const byBuilder = new Map<string, BucketSpend>();
130
+ const byScopeKey = new Map<string, BucketSpend>();
131
+
132
+ let calls = 0, spent = 0, frontier = 0, dark = 0, inferred = 0;
133
+ let allowed = 0, downgraded = 0, blocked = 0, shadow = 0, saved = 0;
134
+ let reserved = 0, settled = 0;
135
+ let minTs: string | null = null, maxTs: string | null = null;
136
+
137
+ for (const entry of entries) {
138
+ const d = entry.decision;
139
+ const kind = d.entryType ?? 'decision';
140
+
141
+ if (kind === 'settlement') {
142
+ if (typeof d.actualCents === 'number') settled += d.actualCents;
143
+ // settlement entries adjust true-up only; not double-counted as calls
144
+ continue;
145
+ }
146
+ if (kind === 'outcome' || kind === 'governance') continue;
147
+
148
+ // provider enforcement decision
149
+ calls += 1;
150
+ const cents = decisionCents(d);
151
+ spent += cents;
152
+ reserved += d.projectedCents ?? 0;
153
+
154
+ const { origin, inferred: wasInferred } = classifyOrigin(d);
155
+ if (origin === 'frontier') frontier += cents;
156
+ else if (origin === 'dark') dark += cents;
157
+ if (wasInferred) inferred += cents;
158
+
159
+ switch (d.action) {
160
+ case 'allow': allowed += 1; break;
161
+ case 'downgrade': downgraded += 1; saved += Math.max(0, (d.projectedCents ?? 0) - cents); break;
162
+ case 'block': blocked += 1; saved += d.projectedCents ?? 0; break;
163
+ case 'shadow': shadow += 1; break;
164
+ }
165
+
166
+ const ts = d.timestamp;
167
+ if (ts) {
168
+ if (!minTs || ts < minTs) minTs = ts;
169
+ if (!maxTs || ts > maxTs) maxTs = ts;
170
+ bump(byHour, ts.slice(0, 13) + ':00', cents); // ISO hour bucket
171
+ }
172
+ bump(byModel, d.modelResolved || d.modelRequested || 'unknown', cents);
173
+ bump(byProvider, d.provider || 'unknown', cents);
174
+ if (d.builderCode) bump(byBuilder, d.builderCode, cents);
175
+ if (d.triggeredScopeKey) bump(byScopeKey, d.triggeredScopeKey, cents);
176
+ }
177
+
178
+ return {
179
+ generatedAt: now,
180
+ windowStart: minTs,
181
+ windowEnd: maxTs,
182
+ totals: {
183
+ calls,
184
+ spentCents: spent,
185
+ frontierCents: frontier,
186
+ darkCents: dark,
187
+ darkSharePct: spent > 0 ? Math.round((dark / spent) * 1000) / 10 : 0,
188
+ inferredCents: inferred,
189
+ },
190
+ enforcement: { allowed, downgraded, blocked, shadow, estimatedSavedCents: saved },
191
+ trueUp: { reservedCents: reserved, settledCents: settled, driftCents: settled - reserved },
192
+ byHour: [...byHour.values()].sort((a, b) => a.key.localeCompare(b.key)),
193
+ byModel: topBuckets(byModel),
194
+ byProvider: topBuckets(byProvider),
195
+ byBuilderCode: topBuckets(byBuilder),
196
+ scopeKeyBreakdown: topBuckets(byScopeKey),
197
+ integrity: integrity ?? { verified: false, entries: entries.length, reason: 'NOT_CHECKED' },
198
+ };
199
+ }
200
+
201
+ export const centsToUsd = (c: number): string => `$${(c / 100).toFixed(2)}`;
@@ -0,0 +1,68 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { workloadProvenance, formatLineage } from './portability';
4
+ import type { SignedDecisionLogEntry, SpendDecision, Provider } from '../types';
5
+ import type { ProvenanceProvider } from './schema';
6
+
7
+ let seq = 0;
8
+ function entry(model: string, fam: ProvenanceProvider, route: string, extra: Partial<SpendDecision> = {}): SignedDecisionLogEntry {
9
+ const decision: SpendDecision = {
10
+ decisionId: `d${seq}`, timestamp: `2026-07-10T14:${String(seq).padStart(2, '0')}:00.000Z`,
11
+ action: 'allow', triggeredCap: null, triggeredScopeKey: 'tenant:acme|agent:planner',
12
+ projectedCents: 10, actualCents: 10, windowSpendBefore: 0, windowSpendAfter: 0,
13
+ provider: (fam === 'self_hosted' ? 'unknown' : fam) as Provider,
14
+ modelRequested: model, modelResolved: model, policyId: 'p', policyVersion: 1,
15
+ enforcementMode: 'enforce', reasons: [],
16
+ provenance: {
17
+ model_identity: { provider: fam, model_id: model, model_version: '1', model_family: fam, weights_origin_country: 'US' },
18
+ hosting: { provider_route: route, jurisdiction_country: 'US', jurisdiction_region: 'us' },
19
+ compliance: { baa_in_force: false, baa_vendor: null, hipaa_eligible: false, data_retention_days: null, data_residency_attested: false, foreign_origin_weight_flag: false, foreign_origin_consent_receipt_id: null, inference_billing: 'customer_managed' },
20
+ captured_at: '2026-07-10T14:00:00.000Z',
21
+ },
22
+ ...extra,
23
+ };
24
+ return { sequence: seq++, decision, previousHash: '0'.repeat(64), entryHash: 'x', signature: 'y', signerFingerprint: 'f' };
25
+ }
26
+
27
+ test('lineage, swaps, and boundary crossing across a hot-swap sequence', () => {
28
+ seq = 0;
29
+ const entries = [
30
+ entry('claude-5', 'anthropic', 'anthropic-api'),
31
+ entry('claude-5', 'anthropic', 'anthropic-api'), // repeat: no swap
32
+ entry('gpt-5-mini', 'openai', 'openai-api'), // frontier→frontier swap
33
+ entry('glm-5.2', 'self_hosted', 'self-hosted-gpu'), // frontier→dark swap (boundary)
34
+ ];
35
+ const report = workloadProvenance(entries, { verified: true, entries: entries.length });
36
+ assert.equal(report.workloads.length, 1);
37
+ const w = report.workloads[0];
38
+ assert.deepEqual(w.lineage, ['claude-5', 'gpt-5-mini', 'glm-5.2']);
39
+ assert.equal(formatLineage(w), 'claude-5 → gpt-5-mini → glm-5.2');
40
+ assert.equal(w.swaps.length, 2);
41
+ assert.equal(w.swaps[0].crossesBoundary, false); // frontier→frontier
42
+ assert.equal(w.swaps[1].crossesBoundary, true); // frontier→dark
43
+ assert.equal(w.swaps[1].fromModel, 'gpt-5-mini');
44
+ assert.equal(w.swaps[1].toModel, 'glm-5.2');
45
+ assert.equal(w.darkCents, 10);
46
+ assert.equal(w.frontierCents, 30);
47
+ assert.equal(report.continuity.verified, true); // one chain spans all swaps
48
+ });
49
+
50
+ test('groups by keyOf; separate workloads do not merge', () => {
51
+ seq = 0;
52
+ const a = entry('claude-5', 'anthropic', 'api', { triggeredScopeKey: 'wf:A' });
53
+ const b = entry('glm-5.2', 'self_hosted', 'self', { triggeredScopeKey: 'wf:B' });
54
+ const report = workloadProvenance([a, b], { verified: true, entries: 2 });
55
+ assert.equal(report.workloads.length, 2);
56
+ assert.ok(report.workloads.every(w => w.swaps.length === 0));
57
+ });
58
+
59
+ test('settlement/outcome entries add no model steps', () => {
60
+ seq = 0;
61
+ const entries = [
62
+ entry('claude-5', 'anthropic', 'api'),
63
+ entry('claude-5', 'anthropic', 'api', { entryType: 'settlement', actualCents: 9 }),
64
+ ];
65
+ const w = workloadProvenance(entries, { verified: true, entries: 2 }).workloads[0];
66
+ assert.equal(w.calls, 1);
67
+ assert.equal(w.swaps.length, 0);
68
+ });