@nomosprotocol/sdk 1.3.3 → 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/dist/index.d.cts CHANGED
@@ -128,7 +128,10 @@ interface CanResult {
128
128
  }>;
129
129
  /** RECOMMENDED maximum seconds to hold this verdict before re-querying or re-checking the
130
130
  * issuer's revocation list (NOMOS-SPEC-006 §6.2). Advisory; absent when the artifact isn't
131
- * structurally revocable on this deployment — treat absence as "unknown," not "safe forever." */
131
+ * structurally revocable on this deployment — treat absence as "unknown," not "safe forever."
132
+ * Disclosed gap: only ever set on the public authority rail today — client.can() and the
133
+ * presented-artifact rail don't emit it yet, so apply the same "don't hold a stale verdict
134
+ * across a meaningful delay" discipline manually on those two until that's closed. */
132
135
  max_age?: number;
133
136
  /** Present only when your own authenticated artifact answered — that rail's own provenance
134
137
  * (a hash you can store for compliance), not a permanent public transcript like `query_id`. */
@@ -143,8 +146,74 @@ interface CanResult {
143
146
  revoked?: boolean;
144
147
  /** Present only alongside `revoked: true` — the issuer's stated reason. */
145
148
  revocation_reason?: string;
149
+ /** NOMOS-SPEC-007 §5.5 — chain-of-trust rail only. How confidently the chain's own KEY
150
+ * revocation was checked: `"live"` (checked against a real revocation source — always the
151
+ * case on this hosted deployment), `"staple"` (a presenter-supplied freshness staple was
152
+ * used instead — never happens here, since this endpoint always holds its own list), or
153
+ * `"unchecked"` (neither — e.g. the pinned root, which nothing else can vouch for). Omitted
154
+ * (not a guess) on the Exchange/private-artifact rails, where this concept doesn't apply. */
155
+ revocation_checked?: 'live' | 'staple' | 'unchecked';
156
+ }
157
+ interface ConservedStateInfo {
158
+ artifact_id: string;
159
+ scope: string;
160
+ limit_amount: number;
161
+ consumed: number;
162
+ reserved: number;
163
+ available: number;
164
+ /** The raw ledger fact to pass into `can()` as `consumed_before` — already consumed+reserved
165
+ * (outstanding reservations count as spoken-for), not consumed alone. */
166
+ consumed_before: number;
167
+ version: number;
168
+ state_digest: string;
169
+ }
170
+ /** Reserve against a ledger using a real `can()` decision. */
171
+ interface ReserveByQueryParams {
172
+ artifact_id: string;
173
+ scope: string;
174
+ amount: number;
175
+ /** The `query_id` from a real `can()`/`nomos.can()` call that returned AUTHORIZED.
176
+ * `reserve()` looks that decision up itself and derives its own binding from it — this is a
177
+ * lookup key, not a claim you're asserting, and a made-up or already-used one is rejected. */
178
+ query_id: string;
179
+ action: string;
180
+ /** Optimistic-concurrency check: reject unless the ledger's state still matches what you last
181
+ * read via `conservedState.get()`. Omit only when you have no prior read to compare against. */
182
+ expected_state_digest?: string;
183
+ ttl_seconds?: number;
184
+ }
185
+ /** Reserve against a ledger using a NOMOS-SPEC-008 Act: the ledger verifies the Act itself (its
186
+ * own pinned root and audience id, its own `consumed_before`, single-use nonce) and holds exactly
187
+ * `act.action.params.amount` for `act.action.type === 'conserved_state.reserve'`, or nothing. */
188
+ interface ReserveByActParams {
189
+ artifact_id: string;
190
+ scope: string;
191
+ act: Record<string, unknown>;
192
+ ttl_seconds?: number;
193
+ }
194
+ type ReserveParams = ReserveByQueryParams | ReserveByActParams;
195
+ /** The cryptographically-bound reservation object — R-12 §5. */
196
+ interface Reservation {
197
+ reservation_id: string;
198
+ authority_seal: string;
199
+ decision_digest: string;
200
+ agent: string;
201
+ action: string;
202
+ scope: string;
203
+ amount: number;
204
+ state_digest: string;
205
+ expires_at: string;
206
+ status: 'held' | 'settled' | 'released' | 'expired';
146
207
  }
