@atlasent/sdk 1.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,620 @@
1
+ import { webcrypto } from 'node:crypto';
2
+ import { p as protect, c as configure, a as AtlaSentError, A as AtlaSentDeniedError } from './protect-BKxcoR_2.js';
3
+ export { d as AtlaSentDecision, e as AtlaSentDeniedErrorInit, f as AtlaSentErrorCode, g as AtlaSentErrorInit, C as ConfigureOptions, P as Permit, b as ProtectRequest } from './protect-BKxcoR_2.js';
4
+
5
+ /**
6
+ * Shared audit wire types — the `/v1/audit/*` HTTP surface.
7
+ *
8
+ * Source of truth: `atlasent-api/supabase/functions/v1-audit/index.ts`
9
+ * (the edge function that serves `GET /v1/audit/events`,
10
+ * `POST /v1/audit/exports`, and `POST /v1/audit/verify`). The docstring
11
+ * at the top of that file describes these shapes. Keep this module in
12
+ * lockstep with it; `test/audit-types.test.ts` contains type-level
13
+ * assertions against the field set to make drift obvious.
14
+ *
15
+ * Fields are snake_case because that is what the server emits on the
16
+ * wire — unlike the evaluate / verify-permit types in `./types.ts`,
17
+ * which use camelCase on the SDK side and translate at the client
18
+ * boundary, the audit surface is intentionally wire-identical so that
19
+ * signed export bundles round-trip byte-for-byte through verifiers.
20
+ */
21
+ /** Policy decision enum used on `audit_events.decision`. */
22
+ type AuditDecision = 'allow' | 'deny' | 'hold' | 'escalate';
23
+ /**
24
+ * Signing status reported on an export bundle. "signed" is the normal
25
+ * path; "unsigned" means the deployment has no active signing key;
26
+ * "signing_failed" means a key is configured but signing errored and
27
+ * the export was returned anyway (the signature is an empty string).
28
+ */
29
+ type AuditExportSignatureStatus = 'signed' | 'unsigned' | 'signing_failed';
30
+ /**
31
+ * One persisted row from `audit_events`, as returned by
32
+ * `GET /v1/audit/events` and embedded inside `AuditExport.events`.
33
+ *
34
+ * `decision` is nullable because not every event is an evaluation —
35
+ * CRUD-style audit writes (e.g. `policy.updated`) omit it. All other
36
+ * nullable fields follow the same "field doesn't apply to this event
37
+ * type" convention rather than "unknown value".
38
+ */
39
+ interface AuditEvent {
40
+ /** Event UUID. Stable; surfaces as `tampered_event_ids` on verify failure. */
41
+ id: string;
42
+ /** Organization this event belongs to. */
43
+ org_id: string;
44
+ /** Per-org monotonic sequence. Used as the pagination cursor's payload. */
45
+ sequence: number;
46
+ /** Event type tag (e.g. "evaluate.allow", "policy.updated"). */
47
+ type: string;
48
+ /** Policy decision when the event is an evaluation; `null` otherwise. */
49
+ decision: AuditDecision | null;
50
+ /** Actor id (user / API key / agent) when applicable. */
51
+ actor_id: string | null;
52
+ /** Optional resource tag — e.g. "policy". */
53
+ resource_type: string | null;
54
+ /** Optional resource id — paired with `resource_type`. */
55
+ resource_id: string | null;
56
+ /** Canonical JSON of the event payload. Hashed into `hash`. */
57
+ payload: Record<string, unknown> | null;
58
+ /** SHA-256(prev_hash || canonicalJSON(payload)), hex. */
59
+ hash: string;
60
+ /** Previous event's `hash` (genesis is `"0".repeat(64)`). */
61
+ previous_hash: string;
62
+ /** When the underlying action occurred (ISO 8601). */
63
+ occurred_at: string;
64
+ /** When the row was persisted (ISO 8601). */
65
+ created_at: string;
66
+ }
67
+ /**
68
+ * Response shape for `GET /v1/audit/events`.
69
+ *
70
+ * `total` is the filter's full count (not just this page) so callers
71
+ * can show "page 1 of N" without an extra HEAD request.
72
+ *
73
+ * `next_cursor` is an opaque base64url string. Pass it back verbatim
74
+ * as `?cursor=...` to fetch the next page. Absent when this is the
75
+ * last page.
76
+ */
77
+ interface AuditEventsPage {
78
+ events: AuditEvent[];
79
+ total: number;
80
+ next_cursor?: string;
81
+ }
82
+ /**
83
+ * Query parameters accepted by `GET /v1/audit/events`. Serialize as
84
+ * URL search params — `types` is a comma-joined list on the wire
85
+ * (e.g. `types=evaluate.allow,policy.updated`).
86
+ *
87
+ * All fields are optional; the server defaults `limit` to 50 and caps
88
+ * it at 500.
89
+ */
90
+ interface AuditEventsQuery {
91
+ /** Comma-joined list of event types to filter on. */
92
+ types?: string;
93
+ /** Filter to a single actor. */
94
+ actor_id?: string;
95
+ /** Inclusive lower bound on `occurred_at` (ISO 8601). */
96
+ from?: string;
97
+ /** Inclusive upper bound on `occurred_at` (ISO 8601). */
98
+ to?: string;
99
+ /** Page size. Default 50, min 1, max 500. */
100
+ limit?: number;
101
+ /** Opaque cursor returned as `next_cursor` by the prior page. */
102
+ cursor?: string;
103
+ }
104
+ /**
105
+ * Response shape for `POST /v1/audit/exports` — a signed bundle of
106
+ * audit events suitable for offline verification.
107
+ *
108
+ * `signature` is detached Ed25519 over `signedBytesFor(bundle)` (see
109
+ * `./auditBundle.ts`). An empty string means the server attempted to
110
+ * sign but failed; check `signature_status` to distinguish.
111
+ *
112
+ * `tampered_event_ids` surfaces rows whose recomputed hash doesn't
113
+ * match the stored hash — even when `chain_integrity_ok` is false,
114
+ * the signature still covers whatever the server emitted, so callers
115
+ * that trust the signature must still inspect this list.
116
+ */
117
+ interface AuditExport {
118
+ /** Server-assigned UUID for this export. */
119
+ export_id: string;
120
+ /** Organization the bundle belongs to. */
121
+ org_id: string;
122
+ /** Events in canonical (ascending sequence) order. */
123
+ events: AuditEvent[];
124
+ /** Last event's `hash`, or `"0".repeat(64)` if `events` is empty. */
125
+ chain_head_hash: string;
126
+ /** `true` iff adjacency + re-hash succeeded for every event. */
127
+ chain_integrity_ok: boolean;
128
+ /** `AuditEvent.id`s whose recomputed hash != stored hash. */
129
+ tampered_event_ids: string[];
130
+ /** Detached Ed25519 signature (base64url). Empty string on sign failure. */
131
+ signature: string;
132
+ /** Outcome of the signing attempt. */
133
+ signature_status: AuditExportSignatureStatus;
134
+ /** Registry id of the key that signed — absent when unsigned. */
135
+ signing_key_id?: string;
136
+ /** When the bundle was signed (ISO 8601). */
137
+ signed_at: string;
138
+ }
139
+
140
+ /**
141
+ * Public types for the AtlaSent TypeScript SDK.
142
+ *
143
+ * These shapes are deliberately minimal and 1:1 with the AtlaSent
144
+ * authorization API. Request / response fields are camelCase on the
145
+ * SDK side; the client handles snake_case translation on the wire.
146
+ */
147
+
148
+ /** The two possible policy decisions. */
149
+ type Decision = "ALLOW" | "DENY";
150
+ /**
151
+ * Rate-limit state parsed from the server's `X-RateLimit-*` headers.
152
+ *
153
+ * Present on every authenticated response (success and 429) when the
154
+ * server emits the headers. `null` when the server doesn't — older
155
+ * deployments, or internal endpoints that skip per-key rate limiting.
156
+ *
157
+ * Clients should check `remaining` and sleep until `resetAt` to
158
+ * preemptively back off before hitting a 429.
159
+ */
160
+ interface RateLimitState {
161
+ /** Value of `X-RateLimit-Limit` — the per-minute budget. */
162
+ limit: number;
163
+ /** Value of `X-RateLimit-Remaining` — unused budget in the current window. */
164
+ remaining: number;
165
+ /**
166
+ * Parsed `X-RateLimit-Reset` — the UTC instant when the current
167
+ * window's counter zeroes. Accepts either a unix-seconds integer or
168
+ * an ISO 8601 string on the wire.
169
+ */
170
+ resetAt: Date;
171
+ }
172
+ /** Input to {@link AtlaSentClient.evaluate}. */
173
+ interface EvaluateRequest {
174
+ /** Identifier of the calling agent (e.g. "clinical-data-agent"). */
175
+ agent: string;
176
+ /** The action being authorized (e.g. "modify_patient_record"). */
177
+ action: string;
178
+ /** Arbitrary policy context (user, environment, resource IDs). */
179
+ context?: Record<string, unknown>;
180
+ }
181
+ /** Result of {@link AtlaSentClient.evaluate}. */
182
+ interface EvaluateResponse {
183
+ /** "ALLOW" or "DENY". A "DENY" is not thrown — branch on this field. */
184
+ decision: Decision;
185
+ /** Opaque permit identifier, passed to {@link AtlaSentClient.verifyPermit}. */
186
+ permitId: string;
187
+ /** Human-readable explanation from the policy engine. */
188
+ reason: string;
189
+ /** Hash-chained audit-trail entry (21 CFR Part 11 / GxP-ready). */
190
+ auditHash: string;
191
+ /** ISO 8601 timestamp of the decision. */
192
+ timestamp: string;
193
+ /**
194
+ * Per-key rate-limit state for this request's response, parsed from
195
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
196
+ */
197
+ rateLimit: RateLimitState | null;
198
+ }
199
+ /** Input to {@link AtlaSentClient.verifyPermit}. */
200
+ interface VerifyPermitRequest {
201
+ /** The permit ID returned by a prior evaluate() call. */
202
+ permitId: string;
203
+ /** Optional: re-state the action for cross-check with the server. */
204
+ action?: string;
205
+ /** Optional: re-state the agent for cross-check with the server. */
206
+ agent?: string;
207
+ /** Optional: re-state the context for cross-check with the server. */
208
+ context?: Record<string, unknown>;
209
+ }
210
+ /** Result of {@link AtlaSentClient.verifyPermit}. */
211
+ interface VerifyPermitResponse {
212
+ /** `true` when the permit is valid and un-revoked. */
213
+ verified: boolean;
214
+ /** Verification outcome string from the server. */
215
+ outcome: string;
216
+ /** Verification hash bound to the permit. */
217
+ permitHash: string;
218
+ /** ISO 8601 timestamp of the verification. */
219
+ timestamp: string;
220
+ /**
221
+ * Per-key rate-limit state for this request's response, parsed from
222
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
223
+ */
224
+ rateLimit: RateLimitState | null;
225
+ }
226
+ /**
227
+ * Result of {@link AtlaSentClient.keySelf} — self-introspection of the API
228
+ * key the client was constructed with. Returned by `GET /v1/api-key-self`.
229
+ *
230
+ * Never includes the raw key or its hash — introspection is intentionally
231
+ * read-only and safe to surface in operator dashboards. Useful for:
232
+ * - "which key am I?" debugging
233
+ * - IP_NOT_ALLOWED failures — `clientIp` is the IP the server observed
234
+ * - proactive expiry warnings — `expiresAt` is the server-stored expiry
235
+ * (`null` means the key does not auto-expire)
236
+ * - verifying scopes before attempting a scope-gated action
237
+ */
238
+ interface ApiKeySelfResponse {
239
+ /** Server-side UUID of the api_keys row for this key. */
240
+ keyId: string;
241
+ /** Organization the key belongs to. */
242
+ organizationId: string;
243
+ /** "live" or "test" (or any future environment label the server introduces). */
244
+ environment: string;
245
+ /** Granted scopes — e.g. ["evaluate", "audit.read"]. */
246
+ scopes: string[];
247
+ /**
248
+ * Per-key IP allowlist as CIDR strings (e.g. ["10.0.0.0/8"]). `null`
249
+ * when the key is unrestricted.
250
+ */
251
+ allowedCidrs: string[] | null;
252
+ /** Server-enforced per-minute rate limit for this key. */
253
+ rateLimitPerMinute: number;
254
+ /** Client IP as the server observed it (first hop of X-Forwarded-For). */
255
+ clientIp: string | null;
256
+ /** Server-stored expiry; `null` means the key does not auto-expire. */
257
+ expiresAt: string | null;
258
+ /**
259
+ * Per-key rate-limit state for this request's response, parsed from
260
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
261
+ */
262
+ rateLimit: RateLimitState | null;
263
+ }
264
+ /**
265
+ * Result of {@link AtlaSentClient.listAuditEvents}. Extends the raw
266
+ * wire page with a camelCase `rateLimit` alongside the snake_case
267
+ * wire fields — the wire shape (`events`, `total`, `next_cursor`) is
268
+ * untouched so callers that pass it to the offline verifier get
269
+ * byte-identical behaviour.
270
+ */
271
+ interface AuditEventsResult extends AuditEventsPage {
272
+ /**
273
+ * Per-key rate-limit state for this request's response, parsed from
274
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
275
+ */
276
+ rateLimit: RateLimitState | null;
277
+ }
278
+ /**
279
+ * Filter accepted by {@link AtlaSentClient.createAuditExport}. Fields
280
+ * are snake_case to match the server's `POST /v1-audit/exports`
281
+ * request body; an empty object requests a full-org bundle.
282
+ */
283
+ interface AuditExportRequest {
284
+ /** Comma-joined list of event types to include (e.g. `"evaluate.allow,policy.updated"`). */
285
+ types?: string;
286
+ /** Filter to a single actor. */
287
+ actor_id?: string;
288
+ /** Inclusive lower bound on `occurred_at` (ISO 8601). */
289
+ from?: string;
290
+ /** Inclusive upper bound on `occurred_at` (ISO 8601). */
291
+ to?: string;
292
+ }
293
+ /**
294
+ * Result of {@link AtlaSentClient.createAuditExport}. Extends the
295
+ * signed bundle shape with a camelCase `rateLimit`. The signed
296
+ * envelope fields (`export_id`, `org_id`, `chain_head_hash`,
297
+ * `event_count`, `signed_at`, `events`, `signature`) are preserved
298
+ * byte-for-byte so the object can be handed straight to
299
+ * `verifyAuditBundle(bundle, keys)`.
300
+ */
301
+ interface AuditExportResult extends AuditExport {
302
+ /**
303
+ * Per-key rate-limit state for this request's response, parsed from
304
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
305
+ */
306
+ rateLimit: RateLimitState | null;
307
+ }
308
+ /** Constructor options for {@link AtlaSentClient}. */
309
+ interface AtlaSentClientOptions {
310
+ /** Required. Your AtlaSent API key. */
311
+ apiKey: string;
312
+ /** API base URL. Defaults to "https://api.atlasent.io". */
313
+ baseUrl?: string;
314
+ /** Per-request timeout in milliseconds. Defaults to 10_000. */
315
+ timeoutMs?: number;
316
+ /**
317
+ * Inject a fetch implementation (primarily for testing).
318
+ * Defaults to `globalThis.fetch`.
319
+ */
320
+ fetch?: typeof fetch;
321
+ }
322
+
323
+ /**
324
+ * AtlaSent HTTP client.
325
+ *
326
+ * Two public methods, both backed by native `fetch`:
327
+ * - {@link AtlaSentClient.evaluate} → POST {baseUrl}/v1-evaluate
328
+ * - {@link AtlaSentClient.verifyPermit} → POST {baseUrl}/v1-verify-permit
329
+ *
330
+ * Fail-closed: a clean policy DENY is returned (not thrown), but
331
+ * network, timeout, bad response, 4xx/5xx, and rate-limit conditions
332
+ * all throw {@link AtlaSentError}.
333
+ */
334
+
335
+ declare class AtlaSentClient {
336
+ private readonly apiKey;
337
+ private readonly baseUrl;
338
+ private readonly timeoutMs;
339
+ private readonly fetchImpl;
340
+ constructor(options: AtlaSentClientOptions);
341
+ /**
342
+ * Ask the policy engine whether an agent action is permitted.
343
+ *
344
+ * A "DENY" is **not** thrown — it is returned in
345
+ * `response.decision`. Network errors, invalid API key, rate
346
+ * limits, timeouts, and malformed responses throw
347
+ * {@link AtlaSentError}.
348
+ */
349
+ evaluate(input: EvaluateRequest): Promise<EvaluateResponse>;
350
+ /**
351
+ * Verify that a previously issued permit is still valid.
352
+ *
353
+ * A `verified: false` response is **not** thrown — inspect the
354
+ * returned object. Only transport / server errors throw.
355
+ */
356
+ verifyPermit(input: VerifyPermitRequest): Promise<VerifyPermitResponse>;
357
+ /**
358
+ * Self-introspection: ask the server to describe the API key this
359
+ * client was constructed with. Returns the key's ID, organization,
360
+ * environment, scopes, IP allowlist, per-minute rate limit, the
361
+ * client IP the server observed, and the expiry (if any).
362
+ *
363
+ * Never includes the raw key or its hash. Safe to surface in operator
364
+ * dashboards. Useful for `IP_NOT_ALLOWED` debugging (the server tells
365
+ * you exactly which IP it saw) and for proactive expiry warnings.
366
+ *
367
+ * Throws {@link AtlaSentError} on transport / auth failures — same
368
+ * taxonomy as {@link AtlaSentClient.evaluate}.
369
+ */
370
+ keySelf(): Promise<ApiKeySelfResponse>;
371
+ /**
372
+ * List persisted audit events for the authenticated organization
373
+ * (`GET /v1-audit/events`). Returned rows are wire-identical with
374
+ * the server: snake_case field names, including `previous_hash` and
375
+ * the `hash` chain, so the response can be fed straight into the
376
+ * offline verifier when paired with a signed export.
377
+ *
378
+ * `query.types` is a comma-joined list (e.g.
379
+ * `"evaluate.allow,policy.updated"`). `cursor` is the opaque
380
+ * `next_cursor` from the prior page. All fields are optional; the
381
+ * server defaults `limit` to 50 (capped at 500).
382
+ *
383
+ * Throws {@link AtlaSentError} on transport / auth failures — same
384
+ * taxonomy as {@link AtlaSentClient.evaluate}.
385
+ */
386
+ listAuditEvents(query?: AuditEventsQuery): Promise<AuditEventsResult>;
387
+ /**
388
+ * Request a signed audit export bundle
389
+ * (`POST /v1-audit/exports`). The returned object is wire-identical
390
+ * with the server — `signature`, `chain_head_hash`, `events`, and
391
+ * friends survive untouched so the bundle can be persisted to disk
392
+ * and handed to the offline verifier (`verifyBundle` /
393
+ * `verifyAuditBundle`) without any reshaping.
394
+ *
395
+ * Pass `filter.types`, `filter.from`, `filter.to`, or `filter.actor_id`
396
+ * to narrow the export; omit for a full-org bundle. `rateLimit` is
397
+ * attached alongside the wire fields for observability.
398
+ *
399
+ * Throws {@link AtlaSentError} on transport / auth failures — same
400
+ * taxonomy as {@link AtlaSentClient.evaluate}.
401
+ */
402
+ createAuditExport(filter?: AuditExportRequest): Promise<AuditExportResult>;
403
+ private post;
404
+ private get;
405
+ private request;
406
+ }
407
+
408
+ /** Node's webcrypto CryptoKey — kept local so the module doesn't depend on DOM types. */
409
+ type WebCryptoKey = webcrypto.CryptoKey;
410
+ /** Public key candidate the verifier will try, tagged with its registry id. */
411
+ interface VerifyKey {
412
+ keyId: string;
413
+ publicKey: WebCryptoKey;
414
+ }
415
+ interface BundleVerificationResult {
416
+ /**
417
+ * AND of three checks: adjacency (each event's `previous_hash`
418
+ * equals the prior event's `hash`), per-event hash recomputation
419
+ * from the canonical payload, and `chain_head_hash` matching the
420
+ * last event's stored hash.
421
+ */
422
+ chainIntegrityOk: boolean;
423
+ /** Ed25519 signature verified against one of the supplied public keys. */
424
+ signatureValid: boolean;
425
+ /** `chain_head_hash` equals the last event's stored `hash`. */
426
+ headHashMatches: boolean;
427
+ /** Event ids whose recomputed hash != stored hash. */
428
+ tamperedEventIds: string[];
429
+ /** Which registry key id matched, when `signatureValid` is true. */
430
+ matchedKeyId?: string | undefined;
431
+ /** Non-fatal explanation when a flag is false. */
432
+ reason?: string | undefined;
433
+ /** Convenience: `chainIntegrityOk && signatureValid`. */
434
+ verified: boolean;
435
+ }
436
+ /** Parsed bundle shape the verifier consumes. Fields beyond these are ignored. */
437
+ interface AuditBundle {
438
+ export_id?: unknown;
439
+ org_id?: unknown;
440
+ chain_head_hash?: unknown;
441
+ event_count?: unknown;
442
+ signed_at?: unknown;
443
+ events?: unknown;
444
+ signature?: unknown;
445
+ signing_key_id?: unknown;
446
+ [k: string]: unknown;
447
+ }
448
+ interface VerifyBundleOptions {
449
+ /** SPKI-PEM strings (one per key in the active trust set). */
450
+ publicKeysPem?: readonly string[];
451
+ /** Already-imported keys, paired with registry ids (rotation hint). */
452
+ keys?: readonly VerifyKey[];
453
+ }
454
+ /**
455
+ * Reproduces `_shared/rules.ts::canonicalJSON` byte-for-byte:
456
+ * - object keys sorted at every depth
457
+ * - no whitespace
458
+ * - `null`, `undefined`, `NaN`, `±Infinity` all render as `"null"`
459
+ * - strings use standard `JSON.stringify` escapes
460
+ */
461
+ declare function canonicalJSON(value: unknown): string;
462
+ /**
463
+ * Recreate the exact bytes `handleExport` signed. Key order is
464
+ * load-bearing — must match the object literal in
465
+ * `v1-audit/index.ts::handleExport`. V8 preserves insertion order, so
466
+ * the literal below is byte-identical with what the backend signs.
467
+ */
468
+ declare function signedBytesFor(bundle: AuditBundle): Uint8Array<ArrayBuffer>;
469
+ declare function verifyAuditBundle(bundle: AuditBundle, keys: readonly VerifyKey[]): Promise<BundleVerificationResult>;
470
+ /**
471
+ * Load a bundle from disk (or a parsed object) and verify it.
472
+ *
473
+ * `publicKeysPem` is the active SPKI-PEM set from
474
+ * `GET /v1-signing-keys`. When omitted, the chain check still runs
475
+ * but `signatureValid` will be false with an explanatory `reason` —
476
+ * callers that want a complete offline check MUST supply the trust
477
+ * set.
478
+ */
479
+ declare function verifyBundle(pathOrBundle: string | AuditBundle, options?: VerifyBundleOptions): Promise<BundleVerificationResult>;
480
+
481
+ /**
482
+ * Retry-policy helpers for the AtlaSent TypeScript SDK.
483
+ *
484
+ * This module is **pure**: no I/O, no network, no globals beyond
485
+ * `Math.random`. The intent is that {@link AtlaSentClient} (and any
486
+ * caller wrapping `protect()` / `evaluate()`) can ask
487
+ * {@link isRetryable} whether to retry a given {@link AtlaSentError},
488
+ * then ask {@link computeBackoffMs} how long to sleep before the next
489
+ * attempt.
490
+ *
491
+ * Wire-up into the client itself is intentionally deferred — see
492
+ * ROADMAP item #7 (Post-GA). Sentry breadcrumb emission is also
493
+ * deferred; both will land together once a transport-level retry
494
+ * loop is wired into `client.ts`.
495
+ *
496
+ * Retry classification (matches the server's documented contract):
497
+ * - `network` / `timeout` → retry (transient transport)
498
+ * - `server_error` (HTTP 5xx) → retry
499
+ * - `rate_limited` (HTTP 429) → retry, honour `retryAfterMs`
500
+ * - `bad_response` → retry (likely truncated body)
501
+ * - `invalid_api_key`/`forbidden`/`bad_request` → never retry
502
+ *
503
+ * Backoff: capped exponential with full jitter.
504
+ * delay = min(maxDelayMs, baseDelayMs * 2^attempt) * random[0, 1)
505
+ *
506
+ * "Full jitter" is the AWS-recommended scheme — see
507
+ * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/.
508
+ * It avoids thundering-herd retries from many SDK instances that hit
509
+ * a 429 in the same window.
510
+ */
511
+ /** Defaults for {@link RetryPolicy}. Conservative — three retries, ~7s ceiling. */
512
+ declare const DEFAULT_RETRY_POLICY: Required<RetryPolicy>;
513
+ /**
514
+ * Caller-tunable retry policy. All fields optional; missing fields
515
+ * fall back to {@link DEFAULT_RETRY_POLICY}.
516
+ */
517
+ interface RetryPolicy {
518
+ /**
519
+ * Total attempts including the first try. `1` disables retries
520
+ * entirely. Must be `>= 1`; values below are clamped to `1`.
521
+ */
522
+ maxAttempts?: number;
523
+ /**
524
+ * Initial backoff for `attempt = 0`. Doubles per attempt up to
525
+ * `maxDelayMs`. Must be `>= 0`.
526
+ */
527
+ baseDelayMs?: number;
528
+ /**
529
+ * Hard ceiling on the per-attempt sleep, applied **before** jitter.
530
+ * The actual sleep is uniformly distributed in `[0, ceiling]`.
531
+ */
532
+ maxDelayMs?: number;
533
+ }
534
+ /**
535
+ * Decide whether `err` is worth a retry. Anything that isn't an
536
+ * {@link AtlaSentError} is treated as non-retryable — the SDK's
537
+ * transport layer always wraps fetch failures in `AtlaSentError`,
538
+ * so a non-AtlaSent throwable is by definition a programmer bug
539
+ * (a bad input, an assertion in user code) and should propagate.
540
+ */
541
+ declare function isRetryable(err: unknown): boolean;
542
+ /**
543
+ * Compute how long to sleep before retry attempt `attempt`
544
+ * (zero-indexed: `attempt = 0` is the first retry, i.e. the second
545
+ * total request). Uses capped exponential backoff with full jitter.
546
+ *
547
+ * When `err` carries a `retryAfterMs` (server-provided `Retry-After`
548
+ * header), the result is `max(retryAfterMs, jitteredDelay)` — the
549
+ * server's hint is treated as a floor so we never retry sooner than
550
+ * the server asked.
551
+ *
552
+ * @param attempt Zero-indexed retry attempt (0, 1, 2, ...).
553
+ * @param policy Optional override of {@link DEFAULT_RETRY_POLICY}.
554
+ * @param err Optional error whose `retryAfterMs` is honoured.
555
+ * @param random Injectable RNG, defaults to `Math.random`. Must
556
+ * return values in `[0, 1)` to preserve the
557
+ * distribution.
558
+ */
559
+ declare function computeBackoffMs(attempt: number, policy?: RetryPolicy, err?: unknown, random?: () => number): number;
560
+ /**
561
+ * Returns `true` when `attempt` (zero-indexed) is below the policy's
562
+ * `maxAttempts - 1` ceiling — i.e. when there is still budget for at
563
+ * least one more try after this one. Convenience wrapper so retry
564
+ * loops read top-to-bottom:
565
+ *
566
+ * ```ts
567
+ * for (let attempt = 0; ; attempt++) {
568
+ * try { return await op(); }
569
+ * catch (err) {
570
+ * if (!isRetryable(err) || !hasAttemptsLeft(attempt, policy)) throw err;
571
+ * await sleep(computeBackoffMs(attempt, policy, err));
572
+ * }
573
+ * }
574
+ * ```
575
+ */
576
+ declare function hasAttemptsLeft(attempt: number, policy?: RetryPolicy): boolean;
577
+ /**
578
+ * Merge a partial policy with {@link DEFAULT_RETRY_POLICY} and clamp
579
+ * each field into a sensible range. Exported for tests and for
580
+ * callers that want to log the resolved policy.
581
+ */
582
+ declare function mergePolicy(policy: RetryPolicy): Required<RetryPolicy>;
583
+
584
+ /**
585
+ * @atlasent/sdk — execution-time authorization for AI agents.
586
+ *
587
+ * Primary API is the default export:
588
+ *
589
+ * ```ts
590
+ * import atlasent from "@atlasent/sdk";
591
+ *
592
+ * const permit = await atlasent.protect({
593
+ * agent: "deploy-bot",
594
+ * action: "deploy_to_production",
595
+ * context: { commit, approver },
596
+ * });
597
+ * ```
598
+ *
599
+ * Named exports remain available for the lower-level
600
+ * {@link AtlaSentClient} and the error taxonomy.
601
+ */
602
+
603
+ /**
604
+ * Default export. The opinionated, category-defining entry point:
605
+ *
606
+ * ```ts
607
+ * import atlasent from "@atlasent/sdk";
608
+ * await atlasent.protect({ ... });
609
+ * ```
610
+ */
611
+ declare const atlasent: {
612
+ readonly protect: typeof protect;
613
+ readonly configure: typeof configure;
614
+ readonly verifyBundle: typeof verifyBundle;
615
+ readonly AtlaSentClient: typeof AtlaSentClient;
616
+ readonly AtlaSentError: typeof AtlaSentError;
617
+ readonly AtlaSentDeniedError: typeof AtlaSentDeniedError;
618
+ };
619
+
620
+ export { type ApiKeySelfResponse, AtlaSentClient, type AtlaSentClientOptions, AtlaSentDeniedError, AtlaSentError, type AuditBundle, type AuditDecision, type AuditEvent, type AuditEventsPage, type AuditEventsQuery, type AuditEventsResult, type AuditExport, type AuditExportRequest, type AuditExportResult, type AuditExportSignatureStatus, type BundleVerificationResult, DEFAULT_RETRY_POLICY, type Decision, type EvaluateRequest, type EvaluateResponse, type RateLimitState, type RetryPolicy, type VerifyBundleOptions, type VerifyKey, type VerifyPermitRequest, type VerifyPermitResponse, canonicalJSON, computeBackoffMs, configure, atlasent as default, hasAttemptsLeft, isRetryable, mergePolicy, protect, signedBytesFor, verifyAuditBundle, verifyBundle };