@atlasent/sdk 1.5.0 → 2.10.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,1776 @@
1
+ /**
2
+ * Error types for the AtlaSent TypeScript SDK.
3
+ *
4
+ * The SDK follows a fail-closed design: a clean policy DENY is
5
+ * returned as `EvaluateResponse.decision === "deny"` (not thrown),
6
+ * but any failure to confirm authorization — network, timeout,
7
+ * bad response, invalid key, rate limit — throws an
8
+ * {@link AtlaSentError}.
9
+ */
10
+ /**
11
+ * Thrown when no SSE event arrives within the configured timeout window.
12
+ *
13
+ * Callers can catch this specifically to distinguish a stalled stream
14
+ * from other network or parse failures:
15
+ *
16
+ * ```ts
17
+ * catch (e) {
18
+ * if (e instanceof StreamTimeoutError) { // reconnect or alert }
19
+ * }
20
+ * ```
21
+ */
22
+ declare class StreamTimeoutError extends Error {
23
+ name: string;
24
+ /** Timeout that was exceeded, in milliseconds. */
25
+ readonly timeoutMs: number;
26
+ constructor(timeoutMs: number);
27
+ }
28
+ /**
29
+ * Thrown when the SSE stream closes with a partial / malformed JSON payload.
30
+ *
31
+ * This is a recoverable condition — the stream closed mid-JSON. The
32
+ * caller can reconnect using the last received `Last-Event-ID` and
33
+ * resume from where the server left off.
34
+ *
35
+ * ```ts
36
+ * catch (e) {
37
+ * if (e instanceof StreamParseError) { // log raw data, maybe reconnect }
38
+ * }
39
+ * ```
40
+ */
41
+ declare class StreamParseError extends Error {
42
+ name: string;
43
+ /** The raw data string that failed to parse. */
44
+ readonly rawData: string;
45
+ constructor(rawData: string, cause?: unknown);
46
+ }
47
+ /** Discriminator for {@link AtlaSentError.code}. */
48
+ type AtlaSentErrorCode = "invalid_api_key" | "forbidden" | "rate_limited" | "timeout" | "network" | "bad_response" | "bad_request" | "server_error" | "feature_disabled" | "claim_evidence_incomplete";
49
+ /** Initialization options for {@link AtlaSentError}. */
50
+ interface AtlaSentErrorInit {
51
+ status?: number;
52
+ code?: AtlaSentErrorCode;
53
+ requestId?: string;
54
+ retryAfterMs?: number;
55
+ cause?: unknown;
56
+ }
57
+ /**
58
+ * The only error type this SDK throws.
59
+ *
60
+ * Flat top-level properties mirror the convention used by Stripe,
61
+ * Octokit, and Supabase. `cause` is forwarded to the standard
62
+ * ES2022 `Error` constructor.
63
+ */
64
+ declare class AtlaSentError extends Error {
65
+ name: string;
66
+ /** HTTP status code, when the error originated from an API response. */
67
+ readonly status: number | undefined;
68
+ /** Coarse category — useful for `switch` statements at call sites. */
69
+ readonly code: AtlaSentErrorCode | undefined;
70
+ /** Correlation ID echoed from the `X-Request-ID` header the SDK sent. */
71
+ readonly requestId: string | undefined;
72
+ /** Parsed `Retry-After` header value, in milliseconds. Only set for 429. */
73
+ readonly retryAfterMs: number | undefined;
74
+ constructor(message: string, init?: AtlaSentErrorInit);
75
+ }
76
+ /**
77
+ * Outcome of a denied decision.
78
+ *
79
+ * `"deny"` is what the current `/v1-evaluate` API returns. `"hold"`
80
+ * and `"escalate"` are reserved for forthcoming API decisions that
81
+ * put a permit into a pending state requiring human review; the
82
+ * union is declared now so call sites can `switch` exhaustively
83
+ * from the start and adopt new decisions without a breaking change.
84
+ */
85
+ type AtlaSentDecision = "deny" | "hold" | "escalate";
86
+ /**
87
+ * Reason an already-issued permit failed verification.
88
+ *
89
+ * Surfaced on {@link AtlaSentDeniedError.outcome} so callers can
90
+ * distinguish replay (`permit_consumed`) from revocation
91
+ * (`permit_revoked`) from natural expiry (`permit_expired`) without
92
+ * parsing {@link AtlaSentDeniedError.reason}. The set is defined by
93
+ * `contract/vectors/permit_outcomes.json`; any new outcome MUST be
94
+ * added there first.
95
+ *
96
+ * Mirrors the Python SDK's `PermitOutcome`. See
97
+ * `atlasent/docs/REVOCATION_RUNBOOK.md` for the operator-facing
98
+ * matrix this discriminator drives.
99
+ */
100
+ type PermitOutcome = "permit_consumed" | "permit_expired" | "permit_revoked" | "permit_not_found";
101
+ /**
102
+ * Map a server-supplied `outcome` string to {@link PermitOutcome}.
103
+ *
104
+ * Returns `undefined` for `undefined`, `""`, `"verified"`, or any
105
+ * unrecognized value. Used at the SDK's deny boundary so we don't
106
+ * surface mis-typed outcomes — when the server adds a new outcome
107
+ * string, callers branching on {@link AtlaSentDeniedError.outcome}
108
+ * see `undefined` and fall through to their generic deny path
109
+ * rather than match an unknown literal.
110
+ */
111
+ declare function normalizePermitOutcome(raw: string | undefined): PermitOutcome | undefined;
112
+ /** Initialization options for {@link AtlaSentDeniedError}. */
113
+ interface AtlaSentDeniedErrorInit {
114
+ decision: AtlaSentDecision;
115
+ evaluationId: string;
116
+ reason?: string;
117
+ requestId?: string;
118
+ auditHash?: string;
119
+ /**
120
+ * When the denial came from permit verification (not policy
121
+ * evaluation), the discriminator that distinguishes replay,
122
+ * expiry, revocation, and missing-record failures. `undefined`
123
+ * for evaluate-time denials.
124
+ */
125
+ outcome?: PermitOutcome;
126
+ }
127
+ /**
128
+ * Thrown by {@link atlasent.protect} when the policy engine refuses
129
+ * the action, or when a permit fails end-to-end verification.
130
+ *
131
+ * This is the **fail-closed boundary** of the SDK: every code path
132
+ * that short-circuits an action because authorization was not
133
+ * confirmed raises an `AtlaSentDeniedError`. Callers cannot silently
134
+ * proceed on a denial by forgetting to branch on a return value.
135
+ *
136
+ * Extends {@link AtlaSentError} so `instanceof AtlaSentError`
137
+ * catches denials as part of the SDK's single exception family;
138
+ * use `instanceof AtlaSentDeniedError` to distinguish a policy
139
+ * denial from a transport/auth error.
140
+ */
141
+ declare class AtlaSentDeniedError extends AtlaSentError {
142
+ name: string;
143
+ /** Policy decision — `"deny"` today; `"hold"` / `"escalate"` reserved. */
144
+ readonly decision: AtlaSentDecision;
145
+ /** Opaque permit/decision id from `/v1-evaluate`. */
146
+ readonly evaluationId: string;
147
+ /** Human-readable explanation from the policy engine, if provided. */
148
+ readonly reason: string | undefined;
149
+ /** Hash-chained audit-trail entry associated with the decision. */
150
+ readonly auditHash: string | undefined;
151
+ /**
152
+ * Discriminator for permit-side denial reasons. Populated only
153
+ * when the server reported `verified=false` from `/v1-verify-permit`;
154
+ * `undefined` for evaluate-time denials. See {@link PermitOutcome}.
155
+ */
156
+ readonly outcome: PermitOutcome | undefined;
157
+ constructor(init: AtlaSentDeniedErrorInit);
158
+ /** `true` when the permit was explicitly revoked (D3 endpoint). */
159
+ get isRevoked(): boolean;
160
+ /** `true` when the permit's TTL passed before verification. */
161
+ get isExpired(): boolean;
162
+ /**
163
+ * `true` when the permit was already consumed by a prior verify
164
+ * (v1 single-use replay protection).
165
+ */
166
+ get isConsumed(): boolean;
167
+ /**
168
+ * `true` when the permit id wasn't recognized server-side
169
+ * (typo, cross-tenant lookup, or pre-issuance race).
170
+ */
171
+ get isNotFound(): boolean;
172
+ }
173
+ /** Initialization options for {@link AtlaSentEscalateError}. */
174
+ interface AtlaSentEscalateErrorInit {
175
+ requestId?: string;
176
+ userId?: string;
177
+ cause?: unknown;
178
+ }
179
+ /**
180
+ * Thrown when an evaluate response carries `decision: "escalate"`.
181
+ *
182
+ * Distinct from {@link AtlaSentDeniedError} — an escalation does not
183
+ * constitute a hard denial. It signals that the policy engine has
184
+ * deferred the authorization decision to a human review queue.
185
+ * Middleware and agent orchestrators should catch this specifically
186
+ * and route the pending action to the appropriate HITL channel.
187
+ *
188
+ * ```ts
189
+ * catch (e) {
190
+ * if (e instanceof AtlaSentEscalateError) {
191
+ * await humanReviewQueue.submit({ userId: e.userId, requestId: e.requestId });
192
+ * }
193
+ * }
194
+ * ```
195
+ *
196
+ * Extends {@link AtlaSentError} so `instanceof AtlaSentError` catches
197
+ * escalations alongside other SDK errors; use
198
+ * `instanceof AtlaSentEscalateError` to branch specifically.
199
+ */
200
+ declare class AtlaSentEscalateError extends AtlaSentError {
201
+ name: string;
202
+ /** Always `"escalate"` — discriminates this error from other AtlaSent errors. */
203
+ readonly decision: "escalate";
204
+ /** The user whose action triggered the escalation, if available. */
205
+ readonly userId: string | undefined;
206
+ constructor(message: string, opts?: AtlaSentEscalateErrorInit);
207
+ }
208
+ /**
209
+ * Thrown by an SDK guard heartbeat when `GET /v1/permits/:id/valid`
210
+ * returns `status: 'revoked'` during tool execution (PROD-D9
211
+ * continuous-authorization lease model).
212
+ *
213
+ * This error is **always re-thrown** — it is never serialized as a
214
+ * `tool-result` denial because it represents a live enforcement action,
215
+ * not a policy evaluation at request time. Callers should treat it as
216
+ * an immediate halt signal.
217
+ *
218
+ * ```ts
219
+ * catch (e) {
220
+ * if (e instanceof PermitRevoked) {
221
+ * // log e.permitId and e.revocationId for incident correlation
222
+ * await incidentLog.record({ permitId: e.permitId, revocationId: e.revocationId });
223
+ * }
224
+ * }
225
+ * ```
226
+ *
227
+ * Guard heartbeat is configured via `permitRevalidationIntervalMs` in
228
+ * the guard options (minimum 1000 ms). The heartbeat activates only
229
+ * when the {@link AtlaSentClient} exposes `checkPermitValid` — i.e.
230
+ * when `atlasent-api` has deployed `GET /v1/permits/:id/valid`.
231
+ */
232
+ declare class PermitRevoked extends AtlaSentError {
233
+ name: string;
234
+ /** The id of the permit that was revoked mid-execution. */
235
+ readonly permitId: string;
236
+ /** The `scope_revocations.id` that triggered the revocation, when available. */
237
+ readonly revocationId: string | undefined;
238
+ constructor(permitId: string, revocationId?: string);
239
+ }
240
+
241
+ /**
242
+ * Retry-policy helpers for the AtlaSent TypeScript SDK.
243
+ *
244
+ * This module is **pure**: no I/O, no network, no globals beyond
245
+ * `Math.random`. The intent is that {@link AtlaSentClient} (and any
246
+ * caller wrapping `protect()` / `evaluate()`) can ask
247
+ * {@link isRetryable} whether to retry a given {@link AtlaSentError},
248
+ * then ask {@link computeBackoffMs} how long to sleep before the next
249
+ * attempt.
250
+ *
251
+ * Wire-up into the client itself is intentionally deferred — see
252
+ * ROADMAP item #7 (Post-GA). Sentry breadcrumb emission is also
253
+ * deferred; both will land together once a transport-level retry
254
+ * loop is wired into `client.ts`.
255
+ *
256
+ * Retry classification (matches the server's documented contract):
257
+ * - `network` / `timeout` → retry (transient transport)
258
+ * - `server_error` (HTTP 5xx) → retry
259
+ * - `rate_limited` (HTTP 429) → retry, honour `retryAfterMs`
260
+ * - `bad_response` → retry (likely truncated body)
261
+ * - `invalid_api_key`/`forbidden`/`bad_request` → never retry
262
+ *
263
+ * Backoff: capped exponential with full jitter.
264
+ * delay = min(maxDelayMs, baseDelayMs * 2^attempt) * random[0, 1)
265
+ *
266
+ * "Full jitter" is the AWS-recommended scheme — see
267
+ * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/.
268
+ * It avoids thundering-herd retries from many SDK instances that hit
269
+ * a 429 in the same window.
270
+ *
271
+ * Default schedule matches the Python SDK:
272
+ * attempt 0 (1st retry): base 2 000 ms, jittered in [0, 2 000)
273
+ * attempt 1 (2nd retry): base 4 000 ms, jittered in [0, 4 000)
274
+ * attempt 2 (3rd retry): base 8 000 ms, jittered in [0, 8 000)
275
+ * attempt 3 (4th retry): capped at 16 000 ms, jittered in [0, 16 000)
276
+ * Total attempts (including initial): 4
277
+ */
278
+ /**
279
+ * Defaults for {@link RetryPolicy}.
280
+ *
281
+ * Matches the Python SDK's backoff schedule:
282
+ * 2 s → 4 s → 8 s → 16 s (4 total attempts, cap at 16 s).
283
+ */
284
+ declare const DEFAULT_RETRY_POLICY: Required<RetryPolicy>;
285
+ /**
286
+ * Caller-tunable retry policy. All fields optional; missing fields
287
+ * fall back to {@link DEFAULT_RETRY_POLICY}.
288
+ */
289
+ interface RetryPolicy {
290
+ /**
291
+ * Total attempts including the first try. `1` disables retries
292
+ * entirely. Must be `>= 1`; values below are clamped to `1`.
293
+ */
294
+ maxAttempts?: number;
295
+ /**
296
+ * Initial backoff for `attempt = 0`. Doubles per attempt up to
297
+ * `maxDelayMs`. Must be `>= 0`.
298
+ */
299
+ baseDelayMs?: number;
300
+ /**
301
+ * Hard ceiling on the per-attempt sleep, applied **before** jitter.
302
+ * The actual sleep is uniformly distributed in `[0, ceiling]`.
303
+ */
304
+ maxDelayMs?: number;
305
+ }
306
+ /**
307
+ * Decide whether `err` is worth a retry. Anything that isn't an
308
+ * {@link AtlaSentError} is treated as non-retryable — the SDK's
309
+ * transport layer always wraps fetch failures in `AtlaSentError`,
310
+ * so a non-AtlaSent throwable is by definition a programmer bug
311
+ * (a bad input, an assertion in user code) and should propagate.
312
+ */
313
+ declare function isRetryable(err: unknown): boolean;
314
+ /**
315
+ * Compute how long to sleep before retry attempt `attempt`
316
+ * (zero-indexed: `attempt = 0` is the first retry, i.e. the second
317
+ * total request). Uses capped exponential backoff with full jitter.
318
+ *
319
+ * When `err` carries a `retryAfterMs` (server-provided `Retry-After`
320
+ * header), the result is `max(retryAfterMs, jitteredDelay)` — the
321
+ * server's hint is treated as a floor so we never retry sooner than
322
+ * the server asked.
323
+ *
324
+ * @param attempt Zero-indexed retry attempt (0, 1, 2, ...). For
325
+ * the default policy this produces delays drawn from
326
+ * [0, 2 000), [0, 4 000), [0, 8 000), [0, 16 000).
327
+ * @param policy Optional override of {@link DEFAULT_RETRY_POLICY}.
328
+ * @param err Optional error whose `retryAfterMs` is honoured.
329
+ * @param random Injectable RNG, defaults to `Math.random`. Must
330
+ * return values in `[0, 1)` to preserve the
331
+ * distribution.
332
+ */
333
+ declare function computeBackoffMs(attempt: number, policy?: RetryPolicy, err?: unknown, random?: () => number): number;
334
+ /**
335
+ * Returns `true` when `attempt` (zero-indexed) is below the policy's
336
+ * `maxAttempts - 1` ceiling — i.e. when there is still budget for at
337
+ * least one more try after this one. Convenience wrapper so retry
338
+ * loops read top-to-bottom:
339
+ *
340
+ * ```ts
341
+ * for (let attempt = 0; ; attempt++) {
342
+ * try { return await op(); }
343
+ * catch (err) {
344
+ * if (!isRetryable(err) || !hasAttemptsLeft(attempt, policy)) throw err;
345
+ * await sleep(computeBackoffMs(attempt, policy, err));
346
+ * }
347
+ * }
348
+ * ```
349
+ */
350
+ declare function hasAttemptsLeft(attempt: number, policy?: RetryPolicy): boolean;
351
+ /**
352
+ * Merge a partial policy with {@link DEFAULT_RETRY_POLICY} and clamp
353
+ * each field into a sensible range. Exported for tests and for
354
+ * callers that want to log the resolved policy.
355
+ */
356
+ declare function mergePolicy(policy: RetryPolicy): Required<RetryPolicy>;
357
+
358
+ /**
359
+ * Shared audit wire types — the `/v1/audit/*` HTTP surface.
360
+ *
361
+ * Source of truth: `atlasent-api/supabase/functions/v1-audit/index.ts`
362
+ * (the edge function that serves `GET /v1/audit/events`,
363
+ * `POST /v1/audit/exports`, and `POST /v1/audit/verify`). The docstring
364
+ * at the top of that file describes these shapes. Keep this module in
365
+ * lockstep with it; `test/audit-types.test.ts` contains type-level
366
+ * assertions against the field set to make drift obvious.
367
+ *
368
+ * Fields are snake_case because that is what the server emits on the
369
+ * wire — unlike the evaluate / verify-permit types in `./types.ts`,
370
+ * which use camelCase on the SDK side and translate at the client
371
+ * boundary, the audit surface is intentionally wire-identical so that
372
+ * signed export bundles round-trip byte-for-byte through verifiers.
373
+ */
374
+ /** Policy decision enum used on `audit_events.decision`. */
375
+ type AuditDecision = 'allow' | 'deny' | 'hold' | 'escalate';
376
+ /**
377
+ * Signing status reported on an export bundle. "signed" is the normal
378
+ * path; "unsigned" means the deployment has no active signing key;
379
+ * "signing_failed" means a key is configured but signing errored and
380
+ * the export was returned anyway (the signature is an empty string).
381
+ */
382
+ type AuditExportSignatureStatus = 'signed' | 'unsigned' | 'signing_failed';
383
+ /**
384
+ * One persisted row from `audit_events`, as returned by
385
+ * `GET /v1/audit/events` and embedded inside `AuditExport.events`.
386
+ *
387
+ * `decision` is nullable because not every event is an evaluation —
388
+ * CRUD-style audit writes (e.g. `policy.updated`) omit it. All other
389
+ * nullable fields follow the same "field doesn't apply to this event
390
+ * type" convention rather than "unknown value".
391
+ */
392
+ interface AuditEvent {
393
+ /** Event UUID. Stable; surfaces as `tampered_event_ids` on verify failure. */
394
+ id: string;
395
+ /** Organization this event belongs to. */
396
+ org_id: string;
397
+ /** Per-org monotonic sequence. Used as the pagination cursor's payload. */
398
+ sequence: number;
399
+ /** Event type tag (e.g. "evaluate.allow", "policy.updated"). */
400
+ type: string;
401
+ /** Policy decision when the event is an evaluation; `null` otherwise. */
402
+ decision: AuditDecision | null;
403
+ /** Actor id (user / API key / agent) when applicable. */
404
+ actor_id: string | null;
405
+ /** Optional resource tag — e.g. "policy". */
406
+ resource_type: string | null;
407
+ /** Optional resource id — paired with `resource_type`. */
408
+ resource_id: string | null;
409
+ /** Canonical JSON of the event payload. Hashed into `hash`. */
410
+ payload: Record<string, unknown> | null;
411
+ /** SHA-256(prev_hash || canonicalJSON(payload)), hex. */
412
+ hash: string;
413
+ /** Previous event's `hash` (genesis is `"0".repeat(64)`). */
414
+ previous_hash: string;
415
+ /** When the underlying action occurred (ISO 8601). */
416
+ occurred_at: string;
417
+ /** When the row was persisted (ISO 8601). */
418
+ created_at: string;
419
+ }
420
+ /**
421
+ * Response shape for `GET /v1/audit/events`.
422
+ *
423
+ * `total` is the filter's full count (not just this page) so callers
424
+ * can show "page 1 of N" without an extra HEAD request.
425
+ *
426
+ * `next_cursor` is an opaque base64url string. Pass it back verbatim
427
+ * as `?cursor=...` to fetch the next page. Absent when this is the
428
+ * last page.
429
+ */
430
+ interface AuditEventsPage {
431
+ events: AuditEvent[];
432
+ total: number;
433
+ next_cursor?: string;
434
+ }
435
+ /**
436
+ * Query parameters accepted by `GET /v1/audit/events`. Serialize as
437
+ * URL search params — `types` is a comma-joined list on the wire
438
+ * (e.g. `types=evaluate.allow,policy.updated`).
439
+ *
440
+ * All fields are optional; the server defaults `limit` to 50 and caps
441
+ * it at 500.
442
+ */
443
+ interface AuditEventsQuery {
444
+ /** Comma-joined list of event types to filter on. */
445
+ types?: string;
446
+ /** Filter to a single actor. */
447
+ actor_id?: string;
448
+ /** Inclusive lower bound on `occurred_at` (ISO 8601). */
449
+ from?: string;
450
+ /** Inclusive upper bound on `occurred_at` (ISO 8601). */
451
+ to?: string;
452
+ /** Page size. Default 50, min 1, max 500. */
453
+ limit?: number;
454
+ /** Opaque cursor returned as `next_cursor` by the prior page. */
455
+ cursor?: string;
456
+ }
457
+ /**
458
+ * Response shape for `POST /v1/audit/exports` — a signed bundle of
459
+ * audit events suitable for offline verification.
460
+ *
461
+ * `signature` is detached Ed25519 over `signedBytesFor(bundle)` (see
462
+ * `./auditBundle.ts`). An empty string means the server attempted to
463
+ * sign but failed; check `signature_status` to distinguish.
464
+ *
465
+ * `tampered_event_ids` surfaces rows whose recomputed hash doesn't
466
+ * match the stored hash — even when `chain_integrity_ok` is false,
467
+ * the signature still covers whatever the server emitted, so callers
468
+ * that trust the signature must still inspect this list.
469
+ */
470
+ interface AuditExport {
471
+ /** Server-assigned UUID for this export. */
472
+ export_id: string;
473
+ /** Organization the bundle belongs to. */
474
+ org_id: string;
475
+ /** Events in canonical (ascending sequence) order. */
476
+ events: AuditEvent[];
477
+ /** Last event's `hash`, or `"0".repeat(64)` if `events` is empty. */
478
+ chain_head_hash: string;
479
+ /** `true` iff adjacency + re-hash succeeded for every event. */
480
+ chain_integrity_ok: boolean;
481
+ /** `AuditEvent.id`s whose recomputed hash != stored hash. */
482
+ tampered_event_ids: string[];
483
+ /** Detached Ed25519 signature (base64url). Empty string on sign failure. */
484
+ signature: string;
485
+ /** Outcome of the signing attempt. */
486
+ signature_status: AuditExportSignatureStatus;
487
+ /** Registry id of the key that signed — absent when unsigned. */
488
+ signing_key_id?: string;
489
+ /** When the bundle was signed (ISO 8601). */
490
+ signed_at: string;
491
+ }
492
+
493
+ /**
494
+ * Canonical 4-value policy decision, byte-identical to the wire.
495
+ *
496
+ * - `allow` — action is authorized; a Permit is issued.
497
+ * - `deny` — action is blocked.
498
+ * - `hold` — decision deferred (e.g. waiting on an approval signal).
499
+ * - `escalate` — routed to a human reviewer queue.
500
+ *
501
+ * Pin to this type on new code.
502
+ */
503
+ type DecisionCanonical = "allow" | "deny" | "hold" | "escalate";
504
+ /**
505
+ * Decision type — unified with the canonical 4-value vocabulary.
506
+ *
507
+ * This type previously emitted `"ALLOW"` / `"DENY"` (uppercase, 2-value).
508
+ * It now reflects the canonical wire values (`"allow" | "deny" | "hold" |
509
+ * "escalate"`) so the `decision` and `decision_canonical` fields on
510
+ * {@link EvaluateResponse} carry identical values and types.
511
+ *
512
+ * Backward compatibility: the SDK normalises API response values to
513
+ * lowercase (`.toLowerCase()`) before returning them, so callers that
514
+ * previously checked `=== "ALLOW"` must update to `=== "allow"`. The
515
+ * canonical field `decision_canonical` is also available and was always
516
+ * lowercase — prefer it on new code.
517
+ *
518
+ * Legacy uppercase input accepted by the SDK is normalised to lowercase
519
+ * output; `"ALLOW"` in → `"allow"` out, `"DENY"` in → `"deny"` out.
520
+ */
521
+ type Decision = DecisionCanonical;
522
+ /**
523
+ * Rate-limit state parsed from the server's `X-RateLimit-*` headers.
524
+ *
525
+ * Present on every authenticated response (success and 429) when the
526
+ * server emits the headers. `null` when the server doesn't — older
527
+ * deployments, or internal endpoints that skip per-key rate limiting.
528
+ *
529
+ * Clients should check `remaining` and sleep until `resetAt` to
530
+ * preemptively back off before hitting a 429.
531
+ */
532
+ interface RateLimitState {
533
+ /** Value of `X-RateLimit-Limit` — the per-minute budget. */
534
+ limit: number;
535
+ /** Value of `X-RateLimit-Remaining` — unused budget in the current window. */
536
+ remaining: number;
537
+ /**
538
+ * Parsed `X-RateLimit-Reset` — the UTC instant when the current
539
+ * window's counter zeroes. Accepts either a unix-seconds integer or
540
+ * an ISO 8601 string on the wire.
541
+ */
542
+ resetAt: Date;
543
+ }
544
+ /**
545
+ * Canonical Deploy Gate V1 protected action.
546
+ *
547
+ * Use this constant (or its string value `"production.deploy"`) on all
548
+ * new code. Server-side `action_classes.slug` was canonicalised to
549
+ * `production.deploy` in atlasent-api PR #662 / atlasent-console
550
+ * PR #432; the SDK default now matches.
551
+ */
552
+ declare const PRODUCTION_DEPLOY_ACTION: "production.deploy";
553
+ /**
554
+ * Legacy alias for {@link PRODUCTION_DEPLOY_ACTION}.
555
+ *
556
+ * @deprecated since 2.3.0 — use {@link PRODUCTION_DEPLOY_ACTION}. The
557
+ * server alias-tolerates `deployment.production` during the V1 alias
558
+ * window, so existing callers continue to work unchanged; please
559
+ * migrate by the next minor release.
560
+ */
561
+ declare const DEPLOYMENT_PRODUCTION_ACTION: "deployment.production";
562
+ /**
563
+ * Permit claim for `production.deploy` evaluations (Rule 3).
564
+ *
565
+ * Pass as `permit` inside {@link DeployGateContext}.
566
+ * The `verified` flag is set by the verify-permit service after a
567
+ * successful `/v1-verify-permit` call — do not self-assert it.
568
+ */
569
+ interface DeployPermitClaim {
570
+ permit_id?: string;
571
+ environment?: string;
572
+ action_type?: string;
573
+ /** ISO-8601 timestamp when the permit was issued. */
574
+ issued_at?: string;
575
+ /** Set server-side by the verify-permit service. Do not self-assert. */
576
+ verified?: boolean;
577
+ }
578
+ /**
579
+ * Override claim for `production.deploy` evaluations (Rule 8).
580
+ *
581
+ * Both `override_reason` and `authority_basis` must be non-empty to
582
+ * receive `OVERRIDE_APPROVED`. Missing or blank fields return `DENY_POLICY`.
583
+ */
584
+ interface DeployOverrideClaim {
585
+ /** Human-readable reason. Required and non-empty. */
586
+ override_reason?: string;
587
+ /** Authoritative basis — runbook section, incident ticket, etc. Required and non-empty. */
588
+ authority_basis?: string;
589
+ /** Approver actor ID (audit record; does not gate the decision). */
590
+ approver_actor_id?: string;
591
+ }
592
+ /**
593
+ * Typed context shape for `production.deploy` evaluations.
594
+ *
595
+ * Pass as `context` to `protect()`, `deployGate()`, or
596
+ * {@link AtlaSentClient.evaluate} for the Deploy Gate V1 flow.
597
+ *
598
+ * @example
599
+ * ```ts
600
+ * const permit = await atlasent.protect({
601
+ * agent: "deploy-bot",
602
+ * action: PRODUCTION_DEPLOY_ACTION,
603
+ * context: {
604
+ * environment: "production",
605
+ * evaluation_confirmed: true,
606
+ * actorMetadata: { role: "deploy_engineer" },
607
+ * permit: {
608
+ * permit_id: permitToken,
609
+ * environment: "production",
610
+ * action_type: PRODUCTION_DEPLOY_ACTION,
611
+ * issued_at: new Date().toISOString(),
612
+ * verified: true,
613
+ * },
614
+ * } satisfies DeployGateContext,
615
+ * });
616
+ * ```
617
+ */
618
+ interface DeployGateContext {
619
+ /** Must be `"production"` for the production gate to apply. */
620
+ environment?: "production" | "staging" | "development";
621
+ /**
622
+ * When `true`, all rule failures are shadowed to `allow` (fail-open).
623
+ * Malformed-timestamp inconsistencies still escalate.
624
+ * Use for initial rollout before locking enforcement.
625
+ */
626
+ pilot_mode?: boolean;
627
+ /** Must be `true` — confirms an evaluation record exists before proceeding. */
628
+ evaluation_confirmed?: boolean;
629
+ /** ISO-8601 timestamp of when evaluation was confirmed. */
630
+ evaluation_confirmed_at?: string;
631
+ /** Actor role metadata. `role` must be one of the approved deploy roles. */
632
+ actorMetadata?: {
633
+ role?: string;
634
+ };
635
+ /** Signed permit claim — required for non-pilot production deployments. */
636
+ permit?: DeployPermitClaim;
637
+ /** Override claim — short-circuits all rules when both fields are non-empty. */
638
+ override?: DeployOverrideClaim;
639
+ [key: string]: unknown;
640
+ }
641
+ /**
642
+ * Canonical deploy gate decision codes emitted for `production.deploy`.
643
+ *
644
+ * Appears as `deny_code` / `matchedRuleId` on evaluation responses.
645
+ * Pin dashboards, alerting, and routing logic to these codes — not to
646
+ * `deny_reason` strings, which may change.
647
+ */
648
+ type DeployGateDenyCode = "ALLOW" | "DENY_POLICY" | "DENY_AUTHORITY" | "DENY_ENVIRONMENT" | "PERMIT_EXPIRED" | "VERIFY_FAILED" | "ESCALATE_REQUIRED" | "OVERRIDE_APPROVED";
649
+ /** Typed constants for {@link DeployGateDenyCode}. */
650
+ declare const DEPLOY_GATE_CODES: Readonly<{
651
+ ALLOW: "ALLOW";
652
+ DENY_POLICY: "DENY_POLICY";
653
+ DENY_AUTHORITY: "DENY_AUTHORITY";
654
+ DENY_ENVIRONMENT: "DENY_ENVIRONMENT";
655
+ PERMIT_EXPIRED: "PERMIT_EXPIRED";
656
+ VERIFY_FAILED: "VERIFY_FAILED";
657
+ ESCALATE_REQUIRED: "ESCALATE_REQUIRED";
658
+ OVERRIDE_APPROVED: "OVERRIDE_APPROVED";
659
+ }>;
660
+ /** Input to {@link AtlaSentClient.deployGate}. */
661
+ interface DeployGateRequest {
662
+ /** CI/repo actor performing the deployment. Defaults to `ci-deploy-bot`. */
663
+ agent?: string;
664
+ /** Protected action. Defaults to `production.deploy`. */
665
+ action?: typeof PRODUCTION_DEPLOY_ACTION | typeof DEPLOYMENT_PRODUCTION_ACTION | string;
666
+ /** Typed deploy gate context for `production.deploy`. */
667
+ context?: DeployGateContext | Record<string, unknown>;
668
+ }
669
+ /** Evidence metadata returned by {@link AtlaSentClient.deployGate}. */
670
+ interface DeployGateEvidence {
671
+ permitId?: string;
672
+ permitHash?: string;
673
+ auditHash?: string;
674
+ verifiedAt?: string;
675
+ }
676
+ /** Result of the canonical Deploy Gate V1 flow. */
677
+ interface DeployGateResponse {
678
+ /** True only after evaluate allowed AND `/v1-verify-permit` verified server-side. */
679
+ allowed: boolean;
680
+ /** Evaluation response from `POST /v1-evaluate`, when available. */
681
+ evaluation?: EvaluateResponse;
682
+ /** Verification response from `POST /v1-verify-permit`, when evaluation allowed. */
683
+ verification?: VerifyPermitResponse;
684
+ /** Human-readable block/allow reason. */
685
+ reason: string;
686
+ /** Best-effort audit/evidence metadata available to the SDK. */
687
+ evidence: DeployGateEvidence;
688
+ }
689
+ /**
690
+ * Frozen BVS snapshot wire shape (BI4).
691
+ * Carried in {@link EvaluateRequest}.context.bvsSnapshot when
692
+ * the `behavior_conditioning` flag is enabled for the tenant.
693
+ * Produced by behavior-insights GET /api/patterns/snapshot/:userId
694
+ * and attached via `@atlasent/behavior` attachToEvaluate().
695
+ */
696
+ interface BvsSnapshot {
697
+ user_id: string;
698
+ /** Factor model output — keyed by BVS factor slug, value is score 0-1. */
699
+ factors: Record<string, number>;
700
+ /** Aggregate confidence score (0-1). Decays on a 60-day half-life. */
701
+ confidence: number;
702
+ /** True when the aggregate is fresh-and-thin (too few events to trust). */
703
+ confidence_low: boolean;
704
+ /** ISO-8601 timestamp of the compute run that produced this snapshot. */
705
+ computed_at: string;
706
+ }
707
+ /**
708
+ * Consent-class projection (BI5) — the privacy-safe aggregate shape that
709
+ * third-party apps (LedgersMe, hiCoach, echobloom) receive when reading a
710
+ * user's behavioral summary. Counts and timestamps only; no raw free-text.
711
+ * Produced by behavior-insights `/api/patterns/summary/:userId` and fetched
712
+ * via `@atlasent/behavior` getStateSummary(). The SDK enforces
713
+ * {@link https://github.com/AtlaSent-Systems-Inc/atlasent-sdk | assertNoRawText}
714
+ * client-side before returning this shape to callers.
715
+ */
716
+ interface ConsentClassProjection {
717
+ user_id: string;
718
+ window_start: string;
719
+ window_end: string;
720
+ event_count: number;
721
+ category_counts: Partial<Record<string, number>>;
722
+ }
723
+ /** Input to {@link AtlaSentClient.evaluate}. */
724
+ interface EvaluateRequest {
725
+ /** Identifier of the calling agent (e.g. "clinical-data-agent"). */
726
+ agent: string;
727
+ /** The action being authorized (e.g. "modify_patient_record"). */
728
+ action: string;
729
+ /** Arbitrary policy context (user, environment, resource IDs). */
730
+ context?: Record<string, unknown>;
731
+ /**
732
+ * When `true`, the server populates `riskEnvelope.factors` with a
733
+ * per-factor breakdown of the weighted risk score. Omit (or `false`)
734
+ * to keep response payloads small.
735
+ */
736
+ explain?: boolean;
737
+ }
738
+ /**
739
+ * Slim permit object embedded in {@link EvaluateResponse} when the decision
740
+ * is `"allow"`. Contains the essential fields needed to act on the permit
741
+ * immediately without a separate `GET /v1/permits/:id` round-trip.
742
+ *
743
+ * Mirrors the `Permit` schema in atlasent-control-plane
744
+ * `api/src/schemas/permits.ts`.
745
+ */
746
+ interface EvaluateResponsePermit {
747
+ id: string;
748
+ orgId: string;
749
+ subject: string;
750
+ scope: string;
751
+ status: "active" | "revoked" | "expired";
752
+ /** The evaluation that produced this permit. */
753
+ evaluationId: string | null;
754
+ issuedBy: string;
755
+ revokedBy: string | null;
756
+ /** ISO-8601 issuance timestamp. */
757
+ issuedAt: string;
758
+ revokedAt: string | null;
759
+ expiresAt: string | null;
760
+ metadata: Record<string, unknown> | null;
761
+ }
762
+ /** Result of {@link AtlaSentClient.evaluate}. */
763
+ interface EvaluateResponse {
764
+ /**
765
+ * Policy decision — canonical 4-value lowercase vocabulary:
766
+ * `"allow"`, `"deny"`, `"hold"`, or `"escalate"`.
767
+ *
768
+ * Previously emitted `"ALLOW"` / `"DENY"` (uppercase, 2-value);
769
+ * the SDK now normalises all values to lowercase and passes `hold`
770
+ * and `escalate` through rather than collapsing them to `"DENY"`.
771
+ *
772
+ * The `decision_canonical` field carries the same value and is the
773
+ * recommended field for new code.
774
+ */
775
+ decision: Decision;
776
+ /**
777
+ * Canonical 4-value decision, byte-identical to the wire.
778
+ *
779
+ * One of `"allow"`, `"deny"`, `"hold"`, `"escalate"`. Branch on
780
+ * this field on new code. `hold` and `escalate` are non-terminal
781
+ * states that route to a human reviewer / approval signal — they
782
+ * are not equivalent to a `deny`.
783
+ */
784
+ decision_canonical: DecisionCanonical;
785
+ /**
786
+ * Server-assigned identifier for this evaluation decision.
787
+ *
788
+ * Stable across retries and used as the key for proof retrieval
789
+ * (`GET /v1/proof/:evaluationId`) and override requests. Also
790
+ * available as the legacy `permitId` field for backward compatibility.
791
+ */
792
+ evaluationId: string;
793
+ /** Opaque permit identifier, passed to {@link AtlaSentClient.verifyPermit}.
794
+ *
795
+ * @deprecated Prefer `evaluationId`. This field is kept for backward
796
+ * compatibility and points to the same server-assigned ID.
797
+ */
798
+ permitId: string;
799
+ /**
800
+ * Slim permit object issued when `decision === "allow"`.
801
+ * `null` on deny, hold, or escalate decisions.
802
+ *
803
+ * Mirrors the `Permit` schema from the control-plane.
804
+ */
805
+ permit: EvaluateResponsePermit | null;
806
+ /**
807
+ * Opaque HMAC-signed permit token issued when `decision === "allow"`.
808
+ * Pass to `POST /v1/verify-permit` to verify the permit server-side.
809
+ * `null` on deny, hold, or escalate decisions.
810
+ */
811
+ permitToken: string | null;
812
+ /**
813
+ * Machine-readable reasons emitted by the policy engine.
814
+ *
815
+ * The array may be empty. For deny/hold/escalate decisions the array
816
+ * typically contains a single human-readable explanation; for allow
817
+ * decisions it is often empty. Do not parse these strings — use
818
+ * `decision` for branching.
819
+ */
820
+ reasons: string[];
821
+ /** Human-readable explanation from the policy engine.
822
+ *
823
+ * @deprecated Prefer `reasons[0]` or `reasons`. This field is the
824
+ * first element of `reasons` (or an empty string) for backward compat.
825
+ */
826
+ reason: string;
827
+ /** Hash-chained audit-trail entry (21 CFR Part 11 / GxP-ready). */
828
+ auditHash: string;
829
+ /** ISO 8601 timestamp of the decision. */
830
+ timestamp: string;
831
+ /**
832
+ * Per-key rate-limit state for this request's response, parsed from
833
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
834
+ */
835
+ rateLimit: RateLimitState | null;
836
+ /**
837
+ * Risk envelope summary from the policy engine. Present on all responses
838
+ * from engine version wire-v1@1.0.0+. Provides the weighted risk score,
839
+ * the pre/post-promotion decisions, and (when evaluate was called with
840
+ * `explain: true`) a per-factor breakdown.
841
+ *
842
+ * The envelope can only raise severity — it structurally cannot soften
843
+ * a deny to allow. When `promoted` is true the live `decision` was
844
+ * upgraded from `engineDecision` to `envelopeDecision`.
845
+ */
846
+ riskEnvelope?: EvaluateRiskEnvelope;
847
+ }
848
+ /** Per-factor contribution in a {@link EvaluateRiskEnvelope}. */
849
+ interface EvaluateRiskEnvelopeFactor {
850
+ /** Factor identifier, e.g. `"ACTION_SENSITIVITY"`. */
851
+ factor: string;
852
+ /** Factor score in [0, 1]. Higher = more risk. */
853
+ value: number;
854
+ /** Configured weight for this factor. */
855
+ weight: number;
856
+ /** Human-readable explanation for the score. */
857
+ reason: string;
858
+ }
859
+ /** Risk envelope summary returned in a top-level {@link EvaluateResponse}. */
860
+ interface EvaluateRiskEnvelope {
861
+ /** Weighted risk score in [0, 1]. Score ≥ 0.70 triggers a hold. */
862
+ weightedScore: number;
863
+ /** Policy engine decision before envelope promotion. */
864
+ engineDecision: Decision;
865
+ /** Decision resolved by the risk envelope. */
866
+ envelopeDecision: Decision;
867
+ /** `true` when the envelope raised the decision's severity (most-restrictive-wins). */
868
+ promoted: boolean;
869
+ /** Deny codes that unconditionally block regardless of score. */
870
+ hardBlocks: string[];
871
+ /** Per-factor breakdown. Present only when `explain: true` was passed. */
872
+ factors?: EvaluateRiskEnvelopeFactor[];
873
+ }
874
+ /** Input to {@link AtlaSentClient.verifyPermit}. */
875
+ interface VerifyPermitRequest {
876
+ /** The permit ID returned by a prior evaluate() call. */
877
+ permitId: string;
878
+ /** Optional: re-state the action for cross-check with the server. */
879
+ action?: string;
880
+ /** Optional: re-state the agent for cross-check with the server. */
881
+ agent?: string;
882
+ /** Optional: re-state the context for cross-check with the server. */
883
+ context?: Record<string, unknown>;
884
+ /**
885
+ * Environment of the permit being verified. Sourced from the evaluate
886
+ * payload (context.environment → top-level environment → "production").
887
+ * Required by the server for production permits as of 2026-05-14.
888
+ * P1-1 fix: withPermit/protect now always populates this field.
889
+ */
890
+ environment?: string;
891
+ /**
892
+ * SHA-256 hex digest of the recursively key-sorted canonical JSON of the
893
+ * original evaluate payload. Required by the server for production permits
894
+ * as of 2026-05-14.
895
+ * P1-5 fix: withPermit/protect now always computes and sends this field.
896
+ */
897
+ execution_hash?: string;
898
+ }
899
+ /**
900
+ * Result of {@link AtlaSentClient.verifyPermit}.
901
+ *
902
+ * @deprecated Use {@link VerifyPermitByIdResponse} via
903
+ * {@link AtlaSentClient.verifyPermitById} — the canonical REST surface
904
+ * (`POST /v1/permits/{id}/verify`) returns the unified verification
905
+ * envelope (`valid`, `verification_type`, `reason`, `verified_at`,
906
+ * `evidence`) plus the full {@link PermitRecord} fields. Will be
907
+ * removed in `@atlasent/sdk@3`.
908
+ */
909
+ interface VerifyPermitResponse {
910
+ /** `true` when the permit is valid and un-revoked. */
911
+ verified: boolean;
912
+ /** Verification outcome string from the server. */
913
+ outcome: string;
914
+ /** Verification hash bound to the permit. */
915
+ permitHash: string;
916
+ /** ISO 8601 timestamp of the verification. */
917
+ timestamp: string;
918
+ /**
919
+ * ISO-8601 expiration timestamp of the permit. `null` on pre-rollout
920
+ * server versions that do not yet surface this field.
921
+ */
922
+ expiresAt: string | null;
923
+ /**
924
+ * Per-key rate-limit state for this request's response, parsed from
925
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
926
+ */
927
+ rateLimit: RateLimitState | null;
928
+ }
929
+ /**
930
+ * Result of {@link AtlaSentClient.keySelf} — self-introspection of the API
931
+ * key the client was constructed with. Returned by `GET /v1/api-key-self`.
932
+ *
933
+ * Never includes the raw key or its hash — introspection is intentionally
934
+ * read-only and safe to surface in operator dashboards. Useful for:
935
+ * - "which key am I?" debugging
936
+ * - IP_NOT_ALLOWED failures — `clientIp` is the IP the server observed
937
+ * - proactive expiry warnings — `expiresAt` is the server-stored expiry
938
+ * (`null` means the key does not auto-expire)
939
+ * - verifying scopes before attempting a scope-gated action
940
+ */
941
+ interface ApiKeySelfResponse {
942
+ /** Server-side UUID of the api_keys row for this key. */
943
+ keyId: string;
944
+ /** Organization the key belongs to. */
945
+ organizationId: string;
946
+ /** "live" or "test" (or any future environment label the server introduces). */
947
+ environment: string;
948
+ /** Granted scopes — e.g. ["evaluate", "audit.read"]. */
949
+ scopes: string[];
950
+ /**
951
+ * Per-key IP allowlist as CIDR strings (e.g. ["10.0.0.0/8"]). `null`
952
+ * when the key is unrestricted.
953
+ */
954
+ allowedCidrs: string[] | null;
955
+ /** Server-enforced per-minute rate limit for this key. */
956
+ rateLimitPerMinute: number;
957
+ /** Client IP as the server observed it (first hop of X-Forwarded-For). */
958
+ clientIp: string | null;
959
+ /** Server-stored expiry; `null` means the key does not auto-expire. */
960
+ expiresAt: string | null;
961
+ /**
962
+ * Per-key rate-limit state for this request's response, parsed from
963
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
964
+ */
965
+ rateLimit: RateLimitState | null;
966
+ }
967
+ /**
968
+ * Result of {@link AtlaSentClient.listAuditEvents}. Extends the raw
969
+ * wire page with a camelCase `rateLimit` alongside the snake_case
970
+ * wire fields — the wire shape (`events`, `total`, `next_cursor`) is
971
+ * untouched so callers that pass it to the offline verifier get
972
+ * byte-identical behaviour.
973
+ */
974
+ interface AuditEventsResult extends AuditEventsPage {
975
+ /**
976
+ * Per-key rate-limit state for this request's response, parsed from
977
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
978
+ */
979
+ rateLimit: RateLimitState | null;
980
+ }
981
+ /**
982
+ * Filter accepted by {@link AtlaSentClient.createAuditExport}. Fields
983
+ * are snake_case to match the server's `POST /v1-audit/exports`
984
+ * request body; an empty object requests a full-org bundle.
985
+ */
986
+ interface AuditExportRequest {
987
+ /** Comma-joined list of event types to include (e.g. `"evaluate.allow,policy.updated"`). */
988
+ types?: string;
989
+ /** Filter to a single actor. */
990
+ actor_id?: string;
991
+ /** Inclusive lower bound on `occurred_at` (ISO 8601). */
992
+ from?: string;
993
+ /** Inclusive upper bound on `occurred_at` (ISO 8601). */
994
+ to?: string;
995
+ }
996
+ /**
997
+ * Result of {@link AtlaSentClient.createAuditExport}. Extends the
998
+ * signed bundle shape with a camelCase `rateLimit`. The signed
999
+ * envelope fields (`export_id`, `org_id`, `chain_head_hash`,
1000
+ * `event_count`, `signed_at`, `events`, `signature`) are preserved
1001
+ * byte-for-byte so the object can be handed straight to
1002
+ * `verifyAuditBundle(bundle, keys)`.
1003
+ */
1004
+ interface AuditExportResult extends AuditExport {
1005
+ /**
1006
+ * Per-key rate-limit state for this request's response, parsed from
1007
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
1008
+ */
1009
+ rateLimit: RateLimitState | null;
1010
+ }
1011
+ /** Constructor options for {@link AtlaSentClient}. */
1012
+ interface AtlaSentClientOptions {
1013
+ /** Required. Your AtlaSent API key. */
1014
+ apiKey: string;
1015
+ /** API base URL. Defaults to "https://api.atlasent.io". */
1016
+ baseUrl?: string;
1017
+ /** Per-request timeout in milliseconds. Defaults to 10_000. */
1018
+ timeoutMs?: number;
1019
+ /**
1020
+ * Inject a fetch implementation (primarily for testing).
1021
+ * Defaults to `globalThis.fetch`.
1022
+ */
1023
+ fetch?: typeof fetch;
1024
+ /**
1025
+ * Retry policy for transient failures (network errors, timeouts,
1026
+ * 429 rate-limit, 5xx server errors, malformed responses).
1027
+ * Omit to use the default: 4 total attempts, 2 000 ms base, 16 000 ms cap,
1028
+ * full-jitter exponential backoff matching the Python SDK schedule
1029
+ * (2 s → 4 s → 8 s → 16 s).
1030
+ * Pass `{ maxAttempts: 1 }` to disable retries entirely.
1031
+ */
1032
+ retryPolicy?: RetryPolicy;
1033
+ }
1034
+ /** Permit lifecycle status. */
1035
+ type PermitStatus = "issued" | "verified" | "consumed" | "expired" | "revoked";
1036
+ /**
1037
+ * Wire shape of a Permit row, returned by {@link AtlaSentClient.getPermit}
1038
+ * and {@link AtlaSentClient.listPermits}. Mirrors the openapi `Permit`
1039
+ * schema.
1040
+ *
1041
+ * Revocation fields (`revoked_at`, `revoked_by`, `revoke_reason`) are
1042
+ * populated only when `status === 'revoked'`; null otherwise.
1043
+ */
1044
+ interface PermitRecord {
1045
+ id: string;
1046
+ org_id: string;
1047
+ actor_id: string;
1048
+ action_id: string;
1049
+ target_id?: string;
1050
+ environment?: string;
1051
+ status: PermitStatus;
1052
+ issued_at: string;
1053
+ expires_at: string;
1054
+ consumed_at?: string | null;
1055
+ revoked_at?: string | null;
1056
+ revoked_by?: string | null;
1057
+ revoke_reason?: string | null;
1058
+ signature?: string;
1059
+ payload_hash?: string | null;
1060
+ decision_id?: string | null;
1061
+ }
1062
+ /** Optional filters for {@link AtlaSentClient.listPermits}. */
1063
+ interface ListPermitsRequest {
1064
+ status?: PermitStatus;
1065
+ actorId?: string;
1066
+ actionType?: string;
1067
+ /** ISO-8601 lower bound on `created_at`. */
1068
+ from?: string;
1069
+ /** ISO-8601 upper bound on `created_at`. */
1070
+ to?: string;
1071
+ /** Page size. Server max is 500; default 50. */
1072
+ limit?: number;
1073
+ /** Pass `nextCursor` from a prior response to page forward. */
1074
+ cursor?: string;
1075
+ }
1076
+ /** Response from {@link AtlaSentClient.listPermits}. */
1077
+ interface ListPermitsResponse {
1078
+ permits: PermitRecord[];
1079
+ /** Total matching rows ignoring `limit`/`cursor`. */
1080
+ total: number;
1081
+ /** Pass on next call as `cursor`. Absent when no more rows. */
1082
+ nextCursor?: string;
1083
+ rateLimit: RateLimitState | null;
1084
+ }
1085
+ /** Response from {@link AtlaSentClient.getPermit}. */
1086
+ interface GetPermitResponse {
1087
+ permit: PermitRecord;
1088
+ rateLimit: RateLimitState | null;
1089
+ }
1090
+ /**
1091
+ * Response from {@link AtlaSentClient.checkPermitValid}.
1092
+ *
1093
+ * Lightweight validity snapshot returned by
1094
+ * `GET /v1/permits/{permitId}/valid`. Designed for guard heartbeat
1095
+ * polling — returns only the fields needed to determine whether to
1096
+ * abort a running permit mid-execution (via {@link PermitRevoked}).
1097
+ */
1098
+ interface PermitValidResponse {
1099
+ /** True iff the permit is currently valid (active). */
1100
+ valid: boolean;
1101
+ /**
1102
+ * Current lifecycle status of the permit.
1103
+ * - `"active"` — permit is valid and in-flight.
1104
+ * - `"expired"` — TTL elapsed before revocation or consumption.
1105
+ * - `"revoked"` — administratively revoked (see `revocation_id`).
1106
+ * - `"consumed"` — single-use permit already consumed.
1107
+ */
1108
+ status: "active" | "expired" | "revoked" | "consumed";
1109
+ /** ISO-8601 timestamp when the permit was revoked. Populated only when `status === "revoked"`. */
1110
+ revoked_at?: string;
1111
+ /** Opaque identifier of the revocation record. Populated only when `status === "revoked"`. */
1112
+ revocation_id?: string;
1113
+ }
1114
+ /** Input for {@link AtlaSentClient.revokePermitById}. */
1115
+ interface RevokePermitByIdInput {
1116
+ /** Operator-supplied free-text reason. Recorded on the permit row,
1117
+ * written to the audit trail, and surfaced (truncated) on later
1118
+ * verify responses. Optional but strongly encouraged. */
1119
+ reason?: string;
1120
+ }
1121
+ /**
1122
+ * Response from {@link AtlaSentClient.revokePermitById}.
1123
+ *
1124
+ * Returns the updated {@link PermitRecord} with `status === 'revoked'`
1125
+ * and the populated `revoked_at` / `revoked_by` / `revoke_reason`
1126
+ * fields.
1127
+ */
1128
+ interface RevokePermitByIdResponse {
1129
+ permit: PermitRecord;
1130
+ rateLimit: RateLimitState | null;
1131
+ }
1132
+ /**
1133
+ * Response from {@link AtlaSentClient.verifyPermitById}.
1134
+ *
1135
+ * Returns the canonical verification envelope (`valid`,
1136
+ * `verification_type`, `reason`, `verified_at`, `evidence`) plus the
1137
+ * legacy {@link PermitRecord} fields preserved at the top level for
1138
+ * backward compatibility. The envelope shape matches the unified
1139
+ * verify response in atlasent-api PR #352.
1140
+ */
1141
+ interface VerifyPermitByIdResponse {
1142
+ /** `true` iff the permit verified — i.e. unconsumed, unexpired, and signature OK. */
1143
+ valid: boolean;
1144
+ /** Always `'permit'` on this surface. */
1145
+ verification_type: "permit";
1146
+ /** Operator-readable explanation when `valid` is `false`; `null` on success. */
1147
+ reason: string | null;
1148
+ /** Server clock at the moment verification ran. */
1149
+ verified_at: string;
1150
+ /** Type-specific evidence — same fields as the openapi PermitVerifyEvidence schema. */
1151
+ evidence: {
1152
+ permit_id: string;
1153
+ status: PermitStatus;
1154
+ actor_id?: string;
1155
+ action_id?: string;
1156
+ expires_at?: string;
1157
+ payload_hash?: string | null;
1158
+ decision_id?: string | null;
1159
+ };
1160
+ /** Legacy: full permit row preserved at the top level. */
1161
+ permit: PermitRecord;
1162
+ rateLimit: RateLimitState | null;
1163
+ }
1164
+ /** Input for {@link AtlaSentClient.revokePermit}. */
1165
+ interface RevokePermitRequest {
1166
+ /** The permit ID returned by a prior evaluate() call. */
1167
+ permitId: string;
1168
+ /** Optional human-readable reason stored in the audit log. */
1169
+ reason?: string;
1170
+ }
1171
+ /**
1172
+ * Result of {@link AtlaSentClient.revokePermit}.
1173
+ *
1174
+ * @deprecated Use {@link RevokePermitByIdResponse} via
1175
+ * {@link AtlaSentClient.revokePermitById} — the canonical REST surface
1176
+ * (`POST /v1/permits/{id}/revoke`) returns the full updated
1177
+ * {@link PermitRecord} with `revoked_at`/`revoked_by`/`revoke_reason`
1178
+ * populated, instead of the legacy `{revoked, permitId}` envelope.
1179
+ * Will be removed in `@atlasent/sdk@3`.
1180
+ */
1181
+ interface RevokePermitResponse {
1182
+ /** `true` when the permit was found and successfully revoked. */
1183
+ revoked: boolean;
1184
+ /** Echo of the revoked permit's ID. */
1185
+ permitId: string;
1186
+ /** ISO-8601 timestamp of when the revocation was recorded. `undefined` when not returned by the server. */
1187
+ revokedAt?: string | undefined;
1188
+ /** Audit hash for the revocation event. `undefined` when not returned by the server. */
1189
+ auditHash?: string | undefined;
1190
+ /** Per-key rate-limit state. `null` when the server didn't emit headers. */
1191
+ rateLimit: RateLimitState | null;
1192
+ }
1193
+ /**
1194
+ * One stage of a single policy's constraint evaluation.
1195
+ *
1196
+ * Mirrors `ConstraintTraceStage` in
1197
+ * `atlasent-api/packages/types/src/index.ts`. Emitted by the rule
1198
+ * engine when the request URL carries `?include=constraint_trace`.
1199
+ *
1200
+ * Forward-compat: extra engine-side keys are tolerated; readers
1201
+ * should not assume this is a closed shape.
1202
+ */
1203
+ interface ConstraintTraceStage {
1204
+ /** Engine stage name (e.g. `"role_check"`, `"context"`). */
1205
+ readonly stage: string;
1206
+ /** Optional rule identifier; absent for wrapper stages. */
1207
+ readonly rule?: string;
1208
+ /** True iff this stage's predicate fired. */
1209
+ readonly matched: boolean;
1210
+ /** Optional human-readable note from the engine. */
1211
+ readonly detail?: string;
1212
+ /** Zero-based position within the policy's `stages` array. */
1213
+ readonly order: number;
1214
+ /** Forward-compat: tolerate unknown engine-side keys without crashing. */
1215
+ readonly [key: string]: unknown;
1216
+ }
1217
+ /**
1218
+ * Per-policy block of a constraint trace.
1219
+ *
1220
+ * Mirrors `ConstraintTracePolicy` in
1221
+ * `atlasent-api/packages/types/src/index.ts`. The handler iterates
1222
+ * active policies in order until first non-allow; the policy that
1223
+ * produced the outer decision has `decision !== "allow"`.
1224
+ */
1225
+ interface ConstraintTracePolicy {
1226
+ /** Stable identifier of the evaluated policy. */
1227
+ readonly policy_id: string;
1228
+ /** Policy-level decision (`"allow"|"deny"|"hold"|"escalate"`). */
1229
+ readonly decision: string;
1230
+ /** Engine-side fingerprint of the bundle row. */
1231
+ readonly fingerprint: string;
1232
+ /**
1233
+ * Optional engine-computed risk score from a `risk` rule clause.
1234
+ * Distinct from the heuristic risk score on the outer envelope.
1235
+ */
1236
+ readonly risk_score?: number;
1237
+ /** Ordered stages produced while evaluating this policy. */
1238
+ readonly stages: ReadonlyArray<ConstraintTraceStage>;
1239
+ /** Forward-compat: tolerate unknown engine-side keys. */
1240
+ readonly [key: string]: unknown;
1241
+ }
1242
+ /**
1243
+ * Top-level constraint trace returned by
1244
+ * `/v1-evaluate?include=constraint_trace`.
1245
+ *
1246
+ * Mirrors `ConstraintTraceResponse` in
1247
+ * `atlasent-api/packages/types/src/index.ts`. Present iff the
1248
+ * caller requested the trace; the SDK's preflight helper always
1249
+ * requests it.
1250
+ */
1251
+ interface ConstraintTrace {
1252
+ /** Per-policy blocks in evaluation order. */
1253
+ readonly rules_evaluated: ReadonlyArray<ConstraintTracePolicy>;
1254
+ /**
1255
+ * Policy id whose evaluation produced the outer decision. Equals
1256
+ * the outer `matched_policy_id` on non-allow paths; `undefined` on
1257
+ * a clean allow (all policies passed).
1258
+ */
1259
+ readonly matching_policy_id?: string;
1260
+ /** Forward-compat: tolerate unknown engine-side keys. */
1261
+ readonly [key: string]: unknown;
1262
+ }
1263
+ /**
1264
+ * Result of {@link AtlaSentClient.evaluatePreflight}.
1265
+ *
1266
+ * Wraps the regular {@link EvaluateResponse} plus the
1267
+ * {@link ConstraintTrace} returned when the request URL carries
1268
+ * `?include=constraint_trace`. The whole point of preflight is to
1269
+ * surface which stages / policies WOULD fire BEFORE pushing the
1270
+ * request onto an approval queue, so workflows can reject trivially
1271
+ * defective requests at submission time and only forward viable
1272
+ * requests to a human reviewer.
1273
+ *
1274
+ * `constraintTrace` is `null` on responses from older atlasent-api
1275
+ * deployments that do not echo the trace — forward-compatible
1276
+ * degradation.
1277
+ */
1278
+ interface EvaluatePreflightResponse {
1279
+ /** The regular evaluate response (decision, permitId, ...). */
1280
+ readonly evaluation: EvaluateResponse;
1281
+ /**
1282
+ * The constraint trace, or `null` when the server omitted it
1283
+ * (older atlasent-api version).
1284
+ */
1285
+ readonly constraintTrace: ConstraintTrace | null;
1286
+ }
1287
+ /**
1288
+ * Options for {@link AtlaSentClient.protectStream}.
1289
+ *
1290
+ * All fields are optional; defaults are used when omitted.
1291
+ */
1292
+ interface StreamOptions {
1293
+ /**
1294
+ * Optional abort signal to cancel the stream from the caller side.
1295
+ */
1296
+ signal?: AbortSignal;
1297
+ /**
1298
+ * Per-event timeout in milliseconds: if no SSE event arrives within
1299
+ * this window the stream throws {@link StreamTimeoutError}.
1300
+ * Defaults to 30 000 ms (30 s). Pass `0` to disable.
1301
+ */
1302
+ timeoutMs?: number;
1303
+ /**
1304
+ * Maximum reconnection attempts on network drop before the stream
1305
+ * gives up and throws. Defaults to 3.
1306
+ */
1307
+ maxRetries?: number;
1308
+ }
1309
+ /** A policy decision emitted mid-stream. */
1310
+ interface StreamDecisionEvent {
1311
+ type: "decision";
1312
+ /**
1313
+ * Policy decision — canonical 4-value lowercase vocabulary:
1314
+ * `"allow"`, `"deny"`, `"hold"`, or `"escalate"`.
1315
+ *
1316
+ * Previously emitted `"ALLOW"` / `"DENY"` (uppercase, 2-value);
1317
+ * now unified with `decision_canonical`.
1318
+ *
1319
+ * @deprecated Read `decision_canonical` instead for forward-compatible
1320
+ * branching. Both fields now carry the same value. Will be
1321
+ * removed/changed in `@atlasent/sdk@3`.
1322
+ */
1323
+ decision: Decision;
1324
+ /**
1325
+ * Canonical 4-value decision, byte-identical to the wire.
1326
+ * One of `"allow"`, `"deny"`, `"hold"`, `"escalate"`.
1327
+ */
1328
+ decision_canonical: DecisionCanonical;
1329
+ /** Opaque permit identifier for a final allow. Pass to verifyPermit. */
1330
+ permitId: string;
1331
+ /** Human-readable explanation from the policy engine. */
1332
+ reason: string;
1333
+ /** Audit hash bound to this decision. */
1334
+ auditHash: string;
1335
+ /** ISO-8601 timestamp of the decision. */
1336
+ timestamp: string;
1337
+ /** When true the stream will emit done and close after this event. */
1338
+ isFinal: boolean;
1339
+ }
1340
+ /** An intermediate progress hint emitted before the final decision. */
1341
+ interface StreamProgressEvent {
1342
+ type: "progress";
1343
+ /** Human-readable stage name (e.g. "policy_loading", "context_enrichment"). */
1344
+ stage: string;
1345
+ /** Additional server-defined fields — forward-compat, do not rely on shape. */
1346
+ [key: string]: unknown;
1347
+ }
1348
+ /** Union of all events yielded by {@link AtlaSentClient.protectStream}. */
1349
+ type StreamEvent = StreamDecisionEvent | StreamProgressEvent;
1350
+ /**
1351
+ * A single item in a {@link AtlaSentClient.evaluateBatch} call.
1352
+ * Same shape as {@link EvaluateRequest}.
1353
+ */
1354
+ interface BatchEvalItem {
1355
+ /** Identifier of the calling agent. */
1356
+ agent: string;
1357
+ /** The action being authorized. */
1358
+ action: string;
1359
+ /** Arbitrary policy context. */
1360
+ context?: Record<string, unknown>;
1361
+ }
1362
+ /**
1363
+ * Per-item result in an {@link EvaluateBatchResponse}.
1364
+ *
1365
+ * Success items carry `decision`, `decisionId`, `permitToken`, `auditHash`,
1366
+ * and `timestamp`. Error items (when the per-item RPC layer failed) carry
1367
+ * only `index`, `error`, and optionally `message`.
1368
+ */
1369
+ interface EvaluateBatchResultItem {
1370
+ /** 0-based position matching the input order. */
1371
+ index: number;
1372
+ /**
1373
+ * Policy decision for this item. Present on success items.
1374
+ * `"allow"`, `"deny"`, `"hold"`, or `"escalate"`.
1375
+ */
1376
+ decision?: DecisionCanonical;
1377
+ /** Server-assigned permit / decision identifier. */
1378
+ decisionId?: string;
1379
+ /** Opaque permit token (allow decisions only). Pass to verifyPermit(). */
1380
+ permitToken?: string | null;
1381
+ /** Machine-readable denial / hold reason. */
1382
+ reason?: string;
1383
+ /** Hash-chained audit-trail entry. */
1384
+ auditHash?: string;
1385
+ /** ISO-8601 decision timestamp. */
1386
+ timestamp?: string;
1387
+ /** Error code when the item itself failed at the RPC layer. */
1388
+ error?: string;
1389
+ /** Human-readable detail when `error` is set. */
1390
+ message?: string;
1391
+ }
1392
+ /**
1393
+ * Response from {@link AtlaSentClient.evaluateBatch}.
1394
+ *
1395
+ * - `items` is in the same order as the input `requests` array.
1396
+ * - `partial: true` means at least one item errored at the RPC layer
1397
+ * (not a policy deny — those are surfaced via `decision: "deny"` on
1398
+ * the item). Check `item.error` on items without a `decision`.
1399
+ * - `replayed: true` means the response was served from the idempotency
1400
+ * cache (a prior call with the same `batchId` completed within 24 h).
1401
+ */
1402
+ interface BatchEvalResponse {
1403
+ /** Server-assigned (or caller-supplied) batch identifier. */
1404
+ batchId: string;
1405
+ /** Per-item results, in input order. */
1406
+ items: EvaluateBatchResultItem[];
1407
+ /** `true` when at least one item failed at the RPC layer. */
1408
+ partial: boolean;
1409
+ /** `true` when served from the idempotency cache. */
1410
+ replayed?: boolean;
1411
+ /** Rate-limit state from the batch response headers. */
1412
+ rateLimit: RateLimitState | null;
1413
+ }
1414
+ /**
1415
+ * Options for {@link AtlaSentClient.subscribeDecisions}.
1416
+ */
1417
+ interface SubscribeDecisionsOptions {
1418
+ /**
1419
+ * Filter to specific event types (e.g. `["evaluate.allow", "evaluate.deny"]`).
1420
+ * Omit to receive all types.
1421
+ */
1422
+ types?: string[];
1423
+ /** Filter to a specific actor ID. */
1424
+ actorId?: string;
1425
+ /**
1426
+ * Resume from a prior event. Pass the `id` of the last received event.
1427
+ * The server replays everything after that sequence position, then
1428
+ * transitions to live polling.
1429
+ */
1430
+ lastEventId?: string;
1431
+ /**
1432
+ * Maximum session duration in seconds. The server emits `session_end`
1433
+ * and closes after this window; the caller should reconnect with the
1434
+ * last received `lastEventId`. Defaults to 1800 (30 min), max 3600 (1 h).
1435
+ */
1436
+ maxSeconds?: number;
1437
+ /** Abort signal to cancel the stream. */
1438
+ signal?: AbortSignal;
1439
+ }
1440
+ /**
1441
+ * A single event from {@link AtlaSentClient.subscribeDecisions}.
1442
+ *
1443
+ * The `type` field maps to the audit-event type emitted by the server
1444
+ * (e.g. `"evaluate.allow"`, `"evaluate.deny"`, `"permit.verified"`).
1445
+ * `"heartbeat"` is a synthetic type emitted by the SDK — not a server
1446
+ * event — indicating the server sent a keepalive ping.
1447
+ * `"session_end"` signals the server-side max-seconds limit was reached;
1448
+ * reconnect with `lastEventId` to continue.
1449
+ */
1450
+ interface DecisionStreamEvent {
1451
+ /** Stable server-assigned ID. Pass as `lastEventId` to resume. */
1452
+ id?: string;
1453
+ /**
1454
+ * Audit-event type, e.g. `"evaluate.allow"`, `"evaluate.deny"`,
1455
+ * `"evaluate.hold"`, `"permit.verified"`, `"permit.revoked"`,
1456
+ * `"heartbeat"`, `"session_end"`.
1457
+ */
1458
+ type: string;
1459
+ decision?: DecisionCanonical;
1460
+ actorId?: string;
1461
+ resourceType?: string;
1462
+ resourceId?: string;
1463
+ payload?: Record<string, unknown>;
1464
+ hash?: string;
1465
+ previousHash?: string;
1466
+ occurredAt?: string;
1467
+ }
1468
+
1469
+ /**
1470
+ * Evidence Engine — per-decision proof artifacts, "why" traces, and
1471
+ * compliance-ready bundles.
1472
+ *
1473
+ * Turn every AtlaSent decision into tamper-evident proof that buyers
1474
+ * can hand to auditors, compliance teams, and regulators.
1475
+ *
1476
+ * Primary entry points:
1477
+ *
1478
+ * 1. `buildWhyTrace(decision, reasons, constraintTrace)` — converts
1479
+ * the ConstraintTrace from `?include=constraint_trace` into a
1480
+ * structured human/machine-readable "why allowed / why denied" trace.
1481
+ *
1482
+ * 2. `buildDecisionReceiptPayload(args)` — assembles the canonical
1483
+ * signable payload for a per-decision receipt.
1484
+ *
1485
+ * 3. `signDecisionReceiptHmac(payload, secret)` — HMAC-SHA256 sign.
1486
+ *
1487
+ * 4. `verifyDecisionReceiptHmac(receipt, secret)` — offline verify.
1488
+ *
1489
+ * 5. `computeBundleHash(bundle)` — SHA-256 of an ActionEvidenceBundle.
1490
+ *
1491
+ * 6. `soc2ControlCoverageForDecision(opts)` — map a decision to SOC 2
1492
+ * control coverage.
1493
+ *
1494
+ * The {@link DecisionReceipt} is the category-defining artifact: a
1495
+ * self-contained, signed, human-readable proof that a specific action
1496
+ * was (or was not) authorized at a specific moment. Every enforcement
1497
+ * adapter produces one; every compliance bundle includes one.
1498
+ */
1499
+
1500
+ /**
1501
+ * One evaluated stage within a policy, in the order the engine ran it.
1502
+ */
1503
+ interface WhyStage {
1504
+ /** Engine stage name (e.g. `"role_check"`, `"context"`). */
1505
+ stage: string;
1506
+ /** Rule identifier, if the stage is rule-bound. */
1507
+ rule?: string;
1508
+ /** Whether this stage's predicate fired / matched. */
1509
+ matched: boolean;
1510
+ /** Non-obvious detail from the engine. */
1511
+ detail?: string;
1512
+ /**
1513
+ * Impact classification:
1514
+ * - `"terminal"` — this stage caused the outer decision.
1515
+ * - `"contributing"` — matched but was not the decisive stage.
1516
+ * - `"passing"` — did not match; execution continued.
1517
+ */
1518
+ impact: "terminal" | "contributing" | "passing";
1519
+ }
1520
+ /** Per-policy evaluation block within a WhyTrace. */
1521
+ interface WhyPolicyEvaluation {
1522
+ policy_id: string;
1523
+ /** Policy-level decision. */
1524
+ decision: string;
1525
+ /** Engine-side fingerprint of the policy bundle row. */
1526
+ fingerprint: string;
1527
+ /** Optional risk score from a `risk` rule clause. */
1528
+ risk_score?: number;
1529
+ /** Stages evaluated for this policy, in order. */
1530
+ stages: WhyStage[];
1531
+ /** `true` iff this policy's decision drove the outer envelope decision. */
1532
+ was_decisive: boolean;
1533
+ }
1534
+ /**
1535
+ * Structured "why allowed / why denied" trace.
1536
+ *
1537
+ * Produced by `buildWhyTrace()` from the `ConstraintTrace` returned
1538
+ * by `/v1-evaluate?include=constraint_trace`. Suitable for:
1539
+ *
1540
+ * - UI display ("Why was this denied?")
1541
+ * - Email / Slack notifications
1542
+ * - Compliance-bundle human-readable section
1543
+ * - Machine-readable policy audit by external verifiers
1544
+ *
1545
+ * `summary` is a one-sentence plain-English explanation.
1546
+ */
1547
+ interface WhyTrace {
1548
+ decision: DecisionCanonical;
1549
+ /** One-sentence human-readable explanation. */
1550
+ summary: string;
1551
+ /** Policy whose decision drove the outer result. Absent on clean allow. */
1552
+ matched_policy_id?: string;
1553
+ /** Per-policy evaluation blocks in evaluation order. */
1554
+ policy_evaluations: WhyPolicyEvaluation[];
1555
+ /**
1556
+ * The single stage that caused the terminal outcome, extracted for
1557
+ * quick access. `undefined` on a clean allow (no blocking stage).
1558
+ */
1559
+ terminal_stage?: WhyStage;
1560
+ /** Total stages evaluated across all policies. */
1561
+ total_stages_evaluated: number;
1562
+ }
1563
+ /** Signing algorithm tag on a {@link DecisionReceipt}. */
1564
+ type DecisionReceiptAlgorithm = "hmac-sha256" | "ed25519" | "none";
1565
+ /**
1566
+ * The canonical signed payload of a {@link DecisionReceipt}.
1567
+ *
1568
+ * Field order is load-bearing: HMAC and chain verifiers stringify
1569
+ * this object and must reproduce byte-identical output. Never reorder
1570
+ * the fields; add new optional fields at the end only.
1571
+ */
1572
+ interface DecisionReceiptPayload {
1573
+ receipt_id: string;
1574
+ evaluation_id: string;
1575
+ org_id: string;
1576
+ decision: DecisionCanonical;
1577
+ action: string;
1578
+ actor: string;
1579
+ resource_type: string | null;
1580
+ resource_id: string | null;
1581
+ reasons: string[];
1582
+ /** One-sentence human-readable summary from the WhyTrace. */
1583
+ why_summary: string;
1584
+ /** Permit ID when the decision was `"allow"`. */
1585
+ permit_id: string | null;
1586
+ /** Permit verification hash when the decision was `"allow"`. */
1587
+ permit_hash: string | null;
1588
+ /** Hash-chained audit-trail entry from the evaluate response. */
1589
+ audit_hash: string;
1590
+ /** SHA-256 hex of canonical JSON of the evaluate context. */
1591
+ context_hash: string;
1592
+ /** ISO-8601 when this receipt was issued. */
1593
+ issued_at: string;
1594
+ /** ISO-8601 TTL, or `null` for non-expiring receipts. */
1595
+ expires_at: string | null;
1596
+ }
1597
+ /**
1598
+ * A signed, tamper-evident record of a single AtlaSent authorization
1599
+ * decision. Self-contained: contains everything an auditor needs to
1600
+ * verify the decision without querying the API.
1601
+ *
1602
+ * **Signature semantics (HMAC-SHA256):**
1603
+ *
1604
+ * `HMAC-SHA256(secret,
1605
+ * receipt_id + "\\n" + issued_at + "\\n" + JSON.stringify(payload))`
1606
+ *
1607
+ * When `algorithm === "ed25519"`, `signature` is hex-encoded Ed25519
1608
+ * over the same input string encoded as UTF-8.
1609
+ *
1610
+ * Offline verification: `verifyDecisionReceiptHmac(receipt, secret)`.
1611
+ *
1612
+ * Callers MUST reject receipts where `algorithm === "none"` in any
1613
+ * context requiring tamper-evidence.
1614
+ */
1615
+ interface DecisionReceipt {
1616
+ receipt_id: string;
1617
+ evaluation_id: string;
1618
+ org_id: string;
1619
+ decision: DecisionCanonical;
1620
+ action: string;
1621
+ actor: string;
1622
+ resource_type: string | null;
1623
+ resource_id: string | null;
1624
+ reasons: string[];
1625
+ /**
1626
+ * Full structured "why" trace. `null` when the evaluation was
1627
+ * performed without `?include=constraint_trace`.
1628
+ */
1629
+ why_trace: WhyTrace | null;
1630
+ permit_id: string | null;
1631
+ permit_hash: string | null;
1632
+ audit_hash: string;
1633
+ /** SHA-256 hex of canonical JSON of the evaluate context. */
1634
+ context_hash: string;
1635
+ issued_at: string;
1636
+ expires_at: string | null;
1637
+ algorithm: DecisionReceiptAlgorithm;
1638
+ /**
1639
+ * Hex (HMAC-SHA256 or Ed25519) signature, or `null` when
1640
+ * `algorithm === "none"`.
1641
+ */
1642
+ signature: string | null;
1643
+ /** Registry key ID that signed, when `algorithm !== "none"`. */
1644
+ signing_key_id: string | null;
1645
+ /**
1646
+ * Full payload that was signed. Pass to `verifyDecisionReceiptHmac`
1647
+ * or reconstruct independently for external verification.
1648
+ */
1649
+ payload: DecisionReceiptPayload;
1650
+ }
1651
+
1652
+ /** Input to {@link protect}. Same shape as `EvaluateRequest`. */
1653
+ interface ProtectRequest {
1654
+ agent: string;
1655
+ action: string;
1656
+ context?: Record<string, unknown>;
1657
+ }
1658
+ /**
1659
+ * Success return from {@link protect}. The action is authorized
1660
+ * end-to-end — evaluation allowed AND the resulting permit verified.
1661
+ */
1662
+ interface Permit {
1663
+ /** Opaque permit / decision identifier. */
1664
+ permitId: string;
1665
+ /** Verification hash bound to the permit. */
1666
+ permitHash: string;
1667
+ /** Audit-trail entry associated with the decision (hash-chained). */
1668
+ auditHash: string;
1669
+ /** Human-readable reason from the policy engine. */
1670
+ reason: string;
1671
+ /** ISO 8601 timestamp of the verification. */
1672
+ timestamp: string;
1673
+ /** ISO-8601 expiration timestamp of the permit. null on pre-rollout servers. */
1674
+ permitExpiresAt: string | null;
1675
+ }
1676
+ /** Configuration for the process-wide singleton used by {@link protect}. */
1677
+ interface ConfigureOptions {
1678
+ /** Overrides `ATLASENT_API_KEY` env var. */
1679
+ apiKey?: string;
1680
+ /** Overrides the default `https://api.atlasent.io`. */
1681
+ baseUrl?: string;
1682
+ /** Per-request timeout in ms. */
1683
+ timeoutMs?: number;
1684
+ /** Inject a custom fetch (primarily for tests). */
1685
+ fetch?: typeof fetch;
1686
+ /** Override the retry policy. Pass `{ maxAttempts: 1 }` to disable retries. */
1687
+ retryPolicy?: RetryPolicy;
1688
+ }
1689
+ /**
1690
+ * Configure the singleton client used by {@link protect}. Optional —
1691
+ * if `ATLASENT_API_KEY` is set in the environment, `protect` works
1692
+ * without any configuration. Calling `configure` again replaces the
1693
+ * singleton; subsequent `protect` calls use the new settings.
1694
+ */
1695
+ declare function configure(options: ConfigureOptions): void;
1696
+ /**
1697
+ * Run the canonical Deploy Gate V1 helper using the process-wide client.
1698
+ * Defaults to action `production.deploy`; execution is allowed only after
1699
+ * server-side `/v1-evaluate` and `/v1-verify-permit` both pass.
1700
+ */
1701
+ declare function deployGate(request?: DeployGateRequest): Promise<DeployGateResponse>;
1702
+ /**
1703
+ * Authorize an action end-to-end. On allow, returns a verified
1704
+ * {@link Permit}. On anything else, throws:
1705
+ *
1706
+ * - {@link AtlaSentDeniedError} — policy denied, or the permit
1707
+ * failed verification. Fail-closed: if this throws, the action
1708
+ * MUST NOT proceed.
1709
+ * - {@link AtlaSentError} — transport, timeout, auth, rate-limit,
1710
+ * or server error. Same fail-closed contract: do not proceed.
1711
+ */
1712
+ declare function protect(request: ProtectRequest): Promise<Permit>;
1713
+ /**
1714
+ * A verified {@link Permit} with an embedded signed {@link DecisionReceipt}.
1715
+ *
1716
+ * Returned by {@link protectWithEvidence}. Store `receipt` alongside
1717
+ * your action record (deploy logs, payment records, close workflows)
1718
+ * to give auditors a self-contained proof of authorization.
1719
+ */
1720
+ interface PermitWithEvidence extends Permit {
1721
+ /** Signed per-decision receipt. `algorithm: "none"` when no signing secret was supplied. */
1722
+ receipt: DecisionReceipt;
1723
+ }
1724
+ /** Options for {@link protectWithEvidence}. */
1725
+ interface ProtectWithEvidenceOptions {
1726
+ /**
1727
+ * HMAC-SHA256 signing secret. When provided, the receipt is signed
1728
+ * and can be verified offline with `verifyDecisionReceiptHmac`.
1729
+ * Recommend `process.env.ATLASENT_RECEIPT_SIGNING_SECRET`.
1730
+ */
1731
+ signingSecret?: string;
1732
+ /**
1733
+ * Registry key ID recorded on the receipt, paired with `signingSecret`.
1734
+ * Used for key rotation: store the ID alongside the receipt so
1735
+ * verifiers know which key to use.
1736
+ */
1737
+ signingKeyId?: string;
1738
+ /**
1739
+ * If you have already called `client.evaluatePreflight()` for this
1740
+ * request, pass `constraintTrace` here to populate
1741
+ * `receipt.why_trace` with the full stage-by-stage "why" trace.
1742
+ * When omitted, `why_trace` is `null` on the receipt.
1743
+ */
1744
+ constraintTrace?: ConstraintTrace | null;
1745
+ }
1746
+ /**
1747
+ * Authorize an action end-to-end and mint a signed {@link DecisionReceipt}.
1748
+ *
1749
+ * Same fail-closed contract as {@link protect} — throws
1750
+ * {@link AtlaSentDeniedError} on deny, {@link AtlaSentError} on
1751
+ * transport failure. The action MUST NOT proceed if this throws.
1752
+ *
1753
+ * On allow, returns the verified `Permit` plus a signed `DecisionReceipt`
1754
+ * that captures:
1755
+ * - The evaluation ID and decision
1756
+ * - Human-readable reasons
1757
+ * - Permit ID and hash
1758
+ * - Audit-trail hash (hash-chain link)
1759
+ * - SHA-256 of the evaluate context (tamper-evidence for the inputs)
1760
+ * - Optional "why" trace (pass `constraintTrace` from `evaluatePreflight`)
1761
+ *
1762
+ * ```ts
1763
+ * const { permit, receipt } = await protectWithEvidence(
1764
+ * { agent: "deploy-bot", action: "production.deploy", context },
1765
+ * {
1766
+ * signingSecret: process.env.ATLASENT_RECEIPT_SIGNING_SECRET,
1767
+ * signingKeyId: "key-v1",
1768
+ * },
1769
+ * );
1770
+ * // Store alongside the deployment record.
1771
+ * await db.deployments.create({ commitSha, permit, receipt });
1772
+ * ```
1773
+ */
1774
+ declare function protectWithEvidence(request: ProtectRequest, opts?: ProtectWithEvidenceOptions): Promise<PermitWithEvidence>;
1775
+
1776
+ export { type ConstraintTrace as $, AtlaSentDeniedError as A, type BatchEvalItem as B, protect as C, type DecisionCanonical as D, type EvaluateRequest as E, deployGate as F, type GetPermitResponse as G, configure as H, type AtlaSentDecision as I, type AtlaSentDeniedErrorInit as J, type AtlaSentErrorCode as K, type ListPermitsRequest as L, type AtlaSentErrorInit as M, AtlaSentEscalateError as N, type AtlaSentEscalateErrorInit as O, type Permit as P, type AuditDecision as Q, type RateLimitState as R, type SubscribeDecisionsOptions as S, type AuditEvent as T, type AuditEventsPage as U, type VerifyPermitRequest as V, type AuditExport as W, type AuditExportSignatureStatus as X, type BvsSnapshot as Y, type ConfigureOptions as Z, type ConsentClassProjection as _, AtlaSentError as a, type ConstraintTracePolicy as a0, type ConstraintTraceStage as a1, DEFAULT_RETRY_POLICY as a2, DEPLOYMENT_PRODUCTION_ACTION as a3, DEPLOY_GATE_CODES as a4, type Decision as a5, type DeployGateContext as a6, type DeployGateDenyCode as a7, type DeployGateEvidence as a8, type DeployOverrideClaim as a9, type DeployPermitClaim as aa, type EvaluateBatchResultItem as ab, type EvaluateResponsePermit as ac, type EvaluateRiskEnvelope as ad, type EvaluateRiskEnvelopeFactor as ae, PRODUCTION_DEPLOY_ACTION as af, type PermitOutcome as ag, type PermitRecord as ah, PermitRevoked as ai, type PermitStatus as aj, type PermitWithEvidence as ak, type ProtectWithEvidenceOptions as al, type RetryPolicy as am, type StreamDecisionEvent as an, StreamParseError as ao, type StreamProgressEvent as ap, StreamTimeoutError as aq, computeBackoffMs as ar, hasAttemptsLeft as as, isRetryable as at, mergePolicy as au, normalizePermitOutcome as av, protectWithEvidence as aw, type ProtectRequest as b, type AtlaSentClientOptions as c, type EvaluateResponse as d, type BatchEvalResponse as e, type DecisionStreamEvent as f, type EvaluatePreflightResponse as g, type VerifyPermitResponse as h, type DeployGateRequest as i, type DeployGateResponse as j, type RevokePermitRequest as k, type RevokePermitResponse as l, type RevokePermitByIdInput as m, type RevokePermitByIdResponse as n, type VerifyPermitByIdResponse as o, type PermitValidResponse as p, type ListPermitsResponse as q, type ApiKeySelfResponse as r, type AuditEventsQuery as s, type AuditEventsResult as t, type AuditExportRequest as u, type AuditExportResult as v, type StreamOptions as w, type StreamEvent as x, type DecisionReceipt as y, type DecisionReceiptAlgorithm as z };