@atlasent/sdk 1.5.0 → 2.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,1329 @@
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";
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
+ /** Input to {@link AtlaSentClient.evaluate}. */
690
+ interface EvaluateRequest {
691
+ /** Identifier of the calling agent (e.g. "clinical-data-agent"). */
692
+ agent: string;
693
+ /** The action being authorized (e.g. "modify_patient_record"). */
694
+ action: string;
695
+ /** Arbitrary policy context (user, environment, resource IDs). */
696
+ context?: Record<string, unknown>;
697
+ }
698
+ /**
699
+ * Slim permit object embedded in {@link EvaluateResponse} when the decision
700
+ * is `"allow"`. Contains the essential fields needed to act on the permit
701
+ * immediately without a separate `GET /v1/permits/:id` round-trip.
702
+ *
703
+ * Mirrors the `Permit` schema in atlasent-control-plane
704
+ * `api/src/schemas/permits.ts`.
705
+ */
706
+ interface EvaluateResponsePermit {
707
+ id: string;
708
+ orgId: string;
709
+ subject: string;
710
+ scope: string;
711
+ status: "active" | "revoked" | "expired";
712
+ /** The evaluation that produced this permit. */
713
+ evaluationId: string | null;
714
+ issuedBy: string;
715
+ revokedBy: string | null;
716
+ /** ISO-8601 issuance timestamp. */
717
+ issuedAt: string;
718
+ revokedAt: string | null;
719
+ expiresAt: string | null;
720
+ metadata: Record<string, unknown> | null;
721
+ }
722
+ /** Result of {@link AtlaSentClient.evaluate}. */
723
+ interface EvaluateResponse {
724
+ /**
725
+ * Policy decision — canonical 4-value lowercase vocabulary:
726
+ * `"allow"`, `"deny"`, `"hold"`, or `"escalate"`.
727
+ *
728
+ * Previously emitted `"ALLOW"` / `"DENY"` (uppercase, 2-value);
729
+ * the SDK now normalises all values to lowercase and passes `hold`
730
+ * and `escalate` through rather than collapsing them to `"DENY"`.
731
+ *
732
+ * The `decision_canonical` field carries the same value and is the
733
+ * recommended field for new code.
734
+ */
735
+ decision: Decision;
736
+ /**
737
+ * Canonical 4-value decision, byte-identical to the wire.
738
+ *
739
+ * One of `"allow"`, `"deny"`, `"hold"`, `"escalate"`. Branch on
740
+ * this field on new code. `hold` and `escalate` are non-terminal
741
+ * states that route to a human reviewer / approval signal — they
742
+ * are not equivalent to a `deny`.
743
+ */
744
+ decision_canonical: DecisionCanonical;
745
+ /**
746
+ * Server-assigned identifier for this evaluation decision.
747
+ *
748
+ * Stable across retries and used as the key for proof retrieval
749
+ * (`GET /v1/proof/:evaluationId`) and override requests. Also
750
+ * available as the legacy `permitId` field for backward compatibility.
751
+ */
752
+ evaluationId: string;
753
+ /** Opaque permit identifier, passed to {@link AtlaSentClient.verifyPermit}.
754
+ *
755
+ * @deprecated Prefer `evaluationId`. This field is kept for backward
756
+ * compatibility and points to the same server-assigned ID.
757
+ */
758
+ permitId: string;
759
+ /**
760
+ * Slim permit object issued when `decision === "allow"`.
761
+ * `null` on deny, hold, or escalate decisions.
762
+ *
763
+ * Mirrors the `Permit` schema from the control-plane.
764
+ */
765
+ permit: EvaluateResponsePermit | null;
766
+ /**
767
+ * Opaque HMAC-signed permit token issued when `decision === "allow"`.
768
+ * Pass to `POST /v1/verify-permit` to verify the permit server-side.
769
+ * `null` on deny, hold, or escalate decisions.
770
+ */
771
+ permitToken: string | null;
772
+ /**
773
+ * Machine-readable reasons emitted by the policy engine.
774
+ *
775
+ * The array may be empty. For deny/hold/escalate decisions the array
776
+ * typically contains a single human-readable explanation; for allow
777
+ * decisions it is often empty. Do not parse these strings — use
778
+ * `decision` for branching.
779
+ */
780
+ reasons: string[];
781
+ /** Human-readable explanation from the policy engine.
782
+ *
783
+ * @deprecated Prefer `reasons[0]` or `reasons`. This field is the
784
+ * first element of `reasons` (or an empty string) for backward compat.
785
+ */
786
+ reason: string;
787
+ /** Hash-chained audit-trail entry (21 CFR Part 11 / GxP-ready). */
788
+ auditHash: string;
789
+ /** ISO 8601 timestamp of the decision. */
790
+ timestamp: string;
791
+ /**
792
+ * Per-key rate-limit state for this request's response, parsed from
793
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
794
+ */
795
+ rateLimit: RateLimitState | null;
796
+ }
797
+ /** Input to {@link AtlaSentClient.verifyPermit}. */
798
+ interface VerifyPermitRequest {
799
+ /** The permit ID returned by a prior evaluate() call. */
800
+ permitId: string;
801
+ /** Optional: re-state the action for cross-check with the server. */
802
+ action?: string;
803
+ /** Optional: re-state the agent for cross-check with the server. */
804
+ agent?: string;
805
+ /** Optional: re-state the context for cross-check with the server. */
806
+ context?: Record<string, unknown>;
807
+ /**
808
+ * Environment of the permit being verified. Sourced from the evaluate
809
+ * payload (context.environment → top-level environment → "production").
810
+ * Required by the server for production permits as of 2026-05-14.
811
+ * P1-1 fix: withPermit/protect now always populates this field.
812
+ */
813
+ environment?: string;
814
+ /**
815
+ * SHA-256 hex digest of the recursively key-sorted canonical JSON of the
816
+ * original evaluate payload. Required by the server for production permits
817
+ * as of 2026-05-14.
818
+ * P1-5 fix: withPermit/protect now always computes and sends this field.
819
+ */
820
+ execution_hash?: string;
821
+ }
822
+ /**
823
+ * Result of {@link AtlaSentClient.verifyPermit}.
824
+ *
825
+ * @deprecated Use {@link VerifyPermitByIdResponse} via
826
+ * {@link AtlaSentClient.verifyPermitById} — the canonical REST surface
827
+ * (`POST /v1/permits/{id}/verify`) returns the unified verification
828
+ * envelope (`valid`, `verification_type`, `reason`, `verified_at`,
829
+ * `evidence`) plus the full {@link PermitRecord} fields. Will be
830
+ * removed in `@atlasent/sdk@3`.
831
+ */
832
+ interface VerifyPermitResponse {
833
+ /** `true` when the permit is valid and un-revoked. */
834
+ verified: boolean;
835
+ /** Verification outcome string from the server. */
836
+ outcome: string;
837
+ /** Verification hash bound to the permit. */
838
+ permitHash: string;
839
+ /** ISO 8601 timestamp of the verification. */
840
+ timestamp: string;
841
+ /**
842
+ * Per-key rate-limit state for this request's response, parsed from
843
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
844
+ */
845
+ rateLimit: RateLimitState | null;
846
+ }
847
+ /**
848
+ * Result of {@link AtlaSentClient.keySelf} — self-introspection of the API
849
+ * key the client was constructed with. Returned by `GET /v1/api-key-self`.
850
+ *
851
+ * Never includes the raw key or its hash — introspection is intentionally
852
+ * read-only and safe to surface in operator dashboards. Useful for:
853
+ * - "which key am I?" debugging
854
+ * - IP_NOT_ALLOWED failures — `clientIp` is the IP the server observed
855
+ * - proactive expiry warnings — `expiresAt` is the server-stored expiry
856
+ * (`null` means the key does not auto-expire)
857
+ * - verifying scopes before attempting a scope-gated action
858
+ */
859
+ interface ApiKeySelfResponse {
860
+ /** Server-side UUID of the api_keys row for this key. */
861
+ keyId: string;
862
+ /** Organization the key belongs to. */
863
+ organizationId: string;
864
+ /** "live" or "test" (or any future environment label the server introduces). */
865
+ environment: string;
866
+ /** Granted scopes — e.g. ["evaluate", "audit.read"]. */
867
+ scopes: string[];
868
+ /**
869
+ * Per-key IP allowlist as CIDR strings (e.g. ["10.0.0.0/8"]). `null`
870
+ * when the key is unrestricted.
871
+ */
872
+ allowedCidrs: string[] | null;
873
+ /** Server-enforced per-minute rate limit for this key. */
874
+ rateLimitPerMinute: number;
875
+ /** Client IP as the server observed it (first hop of X-Forwarded-For). */
876
+ clientIp: string | null;
877
+ /** Server-stored expiry; `null` means the key does not auto-expire. */
878
+ expiresAt: string | null;
879
+ /**
880
+ * Per-key rate-limit state for this request's response, parsed from
881
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
882
+ */
883
+ rateLimit: RateLimitState | null;
884
+ }
885
+ /**
886
+ * Result of {@link AtlaSentClient.listAuditEvents}. Extends the raw
887
+ * wire page with a camelCase `rateLimit` alongside the snake_case
888
+ * wire fields — the wire shape (`events`, `total`, `next_cursor`) is
889
+ * untouched so callers that pass it to the offline verifier get
890
+ * byte-identical behaviour.
891
+ */
892
+ interface AuditEventsResult extends AuditEventsPage {
893
+ /**
894
+ * Per-key rate-limit state for this request's response, parsed from
895
+ * `X-RateLimit-*` headers. `null` when the server didn't emit them.
896
+ */
897
+ rateLimit: RateLimitState | null;
898
+ }
899
+ /**
900
+ * Filter accepted by {@link AtlaSentClient.createAuditExport}. Fields
901
+ * are snake_case to match the server's `POST /v1-audit/exports`
902
+ * request body; an empty object requests a full-org bundle.
903
+ */
904
+ interface AuditExportRequest {
905
+ /** Comma-joined list of event types to include (e.g. `"evaluate.allow,policy.updated"`). */
906
+ types?: string;
907
+ /** Filter to a single actor. */
908
+ actor_id?: string;
909
+ /** Inclusive lower bound on `occurred_at` (ISO 8601). */
910
+ from?: string;
911
+ /** Inclusive upper bound on `occurred_at` (ISO 8601). */
912
+ to?: string;
913
+ }
914
+ /**
915
+ * Result of {@link AtlaSentClient.createAuditExport}. Extends the
916
+ * signed bundle shape with a camelCase `rateLimit`. The signed
917
+ * envelope fields (`export_id`, `org_id`, `chain_head_hash`,
918
+ * `event_count`, `signed_at`, `events`, `signature`) are preserved
919
+ * byte-for-byte so the object can be handed straight to
920
+ * `verifyAuditBundle(bundle, keys)`.
921
+ */
922
+ interface AuditExportResult extends AuditExport {
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
+ /** Constructor options for {@link AtlaSentClient}. */
930
+ interface AtlaSentClientOptions {
931
+ /** Required. Your AtlaSent API key. */
932
+ apiKey: string;
933
+ /** API base URL. Defaults to "https://api.atlasent.io". */
934
+ baseUrl?: string;
935
+ /** Per-request timeout in milliseconds. Defaults to 10_000. */
936
+ timeoutMs?: number;
937
+ /**
938
+ * Inject a fetch implementation (primarily for testing).
939
+ * Defaults to `globalThis.fetch`.
940
+ */
941
+ fetch?: typeof fetch;
942
+ /**
943
+ * Retry policy for transient failures (network errors, timeouts,
944
+ * 429 rate-limit, 5xx server errors, malformed responses).
945
+ * Omit to use the default: 4 total attempts, 2 000 ms base, 16 000 ms cap,
946
+ * full-jitter exponential backoff matching the Python SDK schedule
947
+ * (2 s → 4 s → 8 s → 16 s).
948
+ * Pass `{ maxAttempts: 1 }` to disable retries entirely.
949
+ */
950
+ retryPolicy?: RetryPolicy;
951
+ }
952
+ /** Permit lifecycle status. */
953
+ type PermitStatus = "issued" | "verified" | "consumed" | "expired" | "revoked";
954
+ /**
955
+ * Wire shape of a Permit row, returned by {@link AtlaSentClient.getPermit}
956
+ * and {@link AtlaSentClient.listPermits}. Mirrors the openapi `Permit`
957
+ * schema.
958
+ *
959
+ * Revocation fields (`revoked_at`, `revoked_by`, `revoke_reason`) are
960
+ * populated only when `status === 'revoked'`; null otherwise.
961
+ */
962
+ interface PermitRecord {
963
+ id: string;
964
+ org_id: string;
965
+ actor_id: string;
966
+ action_id: string;
967
+ target_id?: string;
968
+ environment?: string;
969
+ status: PermitStatus;
970
+ issued_at: string;
971
+ expires_at: string;
972
+ consumed_at?: string | null;
973
+ revoked_at?: string | null;
974
+ revoked_by?: string | null;
975
+ revoke_reason?: string | null;
976
+ signature?: string;
977
+ payload_hash?: string | null;
978
+ decision_id?: string | null;
979
+ }
980
+ /** Optional filters for {@link AtlaSentClient.listPermits}. */
981
+ interface ListPermitsRequest {
982
+ status?: PermitStatus;
983
+ actorId?: string;
984
+ actionType?: string;
985
+ /** ISO-8601 lower bound on `created_at`. */
986
+ from?: string;
987
+ /** ISO-8601 upper bound on `created_at`. */
988
+ to?: string;
989
+ /** Page size. Server max is 500; default 50. */
990
+ limit?: number;
991
+ /** Pass `nextCursor` from a prior response to page forward. */
992
+ cursor?: string;
993
+ }
994
+ /** Response from {@link AtlaSentClient.listPermits}. */
995
+ interface ListPermitsResponse {
996
+ permits: PermitRecord[];
997
+ /** Total matching rows ignoring `limit`/`cursor`. */
998
+ total: number;
999
+ /** Pass on next call as `cursor`. Absent when no more rows. */
1000
+ nextCursor?: string;
1001
+ rateLimit: RateLimitState | null;
1002
+ }
1003
+ /** Response from {@link AtlaSentClient.getPermit}. */
1004
+ interface GetPermitResponse {
1005
+ permit: PermitRecord;
1006
+ rateLimit: RateLimitState | null;
1007
+ }
1008
+ /**
1009
+ * Response from {@link AtlaSentClient.checkPermitValid}.
1010
+ *
1011
+ * Lightweight validity snapshot returned by
1012
+ * `GET /v1/permits/{permitId}/valid`. Designed for guard heartbeat
1013
+ * polling — returns only the fields needed to determine whether to
1014
+ * abort a running permit mid-execution (via {@link PermitRevoked}).
1015
+ */
1016
+ interface PermitValidResponse {
1017
+ /** True iff the permit is currently valid (active). */
1018
+ valid: boolean;
1019
+ /**
1020
+ * Current lifecycle status of the permit.
1021
+ * - `"active"` — permit is valid and in-flight.
1022
+ * - `"expired"` — TTL elapsed before revocation or consumption.
1023
+ * - `"revoked"` — administratively revoked (see `revocation_id`).
1024
+ * - `"consumed"` — single-use permit already consumed.
1025
+ */
1026
+ status: "active" | "expired" | "revoked" | "consumed";
1027
+ /** ISO-8601 timestamp when the permit was revoked. Populated only when `status === "revoked"`. */
1028
+ revoked_at?: string;
1029
+ /** Opaque identifier of the revocation record. Populated only when `status === "revoked"`. */
1030
+ revocation_id?: string;
1031
+ }
1032
+ /** Input for {@link AtlaSentClient.revokePermitById}. */
1033
+ interface RevokePermitByIdInput {
1034
+ /** Operator-supplied free-text reason. Recorded on the permit row,
1035
+ * written to the audit trail, and surfaced (truncated) on later
1036
+ * verify responses. Optional but strongly encouraged. */
1037
+ reason?: string;
1038
+ }
1039
+ /**
1040
+ * Response from {@link AtlaSentClient.revokePermitById}.
1041
+ *
1042
+ * Returns the updated {@link PermitRecord} with `status === 'revoked'`
1043
+ * and the populated `revoked_at` / `revoked_by` / `revoke_reason`
1044
+ * fields.
1045
+ */
1046
+ interface RevokePermitByIdResponse {
1047
+ permit: PermitRecord;
1048
+ rateLimit: RateLimitState | null;
1049
+ }
1050
+ /**
1051
+ * Response from {@link AtlaSentClient.verifyPermitById}.
1052
+ *
1053
+ * Returns the canonical verification envelope (`valid`,
1054
+ * `verification_type`, `reason`, `verified_at`, `evidence`) plus the
1055
+ * legacy {@link PermitRecord} fields preserved at the top level for
1056
+ * backward compatibility. The envelope shape matches the unified
1057
+ * verify response in atlasent-api PR #352.
1058
+ */
1059
+ interface VerifyPermitByIdResponse {
1060
+ /** `true` iff the permit verified — i.e. unconsumed, unexpired, and signature OK. */
1061
+ valid: boolean;
1062
+ /** Always `'permit'` on this surface. */
1063
+ verification_type: "permit";
1064
+ /** Operator-readable explanation when `valid` is `false`; `null` on success. */
1065
+ reason: string | null;
1066
+ /** Server clock at the moment verification ran. */
1067
+ verified_at: string;
1068
+ /** Type-specific evidence — same fields as the openapi PermitVerifyEvidence schema. */
1069
+ evidence: {
1070
+ permit_id: string;
1071
+ status: PermitStatus;
1072
+ actor_id?: string;
1073
+ action_id?: string;
1074
+ expires_at?: string;
1075
+ payload_hash?: string | null;
1076
+ decision_id?: string | null;
1077
+ };
1078
+ /** Legacy: full permit row preserved at the top level. */
1079
+ permit: PermitRecord;
1080
+ rateLimit: RateLimitState | null;
1081
+ }
1082
+ /** Input for {@link AtlaSentClient.revokePermit}. */
1083
+ interface RevokePermitRequest {
1084
+ /** The permit ID returned by a prior evaluate() call. */
1085
+ permitId: string;
1086
+ /** Optional human-readable reason stored in the audit log. */
1087
+ reason?: string;
1088
+ }
1089
+ /**
1090
+ * Result of {@link AtlaSentClient.revokePermit}.
1091
+ *
1092
+ * @deprecated Use {@link RevokePermitByIdResponse} via
1093
+ * {@link AtlaSentClient.revokePermitById} — the canonical REST surface
1094
+ * (`POST /v1/permits/{id}/revoke`) returns the full updated
1095
+ * {@link PermitRecord} with `revoked_at`/`revoked_by`/`revoke_reason`
1096
+ * populated, instead of the legacy `{revoked, permitId}` envelope.
1097
+ * Will be removed in `@atlasent/sdk@3`.
1098
+ */
1099
+ interface RevokePermitResponse {
1100
+ /** `true` when the permit was found and successfully revoked. */
1101
+ revoked: boolean;
1102
+ /** Echo of the revoked permit's ID. */
1103
+ permitId: string;
1104
+ /** ISO-8601 timestamp of when the revocation was recorded. `undefined` when not returned by the server. */
1105
+ revokedAt?: string | undefined;
1106
+ /** Audit hash for the revocation event. `undefined` when not returned by the server. */
1107
+ auditHash?: string | undefined;
1108
+ /** Per-key rate-limit state. `null` when the server didn't emit headers. */
1109
+ rateLimit: RateLimitState | null;
1110
+ }
1111
+ /**
1112
+ * One stage of a single policy's constraint evaluation.
1113
+ *
1114
+ * Mirrors `ConstraintTraceStage` in
1115
+ * `atlasent-api/packages/types/src/index.ts`. Emitted by the rule
1116
+ * engine when the request URL carries `?include=constraint_trace`.
1117
+ *
1118
+ * Forward-compat: extra engine-side keys are tolerated; readers
1119
+ * should not assume this is a closed shape.
1120
+ */
1121
+ interface ConstraintTraceStage {
1122
+ /** Engine stage name (e.g. `"role_check"`, `"context"`). */
1123
+ readonly stage: string;
1124
+ /** Optional rule identifier; absent for wrapper stages. */
1125
+ readonly rule?: string;
1126
+ /** True iff this stage's predicate fired. */
1127
+ readonly matched: boolean;
1128
+ /** Optional human-readable note from the engine. */
1129
+ readonly detail?: string;
1130
+ /** Zero-based position within the policy's `stages` array. */
1131
+ readonly order: number;
1132
+ /** Forward-compat: tolerate unknown engine-side keys without crashing. */
1133
+ readonly [key: string]: unknown;
1134
+ }
1135
+ /**
1136
+ * Per-policy block of a constraint trace.
1137
+ *
1138
+ * Mirrors `ConstraintTracePolicy` in
1139
+ * `atlasent-api/packages/types/src/index.ts`. The handler iterates
1140
+ * active policies in order until first non-allow; the policy that
1141
+ * produced the outer decision has `decision !== "allow"`.
1142
+ */
1143
+ interface ConstraintTracePolicy {
1144
+ /** Stable identifier of the evaluated policy. */
1145
+ readonly policy_id: string;
1146
+ /** Policy-level decision (`"allow"|"deny"|"hold"|"escalate"`). */
1147
+ readonly decision: string;
1148
+ /** Engine-side fingerprint of the bundle row. */
1149
+ readonly fingerprint: string;
1150
+ /**
1151
+ * Optional engine-computed risk score from a `risk` rule clause.
1152
+ * Distinct from the heuristic risk score on the outer envelope.
1153
+ */
1154
+ readonly risk_score?: number;
1155
+ /** Ordered stages produced while evaluating this policy. */
1156
+ readonly stages: ReadonlyArray<ConstraintTraceStage>;
1157
+ /** Forward-compat: tolerate unknown engine-side keys. */
1158
+ readonly [key: string]: unknown;
1159
+ }
1160
+ /**
1161
+ * Top-level constraint trace returned by
1162
+ * `/v1-evaluate?include=constraint_trace`.
1163
+ *
1164
+ * Mirrors `ConstraintTraceResponse` in
1165
+ * `atlasent-api/packages/types/src/index.ts`. Present iff the
1166
+ * caller requested the trace; the SDK's preflight helper always
1167
+ * requests it.
1168
+ */
1169
+ interface ConstraintTrace {
1170
+ /** Per-policy blocks in evaluation order. */
1171
+ readonly rules_evaluated: ReadonlyArray<ConstraintTracePolicy>;
1172
+ /**
1173
+ * Policy id whose evaluation produced the outer decision. Equals
1174
+ * the outer `matched_policy_id` on non-allow paths; `undefined` on
1175
+ * a clean allow (all policies passed).
1176
+ */
1177
+ readonly matching_policy_id?: string;
1178
+ /** Forward-compat: tolerate unknown engine-side keys. */
1179
+ readonly [key: string]: unknown;
1180
+ }
1181
+ /**
1182
+ * Result of {@link AtlaSentClient.evaluatePreflight}.
1183
+ *
1184
+ * Wraps the regular {@link EvaluateResponse} plus the
1185
+ * {@link ConstraintTrace} returned when the request URL carries
1186
+ * `?include=constraint_trace`. The whole point of preflight is to
1187
+ * surface which stages / policies WOULD fire BEFORE pushing the
1188
+ * request onto an approval queue, so workflows can reject trivially
1189
+ * defective requests at submission time and only forward viable
1190
+ * requests to a human reviewer.
1191
+ *
1192
+ * `constraintTrace` is `null` on responses from older atlasent-api
1193
+ * deployments that do not echo the trace — forward-compatible
1194
+ * degradation.
1195
+ */
1196
+ interface EvaluatePreflightResponse {
1197
+ /** The regular evaluate response (decision, permitId, ...). */
1198
+ readonly evaluation: EvaluateResponse;
1199
+ /**
1200
+ * The constraint trace, or `null` when the server omitted it
1201
+ * (older atlasent-api version).
1202
+ */
1203
+ readonly constraintTrace: ConstraintTrace | null;
1204
+ }
1205
+ /**
1206
+ * Options for {@link AtlaSentClient.protectStream}.
1207
+ *
1208
+ * All fields are optional; defaults are used when omitted.
1209
+ */
1210
+ interface StreamOptions {
1211
+ /**
1212
+ * Optional abort signal to cancel the stream from the caller side.
1213
+ */
1214
+ signal?: AbortSignal;
1215
+ /**
1216
+ * Per-event timeout in milliseconds: if no SSE event arrives within
1217
+ * this window the stream throws {@link StreamTimeoutError}.
1218
+ * Defaults to 30 000 ms (30 s). Pass `0` to disable.
1219
+ */
1220
+ timeoutMs?: number;
1221
+ /**
1222
+ * Maximum reconnection attempts on network drop before the stream
1223
+ * gives up and throws. Defaults to 3.
1224
+ */
1225
+ maxRetries?: number;
1226
+ }
1227
+ /** A policy decision emitted mid-stream. */
1228
+ interface StreamDecisionEvent {
1229
+ type: "decision";
1230
+ /**
1231
+ * Policy decision — canonical 4-value lowercase vocabulary:
1232
+ * `"allow"`, `"deny"`, `"hold"`, or `"escalate"`.
1233
+ *
1234
+ * Previously emitted `"ALLOW"` / `"DENY"` (uppercase, 2-value);
1235
+ * now unified with `decision_canonical`.
1236
+ *
1237
+ * @deprecated Read `decision_canonical` instead for forward-compatible
1238
+ * branching. Both fields now carry the same value. Will be
1239
+ * removed/changed in `@atlasent/sdk@3`.
1240
+ */
1241
+ decision: Decision;
1242
+ /**
1243
+ * Canonical 4-value decision, byte-identical to the wire.
1244
+ * One of `"allow"`, `"deny"`, `"hold"`, `"escalate"`.
1245
+ */
1246
+ decision_canonical: DecisionCanonical;
1247
+ /** Opaque permit identifier for a final allow. Pass to verifyPermit. */
1248
+ permitId: string;
1249
+ /** Human-readable explanation from the policy engine. */
1250
+ reason: string;
1251
+ /** Audit hash bound to this decision. */
1252
+ auditHash: string;
1253
+ /** ISO-8601 timestamp of the decision. */
1254
+ timestamp: string;
1255
+ /** When true the stream will emit done and close after this event. */
1256
+ isFinal: boolean;
1257
+ }
1258
+ /** An intermediate progress hint emitted before the final decision. */
1259
+ interface StreamProgressEvent {
1260
+ type: "progress";
1261
+ /** Human-readable stage name (e.g. "policy_loading", "context_enrichment"). */
1262
+ stage: string;
1263
+ /** Additional server-defined fields — forward-compat, do not rely on shape. */
1264
+ [key: string]: unknown;
1265
+ }
1266
+ /** Union of all events yielded by {@link AtlaSentClient.protectStream}. */
1267
+ type StreamEvent = StreamDecisionEvent | StreamProgressEvent;
1268
+
1269
+ /** Input to {@link protect}. Same shape as `EvaluateRequest`. */
1270
+ interface ProtectRequest {
1271
+ agent: string;
1272
+ action: string;
1273
+ context?: Record<string, unknown>;
1274
+ }
1275
+ /**
1276
+ * Success return from {@link protect}. The action is authorized
1277
+ * end-to-end — evaluation allowed AND the resulting permit verified.
1278
+ */
1279
+ interface Permit {
1280
+ /** Opaque permit / decision identifier. */
1281
+ permitId: string;
1282
+ /** Verification hash bound to the permit. */
1283
+ permitHash: string;
1284
+ /** Audit-trail entry associated with the decision (hash-chained). */
1285
+ auditHash: string;
1286
+ /** Human-readable reason from the policy engine. */
1287
+ reason: string;
1288
+ /** ISO 8601 timestamp of the verification. */
1289
+ timestamp: string;
1290
+ }
1291
+ /** Configuration for the process-wide singleton used by {@link protect}. */
1292
+ interface ConfigureOptions {
1293
+ /** Overrides `ATLASENT_API_KEY` env var. */
1294
+ apiKey?: string;
1295
+ /** Overrides the default `https://api.atlasent.io`. */
1296
+ baseUrl?: string;
1297
+ /** Per-request timeout in ms. */
1298
+ timeoutMs?: number;
1299
+ /** Inject a custom fetch (primarily for tests). */
1300
+ fetch?: typeof fetch;
1301
+ /** Override the retry policy. Pass `{ maxAttempts: 1 }` to disable retries. */
1302
+ retryPolicy?: RetryPolicy;
1303
+ }
1304
+ /**
1305
+ * Configure the singleton client used by {@link protect}. Optional —
1306
+ * if `ATLASENT_API_KEY` is set in the environment, `protect` works
1307
+ * without any configuration. Calling `configure` again replaces the
1308
+ * singleton; subsequent `protect` calls use the new settings.
1309
+ */
1310
+ declare function configure(options: ConfigureOptions): void;
1311
+ /**
1312
+ * Run the canonical Deploy Gate V1 helper using the process-wide client.
1313
+ * Defaults to action `production.deploy`; execution is allowed only after
1314
+ * server-side `/v1-evaluate` and `/v1-verify-permit` both pass.
1315
+ */
1316
+ declare function deployGate(request?: DeployGateRequest): Promise<DeployGateResponse>;
1317
+ /**
1318
+ * Authorize an action end-to-end. On allow, returns a verified
1319
+ * {@link Permit}. On anything else, throws:
1320
+ *
1321
+ * - {@link AtlaSentDeniedError} — policy denied, or the permit
1322
+ * failed verification. Fail-closed: if this throws, the action
1323
+ * MUST NOT proceed.
1324
+ * - {@link AtlaSentError} — transport, timeout, auth, rate-limit,
1325
+ * or server error. Same fail-closed contract: do not proceed.
1326
+ */
1327
+ declare function protect(request: ProtectRequest): Promise<Permit>;
1328
+
1329
+ export { type DeployGateDenyCode as $, AtlaSentDeniedError as A, type AtlaSentErrorInit as B, AtlaSentEscalateError as C, type DeployGateRequest as D, type EvaluateRequest as E, type AtlaSentEscalateErrorInit as F, type GetPermitResponse as G, type AuditDecision as H, type AuditEvent as I, type AuditEventsPage as J, type AuditExport as K, type ListPermitsRequest as L, type AuditExportSignatureStatus as M, type ConfigureOptions as N, type ConstraintTrace as O, type Permit as P, type ConstraintTracePolicy as Q, type RateLimitState as R, type StreamOptions as S, type ConstraintTraceStage as T, DEFAULT_RETRY_POLICY as U, type VerifyPermitRequest as V, DEPLOYMENT_PRODUCTION_ACTION as W, DEPLOY_GATE_CODES as X, type Decision as Y, type DecisionCanonical as Z, type DeployGateContext as _, AtlaSentError as a, type DeployGateEvidence as a0, type DeployOverrideClaim as a1, type DeployPermitClaim as a2, type EvaluateResponsePermit as a3, PRODUCTION_DEPLOY_ACTION as a4, type PermitOutcome as a5, type PermitRecord as a6, PermitRevoked as a7, type PermitStatus as a8, type RetryPolicy as a9, type StreamDecisionEvent as aa, StreamParseError as ab, type StreamProgressEvent as ac, StreamTimeoutError as ad, computeBackoffMs as ae, hasAttemptsLeft as af, isRetryable as ag, mergePolicy as ah, normalizePermitOutcome as ai, type ProtectRequest as b, type AtlaSentClientOptions as c, type EvaluateResponse as d, type EvaluatePreflightResponse as e, type VerifyPermitResponse as f, type DeployGateResponse as g, type RevokePermitRequest as h, type RevokePermitResponse as i, type RevokePermitByIdInput as j, type RevokePermitByIdResponse as k, type VerifyPermitByIdResponse as l, type PermitValidResponse as m, type ListPermitsResponse as n, type ApiKeySelfResponse as o, type AuditEventsQuery as p, type AuditEventsResult as q, type AuditExportRequest as r, type AuditExportResult as s, type StreamEvent as t, protect as u, deployGate as v, configure as w, type AtlaSentDecision as x, type AtlaSentDeniedErrorInit as y, type AtlaSentErrorCode as z };