147
- type ChainVerdictDecision = 'ALLOWED' | 'ISSUER_NOT_RECOGNIZED' | 'KEY_REVOKED' | 'SEAL_INVALID' | 'MALFORMED';
208
+ interface ConservedStateReceipt {
209
+ reservation_id: string;
210
+ outcome: 'settled' | 'released';
211
+ amount: number;
212
+ ledger_key: string;
213
+ resolved_at: string;
214
+ reference?: string;
215
+ }
216
+ type ChainVerdictDecision = 'ALLOWED' | 'ISSUER_NOT_RECOGNIZED' | 'KEY_REVOKED' | 'CERTIFICATE_REVOKED' | 'OUT_OF_SCOPE' | 'SEAL_INVALID' | 'MALFORMED';
148
217
  interface KeyCertificate {
149
218
  parent_kid: string;
150
219
  child_kid: string;
@@ -179,6 +248,15 @@ interface VerifyChainResult {
179
248
  reason?: string;
180
249
  /** Present only when decision === 'KEY_REVOKED'. */
181
250
  revoked_kid?: string;
251
+ /** Present only when decision === 'CERTIFICATE_REVOKED'. */
252
+ revoked_fingerprint?: string;
253
+ /** Present only when decision === 'OUT_OF_SCOPE' — which scope dimension failed. */
254
+ dimension?: string;
255
+ /** Present on ALLOWED and OUT_OF_SCOPE — the resolved chain's accumulated scope. */
256
+ effective_scope?: string;
257
+ /** Present only when decision === 'ALLOWED' (NOMOS-SPEC-007 §5.5). See CanResult's field of
258
+ * the same name for the full tri-state explanation. */
259
+ revocation_checked?: 'live' | 'staple' | 'unchecked';
182
260
  /** Human-readable detail for any non-ALLOWED decision. */
183
261
  detail?: string;
184
262
  }
@@ -509,32 +587,107 @@ declare class Webhooks {
509
587
  verifySignature(payload: string | Uint8Array, signature: string, secret: string, toleranceSeconds?: number): Promise<boolean>;
510
588
  }
511
589
 
590
+ /**
591
+ * The state layer for a conserved (depleting) budget — R-12, a design in progress
592
+ * (docs/design-notes/conserved-state-authority.md in the platform repo; public mirror:
593
+ * computableauthority.com/research/conserved-state-authority). PROTOTYPE, not a numbered spec.
594
+ *
595
+ * `can()` stays exactly what it always was: pure, stateless, given facts and returning a
596
+ * verdict. Nothing here changes that. This resource is what a caller consults BEFORE calling
597
+ * `can()` (to build the `consumed_before` fact) and commits to AFTER `can()` returns AUTHORIZED
598
+ * and the caller has actually decided to act — never at `can()` time itself (repeatedly calling
599
+ * `can()` while deciding must never itself burn the budget).
600
+ *
601
+ * @example
602
+ * const state = await client.conservedState.get('supplier-budget-2026', 'supplier:acme');
603
+ * const verdict = await client.can({ artifact_id: 'supplier-budget-2026', consumed_before: state.consumed_before });
604
+ * if (verdict.allowed) {
605
+ * const reservation = await client.conservedState.reserve({
606
+ * artifact_id: 'supplier-budget-2026', scope: 'supplier:acme',
607
+ * amount: 5000, query_id: verdict.query_id!, action: 'payments.transfer',
608
+ * });
609
+ * try {
610
+ * // ... perform the real action ...
611
+ * await client.conservedState.settle(reservation.reservation_id);
612
+ * } catch {
613
+ * await client.conservedState.release(reservation.reservation_id);
614
+ * }
615
+ * }
616
+ */
617
+ declare class ConservedState {
618
+ private readonly http;
619
+ constructor(http: HttpClient);
620
+ /**
621
+ * Read a ledger's current balance — no auth required, same posture as `can()`'s own keyless
622
+ * rail (reading how much budget remains is no more sensitive than reading a verdict).
623
+ */
624
+ get(artifactId: string, scope: string, opts?: RequestOptions): Promise<WithLastResponse<ConservedStateInfo>>;
625
+ /**
626
+ * Commit capacity against a REAL, AUTHORIZED `can()` decision. Authenticated — unlike `can()`,
627
+ * spending against a shared budget requires an API key or session; `agent` on the returned
628
+ * reservation traces to that authenticated identity, never a value you pass in.
629
+ *
630
+ * Throws (via the SDK's standard NomosError hierarchy; `err.details?.code` carries the
631
+ * server's own code): `DECISION_NOT_FOUND` (the query_id names no real query), `DECISION_NOT
632
+ * _AUTHORIZED` (that decision didn't verdict AUTHORIZED for this artifact), `DECISION_ALREADY
633
+ * _USED` (already bound to a reservation — one decision authorizes one, ever), `STATE_CONFLICT`
634
+ * (a stale `expected_state_digest`), `INSUFFICIENT_CAPACITY` (`err.details.requested`/
635
+ * `.available` carry the numbers), or `NO_LEDGER` (nobody has declared this budget yet).
636
+ *
637
+ * Passing `{ artifact_id, scope, act }` instead authorizes the hold with a NOMOS-SPEC-008 Act.
638
+ * Extra codes: `ACT_NOT_ACCEPTED` (the ledger has no Act binding) and `ACT_REJECTED`, with the
639
+ * verifier's full answer in `err.details.result` — for `INCOMPLETE` it names the open rules and
640
+ * the facts whose testimony would decide them.
641
+ */
642
+ reserve(params: ReserveParams, opts?: RequestOptions): Promise<WithLastResponse<Reservation>>;
643
+ /**
644
+ * The reserved action succeeded — moves the hold into real, settled spend. Idempotent when
645
+ * you pass the same `reference` again for a retry (e.g. after a network timeout): you get the
646
+ * ORIGINAL receipt back, never a double-charge.
647
+ */
648
+ settle(reservationId: string, reference?: string, opts?: RequestOptions): Promise<WithLastResponse<ConservedStateReceipt>>;
649
+ /**
650
+ * The reserved action failed, or you decided not to act — gives the hold's capacity back
651
+ * without ever touching what's actually been spent.
652
+ */
653
+ release(reservationId: string, reference?: string, opts?: RequestOptions): Promise<WithLastResponse<ConservedStateReceipt>>;
654
+ }
655
+
512
656
  type NomosErrorCode = 'authentication_error' | 'authorization_error' | 'rate_limit_error' | 'validation_error' | 'not_found' | 'api_error' | 'network_error' | 'webhook_signature_error' | 'payment_required' | 'issuer_not_trusted';
513
657
  declare class NomosError extends Error {
514
658
  readonly code: NomosErrorCode;
515
659
  readonly status?: number;
516
660
  readonly requestId?: string;
517
- constructor(message: string, code: NomosErrorCode, status?: number, requestId?: string);
661
+ /** The full parsed JSON response body, when the server sent one — every field it put there
662
+ * (its own `code`, and anything endpoint-specific like `requested`/`available` on a
663
+ * conserved-state capacity rejection, or `price_usd`/`purchase_url` before those got a
664
+ * dedicated error class). `NomosError`'s own `code` above is the SDK's fixed HTTP-status
665
+ * taxonomy; `details?.code` is the SERVER's own, finer-grained application code — the two
666
+ * are deliberately not conflated into one field. */
667
+ readonly details?: unknown;
668
+ constructor(message: string, code: NomosErrorCode, status?: number, requestId?: string, details?: unknown);
518
669
  }
519
670
  declare class NomosAuthenticationError extends NomosError {
520
- constructor(message?: string, requestId?: string);
671
+ constructor(message?: string, requestId?: string, details?: unknown);
521
672
  }
522
673
  declare class NomosAuthorizationError extends NomosError {
523
- constructor(message?: string, requestId?: string);
674
+ constructor(message?: string, requestId?: string, details?: unknown);
524
675
  }
525
676
  declare class NomosRateLimitError extends NomosError {
526
677
  readonly retryAfter?: number;
527
- constructor(message?: string, retryAfter?: number, requestId?: string);
678
+ constructor(message?: string, retryAfter?: number, requestId?: string, details?: unknown);
528
679
  }
680
+ /** `details` is the parsed response body — kept as its own constructor param (rather than just
681
+ * relying on the base class field) since it long predates NomosError.details and every caller
682
+ * already passes it positionally in this slot; unchanged for backward compatibility. */
529
683
  declare class NomosValidationError extends NomosError {
530
- readonly details?: unknown;
531
684
  constructor(message: string, details?: unknown, requestId?: string);
532
685
  }
533
686
  declare class NomosNotFoundError extends NomosError {
534
- constructor(message: string, requestId?: string);
687
+ constructor(message: string, requestId?: string, details?: unknown);
535
688
  }
536
689
  declare class NomosAPIError extends NomosError {
537
- constructor(message: string, status: number, requestId?: string);
690
+ constructor(message: string, status: number, requestId?: string, details?: unknown);
538
691
  }
539
692
  declare class NomosNetworkError extends NomosError {
540
693
  constructor(message: string);
@@ -551,14 +704,22 @@ declare class NomosPaymentRequiredError extends NomosError {
551
704
  readonly purchaseUrl: string;
552
705
  constructor(message: string, priceUsd: number, purchaseUrl: string, requestId?: string);
553
706
  }
554
- /** can()'s chain-of-trust rail couldn't establish that the presented artifact is trustworthy —
555
- * an unrecognized issuer, a revoked key, a tampered seal, or a malformed presentation. This is
556
- * deliberately NOT a DENIED verdict: "the rules say no" and "we never got far enough to ask the
557
- * rules anything" are different claims, and collapsing them would tell the caller the wrong
558
- * thing to do next (re-presenting a better chain can fix the first case; it can never fix a
559
- * genuinely tampered artifact). `reason` is the underlying chain decision
560
- * (ISSUER_NOT_RECOGNIZED / KEY_REVOKED / SEAL_INVALID / MALFORMED) for callers that need to
561
- * distinguish them. */
707
+ /** can()'s chain-of-trust rail could not establish that the presented artifact carries authority
708
+ * it is entitled to — an unrecognized issuer, a revoked key, a delegation that never covered
709
+ * this artifact, a tampered seal, or a malformed presentation. This is deliberately NOT a DENIED
710
+ * verdict: "the rules say no" and "we never got far enough to ask the rules anything" are
711
+ * different claims, and collapsing them would tell the caller the wrong thing to do next.
712
+ *
713
+ * `reason` carries the underlying chain decision so callers can distinguish them, because the
714
+ * right next action genuinely differs:
715
+ * ISSUER_NOT_RECOGNIZED — presenting a more complete chain may fix this
716
+ * KEY_REVOKED — that key is dead; a different, unrevoked chain might work
717
+ * CERTIFICATE_REVOKED — one delegation was withdrawn; another certificate for the same
718
+ * key may still resolve
719
+ * OUT_OF_SCOPE — the issuer IS recognized but was not delegated authority over this
720
+ * artifact; only a broader (validly issued) delegation fixes it
721
+ * SEAL_INVALID — the artifact itself was altered; nothing about presentation helps
722
+ * MALFORMED — the request shape was wrong (a client-side bug) */
562
723
  declare class NomosIssuerNotTrustedError extends NomosError {
563
724
  readonly reason: string;
564
725
  constructor(message: string, reason: string, requestId?: string);
@@ -596,6 +757,9 @@ declare class Nomos {
596
757
  readonly artifacts: Artifacts;
597
758
  readonly governance: Governance;
598
759
  readonly webhooks: Webhooks;
760
+ /** The R-12 conserved-state (depleting-budget) state layer — PROTOTYPE, not a numbered spec.
761
+ * See resources/conserved-state.ts for the full reserve/settle/release lifecycle. */
762
+ readonly conservedState: ConservedState;
599
763
  /** Whether this instance is using a live or test API key. */
600
764
  readonly mode: 'live' | 'test';
601
765
  static readonly errors: typeof errors;
@@ -645,4 +809,4 @@ declare const nomos: {
645
809
  */
646
810
  declare function verifyChain(params: VerifyChainParams, opts?: VerifyChainOptions): Promise<VerifyChainResult>;
647
811
 
648
- export { type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type CanOptions, type CanParams, type CanResult, type CanVerdict, type ChainVerdictDecision, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type GenerateGovernanceParams, type GenerateGovernanceResult, type KeyCertificate, type ListArtifactsParams, Nomos, NomosAPIError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type RequestOptions, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, type WithLastResponse, can, nomos, verifyChain };
812
+ export { type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type CanOptions, type CanParams, type CanResult, type CanVerdict, type ChainVerdictDecision, type ConservedStateInfo, type ConservedStateReceipt, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type GenerateGovernanceParams, type GenerateGovernanceResult, type KeyCertificate, type ListArtifactsParams, Nomos, NomosAPIError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type RequestOptions, type Reservation, type ReserveByActParams, type ReserveByQueryParams, type ReserveParams, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, type WithLastResponse, can, nomos, verifyChain };
package/dist/index.d.ts CHANGED
@@ -128,7 +128,10 @@ interface CanResult {
128
128
  }>;
129
129
  /** RECOMMENDED maximum seconds to hold this verdict before re-querying or re-checking the
130
130
  * issuer's revocation list (NOMOS-SPEC-006 §6.2). Advisory; absent when the artifact isn't
131
- * structurally revocable on this deployment — treat absence as "unknown," not "safe forever." */
131
+ * structurally revocable on this deployment — treat absence as "unknown," not "safe forever."
132
+ * Disclosed gap: only ever set on the public authority rail today — client.can() and the
133
+ * presented-artifact rail don't emit it yet, so apply the same "don't hold a stale verdict
134
+ * across a meaningful delay" discipline manually on those two until that's closed. */
132
135
  max_age?: number;
133
136
  /** Present only when your own authenticated artifact answered — that rail's own provenance
134
137
  * (a hash you can store for compliance), not a permanent public transcript like `query_id`. */
@@ -143,8 +146,74 @@ interface CanResult {
143
146
  revoked?: boolean;
144
147
  /** Present only alongside `revoked: true` — the issuer's stated reason. */
145
148
  revocation_reason?: string;
149
+ /** NOMOS-SPEC-007 §5.5 — chain-of-trust rail only. How confidently the chain's own KEY
150
+ * revocation was checked: `"live"` (checked against a real revocation source — always the
151
+ * case on this hosted deployment), `"staple"` (a presenter-supplied freshness staple was
152
+ * used instead — never happens here, since this endpoint always holds its own list), or
153
+ * `"unchecked"` (neither — e.g. the pinned root, which nothing else can vouch for). Omitted
154
+ * (not a guess) on the Exchange/private-artifact rails, where this concept doesn't apply. */
155
+ revocation_checked?: 'live' | 'staple' | 'unchecked';
156
+ }
157
+ interface ConservedStateInfo {
158
+ artifact_id: string;
159
+ scope: string;
160
+ limit_amount: number;
161
+ consumed: number;
162
+ reserved: number;
163
+ available: number;
164
+ /** The raw ledger fact to pass into `can()` as `consumed_before` — already consumed+reserved
165
+ * (outstanding reservations count as spoken-for), not consumed alone. */
166
+ consumed_before: number;
167
+ version: number;
168
+ state_digest: string;
169
+ }
170
+ /** Reserve against a ledger using a real `can()` decision. */
171
+ interface ReserveByQueryParams {
172
+ artifact_id: string;
173
+ scope: string;
174
+ amount: number;
175
+ /** The `query_id` from a real `can()`/`nomos.can()` call that returned AUTHORIZED.
176
+ * `reserve()` looks that decision up itself and derives its own binding from it — this is a
177
+ * lookup key, not a claim you're asserting, and a made-up or already-used one is rejected. */
178
+ query_id: string;
179
+ action: string;
180
+ /** Optimistic-concurrency check: reject unless the ledger's state still matches what you last
181
+ * read via `conservedState.get()`. Omit only when you have no prior read to compare against. */
182
+ expected_state_digest?: string;
183
+ ttl_seconds?: number;
184
+ }
185
+ /** Reserve against a ledger using a NOMOS-SPEC-008 Act: the ledger verifies the Act itself (its
186
+ * own pinned root and audience id, its own `consumed_before`, single-use nonce) and holds exactly
187
+ * `act.action.params.amount` for `act.action.type === 'conserved_state.reserve'`, or nothing. */
188
+ interface ReserveByActParams {
189
+ artifact_id: string;
190
+ scope: string;
191
+ act: Record<string, unknown>;
192
+ ttl_seconds?: number;
193
+ }
194
+ type ReserveParams = ReserveByQueryParams | ReserveByActParams;
195
+ /** The cryptographically-bound reservation object — R-12 §5. */
196
+ interface Reservation {
197
+ reservation_id: string;
198
+ authority_seal: string;
199
+ decision_digest: string;
200
+ agent: string;
201
+ action: string;
202
+ scope: string;
203
+ amount: number;
204
+ state_digest: string;
205
+ expires_at: string;
206
+ status: 'held' | 'settled' | 'released' | 'expired';
146
207
  }
147
- type ChainVerdictDecision = 'ALLOWED' | 'ISSUER_NOT_RECOGNIZED' | 'KEY_REVOKED' | 'SEAL_INVALID' | 'MALFORMED';
208
+ interface ConservedStateReceipt {
209
+ reservation_id: string;
210
+ outcome: 'settled' | 'released';
211
+ amount: number;
212
+ ledger_key: string;
213
+ resolved_at: string;
214
+ reference?: string;
215
+ }
216
+ type ChainVerdictDecision = 'ALLOWED' | 'ISSUER_NOT_RECOGNIZED' | 'KEY_REVOKED' | 'CERTIFICATE_REVOKED' | 'OUT_OF_SCOPE' | 'SEAL_INVALID' | 'MALFORMED';
148
217
  interface KeyCertificate {
149
218
  parent_kid: string;
150
219
  child_kid: string;
@@ -179,6 +248,15 @@ interface VerifyChainResult {
179
248
  reason?: string;
180
249
  /** Present only when decision === 'KEY_REVOKED'. */
181
250
  revoked_kid?: string;
251
+ /** Present only when decision === 'CERTIFICATE_REVOKED'. */
252
+ revoked_fingerprint?: string;
253
+ /** Present only when decision === 'OUT_OF_SCOPE' — which scope dimension failed. */
254
+ dimension?: string;
255
+ /** Present on ALLOWED and OUT_OF_SCOPE — the resolved chain's accumulated scope. */
256
+ effective_scope?: string;
257
+ /** Present only when decision === 'ALLOWED' (NOMOS-SPEC-007 §5.5). See CanResult's field of
258
+ * the same name for the full tri-state explanation. */
259
+ revocation_checked?: 'live' | 'staple' | 'unchecked';
182
260
  /** Human-readable detail for any non-ALLOWED decision. */
183
261
  detail?: string;
184
262
  }
@@ -509,32 +587,107 @@ declare class Webhooks {
509
587
  verifySignature(payload: string | Uint8Array, signature: string, secret: string, toleranceSeconds?: number): Promise<boolean>;
510
588
  }
511
589
 
590
+ /**
591
+ * The state layer for a conserved (depleting) budget — R-12, a design in progress
592
+ * (docs/design-notes/conserved-state-authority.md in the platform repo; public mirror:
593
+ * computableauthority.com/research/conserved-state-authority). PROTOTYPE, not a numbered spec.
594
+ *
595
+ * `can()` stays exactly what it always was: pure, stateless, given facts and returning a
596
+ * verdict. Nothing here changes that. This resource is what a caller consults BEFORE calling
597
+ * `can()` (to build the `consumed_before` fact) and commits to AFTER `can()` returns AUTHORIZED
598
+ * and the caller has actually decided to act — never at `can()` time itself (repeatedly calling
599
+ * `can()` while deciding must never itself burn the budget).
600
+ *
601
+ * @example
602
+ * const state = await client.conservedState.get('supplier-budget-2026', 'supplier:acme');
603
+ * const verdict = await client.can({ artifact_id: 'supplier-budget-2026', consumed_before: state.consumed_before });
604
+ * if (verdict.allowed) {
605
+ * const reservation = await client.conservedState.reserve({
606
+ * artifact_id: 'supplier-budget-2026', scope: 'supplier:acme',
607
+ * amount: 5000, query_id: verdict.query_id!, action: 'payments.transfer',
608
+ * });
609
+ * try {
610
+ * // ... perform the real action ...
611
+ * await client.conservedState.settle(reservation.reservation_id);
612
+ * } catch {
613
+ * await client.conservedState.release(reservation.reservation_id);
614
+ * }
615
+ * }
616
+ */
617
+ declare class ConservedState {
618
+ private readonly http;
619
+ constructor(http: HttpClient);
620
+ /**
621
+ * Read a ledger's current balance — no auth required, same posture as `can()`'s own keyless
622
+ * rail (reading how much budget remains is no more sensitive than reading a verdict).
623
+ */
624
+ get(artifactId: string, scope: string, opts?: RequestOptions): Promise<WithLastResponse<ConservedStateInfo>>;
625
+ /**
626
+ * Commit capacity against a REAL, AUTHORIZED `can()` decision. Authenticated — unlike `can()`,
627
+ * spending against a shared budget requires an API key or session; `agent` on the returned
628
+ * reservation traces to that authenticated identity, never a value you pass in.
629
+ *
630
+ * Throws (via the SDK's standard NomosError hierarchy; `err.details?.code` carries the
631
+ * server's own code): `DECISION_NOT_FOUND` (the query_id names no real query), `DECISION_NOT
632
+ * _AUTHORIZED` (that decision didn't verdict AUTHORIZED for this artifact), `DECISION_ALREADY
633
+ * _USED` (already bound to a reservation — one decision authorizes one, ever), `STATE_CONFLICT`
634
+ * (a stale `expected_state_digest`), `INSUFFICIENT_CAPACITY` (`err.details.requested`/
635
+ * `.available` carry the numbers), or `NO_LEDGER` (nobody has declared this budget yet).
636
+ *
637
+ * Passing `{ artifact_id, scope, act }` instead authorizes the hold with a NOMOS-SPEC-008 Act.
638
+ * Extra codes: `ACT_NOT_ACCEPTED` (the ledger has no Act binding) and `ACT_REJECTED`, with the
639
+ * verifier's full answer in `err.details.result` — for `INCOMPLETE` it names the open rules and
640
+ * the facts whose testimony would decide them.
641
+ */
642
+ reserve(params: ReserveParams, opts?: RequestOptions): Promise<WithLastResponse<Reservation>>;
643
+ /**
644
+ * The reserved action succeeded — moves the hold into real, settled spend. Idempotent when
645
+ * you pass the same `reference` again for a retry (e.g. after a network timeout): you get the
646
+ * ORIGINAL receipt back, never a double-charge.
647
+ */
648
+ settle(reservationId: string, reference?: string, opts?: RequestOptions): Promise<WithLastResponse<ConservedStateReceipt>>;
649
+ /**
650
+ * The reserved action failed, or you decided not to act — gives the hold's capacity back
651
+ * without ever touching what's actually been spent.
652
+ */
653
+ release(reservationId: string, reference?: string, opts?: RequestOptions): Promise<WithLastResponse<ConservedStateReceipt>>;
654
+ }
655
+
512
656
  type NomosErrorCode = 'authentication_error' | 'authorization_error' | 'rate_limit_error' | 'validation_error' | 'not_found' | 'api_error' | 'network_error' | 'webhook_signature_error' | 'payment_required' | 'issuer_not_trusted';
513
657
  declare class NomosError extends Error {
514
658
  readonly code: NomosErrorCode;
515
659
  readonly status?: number;
516
660
  readonly requestId?: string;
517
- constructor(message: string, code: NomosErrorCode, status?: number, requestId?: string);
661
+ /** The full parsed JSON response body, when the server sent one — every field it put there
662
+ * (its own `code`, and anything endpoint-specific like `requested`/`available` on a
663
+ * conserved-state capacity rejection, or `price_usd`/`purchase_url` before those got a
664
+ * dedicated error class). `NomosError`'s own `code` above is the SDK's fixed HTTP-status
665
+ * taxonomy; `details?.code` is the SERVER's own, finer-grained application code — the two
666
+ * are deliberately not conflated into one field. */
667
+ readonly details?: unknown;
668
+ constructor(message: string, code: NomosErrorCode, status?: number, requestId?: string, details?: unknown);
518
669
  }
519
670
  declare class NomosAuthenticationError extends NomosError {
520
- constructor(message?: string, requestId?: string);
671
+ constructor(message?: string, requestId?: string, details?: unknown);
521
672
  }
522
673
  declare class NomosAuthorizationError extends NomosError {
523
- constructor(message?: string, requestId?: string);
674
+ constructor(message?: string, requestId?: string, details?: unknown);
524
675
  }
525
676
  declare class NomosRateLimitError extends NomosError {
526
677
  readonly retryAfter?: number;
527
- constructor(message?: string, retryAfter?: number, requestId?: string);
678
+ constructor(message?: string, retryAfter?: number, requestId?: string, details?: unknown);
528
679
  }
680
+ /** `details` is the parsed response body — kept as its own constructor param (rather than just
681
+ * relying on the base class field) since it long predates NomosError.details and every caller
682
+ * already passes it positionally in this slot; unchanged for backward compatibility. */
529
683
  declare class NomosValidationError extends NomosError {
530
- readonly details?: unknown;
531
684
  constructor(message: string, details?: unknown, requestId?: string);
532
685
  }
533
686
  declare class NomosNotFoundError extends NomosError {
534
- constructor(message: string, requestId?: string);
687
+ constructor(message: string, requestId?: string, details?: unknown);
535
688
  }
536
689
  declare class NomosAPIError extends NomosError {
537
- constructor(message: string, status: number, requestId?: string);
690
+ constructor(message: string, status: number, requestId?: string, details?: unknown);
538
691
  }
539
692
  declare class NomosNetworkError extends NomosError {
540
693
  constructor(message: string);
@@ -551,14 +704,22 @@ declare class NomosPaymentRequiredError extends NomosError {
551
704
  readonly purchaseUrl: string;
552
705
  constructor(message: string, priceUsd: number, purchaseUrl: string, requestId?: string);
553
706
  }
554
- /** can()'s chain-of-trust rail couldn't establish that the presented artifact is trustworthy —
555
- * an unrecognized issuer, a revoked key, a tampered seal, or a malformed presentation. This is
556
- * deliberately NOT a DENIED verdict: "the rules say no" and "we never got far enough to ask the
557
- * rules anything" are different claims, and collapsing them would tell the caller the wrong
558
- * thing to do next (re-presenting a better chain can fix the first case; it can never fix a
559
- * genuinely tampered artifact). `reason` is the underlying chain decision
560
- * (ISSUER_NOT_RECOGNIZED / KEY_REVOKED / SEAL_INVALID / MALFORMED) for callers that need to
561
- * distinguish them. */
707
+ /** can()'s chain-of-trust rail could not establish that the presented artifact carries authority
708
+ * it is entitled to — an unrecognized issuer, a revoked key, a delegation that never covered
709
+ * this artifact, a tampered seal, or a malformed presentation. This is deliberately NOT a DENIED
710
+ * verdict: "the rules say no" and "we never got far enough to ask the rules anything" are
711
+ * different claims, and collapsing them would tell the caller the wrong thing to do next.
712
+ *
713
+ * `reason` carries the underlying chain decision so callers can distinguish them, because the
714
+ * right next action genuinely differs:
715
+ * ISSUER_NOT_RECOGNIZED — presenting a more complete chain may fix this
716
+ * KEY_REVOKED — that key is dead; a different, unrevoked chain might work
717
+ * CERTIFICATE_REVOKED — one delegation was withdrawn; another certificate for the same
718
+ * key may still resolve
719
+ * OUT_OF_SCOPE — the issuer IS recognized but was not delegated authority over this
720
+ * artifact; only a broader (validly issued) delegation fixes it
721
+ * SEAL_INVALID — the artifact itself was altered; nothing about presentation helps
722
+ * MALFORMED — the request shape was wrong (a client-side bug) */
562
723
  declare class NomosIssuerNotTrustedError extends NomosError {
563
724
  readonly reason: string;
564
725
  constructor(message: string, reason: string, requestId?: string);
@@ -596,6 +757,9 @@ declare class Nomos {
596
757
  readonly artifacts: Artifacts;
597
758
  readonly governance: Governance;
598
759
  readonly webhooks: Webhooks;
760
+ /** The R-12 conserved-state (depleting-budget) state layer — PROTOTYPE, not a numbered spec.
761
+ * See resources/conserved-state.ts for the full reserve/settle/release lifecycle. */
762
+ readonly conservedState: ConservedState;
599
763
  /** Whether this instance is using a live or test API key. */
600
764
  readonly mode: 'live' | 'test';
601
765
  static readonly errors: typeof errors;
@@ -645,4 +809,4 @@ declare const nomos: {
645
809
  */
646
810
  declare function verifyChain(params: VerifyChainParams, opts?: VerifyChainOptions): Promise<VerifyChainResult>;
647
811
 
648
- export { type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type CanOptions, type CanParams, type CanResult, type CanVerdict, type ChainVerdictDecision, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type GenerateGovernanceParams, type GenerateGovernanceResult, type KeyCertificate, type ListArtifactsParams, Nomos, NomosAPIError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type RequestOptions, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, type WithLastResponse, can, nomos, verifyChain };
812
+ export { type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type CanOptions, type CanParams, type CanResult, type CanVerdict, type ChainVerdictDecision, type ConservedStateInfo, type ConservedStateReceipt, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type GenerateGovernanceParams, type GenerateGovernanceResult, type KeyCertificate, type ListArtifactsParams, Nomos, NomosAPIError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type RequestOptions, type Reservation, type ReserveByActParams, type ReserveByQueryParams, type ReserveParams, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, type WithLastResponse, can, nomos, verifyChain };