@axorum/client 0.0.0-stage → 1.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.
Files changed (79) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +530 -2
  3. package/dist/agent-uri.d.ts +30 -0
  4. package/dist/agent-uri.d.ts.map +1 -0
  5. package/dist/agent-uri.js +218 -0
  6. package/dist/agent-uri.js.map +1 -0
  7. package/dist/authorize.d.ts +671 -0
  8. package/dist/authorize.d.ts.map +1 -0
  9. package/dist/authorize.js +947 -0
  10. package/dist/authorize.js.map +1 -0
  11. package/dist/capabilities.d.ts +30 -0
  12. package/dist/capabilities.d.ts.map +1 -0
  13. package/dist/capabilities.js +40 -0
  14. package/dist/capabilities.js.map +1 -0
  15. package/dist/chart.d.ts +74 -0
  16. package/dist/chart.d.ts.map +1 -0
  17. package/dist/chart.js +107 -0
  18. package/dist/chart.js.map +1 -0
  19. package/dist/client.d.ts +644 -0
  20. package/dist/client.d.ts.map +1 -0
  21. package/dist/client.js +1302 -0
  22. package/dist/client.js.map +1 -0
  23. package/dist/errors.d.ts +646 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +928 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/failover.d.ts +197 -0
  28. package/dist/failover.d.ts.map +1 -0
  29. package/dist/failover.js +289 -0
  30. package/dist/failover.js.map +1 -0
  31. package/dist/freshness.d.ts +65 -0
  32. package/dist/freshness.d.ts.map +1 -0
  33. package/dist/freshness.js +148 -0
  34. package/dist/freshness.js.map +1 -0
  35. package/dist/generated/agent-plane.d.ts +1863 -0
  36. package/dist/generated/agent-plane.d.ts.map +1 -0
  37. package/dist/generated/agent-plane.js +19 -0
  38. package/dist/generated/agent-plane.js.map +1 -0
  39. package/dist/idempotency.d.ts +160 -0
  40. package/dist/idempotency.d.ts.map +1 -0
  41. package/dist/idempotency.js +208 -0
  42. package/dist/idempotency.js.map +1 -0
  43. package/dist/index.d.ts +89 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +87 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/intent.d.ts +163 -0
  48. package/dist/intent.d.ts.map +1 -0
  49. package/dist/intent.js +82 -0
  50. package/dist/intent.js.map +1 -0
  51. package/dist/json.d.ts +49 -0
  52. package/dist/json.d.ts.map +1 -0
  53. package/dist/json.js +259 -0
  54. package/dist/json.js.map +1 -0
  55. package/dist/key.d.ts +64 -0
  56. package/dist/key.d.ts.map +1 -0
  57. package/dist/key.js +71 -0
  58. package/dist/key.js.map +1 -0
  59. package/dist/payment.d.ts +172 -0
  60. package/dist/payment.d.ts.map +1 -0
  61. package/dist/payment.js +178 -0
  62. package/dist/payment.js.map +1 -0
  63. package/dist/refusal.d.ts +121 -0
  64. package/dist/refusal.d.ts.map +1 -0
  65. package/dist/refusal.js +176 -0
  66. package/dist/refusal.js.map +1 -0
  67. package/dist/retry.d.ts +215 -0
  68. package/dist/retry.d.ts.map +1 -0
  69. package/dist/retry.js +297 -0
  70. package/dist/retry.js.map +1 -0
  71. package/dist/typeid.d.ts +64 -0
  72. package/dist/typeid.d.ts.map +1 -0
  73. package/dist/typeid.js +119 -0
  74. package/dist/typeid.js.map +1 -0
  75. package/dist/types.d.ts +245 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +14 -0
  78. package/dist/types.js.map +1 -0
  79. package/package.json +58 -3
