@spendguard/sdk 0.5.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.
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Root of the SpendGuard error hierarchy. Every error thrown by the SDK
3
+ * inherits from this class so adapters can route with one `instanceof` check.
4
+ */
5
+ declare class SpendGuardError extends Error {
6
+ name: string;
7
+ constructor(message: string, opts?: {
8
+ cause?: unknown;
9
+ });
10
+ }
11
+ /**
12
+ * Constructor-time configuration error. Thrown synchronously from
13
+ * `new SpendGuardClient(...)` or `SpendGuardClient.fromEnv()` when:
14
+ * - `socketPath` is missing and `SPENDGUARD_SOCKET_PATH` /
15
+ * `SPENDGUARD_SIDECAR_UDS` are unset.
16
+ * - `tenantId` is missing and `SPENDGUARD_TENANT_ID` is unset.
17
+ * - `otelTracer` and `onSpan` are both provided (mutually exclusive).
18
+ * - `SPENDGUARD_DECISION_TIMEOUT_MS` is not a finite non-negative integer.
19
+ */
20
+ declare class SpendGuardConfigError extends SpendGuardError {
21
+ name: string;
22
+ }
23
+ /**
24
+ * Connection-layer failure: the UDS / gRPC channel could not be opened,
25
+ * the sidecar is unreachable, the deadline expired before reply, or the
26
+ * server cancelled mid-flight.
27
+ *
28
+ * Adapters typically map `SidecarUnavailable` → 503 upstream (the `statusCode`
29
+ * is the conventional HTTP analog; the SDK itself does not speak HTTP).
30
+ *
31
+ * Raised by `mapGrpcStatusToError` (SLICE 5) for:
32
+ * - gRPC `UNAVAILABLE` — channel torn down / sidecar gone.
33
+ * - gRPC `DEADLINE_EXCEEDED` — request timed out before reply.
34
+ * - gRPC `CANCELLED` — server (or client) cancelled mid-flight.
35
+ *
36
+ * The original `RpcError` is preserved in `cause` so adapters can dig into
37
+ * the underlying gRPC trailer metadata if they need to.
38
+ *
39
+ * SLICE 8 wires retry classification for the same cluster into this class
40
+ * via `_classify_rpc_error` parity.
41
+ */
42
+ declare class SidecarUnavailable extends SpendGuardError {
43
+ name: string;
44
+ readonly statusCode: 503;
45
+ }
46
+ /**
47
+ * Connection-not-established error. Thrown when the caller invokes an RPC
48
+ * before `connect()` (or attempts to read `sessionId` before `handshake()`).
49
+ */
50
+ declare class SpendGuardConnectionError extends SpendGuardError {
51
+ name: string;
52
+ }
53
+ /**
54
+ * Handshake-protocol failure: the sidecar replied with a protocol-version
55
+ * mismatch, a capability level the adapter cannot satisfy, or an unsigned
56
+ * announcement when one was required.
57
+ *
58
+ * SLICE 4 wires the handshake payload mapping; the class itself is locked here.
59
+ */
60
+ declare class HandshakeError extends SpendGuardError {
61
+ name: string;
62
+ }
63
+ /**
64
+ * Init payload for `DecisionDenied` (and its subclasses). All decision-typed
65
+ * errors carry the audit chain coordinates so adapters can correlate with the
66
+ * sidecar's emitted CloudEvents.
67
+ */
68
+ interface DecisionDeniedInit {
69
+ decisionId: string;
70
+ reasonCodes?: readonly string[];
71
+ auditDecisionEventId?: string;
72
+ matchedRuleIds?: readonly string[];
73
+ }
74
+ /**
75
+ * Decision-time denial. Thrown when the sidecar returns a non-`CONTINUE` /
76
+ * non-`DEGRADE` outcome. Subclasses discriminate by terminal kind:
77
+ * `DecisionStopped` (STOP / STOP_RUN_PROJECTION), `DecisionSkipped` (SKIP),
78
+ * `ApprovalRequired` (REQUIRE_APPROVAL).
79
+ *
80
+ * SLICE 4 wires the mapping from `DecisionResponse.decision` enum to the
81
+ * concrete subclass; this base class is locked here.
82
+ */
83
+ declare class DecisionDenied extends SpendGuardError {
84
+ name: string;
85
+ readonly statusCode: 403;
86
+ readonly decisionId: string;
87
+ readonly reasonCodes: readonly string[];
88
+ readonly auditDecisionEventId?: string;
89
+ readonly matchedRuleIds: readonly string[];
90
+ constructor(message: string, init: DecisionDeniedInit, opts?: {
91
+ cause?: unknown;
92
+ });
93
+ }
94
+ /**
95
+ * Sidecar returned `STOP` or `STOP_RUN_PROJECTION`. Run loop must unwind
96
+ * without further LLM / tool calls. `reasonCodes` carry the contract-DSL
97
+ * matched-rule outcomes (`projection.run.over_threshold`, etc.).
98
+ */
99
+ declare class DecisionStopped extends DecisionDenied {
100
+ name: string;
101
+ }
102
+ /**
103
+ * Sidecar returned `SKIP` — current step / call must be skipped but the run
104
+ * may continue. Treated as a terminal decision for the in-flight boundary
105
+ * only.
106
+ */
107
+ declare class DecisionSkipped extends DecisionDenied {
108
+ name: string;
109
+ }
110
+ /**
111
+ * Init payload for `ApprovalRequired`. Carries the approval bookkeeping the
112
+ * adapter needs to drive the human-in-the-loop resume round-trip.
113
+ */
114
+ interface ApprovalRequiredInit extends DecisionDeniedInit {
115
+ approvalRequestId: string;
116
+ approverRole?: string;
117
+ tenantId?: string;
118
+ }
119
+ /**
120
+ * Minimal client shape that `ApprovalRequired.resume()` needs. Avoids a
121
+ * circular import between `errors.ts` and `client.ts`; the real
122
+ * `SpendGuardClient` satisfies this contract.
123
+ */
124
+ interface ApprovalResumeClient {
125
+ resumeAfterApproval(req: {
126
+ approvalId: string;
127
+ tenantId: string;
128
+ decisionId: string;
129
+ }): Promise<unknown>;
130
+ }
131
+ /**
132
+ * Sidecar returned `REQUIRE_APPROVAL`. The adapter must surface the approval
133
+ * to a human operator (Slack / control plane / etc.) and call
134
+ * `await err.resume(client)` after the operator acts.
135
+ *
136
+ * `tenantId` is carried explicitly so the resume round-trip can scope the
137
+ * GetApprovalForResume lookup against tenant (per Python `client.py` round-2
138
+ * #9 part 2 PR 9d).
139
+ */
140
+ declare class ApprovalRequired extends DecisionDenied {
141
+ name: string;
142
+ readonly approvalRequestId: string;
143
+ readonly approverRole?: string;
144
+ readonly tenantId?: string;
145
+ constructor(message: string, init: ApprovalRequiredInit, opts?: {
146
+ cause?: unknown;
147
+ });
148
+ /**
149
+ * Resume the decision after a human operator has acted. Delegates to
150
+ * `client.resumeAfterApproval(...)`.
151
+ *
152
+ * @throws ApprovalDeniedError when the operator rejected.
153
+ * @throws ApprovalLapsedError when the approval expired / was cancelled.
154
+ * @throws ApprovalBundleHotReloadedError when bundle rotated mid-approval.
155
+ */
156
+ resume(client: ApprovalResumeClient): Promise<unknown>;
157
+ }
158
+ /**
159
+ * Operator explicitly denied the approval. Carries `approverSubject` and
160
+ * `approverReason` from the audit row so the adapter can surface them upstream.
161
+ *
162
+ * Reason codes are forced to include `approval_denied` as the first entry per
163
+ * Python parity.
164
+ */
165
+ declare class ApprovalDeniedError extends DecisionDenied {
166
+ name: string;
167
+ readonly approverSubject?: string;
168
+ readonly approverReason?: string;
169
+ constructor(message: string, init: DecisionDeniedInit & {
170
+ approverSubject?: string;
171
+ approverReason?: string;
172
+ }, opts?: {
173
+ cause?: unknown;
174
+ });
175
+ }
176
+ /**
177
+ * Approval state was non-terminal at the time the adapter polled. State is
178
+ * one of `pending` / `expired` / `cancelled` / `unknown`; the reason codes
179
+ * include `approval_lapsed_<state>` per Python parity.
180
+ */
181
+ declare class ApprovalLapsedError extends DecisionDenied {
182
+ name: string;
183
+ readonly state: "pending" | "expired" | "cancelled" | "unknown";
184
+ constructor(message: string, init: DecisionDeniedInit & {
185
+ state: "pending" | "expired" | "cancelled" | "unknown";
186
+ }, opts?: {
187
+ cause?: unknown;
188
+ });
189
+ }
190
+ /**
191
+ * Approval was issued under one contract-bundle hash but the sidecar's
192
+ * currently-installed bundle differs. The adapter MUST refuse to resume —
193
+ * re-evaluation may produce a different decision that the operator did not
194
+ * see when they approved.
195
+ *
196
+ * Raised by `mapGrpcStatusToError` (SLICE 5) when a release / reserve trip
197
+ * surfaces gRPC `FAILED_PRECONDITION` with the reason-code metadata field
198
+ * set to `BUNDLE_HOT_RELOADED`. When the bundle hashes are not carried in
199
+ * trailer metadata the constructor receives empty strings; adapters should
200
+ * treat that as "hashes unavailable" and re-fetch from the handshake cache.
201
+ */
202
+ declare class ApprovalBundleHotReloadedError extends SpendGuardError {
203
+ name: string;
204
+ readonly originalBundleHash: string;
205
+ readonly currentBundleHash: string;
206
+ constructor(message: string, init: {
207
+ originalBundleHash: string;
208
+ currentBundleHash: string;
209
+ }, opts?: {
210
+ cause?: unknown;
211
+ });
212
+ }
213
+ /**
214
+ * Adapter's `publish_effect` step failed to apply the mutation patch. The
215
+ * adapter should call `client.safeConfirmApplyFailed(...)` to anchor the
216
+ * rollback in the audit chain.
217
+ *
218
+ * Raised by `mapGrpcStatusToError` (SLICE 5) when a release / reserve trip
219
+ * surfaces gRPC `FAILED_PRECONDITION` with the reason-code metadata field set
220
+ * to `IDEMPOTENCY_CONFLICT` (replay of a release with a different request
221
+ * body), `BUDGET_EXCEEDED` (reservation can no longer be released against the
222
+ * current ledger state), or any unknown FAILED_PRECONDITION reason — the
223
+ * latter is the conservative default so adapters never see a bare
224
+ * `SpendGuardError` for FAILED_PRECONDITION trips.
225
+ */
226
+ declare class MutationApplyFailed extends SpendGuardError {
227
+ name: string;
228
+ }
229
+ /**
230
+ * Decision-time wrapper for arbitrary post-decision errors that don't fit the
231
+ * `DecisionDenied` hierarchy. Provided as a discriminated subtype so adapters
232
+ * that want a single `catch (e: SpendGuardDecisionError)` block work without
233
+ * pattern-matching on the structural `DecisionDenied` chain.
234
+ *
235
+ * SLICE 4 may extend this with concrete subclasses; in SLICE 3 it exists only
236
+ * as the discriminated-subtype contract the slice doc requires.
237
+ */
238
+ declare class SpendGuardDecisionError extends SpendGuardError {
239
+ name: string;
240
+ }
241
+ /**
242
+ * Thrown by `PricingLookup.usdMicrosForCall` when a token bucket has a
243
+ * non-zero count but NO configured price for either the specific token kind
244
+ * or the default kind.
245
+ *
246
+ * Fail-closed rationale: the previous behavior silently coerced the missing
247
+ * price to `0`, booking a `$0` charge for the call and under-counting the
248
+ * budget — exactly the under-charge failure mode the guardrail exists to
249
+ * prevent. Unknown / new models (the most likely to be mispriced) were
250
+ * precisely the ones that escaped accounting. Refusing loudly forces the
251
+ * adapter to supply a price (or explicitly handle the gap) rather than leak
252
+ * spend. Carries the offending `provider` / `model` / `tokenKind` so the
253
+ * caller can pinpoint the missing pricing-table row.
254
+ */
255
+ declare class PricingMissingError extends SpendGuardError {
256
+ name: string;
257
+ readonly provider: string;
258
+ readonly model: string;
259
+ readonly tokenKind: string;
260
+ constructor(args: {
261
+ provider: string;
262
+ model: string;
263
+ tokenKind: string;
264
+ }, opts?: {
265
+ cause?: unknown;
266
+ });
267
+ }
268
+
269
+ export { ApprovalBundleHotReloadedError, ApprovalDeniedError, ApprovalLapsedError, ApprovalRequired, type ApprovalRequiredInit, type ApprovalResumeClient, DecisionDenied, type DecisionDeniedInit, DecisionSkipped, DecisionStopped, HandshakeError, MutationApplyFailed, PricingMissingError, SidecarUnavailable, SpendGuardConfigError, SpendGuardConnectionError, SpendGuardDecisionError, SpendGuardError };
package/dist/errors.js ADDED
@@ -0,0 +1,148 @@
1
+ // src/errors.ts
2
+ var SpendGuardError = class extends Error {
3
+ name = "SpendGuardError";
4
+ constructor(message, opts) {
5
+ super(message);
6
+ if (opts?.cause !== void 0) {
7
+ this.cause = opts.cause;
8
+ }
9
+ Object.defineProperty(this, "name", {
10
+ value: this.name,
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true
14
+ });
15
+ }
16
+ };
17
+ var SpendGuardConfigError = class extends SpendGuardError {
18
+ name = "SpendGuardConfigError";
19
+ };
20
+ var SidecarUnavailable = class extends SpendGuardError {
21
+ name = "SidecarUnavailable";
22
+ statusCode = 503;
23
+ };
24
+ var SpendGuardConnectionError = class extends SpendGuardError {
25
+ name = "SpendGuardConnectionError";
26
+ };
27
+ var HandshakeError = class extends SpendGuardError {
28
+ name = "HandshakeError";
29
+ };
30
+ var DecisionDenied = class extends SpendGuardError {
31
+ name = "DecisionDenied";
32
+ statusCode = 403;
33
+ decisionId;
34
+ reasonCodes;
35
+ auditDecisionEventId;
36
+ matchedRuleIds;
37
+ constructor(message, init, opts) {
38
+ super(message, opts);
39
+ this.decisionId = init.decisionId;
40
+ this.reasonCodes = init.reasonCodes ?? [];
41
+ if (init.auditDecisionEventId !== void 0) {
42
+ this.auditDecisionEventId = init.auditDecisionEventId;
43
+ }
44
+ this.matchedRuleIds = init.matchedRuleIds ?? [];
45
+ }
46
+ };
47
+ var DecisionStopped = class extends DecisionDenied {
48
+ name = "DecisionStopped";
49
+ };
50
+ var DecisionSkipped = class extends DecisionDenied {
51
+ name = "DecisionSkipped";
52
+ };
53
+ var ApprovalRequired = class extends DecisionDenied {
54
+ name = "ApprovalRequired";
55
+ approvalRequestId;
56
+ approverRole;
57
+ tenantId;
58
+ constructor(message, init, opts) {
59
+ super(message, init, opts);
60
+ this.approvalRequestId = init.approvalRequestId;
61
+ if (init.approverRole !== void 0) {
62
+ this.approverRole = init.approverRole;
63
+ }
64
+ if (init.tenantId !== void 0) {
65
+ this.tenantId = init.tenantId;
66
+ }
67
+ }
68
+ /**
69
+ * Resume the decision after a human operator has acted. Delegates to
70
+ * `client.resumeAfterApproval(...)`.
71
+ *
72
+ * @throws ApprovalDeniedError when the operator rejected.
73
+ * @throws ApprovalLapsedError when the approval expired / was cancelled.
74
+ * @throws ApprovalBundleHotReloadedError when bundle rotated mid-approval.
75
+ */
76
+ async resume(client) {
77
+ return client.resumeAfterApproval({
78
+ approvalId: this.approvalRequestId,
79
+ tenantId: this.tenantId ?? "",
80
+ decisionId: this.decisionId
81
+ });
82
+ }
83
+ };
84
+ var ApprovalDeniedError = class extends DecisionDenied {
85
+ name = "ApprovalDeniedError";
86
+ approverSubject;
87
+ approverReason;
88
+ constructor(message, init, opts) {
89
+ super(
90
+ message,
91
+ { ...init, reasonCodes: ["approval_denied", ...init.reasonCodes ?? []] },
92
+ opts
93
+ );
94
+ if (init.approverSubject !== void 0) {
95
+ this.approverSubject = init.approverSubject;
96
+ }
97
+ if (init.approverReason !== void 0) {
98
+ this.approverReason = init.approverReason;
99
+ }
100
+ }
101
+ };
102
+ var ApprovalLapsedError = class extends DecisionDenied {
103
+ name = "ApprovalLapsedError";
104
+ state;
105
+ constructor(message, init, opts) {
106
+ super(
107
+ message,
108
+ { ...init, reasonCodes: [`approval_lapsed_${init.state}`, ...init.reasonCodes ?? []] },
109
+ opts
110
+ );
111
+ this.state = init.state;
112
+ }
113
+ };
114
+ var ApprovalBundleHotReloadedError = class extends SpendGuardError {
115
+ name = "ApprovalBundleHotReloadedError";
116
+ originalBundleHash;
117
+ currentBundleHash;
118
+ constructor(message, init, opts) {
119
+ super(message, opts);
120
+ this.originalBundleHash = init.originalBundleHash;
121
+ this.currentBundleHash = init.currentBundleHash;
122
+ }
123
+ };
124
+ var MutationApplyFailed = class extends SpendGuardError {
125
+ name = "MutationApplyFailed";
126
+ };
127
+ var SpendGuardDecisionError = class extends SpendGuardError {
128
+ name = "SpendGuardDecisionError";
129
+ };
130
+ var PricingMissingError = class extends SpendGuardError {
131
+ name = "PricingMissingError";
132
+ provider;
133
+ model;
134
+ tokenKind;
135
+ constructor(args, opts) {
136
+ super(
137
+ `no price configured for provider=${JSON.stringify(args.provider)} model=${JSON.stringify(args.model)} tokenKind=${JSON.stringify(args.tokenKind)} (neither the specific kind nor the default kind has a price); refusing to charge $0 \u2014 supply a price for this model or handle PricingMissingError`,
138
+ opts
139
+ );
140
+ this.provider = args.provider;
141
+ this.model = args.model;
142
+ this.tokenKind = args.tokenKind;
143
+ }
144
+ };
145
+
146
+ export { ApprovalBundleHotReloadedError, ApprovalDeniedError, ApprovalLapsedError, ApprovalRequired, DecisionDenied, DecisionSkipped, DecisionStopped, HandshakeError, MutationApplyFailed, PricingMissingError, SidecarUnavailable, SpendGuardConfigError, SpendGuardConnectionError, SpendGuardDecisionError, SpendGuardError };
147
+ //# sourceMappingURL=errors.js.map
148
+ //# sourceMappingURL=errors.js.map
package/dist/ids.d.ts ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Mint a UUIDv7 per RFC 9562 §5.7.
3
+ *
4
+ * Layout (128 bits, big-endian):
5
+ * - 48 bits unix epoch ms
6
+ * - 4 bits version (0b0111)
7
+ * - 12 bits random
8
+ * - 2 bits variant (0b10)
9
+ * - 62 bits random
10
+ *
11
+ * Two calls within the same ms are time-ordered to ms precision but
12
+ * randomised within the ms slot. Returned in canonical 36-char hex form
13
+ * (`xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx`).
14
+ */
15
+ declare function newUuid7(): string;
16
+ /**
17
+ * Deterministic idempotency key for a trigger boundary.
18
+ *
19
+ * Same `(tenantId, sessionId, runId, stepId, llmCallId, trigger)` →
20
+ * same key. A retry of the SAME logical step within an agent framework's
21
+ * run loop MUST reuse this so the sidecar's cache short-circuits + the
22
+ * ledger's `UNIQUE` returns Replay.
23
+ *
24
+ * Returns `"sg-"` + 32 hex chars (128-bit BLAKE2b digest).
25
+ *
26
+ * **Cross-language gate (P0 — review-standards §2.2)**: the TS output for
27
+ * any given input is byte-identical to Python
28
+ * `derive_idempotency_key(**kwargs)` (which uses `hashlib.blake2b(...,
29
+ * digest_size=16)`). The implementation here calls the audited
30
+ * `@noble/hashes` BLAKE2b primitive with `dkLen: 16` — the same 128-bit
31
+ * personalised-output mode Python's hashlib exposes. Verified byte-for-byte
32
+ * against `derive_idempotency_key(**kwargs)` for 7 fixtures in
33
+ * `tests/ids.test.ts` (FX1–FX7).
34
+ */
35
+ declare function deriveIdempotencyKey(args: {
36
+ tenantId: string;
37
+ sessionId: string;
38
+ runId: string;
39
+ stepId: string;
40
+ llmCallId: string;
41
+ trigger: string;
42
+ }): string;
43
+ /**
44
+ * Derive a stable UUID (v4-shaped) from a content signature + scope namespace.
45
+ *
46
+ * Used for content-derived `decision_id` / `llm_call_id` slots where a retry
47
+ * of the SAME `Model.request()` MUST produce the same UUID across retries.
48
+ * Scope ("decision_id", "llm_call_id", etc.) namespaces the output so
49
+ * different identifier slots never collide for the same signature.
50
+ *
51
+ * Same `(signature, scope)` → same UUID. Byte-equivalent to Python
52
+ * `spendguard.ids.derive_uuid_from_signature(signature, scope=scope)`:
53
+ * BLAKE2b-128 over `f"{scope}|{signature}"`, with RFC 4122 v4 version (0x4)
54
+ * and variant (0b10) nibbles overlaid on bytes 6 + 8. Verified against
55
+ * 5 fixtures in `tests/ids.test.ts` (FXU1–FXU5).
56
+ */
57
+ declare function deriveUuidFromSignature(signature: string, args: {
58
+ scope: string;
59
+ }): string;
60
+ /**
61
+ * Sidecar workload identity hint.
62
+ *
63
+ * The adapter asserts this in handshake; the sidecar verifies against
64
+ * SO_PEERCRED + signed manifest. Reads `SPENDGUARD_WORKLOAD_INSTANCE_ID`
65
+ * env var; returns empty string when unset.
66
+ */
67
+ declare function workloadInstanceId(): string;
68
+
69
+ export { deriveIdempotencyKey, deriveUuidFromSignature, newUuid7, workloadInstanceId };
package/dist/ids.js ADDED
@@ -0,0 +1,61 @@
1
+ import { randomBytes } from 'crypto';
2
+ import { blake2b } from '@noble/hashes/blake2';
3
+
4
+ // src/ids.ts
5
+ function newUuid7() {
6
+ const tsMs = BigInt(Date.now()) & (1n << 48n) - 1n;
7
+ const randA = randomBytes(2).readUInt16BE(0) & 4095;
8
+ const randB = randomBytes(8);
9
+ randB[0] = randB[0] & 63 | 128;
10
+ const hi = tsMs << 16n | BigInt(randA);
11
+ const buf = Buffer.alloc(16);
12
+ buf.writeBigUInt64BE(hi, 0);
13
+ buf[6] = buf[6] & 15 | 112;
14
+ randB.copy(buf, 8);
15
+ return [
16
+ buf.toString("hex", 0, 4),
17
+ buf.toString("hex", 4, 6),
18
+ buf.toString("hex", 6, 8),
19
+ buf.toString("hex", 8, 10),
20
+ buf.toString("hex", 10, 16)
21
+ ].join("-");
22
+ }
23
+ var FIELD_SEP = "";
24
+ function deriveIdempotencyKey(args) {
25
+ const canonical = [
26
+ "v1",
27
+ args.tenantId,
28
+ args.sessionId,
29
+ args.runId,
30
+ args.stepId,
31
+ args.llmCallId,
32
+ args.trigger
33
+ ].join(FIELD_SEP);
34
+ const digest = blake2b(new TextEncoder().encode(canonical), { dkLen: 16 });
35
+ let hex = "";
36
+ for (let i = 0; i < digest.length; i++) {
37
+ hex += digest[i].toString(16).padStart(2, "0");
38
+ }
39
+ return `sg-${hex}`;
40
+ }
41
+ function deriveUuidFromSignature(signature, args) {
42
+ const canonical = `${args.scope}|${signature}`;
43
+ const digest = blake2b(new TextEncoder().encode(canonical), { dkLen: 16 });
44
+ const buf = Buffer.from(digest);
45
+ buf[6] = buf[6] & 15 | 64;
46
+ buf[8] = buf[8] & 63 | 128;
47
+ return [
48
+ buf.toString("hex", 0, 4),
49
+ buf.toString("hex", 4, 6),
50
+ buf.toString("hex", 6, 8),
51
+ buf.toString("hex", 8, 10),
52
+ buf.toString("hex", 10, 16)
53
+ ].join("-");
54
+ }
55
+ function workloadInstanceId() {
56
+ return process.env.SPENDGUARD_WORKLOAD_INSTANCE_ID ?? "";
57
+ }
58
+
59
+ export { deriveIdempotencyKey, deriveUuidFromSignature, newUuid7, workloadInstanceId };
60
+ //# sourceMappingURL=ids.js.map
61
+ //# sourceMappingURL=ids.js.map
@@ -0,0 +1,61 @@
1
+ import { R as RunProjectionPolicy } from './cache-DOnw8QtJ.js';
2
+ export { A as ApplyFailedRequest, B as BudgetClaim, C as ClaimEstimate, a as CommitEstimatedRequest, b as CommitSessionDeltaOutcome, c as CommitSessionDeltaRequest, D as DEFAULT_CACHE_MAX_ENTRIES, d as DEFAULT_CACHE_TTL_MS, e as DEFAULT_CAPABILITY_LEVEL, f as DEFAULT_DECISION_TIMEOUT_MS, g as DEFAULT_HANDSHAKE_TIMEOUT_MS, h as DEFAULT_MAX_PENDING_SESSION_DELTAS, i as DEFAULT_PROTOCOL_VERSION, j as DEFAULT_PUBLISH_TIMEOUT_MS, k as DEFAULT_TRACE_TIMEOUT_MS, l as DecisionOutcome, E as EmitLlmCallPostRequest, H as HandshakeOutcome, I as IdempotencyCache, m as InMemoryIdempotencyCache, n as InMemoryIdempotencyCacheOptions, N as NoopIdempotencyCache, P as PendingSessionDelta, o as PricingFreeze, p as PublishOutcomeRequest, Q as QueryBudgetRequest, q as QueryBudgetResult, r as ReleaseOutcome, s as ReleaseRequest, t as ReleaseSessionOutcome, u as ReleaseSessionRequest, v as ReserveRequest, w as ReserveSessionOutcome, x as ReserveSessionRequest, y as ResolvedConfig, z as ResumeAfterApprovalRequest, S as SessionCommitOutcome, F as SessionDeltaCommitClient, G as SessionDeltaCommitInput, J as SessionPendingDeltaLimitError, K as SessionReleaseClient, L as SessionReleaseInput, M as SessionReservationHandle, O as SessionReservationHandleError, T as SessionReservationHandleOptions, U as SessionReservationHandleSnapshot, V as SessionReservationReleasedError, W as SessionReservationReplayMismatchError, X as SpanRecord, Y as SpendGuardClient, Z as SpendGuardClientConfig, _ as SpendGuardClientOptions, $ as UnitRef, a0 as buildCommitSessionDeltaRequest, a1 as buildReleaseSessionRequest, a2 as buildReserveSessionRequest } from './cache-DOnw8QtJ.js';
3
+ export { ApprovalBundleHotReloadedError, ApprovalDeniedError, ApprovalLapsedError, ApprovalRequired, ApprovalRequiredInit, ApprovalResumeClient, DecisionDenied, DecisionDeniedInit, DecisionSkipped, DecisionStopped, HandshakeError, MutationApplyFailed, PricingMissingError, SidecarUnavailable, SpendGuardConfigError, SpendGuardConnectionError, SpendGuardDecisionError, SpendGuardError } from './errors.js';
4
+ export { deriveIdempotencyKey, deriveUuidFromSignature, newUuid7, workloadInstanceId } from './ids.js';
5
+ export { computePromptHash } from './promptHash.js';
6
+ export { PriceKey, PriceTable, PricingLookup, USD_MICROS_PER_USD } from './pricing.js';
7
+ export { RunPlan, currentRunPlan, withRunPlan } from './runPlan.js';
8
+ export { OtelAttributeValue, OtelAttributes, SPENDGUARD_OTEL_ATTR, setOtelSpanAttributes, withOtelSpan } from './otel.js';
9
+ export { RpcErrorClassification, RunWithRetryOptions, TRANSIENT_STATUS_CODES, classifyRpcError, runWithRetry } from './retry.js';
10
+ import '@grpc/grpc-js';
11
+ import '@opentelemetry/api';
12
+ import './adapter-D9T3yEEw.js';
13
+ import '@protobuf-ts/runtime-rpc';
14
+ import '@protobuf-ts/runtime';
15
+
16
+ /**
17
+ * Default UDS path used by `SpendGuardClient.fromEnv()` when neither
18
+ * `SPENDGUARD_SOCKET_PATH` nor `SPENDGUARD_SIDECAR_UDS` is set. Matches the
19
+ * sidecar Helm chart's default volume mount per slice spec.
20
+ */
21
+ declare const DEFAULT_SOCKET_PATH: "/var/run/spendguard/adapter.sock";
22
+ /**
23
+ * Resolved env-var snapshot. Every field is optional because the env may be
24
+ * sparsely populated; the constructor combines this snapshot with the explicit
25
+ * options and validates the union.
26
+ *
27
+ * `runProjectionDefault` is typed as the same `RunProjectionPolicy` literal
28
+ * union surfaced by `SpendGuardClientConfig` so the env path produces the
29
+ * same shape the explicit-options path produces (no `string` vs literal-union
30
+ * drift between the two entry points).
31
+ */
32
+ interface ResolvedEnvConfig {
33
+ socketPath?: string;
34
+ tenantId?: string;
35
+ workloadInstanceId?: string;
36
+ decisionTimeoutMs?: number;
37
+ handshakeTimeoutMs?: number;
38
+ runProjectionDefault?: RunProjectionPolicy;
39
+ disabled?: boolean;
40
+ /**
41
+ * Deployment profile, read verbatim from `SPENDGUARD_PROFILE`. The
42
+ * constructor uses this to gate the env-var disable path: a bare
43
+ * `SPENDGUARD_DISABLE` ONLY takes effect when `SPENDGUARD_PROFILE=demo`,
44
+ * mirroring the Rust signing service's `DisabledSigner::for_profile`
45
+ * (`services/signing/src/lib.rs`) so a single mis-set env var cannot defeat
46
+ * enforcement in production. Lowercased for case-insensitive comparison;
47
+ * `undefined` when unset.
48
+ */
49
+ profile?: string;
50
+ }
51
+
52
+ /**
53
+ * The published `@spendguard/sdk` version. Mirror of the Python SDK
54
+ * `spendguard.__version__` (which lives at `0.5.1` on PyPI as of 2026-05-31).
55
+ *
56
+ * SLICE 10 (`COV_S05_10`) wires the publish pipeline. First npm release is
57
+ * `0.5.0`, chosen to track the Python SDK line (PyPI is at 0.6.0).
58
+ */
59
+ declare const VERSION: "0.5.0";
60
+
61
+ export { DEFAULT_SOCKET_PATH, type ResolvedEnvConfig, RunProjectionPolicy, VERSION };