@@ -0,0 +1,644 @@
1
+ /**
2
+ * The client, and the one request funnel every call goes through.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ import { type IdempotencyKey } from "./idempotency.js";
7
+ export { mintedAtWire } from "./idempotency.js";
8
+ import { type Agent, type Authorization } from "./authorize.js";
9
+ import { type RotationReason } from "./failover.js";
10
+ import { type IntentDraft } from "./intent.js";
11
+ import type { Payment } from "./payment.js";
12
+ import { type RetryPolicy } from "./retry.js";
13
+ import type { AgentOutcome, BalanceResponse, DeclaredCurrencies, Envelope, IdempotencyWindow, IntentEnvelope, ObligationsResponse, TransactionRecord, UsageBalanceReport } from "./types.js";
14
+ /**
15
+ * How many policies {@link AxorumClient.submitPinned} will pin against before giving up.
16
+ *
17
+ * A bound, not a retry budget: each attempt pins against the policy the service *just
18
+ * told us* is in force, so a second failure means the policy moved again in the
19
+ * microseconds since. Three is generous; a service changing policy faster than that is
20
+ * not one a client can chase, and looping forever would turn a hot policy rotation into
21
+ * a self-inflicted denial of service.
22
+ */
23
+ export declare const MAX_PINS = 3;
24
+ /** How the client is built. */
25
+ export interface AxorumClientOptions {
26
+ /** The service's root, e.g. `http://127.0.0.1:8080`. The `/api/v1` prefix is added. */
27
+ baseUrl: string;
28
+ /**
29
+ * The API URLs of the deployment's other replicas, tried in order after `baseUrl`.
30
+ * Each is a bare origin (`https://host:port`); `baseUrl`'s path is used on every one.
31
+ *
32
+ * A clustered deployment accepts a write only on its primary, and a follower answers
33
+ * `421 not_primary`. With every replica named here,
34
+ * {@link AxorumClient.authorize} moves an attempt that meets a `421`, a `503`, a
35
+ * connection that never opens, or an attempt timeout on to the next URL at once, under
36
+ * the same deadline and the same transaction id. The next call starts at the URL that
37
+ * last answered. A `421`'s `primaryApiAddr` is never dialled: it is a hint that may
38
+ * be stale. No redirect is followed.
39
+ *
40
+ * Reads fail over too under a {@link retry} policy. A {@link AxorumClient.submitIntent}
41
+ * of the caller's own envelope is sent once, to one URL.
42
+ *
43
+ * Left out, `baseUrl` is assumed to reach the primary (a load balancer that routes to
44
+ * it), and the first client built so in the process warns saying so.
45
+ *
46
+ * ```ts
47
+ * const axorum = new AxorumClient({
48
+ * baseUrl: "https://axorum-0.axorum.svc:8443",
49
+ * failoverUrls: ["https://axorum-1.axorum.svc:8443", "https://axorum-2.axorum.svc:8443"],
50
+ * });
51
+ * ```
52
+ */
53
+ failoverUrls?: readonly string[];
54
+ /**
55
+ * Called each time a request moves to the next endpoint, with the endpoint it left
56
+ * (`scheme://host:port`) and why, as the next attempt is sent. Count it on
57
+ * {@link ROTATIONS_COUNTER}, labelled `reason` with {@link rotationLabel}, to match the
58
+ * Rust SDK's counter. It must not throw; what it throws is ignored.
59
+ */
60
+ onRotation?: (left: string, reason: RotationReason) => void;
61
+ /**
62
+ * Called each time a request is re-sent to the same endpoint, with that endpoint, why,
63
+ * and the number of the attempt about to be sent (`2` for the first re-send), counted
64
+ * over the call. A client with one URL only ever re-sends in place, so a deployment
65
+ * answering `421` to it shows up here. Count it on {@link RETRIES_COUNTER}, labelled
66
+ * `reason` with {@link rotationLabel}, to match the Rust SDK. It never overlaps
67
+ * {@link onRotation}; what it throws is ignored.
68
+ */
69
+ onRetry?: (endpoint: string, reason: RotationReason, attempt: number) => void;
70
+ /**
71
+ * The agent's PASETO v4.public capability attestation — the credential its **reads**
72
+ * are answered against.
73
+ *
74
+ * It is the same token the intent envelope carries, so there is nothing new to obtain:
75
+ * an agent that can submit already holds this. The party-scoped reads present it as
76
+ * `Authorization: Bearer …`; the service takes the reader's identity from the token's
77
+ * verified `agent_uri` claim and answers only within the party that URI is bound to.
78
+ *
79
+ * Optional, because a client that only submits (or only reads {@link
80
+ * AxorumClient.activePolicy}, which discloses no party's data) needs none. Omit it and
81
+ * the party-scoped reads raise {@link MissingAttestationError} locally rather than
82
+ * earning a `401`.
83
+ *
84
+ * This replaced a `bearerToken` option that promised to be "for a deployment that
85
+ * fronts the plane with a gateway". That is no longer a promise the client can keep:
86
+ * the `Authorization` slot now belongs to the attestation, and there is only one of it.
87
+ *
88
+ * Tokens expire — see {@link AxorumClient.withAttestation} to rotate one without
89
+ * rebuilding the client.
90
+ */
91
+ attestation?: string;
92
+ /**
93
+ * The per-request timeout, in milliseconds, each send its own. Defaults to 30 000. Left
94
+ * out, a plain submission to a clustered service that advertises a longer commit wait
95
+ * waits that wait plus 1 s instead, so it hears the service's `503 commit_unavailable`
96
+ * rather than timing out first. Set, it is kept as it is.
97
+ */
98
+ timeoutMs?: number;
99
+ /**
100
+ * Re-send a read that failed in transit. Off when left out, as in the Rust SDK.
101
+ *
102
+ * Covers connection failures, timeouts, and a `429`, `502` or `504` from something in
103
+ * front of the service (or a `423` with `Retry-After`), honouring `Retry-After`, within
104
+ * the policy's `deadlineMs` if it sets one; see {@link RetryPolicy}. Only reads are
105
+ * re-sent: a {@link AxorumClient.submitIntent} of the caller's own envelope is sent
106
+ * once, since the client cannot know a re-send was meant. {@link DEFAULT_RETRY_POLICY}
107
+ * is three sends, 100 ms doubling, capped at 5 s. {@link AxorumClient.authorize} does
108
+ * not use it: it always retries, under its own policy and deadline.
109
+ */
110
+ retry?: RetryPolicy;
111
+ /**
112
+ * Shortens the time budget of one {@link AxorumClient.authorize} call below
113
+ * {@link AUTHORIZE_DEADLINE_MS} (15 000), for a caller that must answer sooner. A
114
+ * payment the shorter budget leaves unanswered is `indeterminate`, as any is. It cannot
115
+ * be lengthened: `ATTESTATION_MIN_REMAINING_MS` guarantees a token outlasts the
116
+ * budget, and is sized for 15 s.
117
+ */
118
+ authorizeDeadlineMs?: number;
119
+ /**
120
+ * Whether {@link AxorumClient.authorize} re-sends a request that went unanswered.
121
+ * **On by default**, under {@link AUTHORIZE_RETRY_POLICY} until the call's deadline.
122
+ * Set `false` and each request of the call is sent once, still bounded by
123
+ * {@link submitAttemptTimeout} and the deadline, and the first unanswered one
124
+ * ends the call as `indeterminate`: for a caller that runs its own re-drive loop over
125
+ * the same payment. A stale pin is still re-pinned: that is a new request, not a
126
+ * re-send.
127
+ */
128
+ authorizeRetries?: boolean;
129
+ /**
130
+ * The client's diagnostic sink: everything it would say at debug level, one line per
131
+ * call, as the Rust client says it through `tracing::debug!`. A client and the clients
132
+ * {@link AxorumClient.withAttestation} derives share it. Unset, nothing is said.
133
+ *
134
+ * Its first message says why a submission attempt on {@link AxorumClient.authorize}'s
135
+ * lane waits {@link SUBMIT_ATTEMPT_FALLBACK_MS} (the service advertises no commit wait,
136
+ * or reading it failed), once per client and its derived clients.
137
+ */
138
+ debug?: (message: string) => void;
139
+ /**
140
+ * The `fetch` to use. Defaults to the global one.
141
+ *
142
+ * Swap it to add tracing, to route through a proxy agent, or — as this client's own
143
+ * unit tests do — to answer without a socket.
144
+ */
145
+ fetch?: typeof globalThis.fetch;
146
+ }
147
+ /** Per-call options. Every method takes them. */
148
+ export interface RequestOptions {
149
+ /**
150
+ * Cancels the request.
151
+ *
152
+ * Composed with the client's timeout, so whichever fires first wins. When *yours*
153
+ * fires, the abort reason is thrown as-is — a cancellation you asked for is not a
154
+ * failure of the ledger, and is not dressed up as one.
155
+ */
156
+ signal?: AbortSignal;
157
+ /**
158
+ * The `x-request-id` for this call. A fresh UUID v4 is generated when you leave it
159
+ * out, so every request is traceable whether or not you thought about it.
160
+ */
161
+ requestId?: string;
162
+ }
163
+ /**
164
+ * A typed client for the Axorum agent plane.
165
+ *
166
+ * # Writes carry their credential; so do reads
167
+ *
168
+ * An intent authenticates **in the envelope**: the draft names the `agent://` URI and the
169
+ * PASETO attestation, and the service verifies them before it proposes anything. The
170
+ * reads used to carry nothing at all, and were answered anyway — so any party's
171
+ * obligations were enumerable, and any observed transaction id disclosed another tenant's
172
+ * record.
173
+ *
174
+ * They are credentialed now, with the **same token**: the party-scoped reads present the
175
+ * agent's attestation as `Authorization: Bearer …`, the service takes the reader's
176
+ * identity from that token's verified `agent_uri` claim, and answers only within the
177
+ * party it is bound to. There is no second credential to obtain — an agent that can
178
+ * submit can already read.
179
+ *
180
+ * So a client that reads must hold an attestation:
181
+ *
182
+ * ```ts
183
+ * const axorum = new AxorumClient({ baseUrl: "http://127.0.0.1:8080", attestation: paseto });
184
+ *
185
+ * const outcome = await axorum.submitPinned(draft);
186
+ *
187
+ * // A refusal RESOLVES. Branch on the verdict, never on a status code.
188
+ * if (outcome.posted) {
189
+ * console.log("permitted, and the entries posted");
190
+ * } else {
191
+ * console.log("refused, and the refusal is on the ledger:", outcome.verdict);
192
+ * }
193
+ *
194
+ * // Reads are scoped to the party the attestation is bound to.
195
+ * const owed = await axorum.obligations();
196
+ * ```
197
+ *
198
+ * A client built **without** one still submits and still reads {@link activePolicy} —
199
+ * both carry their own credential or need none — but any party-scoped read is a
200
+ * {@link MissingAttestationError}, raised locally, without a wasted round trip.
201
+ *
202
+ * An {@link AxorumError} means the ledger never got to answer at all.
203
+ */
204
+ export declare class AxorumClient {
205
+ #private;
206
+ /**
207
+ * Builds a client for the service rooted at `baseUrl`.
208
+ *
209
+ * @throws {ConfigError} If `baseUrl` is not an absolute `http`/`https` URL, a failover
210
+ * URL is not a bare origin of the same scheme or repeats one, the timeout is not a
211
+ * positive number, the retry policy is not whole numbers, or the authorize deadline
212
+ * is not a whole number of milliseconds from 1 to 15 000.
213
+ */
214
+ constructor(options: AxorumClientOptions);
215
+ /**
216
+ * The same client, reading as the bearer of a **different** attestation.
217
+ *
218
+ * Attestations expire, so an agent that outlives one of its tokens must be able to
219
+ * re-credential without rebuilding — and this hands the new client the *same* `fetch`,
220
+ * so whatever connections that `fetch` is keeping alive stay alive. Rotating a token
221
+ * costs a string, not a reconnect.
222
+ *
223
+ * It is also the honest way for one process to act for several agents: each gets its
224
+ * own view, and no view can see another's data, because the service scopes every read
225
+ * to the party the presented token names.
226
+ */
227
+ withAttestation(attestation: string): AxorumClient;
228
+ /**
229
+ * Asks the ledger whether `agent` may make `payment`, in one call.
230
+ *
231
+ * Proposes `purchase(amount, payee)` with the payee bound as **text**, debiting the
232
+ * default chart's spend account and crediting its payable account in the amount's
233
+ * currency. The policy in force must declare
234
+ * `action purchase(amount: amount, payee: text)`, and an operator must have opened the
235
+ * default chart in that currency. A rail governed by the same policy must be set to
236
+ * `payee_binding = "text"`; one left on `"party"` binds a different type.
237
+ *
238
+ * ```ts
239
+ * import { Agent, AxorumClient, AuthorizeError, payment } from "@axorum/client";
240
+ *
241
+ * const axorum = new AxorumClient({ baseUrl: "https://ledger.acme.com" });
242
+ * const buyer = new Agent("agent://acme.com/agent/buyer_01h455vb4pex5vsknk084sn02q", paseto);
243
+ * const lunch = payment({ amount: { minor: 1_250n, currency: "USD" }, payee: "api.openai.com" });
244
+ *
245
+ * const answer = await axorum.authorize(buyer, lunch);
246
+ * if (answer.allowed) {
247
+ * await pay();
248
+ * } else {
249
+ * console.log(answer.refusal.explanation);
250
+ * }
251
+ * ```
252
+ *
253
+ * # A refusal resolves
254
+ *
255
+ * A payment the policy forbids resolves as `{ allowed: false, refusal }`: it was judged
256
+ * and recorded, and nothing posted. A rejected promise ({@link AuthorizeError}) means the
257
+ * ledger never answered.
258
+ *
259
+ * # Retries and time
260
+ *
261
+ * One call has one budget, {@link AUTHORIZE_DEADLINE_MS} (15 s, or less with
262
+ * {@link AxorumClientOptions.authorizeDeadlineMs}), counted from the call and shared by
263
+ * the idempotency window and policy reads, every submission (the first, and each
264
+ * re-pin), every attempt and every pause. Each submission attempt waits the commit
265
+ * wait the service advertises plus 1 s ({@link submitAttemptTimeout}), so a clustered
266
+ * service's own `503 commit_unavailable` is heard rather than cut off, or 6 s
267
+ * ({@link SUBMIT_ATTEMPT_FALLBACK_MS}) when it advertises none; each read waits 6 s.
268
+ * Every attempt is also clamped to what is left of the budget. When that clamp cuts a
269
+ * submission attempt below the commit wait plus 1 s, the service may still be deciding
270
+ * when the client stops waiting, so a call that ends there is honestly
271
+ * `indeterminate`. The advertisement is read once per client, from
272
+ * `GET /api/openapi.json`.
273
+ * Re-sends follow {@link AUTHORIZE_RETRY_POLICY}: as many as the budget allows, full
274
+ * jitter floored at 100 ms and capped at 1.6 s, honouring `Retry-After` within the
275
+ * budget. The client's own `retry` and `timeoutMs` govern its other calls, not this one;
276
+ * {@link AxorumClientOptions.authorizeRetries} turns re-sending off.
277
+ *
278
+ * | Re-sent until the deadline | Not re-sent |
279
+ * |---|---|
280
+ * | connection failures and timeouts | refusals (they are answers) |
281
+ * | `429`, `502`, `503`, `504` (and `423` with `Retry-After`) | `422`, including a missing chart or an unknown payee |
282
+ * | `421 not_primary` and `503`, on the next {@link AxorumClientOptions.failoverUrls} URL | `409 transaction_reused` |
283
+ * | `409` stale pin: re-pinned to `active`, at most 3 times | `400` / `401` / `403` / `404` / `507` |
284
+ *
285
+ * Every send carries the payment's transaction id, so one that did commit replays its
286
+ * answer instead of paying twice. A connection failure, a timeout, a `421` or a `503`
287
+ * moves the next attempt to the next failover URL at once. Whatever is still unanswered
288
+ * when the budget runs out (a `421` and a `503` included) is `indeterminate`: re-drive
289
+ * the same {@link Payment} to learn what happened. A call that proves it wrote nothing
290
+ * is `not_submitted`. `primaryApiAddr` is a hint, never followed.
291
+ *
292
+ * Once an attempt ends without a definite answer (a timeout, or a re-sent status other
293
+ * than `429`), it may have committed, so a later failure that is not the ledger's own
294
+ * answer about the transaction id (a `403`, a `401`, a `429` or `502` once retries are
295
+ * spent) is `indeterminate` too, with cause `earlier_attempt_in_doubt`. A
296
+ * `409 transaction_reused` and a `422 substrate_rejected` stay what they are.
297
+ *
298
+ * # Time
299
+ *
300
+ * The attestation is read once per submission (the first, and each re-pin), and its
301
+ * `exp` bounds that submission too: every send, and every wait between sends
302
+ * (`Retry-After` included), ends 30 seconds (`ATTESTATION_CLOCK_ALLOWANCE_MS`) before
303
+ * `exp` when that comes before the call's deadline. No send starts past that point, each
304
+ * send's timeout is cut to the time left, and a wait that would reach it ends the
305
+ * re-sends.
306
+ *
307
+ * @throws {AuthorizeError} Carrying the transaction id and a `failure` that says what to
308
+ * do next.
309
+ * @throws The caller's own abort reason, as-is, when `options.signal` fires.
310
+ */
311
+ authorize(agent: Agent, payment: Payment, options?: {
312
+ signal?: AbortSignal;
313
+ }): Promise<Authorization>;
314
+ activePolicy(options?: RequestOptions): Promise<string | null>;
315
+ /**
316
+ * The ledger's currency registry: every declared currency and its minor-unit exponent,
317
+ * in canonical (sorted) code order.
318
+ *
319
+ * Requires an attestation, like every agent-plane read of ledger state, but is not
320
+ * owner-scoped: any bound agent reads the whole registry. From contract 5.0.0 an intent
321
+ * in any currency not listed here is refused with `422` and
322
+ * `cause: "undeclared_currency"`, so this is what to read to learn what the ledger will
323
+ * take. An empty list means nothing is declared.
324
+ *
325
+ * @throws {MissingAttestationError} If this client holds no attestation. Nothing is sent.
326
+ * @throws {AttestationError} If the service refuses the attestation.
327
+ * @throws {TransportError} If the service cannot be reached.
328
+ */
329
+ currencies(options?: RequestOptions): Promise<DeclaredCurrencies>;
330
+ /**
331
+ * How long this service answers a retry of a committed transaction id with the
332
+ * original outcome: its idempotency window (contract 5.0.0).
333
+ *
334
+ * Read from the three response headers on `GET /api/openapi.json`
335
+ * ({@link IDEMPOTENCY_WINDOW_HEADERS}); the document itself is the frozen contract and
336
+ * is not parsed. Needs no attestation. `window_seconds` is the window's wall-clock
337
+ * length (24 hours at least, rounded down), or `null` when the service runs no
338
+ * heartbeat and its window has no length in seconds. Read it once and hand it to
339
+ * {@link submitKeyed} or {@link ensureFresh}; it only ever grows.
340
+ *
341
+ * ```ts
342
+ * const window = await client.idempotencyWindow();
343
+ * window.window_seconds; // 86400n
344
+ * ```
345
+ *
346
+ * @throws {IdempotencyWindowError} If the headers state no window: a service older
347
+ * than contract 5.0.0, a writer that could not answer, or a rewritten header.
348
+ * @throws {UnexpectedResponseError} If the spec fetch does not answer `2xx`.
349
+ * @throws {TransportError} If the service cannot be reached.
350
+ */
351
+ idempotencyWindow(options?: RequestOptions): Promise<IdempotencyWindow>;
352
+ /**
353
+ * Submits an envelope under a retry key, refusing the key first if the service's
354
+ * window no longer covers it.
355
+ *
356
+ * Use this for every resubmission you make on your own schedule: a queue that
357
+ * redelivers, a job that restarts. Within the window a resubmission of the identical
358
+ * intent answers the original outcome (`replayed: true`); past it, the answer is
359
+ * undefined and the intent might commit twice, so a stale key is never sent.
360
+ *
361
+ * The envelope goes out with the key's `minted_at_ms`, so a contract 5.0.0 service
362
+ * that runs a heartbeat refuses it too (`409 stale_idempotency_key`, an
363
+ * {@link ApiError}) once it is older than the window less a 5-minute clock-skew
364
+ * margin. A caller-chosen id restated with its true mint time passes that check
365
+ * exactly as a freshly minted one does.
366
+ *
367
+ * # Why re-sending is sound
368
+ *
369
+ * The service looks a transaction id up before it applies anything. A re-send inside
370
+ * the idempotency window returns what the first send committed, and one whose content
371
+ * differs is refused as `409 transaction_reused`, never applied.
372
+ *
373
+ * # Retries and time
374
+ *
375
+ * It runs on {@link authorize}'s transport: one budget of `authorizeDeadlineMs`
376
+ * ({@link AUTHORIZE_DEADLINE_MS} by default), shared by the read of the service's
377
+ * commit wait and every attempt, each bounded by {@link submitAttemptTimeout} of that
378
+ * wait, moving across the `failoverUrls` on a connection failure, a timeout and `421`,
379
+ * and sending a `503` again itself, its body read. `authorizeRetries: false` turns
380
+ * re-sending off here too.
381
+ *
382
+ * | Call | Re-sent? | Moves to the next URL? |
383
+ * |---|---|---|
384
+ * | {@link submitIntent} | no | no |
385
+ * | {@link submitPinned} | only to re-pin after a `409` | no |
386
+ * | `submitKeyed` | yes, until the deadline | yes |
387
+ *
388
+ * ```ts
389
+ * const window = await client.idempotencyWindow();
390
+ * const key = IdempotencyKey.mint();
391
+ * const envelope = pinIntent({ ...draft, transaction: key.transaction }, policy);
392
+ * const outcome = await client.submitKeyed(key, window, envelope);
393
+ * ```
394
+ *
395
+ * @throws {StaleIdempotencyKeyError} If the key is past the window, or the service
396
+ * advertises no wall-clock window. Nothing was sent.
397
+ * @throws {ConfigError} If the envelope's `transaction` is not the key's. Nothing was
398
+ * sent.
399
+ * @throws {TransactionReusedError} If the id committed with different content.
400
+ * @throws {EndpointsExhaustedError | DeadlineExceededError} When no endpoint answered
401
+ * in time. Otherwise as {@link submitIntent}.
402
+ */
403
+ submitKeyed(key: IdempotencyKey, window: IdempotencyWindow, envelope: IntentEnvelope, options?: RequestOptions): Promise<AgentOutcome>;
404
+ /**
405
+ * Submits an intent envelope and returns the ledger's outcome.
406
+ *
407
+ * # A refusal resolves
408
+ *
409
+ * If the policy in force forbids the action, this **resolves** with
410
+ * `{ posted: false, verdict: "ForbiddenRejected", … }` — a `200`. The intent reached
411
+ * the commit point, was judged, and the refusal was *recorded*. That record is the
412
+ * product. Branch on `posted`/`verdict`; a rejected promise means the ledger never got
413
+ * to answer at all.
414
+ *
415
+ * Re-submitting the identical intent under a `transaction` id that already committed
416
+ * replays its stored outcome, marked `replayed: true`, rather than double-posting: the
417
+ * id is the substrate's idempotency key, which is what makes retrying a submission
418
+ * safe within the service's window ({@link idempotencyWindow}). Different content
419
+ * under a committed id is a {@link TransactionReusedError}. A replay also carries
420
+ * `content_verified`: `true` when the resubmitted content matched the stored intent,
421
+ * `false` when the service had no stored content to compare with (an id committed
422
+ * before 1.0.0 whose content hash the old log never recorded, retried inside the
423
+ * window after the upgrade). A `false` replay posted nothing, but it did not check
424
+ * your content either. To have the key's age checked before sending, use
425
+ * {@link submitKeyed}.
426
+ *
427
+ * @throws {ConfigError} If the envelope carries no `minted_at_ms` (a non-negative
428
+ * `bigint` of milliseconds since the Unix epoch). Nothing was sent.
429
+ * @throws {StalePolicyError} If the pin did not hold. Use {@link submitPinned} to
430
+ * handle that automatically.
431
+ * @throws {UnknownAgentError | AttestationError} On an identity or authority failure.
432
+ * @throws {NotPrimaryError} If a follower turned the mutation away. Not followed
433
+ * automatically — see the class docs.
434
+ * @throws {CommitUnavailableError} If a cluster commit could not be confirmed.
435
+ * @throws {TransportError} If the service cannot be reached.
436
+ */
437
+ submitIntent(envelope: IntentEnvelope, options?: RequestOptions): Promise<AgentOutcome>;
438
+ /**
439
+ * Fetches the active policy, pins the draft to it, submits — and if the pin did not
440
+ * hold, re-pins and submits again.
441
+ *
442
+ * This is the loop every correct agent writes, so it ships in the client rather than
443
+ * in each caller. It is the *only* method that recovers automatically, and it is safe
444
+ * to do so for one specific reason: the `transaction` id is minted **once**, before
445
+ * the first attempt, and is the substrate's idempotency key. Every retry carries the
446
+ * same id, so if an earlier attempt in fact landed, the retry replays its stored
447
+ * outcome. The loop cannot double-post.
448
+ *
449
+ * A refusal still resolves (see {@link submitIntent}), and a `421` still rejects:
450
+ * following a redirect to another replica is the caller's decision, not the client's.
451
+ *
452
+ * @throws {NoActivePolicyError} If no policy is in force — there is nothing to pin to.
453
+ * @throws {StalePolicyError} If the policy kept moving out from under {@link MAX_PINS}
454
+ * attempts.
455
+ */
456
+ submitPinned(draft: IntentDraft, options?: RequestOptions): Promise<AgentOutcome>;
457
+ /**
458
+ * {@link submitPinned}, but starting from a policy you already believe is in force —
459
+ * and recovering if you are wrong.
460
+ *
461
+ * This is the shape a long-running agent actually wants. Reading the active policy
462
+ * before *every* submission is a round trip spent re-learning something that changes
463
+ * rarely; an agent can hold the last policy it saw, submit straight against it, and
464
+ * let the `409` tell it when its belief went stale. The recovery is the same loop
465
+ * either way, so caching costs nothing in correctness — the service remains the
466
+ * authority, it is simply consulted only when it disagrees.
467
+ *
468
+ * On a `409` it re-pins against the policy **the rejection itself named**, rather than
469
+ * re-reading the active policy. That is not a shortcut: the rejection is the service's
470
+ * own statement of what is in force as of the moment it refused — strictly fresher
471
+ * than anything a follow-up read could return, and one round trip cheaper. Bounded at
472
+ * {@link MAX_PINS} attempts.
473
+ *
474
+ * @param draft - The intent, minus the pin. Its transaction id and mint time are
475
+ * settled once, here, before the first attempt, and every re-pin carries both.
476
+ * @param policyId - The policy you believe is in force.
477
+ * @throws {StalePolicyError} If the policy kept moving out from under {@link MAX_PINS}
478
+ * attempts — the policy is churning faster than a client can chase it, and looping
479
+ * on would turn a hot rotation into a denial of service.
480
+ */
481
+ submitPinnedFrom(draft: IntentDraft, policyId: string, options?: RequestOptions): Promise<AgentOutcome>;
482
+ /**
483
+ * The stored record for a committed transaction — **if your party proposed it**.
484
+ *
485
+ * Reading it is the idempotent replay lookup: it tells you what a previously-submitted
486
+ * `transaction` id did, without resubmitting it. A recorded refusal is here like any
487
+ * other record, with `posted: false`.
488
+ *
489
+ * The record is {@link TransactionRecord | contractually opaque} in this version of
490
+ * the wire contract.
491
+ *
492
+ * # A transaction id is not a capability
493
+ *
494
+ * Ids are client-minted, and a counterparty may well know one. Holding an id therefore
495
+ * grants nothing: a transaction another party proposed answers `404`, *identically* to
496
+ * an id that never existed. The two are deliberately indistinguishable — a
497
+ * distinguishable "forbidden" would turn any id into an existence oracle against
498
+ * another tenant's ledger.
499
+ *
500
+ * @throws {MissingAttestationError} If this client holds no attestation.
501
+ * @throws {ApiError} With code `unknown_transaction` (404) if there is no such
502
+ * transaction **or it belongs to another party**.
503
+ */
504
+ transaction(txnId: string, options?: RequestOptions): Promise<TransactionRecord>;
505
+ /**
506
+ * The **signed receipt** for a transaction your party proposed — an
507
+ * `axorum.evidence.v0` envelope whose base64 `payload` is the
508
+ * `axorum.receipt.v0` facts of the applied record (contract 4.1.0).
509
+ *
510
+ * Re-minted deterministically from the stored record on every call:
511
+ * byte-identical across fetches and to the inline `receipt` the original
512
+ * submission's outcome carried. Verify it **offline** against the key list
513
+ * published at `GET /api/verification-keys.json` — no credential, no server,
514
+ * no SDK required (see `spec/schemas/` and `spec/evidence-signing.md`).
515
+ *
516
+ * @throws {MissingAttestationError} If this client holds no attestation.
517
+ * @throws {ApiError} With `unknown_transaction` (404) if there is no such
518
+ * transaction, it belongs to another party, **or** its record has been
519
+ * archived out of the live tail — deliberately indistinguishable. With
520
+ * `signing_not_configured` / `signing_unavailable` (503) when the
521
+ * deployment does not sign or its signing service is down (the
522
+ * transaction itself is committed and unaffected — retry later).
523
+ */
524
+ receipt(txnId: string, options?: RequestOptions): Promise<Envelope>;
525
+ /**
526
+ * The open obligations **your party** owes.
527
+ *
528
+ * Whose duties these are is decided by the attestation, never by an argument. That is
529
+ * the whole of the fix: `?actor=` used to *select* the party — through a deterministic,
530
+ * public hash of a name — so every party's duties were enumerable by anyone who could
531
+ * guess a name, with no credential at all.
532
+ *
533
+ * `actor` is now an assertion the service **checks** rather than obeys. Leave it out and
534
+ * you are answered for the party your token names. Supply it — a party id (`party_…`) or
535
+ * a DSL party name — and the service confirms it resolves to that same party, answering
536
+ * `403` if it does not. It is there so a caller can be explicit about who it believes it
537
+ * is, and be told when it is wrong; it can no longer be used to ask about somebody else.
538
+ *
539
+ * A duty-free party is not an error — it answers with an empty list.
540
+ *
541
+ * @throws {MissingAttestationError} If this client holds no attestation.
542
+ * @throws {ApiError} With `403` if `actor` names a party other than the one your
543
+ * attestation is bound to.
544
+ */
545
+ obligations(actor?: string, options?: RequestOptions): Promise<ObligationsResponse>;
546
+ /**
547
+ * An account's net balance, in minor units.
548
+ *
549
+ * The balance is a **`bigint`**: the ledger's minor units are 128-bit integers, and
550
+ * decoding one as a JavaScript `number` would truncate it silently. See `src/json.ts`.
551
+ *
552
+ * # Scope
553
+ *
554
+ * Requires an attestation **and is scoped to the account's owning party**: the service
555
+ * answers only when the attested party is the recorded owner of the account. An account
556
+ * owned by another party — or one opened with no owner at all — answers exactly like an
557
+ * account that does not exist (`404 unknown_account`), fail-closed and deliberately
558
+ * indistinguishable: an account id is never an existence oracle across a tenant boundary.
559
+ *
560
+ * @throws {MissingAttestationError} If this client holds no attestation.
561
+ * @throws {ApiError} With code `unknown_account` (404) if there is no such account *or*
562
+ * the attested party does not own it (the caller cannot tell which).
563
+ */
564
+ balance(accountId: string, options?: RequestOptions): Promise<BalanceResponse>;
565
+ /**
566
+ * A usage balance's three running totals — **if your party owns it**.
567
+ *
568
+ * The report carries `consumed`, `metered` and `billed`, the two headrooms derived from
569
+ * them (`unmetered`, `unbilled`), whether the chain has `reconciled`, and `text` — the
570
+ * balance narrated in compliance English with exact figures. Prefer `text` when you have
571
+ * a human to show: it is the service's own sentence, not a rendering you assemble.
572
+ *
573
+ * # Every counter is a `bigint`
574
+ *
575
+ * More so here than anywhere else on the plane. `consumed` is the top of the chain and
576
+ * takes **no ceiling** — a busy meter can lawfully outgrow `u64` — so decoding a counter
577
+ * as a JavaScript `number` would eventually truncate a real figure silently. This client
578
+ * parses them as `bigint`s; see `src/json.ts`.
579
+ *
580
+ * # Scope
581
+ *
582
+ * Owner-scoped, and fail-closed. A balance owned by another party, one opened with no
583
+ * owner at all, and one that was never opened all answer the **same** `404 unknown_usage`
584
+ * — deliberately indistinguishable, so that a usage id is never an existence oracle
585
+ * across a tenant boundary. The refusal names no owner and does not disclose whether the
586
+ * balance exists; surface it as it stands rather than inferring more from it than it says.
587
+ *
588
+ * @throws {MissingAttestationError} If this client holds no attestation.
589
+ * @throws {ApiError} With code `unknown_usage` (404) if there is no such usage balance
590
+ * *or* the attested party does not own it (the caller cannot tell which), or
591
+ * `invalid_usage_id` (400) if that is not a usage-balance id.
592
+ */
593
+ usage(usageId: string, options?: RequestOptions): Promise<UsageBalanceReport>;
594
+ }
595
+ /**
596
+ * The local checks {@link AxorumClient.submitKeyed} makes before anything is sent: the
597
+ * envelope carries the key's id, and the window still covers the key.
598
+ *
599
+ * @internal Exported for tests.
600
+ * @throws {ConfigError | StaleIdempotencyKeyError}
601
+ */
602
+ export declare function checkKeyed(key: IdempotencyKey, window: IdempotencyWindow, envelope: IntentEnvelope, nowMs: number): void;
603
+ /**
604
+ * Refuses an envelope that carries no mint time, before it is sent: `minted_at_ms` must
605
+ * be a non-negative `bigint` of milliseconds since the Unix epoch, as the wire type says. The type
606
+ * requires it; this holds a caller the type cannot reach (plain JavaScript, a cast).
607
+ * Pure.
608
+ *
609
+ * @internal Exported for tests.
610
+ * @throws {ConfigError}
611
+ */
612
+ export declare function checkMinted(envelope: IntentEnvelope): void;
613
+ /**
614
+ * Why a submission attempt waits {@link SUBMIT_ATTEMPT_FALLBACK_MS}: the service
615
+ * advertises no commit wait (`failure` absent), or reading it failed with `failure`.
616
+ * Pure. Internal: exported for this package's tests, never from its index.
617
+ */
618
+ export declare function fallbackNote(failure: unknown): string;
619
+ /** The earlier of two deadlines, either of which may be absent. Pure. */
620
+ export declare function earliest(a: number | undefined, b: number | undefined): number | undefined;
621
+ /**
622
+ * The retry policy a request is sent under, or `undefined` to send it once. Pure.
623
+ *
624
+ * `authorize`'s lane re-sends every request of the call, its `POST /intents` included:
625
+ * the service answers a resubmitted transaction id with the stored outcome, never
626
+ * applying it twice (`POST /intents`, crates/axorum-serve/src/routes/agent.rs), so a
627
+ * re-send to the same endpoint or the next is safe. Off the lane only reads are
628
+ * re-sent: a caller's own envelope is sent once, to one endpoint.
629
+ */
630
+ export declare function sendPolicy(method: "GET" | "POST", lane: {
631
+ readonly retries: boolean;
632
+ } | undefined, retry: RetryPolicy | undefined): RetryPolicy | undefined;
633
+ /** What a client built with one endpoint logs, once per process. */
634
+ export declare const ONE_ENDPOINT_WARNING = "one endpoint configured: an LB routing to the primary is assumed";
635
+ /**
636
+ * Emits {@link ONE_ENDPOINT_WARNING}, naming `baseUrl`, unless `state` says it already
637
+ * was. The testable seam of {@link warnOneEndpoint}.
638
+ */
639
+ export declare function warnOnce(state: {
640
+ warned: boolean;
641
+ }, emit: (message: string) => void, baseUrl: string): void;
642
+ /** Whether `ms` is a budget `authorizeDeadlineMs` accepts: whole, positive, at most 15 000. Pure. */
643
+ export declare function validAuthorizeDeadline(ms: number): boolean;
644
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}