@atlasent/sdk 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hono.cjs ADDED
@@ -0,0 +1,580 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/hono.ts
21
+ var hono_exports = {};
22
+ __export(hono_exports, {
23
+ AtlaSentDeniedError: () => AtlaSentDeniedError,
24
+ AtlaSentError: () => AtlaSentError,
25
+ atlaSentErrorHandler: () => atlaSentErrorHandler,
26
+ atlaSentGuard: () => atlaSentGuard
27
+ });
28
+ module.exports = __toCommonJS(hono_exports);
29
+
30
+ // src/errors.ts
31
+ var AtlaSentError = class extends Error {
32
+ // Subclasses override to their own literal (e.g. "AtlaSentDeniedError");
33
+ // keep this assignable rather than pinned to a single literal.
34
+ name = "AtlaSentError";
35
+ /** HTTP status code, when the error originated from an API response. */
36
+ status;
37
+ /** Coarse category — useful for `switch` statements at call sites. */
38
+ code;
39
+ /** Correlation ID echoed from the `X-Request-ID` header the SDK sent. */
40
+ requestId;
41
+ /** Parsed `Retry-After` header value, in milliseconds. Only set for 429. */
42
+ retryAfterMs;
43
+ constructor(message, init = {}) {
44
+ super(
45
+ message,
46
+ init.cause !== void 0 ? { cause: init.cause } : void 0
47
+ );
48
+ this.status = init.status;
49
+ this.code = init.code;
50
+ this.requestId = init.requestId;
51
+ this.retryAfterMs = init.retryAfterMs;
52
+ }
53
+ };
54
+ var AtlaSentDeniedError = class extends AtlaSentError {
55
+ name = "AtlaSentDeniedError";
56
+ /** Policy decision — `"deny"` today; `"hold"` / `"escalate"` reserved. */
57
+ decision;
58
+ /** Opaque permit/decision id from `/v1-evaluate`. */
59
+ evaluationId;
60
+ /** Human-readable explanation from the policy engine, if provided. */
61
+ reason;
62
+ /** Hash-chained audit-trail entry associated with the decision. */
63
+ auditHash;
64
+ constructor(init) {
65
+ const msg = init.reason ? `AtlaSent ${init.decision}: ${init.reason}` : `AtlaSent ${init.decision}`;
66
+ const errInit = { status: 200 };
67
+ if (init.requestId !== void 0) errInit.requestId = init.requestId;
68
+ super(msg, errInit);
69
+ this.decision = init.decision;
70
+ this.evaluationId = init.evaluationId;
71
+ this.reason = init.reason;
72
+ this.auditHash = init.auditHash;
73
+ }
74
+ };
75
+
76
+ // src/client.ts
77
+ var DEFAULT_BASE_URL = "https://api.atlasent.io";
78
+ var DEFAULT_TIMEOUT_MS = 1e4;
79
+ var SDK_VERSION = "0.1.0";
80
+ var AtlaSentClient = class {
81
+ apiKey;
82
+ baseUrl;
83
+ timeoutMs;
84
+ fetchImpl;
85
+ constructor(options) {
86
+ if (!options.apiKey || typeof options.apiKey !== "string") {
87
+ throw new AtlaSentError("apiKey is required", {
88
+ code: "invalid_api_key"
89
+ });
90
+ }
91
+ this.apiKey = options.apiKey;
92
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
93
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
94
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
95
+ }
96
+ /**
97
+ * Ask the policy engine whether an agent action is permitted.
98
+ *
99
+ * A "DENY" is **not** thrown — it is returned in
100
+ * `response.decision`. Network errors, invalid API key, rate
101
+ * limits, timeouts, and malformed responses throw
102
+ * {@link AtlaSentError}.
103
+ */
104
+ async evaluate(input) {
105
+ const body = {
106
+ action: input.action,
107
+ agent: input.agent,
108
+ context: input.context ?? {},
109
+ api_key: this.apiKey
110
+ };
111
+ const { body: wire, rateLimit } = await this.post("/v1-evaluate", body);
112
+ if (typeof wire.permitted !== "boolean" || typeof wire.decision_id !== "string") {
113
+ throw new AtlaSentError(
114
+ "Malformed response from /v1-evaluate: missing `permitted` or `decision_id`",
115
+ { code: "bad_response" }
116
+ );
117
+ }
118
+ return {
119
+ decision: wire.permitted ? "ALLOW" : "DENY",
120
+ permitId: wire.decision_id,
121
+ reason: wire.reason ?? "",
122
+ auditHash: wire.audit_hash ?? "",
123
+ timestamp: wire.timestamp ?? "",
124
+ rateLimit
125
+ };
126
+ }
127
+ /**
128
+ * Verify that a previously issued permit is still valid.
129
+ *
130
+ * A `verified: false` response is **not** thrown — inspect the
131
+ * returned object. Only transport / server errors throw.
132
+ */
133
+ async verifyPermit(input) {
134
+ const body = {
135
+ decision_id: input.permitId,
136
+ action: input.action ?? "",
137
+ agent: input.agent ?? "",
138
+ context: input.context ?? {},
139
+ api_key: this.apiKey
140
+ };
141
+ const { body: wire, rateLimit } = await this.post(
142
+ "/v1-verify-permit",
143
+ body
144
+ );
145
+ if (typeof wire.verified !== "boolean") {
146
+ throw new AtlaSentError(
147
+ "Malformed response from /v1-verify-permit: missing `verified`",
148
+ { code: "bad_response" }
149
+ );
150
+ }
151
+ return {
152
+ verified: wire.verified,
153
+ outcome: wire.outcome ?? "",
154
+ permitHash: wire.permit_hash ?? "",
155
+ timestamp: wire.timestamp ?? "",
156
+ rateLimit
157
+ };
158
+ }
159
+ /**
160
+ * Self-introspection: ask the server to describe the API key this
161
+ * client was constructed with. Returns the key's ID, organization,
162
+ * environment, scopes, IP allowlist, per-minute rate limit, the
163
+ * client IP the server observed, and the expiry (if any).
164
+ *
165
+ * Never includes the raw key or its hash. Safe to surface in operator
166
+ * dashboards. Useful for `IP_NOT_ALLOWED` debugging (the server tells
167
+ * you exactly which IP it saw) and for proactive expiry warnings.
168
+ *
169
+ * Throws {@link AtlaSentError} on transport / auth failures — same
170
+ * taxonomy as {@link AtlaSentClient.evaluate}.
171
+ */
172
+ async keySelf() {
173
+ const { body: wire, rateLimit } = await this.get(
174
+ "/v1-api-key-self"
175
+ );
176
+ if (typeof wire.key_id !== "string" || typeof wire.organization_id !== "string") {
177
+ throw new AtlaSentError(
178
+ "Malformed response from /v1-api-key-self: missing `key_id` or `organization_id`",
179
+ { code: "bad_response" }
180
+ );
181
+ }
182
+ return {
183
+ keyId: wire.key_id,
184
+ organizationId: wire.organization_id,
185
+ environment: wire.environment,
186
+ scopes: wire.scopes ?? [],
187
+ allowedCidrs: wire.allowed_cidrs ?? null,
188
+ rateLimitPerMinute: wire.rate_limit_per_minute,
189
+ clientIp: wire.client_ip ?? null,
190
+ expiresAt: wire.expires_at ?? null,
191
+ rateLimit
192
+ };
193
+ }
194
+ /**
195
+ * List persisted audit events for the authenticated organization
196
+ * (`GET /v1-audit/events`). Returned rows are wire-identical with
197
+ * the server: snake_case field names, including `previous_hash` and
198
+ * the `hash` chain, so the response can be fed straight into the
199
+ * offline verifier when paired with a signed export.
200
+ *
201
+ * `query.types` is a comma-joined list (e.g.
202
+ * `"evaluate.allow,policy.updated"`). `cursor` is the opaque
203
+ * `next_cursor` from the prior page. All fields are optional; the
204
+ * server defaults `limit` to 50 (capped at 500).
205
+ *
206
+ * Throws {@link AtlaSentError} on transport / auth failures — same
207
+ * taxonomy as {@link AtlaSentClient.evaluate}.
208
+ */
209
+ async listAuditEvents(query = {}) {
210
+ const { body: wire, rateLimit } = await this.get(
211
+ "/v1-audit/events",
212
+ buildAuditEventsQuery(query)
213
+ );
214
+ if (!Array.isArray(wire.events) || typeof wire.total !== "number") {
215
+ throw new AtlaSentError(
216
+ "Malformed response from /v1-audit/events: missing `events` or `total`",
217
+ { code: "bad_response" }
218
+ );
219
+ }
220
+ return { ...wire, rateLimit };
221
+ }
222
+ /**
223
+ * Request a signed audit export bundle
224
+ * (`POST /v1-audit/exports`). The returned object is wire-identical
225
+ * with the server — `signature`, `chain_head_hash`, `events`, and
226
+ * friends survive untouched so the bundle can be persisted to disk
227
+ * and handed to the offline verifier (`verifyBundle` /
228
+ * `verifyAuditBundle`) without any reshaping.
229
+ *
230
+ * Pass `filter.types`, `filter.from`, `filter.to`, or `filter.actor_id`
231
+ * to narrow the export; omit for a full-org bundle. `rateLimit` is
232
+ * attached alongside the wire fields for observability.
233
+ *
234
+ * Throws {@link AtlaSentError} on transport / auth failures — same
235
+ * taxonomy as {@link AtlaSentClient.evaluate}.
236
+ */
237
+ async createAuditExport(filter = {}) {
238
+ const { body: wire, rateLimit } = await this.post(
239
+ "/v1-audit/exports",
240
+ filter
241
+ );
242
+ if (typeof wire.export_id !== "string" || typeof wire.chain_head_hash !== "string" || !Array.isArray(wire.events)) {
243
+ throw new AtlaSentError(
244
+ "Malformed response from /v1-audit/exports: missing `export_id`, `chain_head_hash`, or `events`",
245
+ { code: "bad_response" }
246
+ );
247
+ }
248
+ return { ...wire, rateLimit };
249
+ }
250
+ async post(path, body) {
251
+ return this.request(path, "POST", body, void 0);
252
+ }
253
+ async get(path, query) {
254
+ return this.request(path, "GET", void 0, query);
255
+ }
256
+ async request(path, method, body, query) {
257
+ const qs = query && Array.from(query).length > 0 ? `?${query.toString()}` : "";
258
+ const url = `${this.baseUrl}${path}${qs}`;
259
+ const requestId = globalThis.crypto.randomUUID();
260
+ const headers = {
261
+ Accept: "application/json",
262
+ Authorization: `Bearer ${this.apiKey}`,
263
+ "User-Agent": `@atlasent/sdk/${SDK_VERSION} node/${process.version}`,
264
+ "X-Request-ID": requestId
265
+ };
266
+ if (method === "POST") headers["Content-Type"] = "application/json";
267
+ const init = {
268
+ method,
269
+ headers,
270
+ signal: AbortSignal.timeout(this.timeoutMs)
271
+ };
272
+ if (method === "POST") init.body = JSON.stringify(body);
273
+ let response;
274
+ try {
275
+ response = await this.fetchImpl(url, init);
276
+ } catch (err) {
277
+ throw mapFetchError(err, requestId);
278
+ }
279
+ if (!response.ok) {
280
+ throw await buildHttpError(response, requestId);
281
+ }
282
+ let parsed;
283
+ try {
284
+ parsed = await response.json();
285
+ } catch (err) {
286
+ throw new AtlaSentError("Invalid JSON response from AtlaSent API", {
287
+ code: "bad_response",
288
+ status: response.status,
289
+ requestId,
290
+ cause: err
291
+ });
292
+ }
293
+ if (parsed === null || typeof parsed !== "object") {
294
+ throw new AtlaSentError("Expected a JSON object from AtlaSent API", {
295
+ code: "bad_response",
296
+ status: response.status,
297
+ requestId
298
+ });
299
+ }
300
+ return {
301
+ body: parsed,
302
+ rateLimit: parseRateLimitHeaders(response.headers)
303
+ };
304
+ }
305
+ };
306
+ function parseRateLimitHeaders(headers) {
307
+ const rawLimit = headers.get("x-ratelimit-limit");
308
+ const rawRemaining = headers.get("x-ratelimit-remaining");
309
+ const rawReset = headers.get("x-ratelimit-reset");
310
+ if (rawLimit === null || rawRemaining === null || rawReset === null) {
311
+ return null;
312
+ }
313
+ const limit = Number(rawLimit);
314
+ const remaining = Number(rawRemaining);
315
+ if (!Number.isFinite(limit) || !Number.isFinite(remaining)) {
316
+ return null;
317
+ }
318
+ const resetAt = parseResetHeader(rawReset);
319
+ if (resetAt === null) {
320
+ return null;
321
+ }
322
+ return { limit, remaining, resetAt };
323
+ }
324
+ function parseResetHeader(raw) {
325
+ const seconds = Number(raw);
326
+ if (Number.isFinite(seconds)) {
327
+ return new Date(seconds * 1e3);
328
+ }
329
+ const ms = Date.parse(raw);
330
+ if (Number.isFinite(ms)) {
331
+ return new Date(ms);
332
+ }
333
+ return null;
334
+ }
335
+ function mapFetchError(err, requestId) {
336
+ if (err instanceof AtlaSentError) return err;
337
+ if (err instanceof DOMException && err.name === "TimeoutError") {
338
+ return new AtlaSentError("Request to AtlaSent API timed out", {
339
+ code: "timeout",
340
+ requestId,
341
+ cause: err
342
+ });
343
+ }
344
+ if (err instanceof Error && err.name === "AbortError") {
345
+ return new AtlaSentError("Request to AtlaSent API timed out", {
346
+ code: "timeout",
347
+ requestId,
348
+ cause: err
349
+ });
350
+ }
351
+ const message = err instanceof Error ? err.message : "network error";
352
+ return new AtlaSentError(`Failed to reach AtlaSent API: ${message}`, {
353
+ code: "network",
354
+ requestId,
355
+ cause: err
356
+ });
357
+ }
358
+ async function buildHttpError(response, requestId) {
359
+ const status = response.status;
360
+ const classified = await classifyHttpStatus(response);
361
+ const init = {
362
+ status,
363
+ code: classified.code,
364
+ requestId
365
+ };
366
+ if (classified.retryAfterMs !== void 0) {
367
+ init.retryAfterMs = classified.retryAfterMs;
368
+ }
369
+ return new AtlaSentError(classified.message, init);
370
+ }
371
+ async function classifyHttpStatus(response) {
372
+ const status = response.status;
373
+ const serverMessage = await readServerMessage(response);
374
+ if (status === 401) {
375
+ return {
376
+ message: serverMessage ?? "Invalid API key",
377
+ code: "invalid_api_key",
378
+ retryAfterMs: void 0
379
+ };
380
+ }
381
+ if (status === 403) {
382
+ return {
383
+ message: serverMessage ?? "Access forbidden \u2014 check your API key permissions",
384
+ code: "forbidden",
385
+ retryAfterMs: void 0
386
+ };
387
+ }
388
+ if (status === 429) {
389
+ return {
390
+ message: serverMessage ?? "Rate limited by AtlaSent API",
391
+ code: "rate_limited",
392
+ retryAfterMs: parseRetryAfter(response.headers.get("retry-after"))
393
+ };
394
+ }
395
+ if (status >= 500) {
396
+ return {
397
+ message: serverMessage ?? `AtlaSent API returned HTTP ${status}`,
398
+ code: "server_error",
399
+ retryAfterMs: void 0
400
+ };
401
+ }
402
+ return {
403
+ message: serverMessage ?? `AtlaSent API returned HTTP ${status}`,
404
+ code: "bad_request",
405
+ retryAfterMs: void 0
406
+ };
407
+ }
408
+ async function readServerMessage(response) {
409
+ try {
410
+ const text = await response.text();
411
+ if (!text) return null;
412
+ try {
413
+ const parsed = JSON.parse(text);
414
+ if (parsed && typeof parsed === "object") {
415
+ const msg = parsed.message;
416
+ const reason = parsed.reason;
417
+ if (typeof msg === "string" && msg.length > 0) return msg;
418
+ if (typeof reason === "string" && reason.length > 0) return reason;
419
+ }
420
+ } catch {
421
+ }
422
+ return text.length > 500 ? `${text.slice(0, 500)}\u2026` : text;
423
+ } catch {
424
+ return null;
425
+ }
426
+ }
427
+ function buildAuditEventsQuery(query) {
428
+ const params = new URLSearchParams();
429
+ if (query.types !== void 0 && query.types !== "") {
430
+ params.set("types", query.types);
431
+ }
432
+ if (query.actor_id !== void 0 && query.actor_id !== "") {
433
+ params.set("actor_id", query.actor_id);
434
+ }
435
+ if (query.from !== void 0 && query.from !== "") {
436
+ params.set("from", query.from);
437
+ }
438
+ if (query.to !== void 0 && query.to !== "") {
439
+ params.set("to", query.to);
440
+ }
441
+ if (query.limit !== void 0) {
442
+ params.set("limit", String(query.limit));
443
+ }
444
+ if (query.cursor !== void 0 && query.cursor !== "") {
445
+ params.set("cursor", query.cursor);
446
+ }
447
+ return params;
448
+ }
449
+ function parseRetryAfter(raw) {
450
+ if (!raw) return void 0;
451
+ const seconds = Number(raw);
452
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
453
+ const date = Date.parse(raw);
454
+ if (Number.isFinite(date)) {
455
+ const delta = date - Date.now();
456
+ return delta > 0 ? delta : 0;
457
+ }
458
+ return void 0;
459
+ }
460
+
461
+ // src/protect.ts
462
+ var sharedClient = null;
463
+ var overrides = {};
464
+ function getClient() {
465
+ if (sharedClient) return sharedClient;
466
+ const apiKey = overrides.apiKey ?? process.env.ATLASENT_API_KEY;
467
+ if (!apiKey) {
468
+ throw new AtlaSentError(
469
+ "AtlaSent is not configured. Set ATLASENT_API_KEY in the environment, or call atlasent.configure({ apiKey }).",
470
+ { code: "invalid_api_key" }
471
+ );
472
+ }
473
+ const options = { apiKey };
474
+ if (overrides.baseUrl !== void 0) options.baseUrl = overrides.baseUrl;
475
+ if (overrides.timeoutMs !== void 0) options.timeoutMs = overrides.timeoutMs;
476
+ if (overrides.fetch !== void 0) options.fetch = overrides.fetch;
477
+ sharedClient = new AtlaSentClient(options);
478
+ return sharedClient;
479
+ }
480
+ function wireDecisionToDenied(serverDecision) {
481
+ const lower = serverDecision.toLowerCase();
482
+ if (lower === "hold" || lower === "escalate") return lower;
483
+ return "deny";
484
+ }
485
+ async function protect(request) {
486
+ const client = getClient();
487
+ const evaluation = await client.evaluate(request);
488
+ if (evaluation.decision !== "ALLOW") {
489
+ throw new AtlaSentDeniedError({
490
+ decision: wireDecisionToDenied(evaluation.decision),
491
+ evaluationId: evaluation.permitId,
492
+ reason: evaluation.reason,
493
+ auditHash: evaluation.auditHash
494
+ });
495
+ }
496
+ const verifyRequest = {
497
+ permitId: evaluation.permitId,
498
+ agent: request.agent,
499
+ action: request.action
500
+ };
501
+ if (request.context !== void 0) verifyRequest.context = request.context;
502
+ const verification = await client.verifyPermit(verifyRequest);
503
+ if (!verification.verified) {
504
+ throw new AtlaSentDeniedError({
505
+ decision: "deny",
506
+ evaluationId: evaluation.permitId,
507
+ reason: `Permit failed verification (${verification.outcome})`,
508
+ auditHash: evaluation.auditHash
509
+ });
510
+ }
511
+ return {
512
+ permitId: evaluation.permitId,
513
+ permitHash: verification.permitHash,
514
+ auditHash: evaluation.auditHash,
515
+ reason: evaluation.reason,
516
+ timestamp: verification.timestamp
517
+ };
518
+ }
519
+
520
+ // src/hono.ts
521
+ var DEFAULT_CONTEXT_KEY = "atlasent";
522
+ async function resolve(value, c) {
523
+ return typeof value === "function" ? await value(c) : value;
524
+ }
525
+ function atlaSentGuard(options) {
526
+ const contextKey = options.key ?? DEFAULT_CONTEXT_KEY;
527
+ return async (c, next) => {
528
+ const [agent, action, ctx] = await Promise.all([
529
+ resolve(options.agent, c),
530
+ resolve(options.action, c),
531
+ options.context ? options.context(c) : Promise.resolve(void 0)
532
+ ]);
533
+ const request = { agent, action };
534
+ if (ctx !== void 0) request.context = ctx;
535
+ const permit = await protect(request);
536
+ c.set(contextKey, permit);
537
+ await next();
538
+ };
539
+ }
540
+ function defaultRenderDeny(err) {
541
+ const body = {
542
+ error: "denied",
543
+ decision: err.decision,
544
+ evaluationId: err.evaluationId
545
+ };
546
+ if (err.reason !== void 0) body.reason = err.reason;
547
+ if (err.requestId !== void 0) body.requestId = err.requestId;
548
+ return body;
549
+ }
550
+ function defaultRenderError(err) {
551
+ const body = {
552
+ error: "unavailable",
553
+ code: err.code ?? "unknown"
554
+ };
555
+ if (err.requestId !== void 0) body.requestId = err.requestId;
556
+ return body;
557
+ }
558
+ function atlaSentErrorHandler(options = {}) {
559
+ const denyStatus = options.denyStatus ?? 403;
560
+ const errorStatus = options.errorStatus ?? 503;
561
+ const renderDeny = options.renderDeny ?? defaultRenderDeny;
562
+ const renderError = options.renderError ?? defaultRenderError;
563
+ return (err, c) => {
564
+ if (err instanceof AtlaSentDeniedError) {
565
+ return c.json(renderDeny(err), denyStatus);
566
+ }
567
+ if (err instanceof AtlaSentError) {
568
+ return c.json(renderError(err), errorStatus);
569
+ }
570
+ throw err;
571
+ };
572
+ }
573
+ // Annotate the CommonJS export names for ESM import in node:
574
+ 0 && (module.exports = {
575
+ AtlaSentDeniedError,
576
+ AtlaSentError,
577
+ atlaSentErrorHandler,
578
+ atlaSentGuard
579
+ });
580
+ //# sourceMappingURL=hono.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hono.ts","../src/errors.ts","../src/client.ts","../src/protect.ts"],"sourcesContent":["/**\n * Hono middleware for AtlaSent execution-time authorization.\n *\n * ```ts\n * import { Hono } from \"hono\";\n * import { atlaSentGuard, atlaSentErrorHandler } from \"@atlasent/sdk/hono\";\n *\n * const app = new Hono();\n *\n * // One-line drop-in protection for a sensitive route.\n * app.post(\n * \"/deploy\",\n * atlaSentGuard({\n * action: \"deploy_to_production\",\n * agent: (c) => c.req.header(\"x-agent-id\") ?? \"anonymous\",\n * context: async (c) => ({ commit: (await c.req.json()).commit }),\n * }),\n * (c) => {\n * // If we got here, AtlaSent allowed the action end-to-end.\n * const permit = c.get(\"atlasent\");\n * return c.json({ ok: true, permitId: permit.permitId });\n * },\n * );\n *\n * // One place to map AtlaSent errors to HTTP responses.\n * app.onError(atlaSentErrorHandler());\n * ```\n *\n * The guard calls {@link protect} under the hood — same fail-closed\n * semantics. On allow, it stashes the {@link Permit} on the Hono\n * context (default key: `\"atlasent\"`) and calls `next()`. On anything\n * else, it **throws**: {@link AtlaSentDeniedError} for policy denials\n * and verification failures, {@link AtlaSentError} for transport /\n * auth / server failures. Attach {@link atlaSentErrorHandler} via\n * `app.onError(...)` to turn those into HTTP responses once, at the\n * app level, rather than wrap every guarded route.\n *\n * `hono` is an optional peer dependency — this module is only pulled\n * in when you import from the `@atlasent/sdk/hono` subpath.\n */\n\nimport type { Context, ErrorHandler, MiddlewareHandler } from \"hono\";\n\nimport {\n AtlaSentDeniedError,\n AtlaSentError,\n} from \"./errors.js\";\nimport { protect, type Permit, type ProtectRequest } from \"./protect.js\";\n\n/** Resolver: a literal string, or a function deriving one from the request. */\ntype Resolver<T extends string | Record<string, unknown>> =\n | T\n | ((c: Context) => T | Promise<T>);\n\n/** Options for {@link atlaSentGuard}. */\nexport interface AtlaSentGuardOptions {\n /**\n * Action being authorized (e.g. `\"deploy_to_production\"`). A string\n * fixes the action; a function lets you derive it per-request (e.g.\n * from route params or the HTTP verb).\n */\n action: Resolver<string>;\n /**\n * Agent identifier. A string fixes the caller; a function lets you\n * read it from an auth header, JWT claim, session, etc.\n */\n agent: Resolver<string>;\n /**\n * Build the policy context dict for the decision. Defaults to `{}`.\n * Receives the Hono context so you can reach into headers, body,\n * route params, or previously-set middleware values.\n */\n context?: (c: Context) => Record<string, unknown> | Promise<Record<string, unknown>>;\n /**\n * Key used to stash the resulting {@link Permit} on the Hono\n * context via `c.set(...)`. Callers read it back with\n * `c.get(options.key)`. Default: `\"atlasent\"`.\n */\n key?: string;\n}\n\nconst DEFAULT_CONTEXT_KEY = \"atlasent\";\n\nasync function resolve<T>(\n value: T | ((c: Context) => T | Promise<T>),\n c: Context,\n): Promise<T> {\n return typeof value === \"function\"\n ? await (value as (c: Context) => T | Promise<T>)(c)\n : value;\n}\n\n/**\n * Hono middleware that calls {@link protect} before the wrapped\n * handler runs. On allow, stores the {@link Permit} on the context;\n * on deny or error, throws. Use {@link atlaSentErrorHandler} with\n * `app.onError(...)` to turn those throws into HTTP responses.\n */\nexport function atlaSentGuard(\n options: AtlaSentGuardOptions,\n): MiddlewareHandler {\n const contextKey = options.key ?? DEFAULT_CONTEXT_KEY;\n return async (c, next) => {\n const [agent, action, ctx] = await Promise.all([\n resolve(options.agent, c),\n resolve(options.action, c),\n options.context ? options.context(c) : Promise.resolve(undefined),\n ]);\n\n const request: ProtectRequest = { agent, action };\n if (ctx !== undefined) request.context = ctx;\n\n const permit: Permit = await protect(request);\n c.set(contextKey, permit);\n await next();\n };\n}\n\n/** Options for {@link atlaSentErrorHandler}. */\nexport interface AtlaSentErrorHandlerOptions {\n /** HTTP status returned on policy denial. Default: 403. */\n denyStatus?: 401 | 403 | 409 | 422;\n /** HTTP status returned on transport / auth / server failure. Default: 503. */\n errorStatus?: 502 | 503 | 500;\n /**\n * Hook to customize the JSON body returned on denial. Receives the\n * error; must return a JSON-serializable object. Defaults to a\n * minimal `{ error, decision, evaluationId, reason?, requestId? }`.\n */\n renderDeny?: (err: AtlaSentDeniedError) => Record<string, unknown>;\n /** Hook for transport/auth/server errors. Defaults to `{ error, code, requestId? }`. */\n renderError?: (err: AtlaSentError) => Record<string, unknown>;\n}\n\nfunction defaultRenderDeny(err: AtlaSentDeniedError): Record<string, unknown> {\n const body: Record<string, unknown> = {\n error: \"denied\",\n decision: err.decision,\n evaluationId: err.evaluationId,\n };\n if (err.reason !== undefined) body.reason = err.reason;\n if (err.requestId !== undefined) body.requestId = err.requestId;\n return body;\n}\n\nfunction defaultRenderError(err: AtlaSentError): Record<string, unknown> {\n const body: Record<string, unknown> = {\n error: \"unavailable\",\n code: err.code ?? \"unknown\",\n };\n if (err.requestId !== undefined) body.requestId = err.requestId;\n return body;\n}\n\n/**\n * Hono error handler that converts AtlaSent exceptions into\n * appropriate HTTP responses. Install once at the app level:\n *\n * ```ts\n * app.onError(atlaSentErrorHandler());\n * ```\n *\n * Non-AtlaSent errors re-throw so other `onError` chains (or Hono's\n * default 500 handler) still see them.\n */\nexport function atlaSentErrorHandler(\n options: AtlaSentErrorHandlerOptions = {},\n): ErrorHandler {\n const denyStatus = options.denyStatus ?? 403;\n const errorStatus = options.errorStatus ?? 503;\n const renderDeny = options.renderDeny ?? defaultRenderDeny;\n const renderError = options.renderError ?? defaultRenderError;\n\n return (err, c) => {\n if (err instanceof AtlaSentDeniedError) {\n return c.json(renderDeny(err), denyStatus);\n }\n if (err instanceof AtlaSentError) {\n return c.json(renderError(err), errorStatus);\n }\n throw err;\n };\n}\n\n// Re-export the types callers need in one place so the subpath is\n// self-contained.\nexport type { Permit, ProtectRequest } from \"./protect.js\";\nexport { AtlaSentDeniedError, AtlaSentError } from \"./errors.js\";\n","/**\n * Error types for the AtlaSent TypeScript SDK.\n *\n * The SDK follows a fail-closed design: a clean policy DENY is\n * returned as `EvaluateResponse.decision === \"DENY\"` (not thrown),\n * but any failure to confirm authorization — network, timeout,\n * bad response, invalid key, rate limit — throws an\n * {@link AtlaSentError}.\n */\n\n/** Discriminator for {@link AtlaSentError.code}. */\nexport type AtlaSentErrorCode =\n | \"invalid_api_key\"\n | \"forbidden\"\n | \"rate_limited\"\n | \"timeout\"\n | \"network\"\n | \"bad_response\"\n | \"bad_request\"\n | \"server_error\";\n\n/** Initialization options for {@link AtlaSentError}. */\nexport interface AtlaSentErrorInit {\n status?: number;\n code?: AtlaSentErrorCode;\n requestId?: string;\n retryAfterMs?: number;\n cause?: unknown;\n}\n\n/**\n * The only error type this SDK throws.\n *\n * Flat top-level properties mirror the convention used by Stripe,\n * Octokit, and Supabase. `cause` is forwarded to the standard\n * ES2022 `Error` constructor.\n */\nexport class AtlaSentError extends Error {\n // Subclasses override to their own literal (e.g. \"AtlaSentDeniedError\");\n // keep this assignable rather than pinned to a single literal.\n override name: string = \"AtlaSentError\";\n\n /** HTTP status code, when the error originated from an API response. */\n readonly status: number | undefined;\n /** Coarse category — useful for `switch` statements at call sites. */\n readonly code: AtlaSentErrorCode | undefined;\n /** Correlation ID echoed from the `X-Request-ID` header the SDK sent. */\n readonly requestId: string | undefined;\n /** Parsed `Retry-After` header value, in milliseconds. Only set for 429. */\n readonly retryAfterMs: number | undefined;\n\n constructor(message: string, init: AtlaSentErrorInit = {}) {\n super(\n message,\n init.cause !== undefined ? { cause: init.cause } : undefined,\n );\n this.status = init.status;\n this.code = init.code;\n this.requestId = init.requestId;\n this.retryAfterMs = init.retryAfterMs;\n }\n}\n\n/**\n * Outcome of a denied decision.\n *\n * `\"deny\"` is what the current `/v1-evaluate` API returns. `\"hold\"`\n * and `\"escalate\"` are reserved for forthcoming API decisions that\n * put a permit into a pending state requiring human review; the\n * union is declared now so call sites can `switch` exhaustively\n * from the start and adopt new decisions without a breaking change.\n */\nexport type AtlaSentDecision = \"deny\" | \"hold\" | \"escalate\";\n\n/** Initialization options for {@link AtlaSentDeniedError}. */\nexport interface AtlaSentDeniedErrorInit {\n decision: AtlaSentDecision;\n evaluationId: string;\n reason?: string;\n requestId?: string;\n auditHash?: string;\n}\n\n/**\n * Thrown by {@link atlasent.protect} when the policy engine refuses\n * the action, or when a permit fails end-to-end verification.\n *\n * This is the **fail-closed boundary** of the SDK: every code path\n * that short-circuits an action because authorization was not\n * confirmed raises an `AtlaSentDeniedError`. Callers cannot silently\n * proceed on a denial by forgetting to branch on a return value.\n *\n * Extends {@link AtlaSentError} so `instanceof AtlaSentError`\n * catches denials as part of the SDK's single exception family;\n * use `instanceof AtlaSentDeniedError` to distinguish a policy\n * denial from a transport/auth error.\n */\nexport class AtlaSentDeniedError extends AtlaSentError {\n override name: string = \"AtlaSentDeniedError\";\n\n /** Policy decision — `\"deny\"` today; `\"hold\"` / `\"escalate\"` reserved. */\n readonly decision: AtlaSentDecision;\n /** Opaque permit/decision id from `/v1-evaluate`. */\n readonly evaluationId: string;\n /** Human-readable explanation from the policy engine, if provided. */\n readonly reason: string | undefined;\n /** Hash-chained audit-trail entry associated with the decision. */\n readonly auditHash: string | undefined;\n\n constructor(init: AtlaSentDeniedErrorInit) {\n const msg = init.reason\n ? `AtlaSent ${init.decision}: ${init.reason}`\n : `AtlaSent ${init.decision}`;\n const errInit: AtlaSentErrorInit = { status: 200 };\n if (init.requestId !== undefined) errInit.requestId = init.requestId;\n super(msg, errInit);\n this.decision = init.decision;\n this.evaluationId = init.evaluationId;\n this.reason = init.reason;\n this.auditHash = init.auditHash;\n }\n}\n","/**\n * AtlaSent HTTP client.\n *\n * Two public methods, both backed by native `fetch`:\n * - {@link AtlaSentClient.evaluate} → POST {baseUrl}/v1-evaluate\n * - {@link AtlaSentClient.verifyPermit} → POST {baseUrl}/v1-verify-permit\n *\n * Fail-closed: a clean policy DENY is returned (not thrown), but\n * network, timeout, bad response, 4xx/5xx, and rate-limit conditions\n * all throw {@link AtlaSentError}.\n */\n\nimport type {\n AuditEventsPage,\n AuditEventsQuery,\n AuditExport,\n} from \"./audit.js\";\nimport {\n AtlaSentError,\n type AtlaSentErrorCode,\n type AtlaSentErrorInit,\n} from \"./errors.js\";\nimport type {\n ApiKeySelfResponse,\n AtlaSentClientOptions,\n AuditEventsResult,\n AuditExportRequest,\n AuditExportResult,\n EvaluateRequest,\n EvaluateResponse,\n RateLimitState,\n VerifyPermitRequest,\n VerifyPermitResponse,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.atlasent.io\";\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst SDK_VERSION = \"0.1.0\";\n\n/** Raw JSON shape received from `POST /v1-evaluate`. */\ninterface EvaluateWire {\n permitted: boolean;\n decision_id: string;\n reason?: string;\n audit_hash?: string;\n timestamp?: string;\n}\n\n/** Raw JSON shape received from `GET /v1-api-key-self`. */\ninterface ApiKeySelfWire {\n key_id: string;\n organization_id: string;\n environment: string;\n scopes?: string[];\n allowed_cidrs?: string[] | null;\n rate_limit_per_minute: number;\n client_ip?: string | null;\n expires_at?: string | null;\n}\n\n/** Raw JSON shape received from `POST /v1-verify-permit`. */\ninterface VerifyPermitWire {\n verified: boolean;\n outcome?: string;\n permit_hash?: string;\n timestamp?: string;\n}\n\nexport class AtlaSentClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: AtlaSentClientOptions) {\n if (!options.apiKey || typeof options.apiKey !== \"string\") {\n throw new AtlaSentError(\"apiKey is required\", {\n code: \"invalid_api_key\",\n });\n }\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n /**\n * Ask the policy engine whether an agent action is permitted.\n *\n * A \"DENY\" is **not** thrown — it is returned in\n * `response.decision`. Network errors, invalid API key, rate\n * limits, timeouts, and malformed responses throw\n * {@link AtlaSentError}.\n */\n async evaluate(input: EvaluateRequest): Promise<EvaluateResponse> {\n const body = {\n action: input.action,\n agent: input.agent,\n context: input.context ?? {},\n api_key: this.apiKey,\n };\n const { body: wire, rateLimit } = await this.post<EvaluateWire>(\"/v1-evaluate\", body);\n\n if (typeof wire.permitted !== \"boolean\" || typeof wire.decision_id !== \"string\") {\n throw new AtlaSentError(\n \"Malformed response from /v1-evaluate: missing `permitted` or `decision_id`\",\n { code: \"bad_response\" },\n );\n }\n\n return {\n decision: wire.permitted ? \"ALLOW\" : \"DENY\",\n permitId: wire.decision_id,\n reason: wire.reason ?? \"\",\n auditHash: wire.audit_hash ?? \"\",\n timestamp: wire.timestamp ?? \"\",\n rateLimit,\n };\n }\n\n /**\n * Verify that a previously issued permit is still valid.\n *\n * A `verified: false` response is **not** thrown — inspect the\n * returned object. Only transport / server errors throw.\n */\n async verifyPermit(\n input: VerifyPermitRequest,\n ): Promise<VerifyPermitResponse> {\n const body = {\n decision_id: input.permitId,\n action: input.action ?? \"\",\n agent: input.agent ?? \"\",\n context: input.context ?? {},\n api_key: this.apiKey,\n };\n const { body: wire, rateLimit } = await this.post<VerifyPermitWire>(\n \"/v1-verify-permit\",\n body,\n );\n\n if (typeof wire.verified !== \"boolean\") {\n throw new AtlaSentError(\n \"Malformed response from /v1-verify-permit: missing `verified`\",\n { code: \"bad_response\" },\n );\n }\n\n return {\n verified: wire.verified,\n outcome: wire.outcome ?? \"\",\n permitHash: wire.permit_hash ?? \"\",\n timestamp: wire.timestamp ?? \"\",\n rateLimit,\n };\n }\n\n /**\n * Self-introspection: ask the server to describe the API key this\n * client was constructed with. Returns the key's ID, organization,\n * environment, scopes, IP allowlist, per-minute rate limit, the\n * client IP the server observed, and the expiry (if any).\n *\n * Never includes the raw key or its hash. Safe to surface in operator\n * dashboards. Useful for `IP_NOT_ALLOWED` debugging (the server tells\n * you exactly which IP it saw) and for proactive expiry warnings.\n *\n * Throws {@link AtlaSentError} on transport / auth failures — same\n * taxonomy as {@link AtlaSentClient.evaluate}.\n */\n async keySelf(): Promise<ApiKeySelfResponse> {\n const { body: wire, rateLimit } = await this.get<ApiKeySelfWire>(\n \"/v1-api-key-self\",\n );\n\n if (typeof wire.key_id !== \"string\" || typeof wire.organization_id !== \"string\") {\n throw new AtlaSentError(\n \"Malformed response from /v1-api-key-self: missing `key_id` or `organization_id`\",\n { code: \"bad_response\" },\n );\n }\n\n return {\n keyId: wire.key_id,\n organizationId: wire.organization_id,\n environment: wire.environment,\n scopes: wire.scopes ?? [],\n allowedCidrs: wire.allowed_cidrs ?? null,\n rateLimitPerMinute: wire.rate_limit_per_minute,\n clientIp: wire.client_ip ?? null,\n expiresAt: wire.expires_at ?? null,\n rateLimit,\n };\n }\n\n /**\n * List persisted audit events for the authenticated organization\n * (`GET /v1-audit/events`). Returned rows are wire-identical with\n * the server: snake_case field names, including `previous_hash` and\n * the `hash` chain, so the response can be fed straight into the\n * offline verifier when paired with a signed export.\n *\n * `query.types` is a comma-joined list (e.g.\n * `\"evaluate.allow,policy.updated\"`). `cursor` is the opaque\n * `next_cursor` from the prior page. All fields are optional; the\n * server defaults `limit` to 50 (capped at 500).\n *\n * Throws {@link AtlaSentError} on transport / auth failures — same\n * taxonomy as {@link AtlaSentClient.evaluate}.\n */\n async listAuditEvents(\n query: AuditEventsQuery = {},\n ): Promise<AuditEventsResult> {\n const { body: wire, rateLimit } = await this.get<AuditEventsPage>(\n \"/v1-audit/events\",\n buildAuditEventsQuery(query),\n );\n\n if (!Array.isArray(wire.events) || typeof wire.total !== \"number\") {\n throw new AtlaSentError(\n \"Malformed response from /v1-audit/events: missing `events` or `total`\",\n { code: \"bad_response\" },\n );\n }\n\n return { ...wire, rateLimit };\n }\n\n /**\n * Request a signed audit export bundle\n * (`POST /v1-audit/exports`). The returned object is wire-identical\n * with the server — `signature`, `chain_head_hash`, `events`, and\n * friends survive untouched so the bundle can be persisted to disk\n * and handed to the offline verifier (`verifyBundle` /\n * `verifyAuditBundle`) without any reshaping.\n *\n * Pass `filter.types`, `filter.from`, `filter.to`, or `filter.actor_id`\n * to narrow the export; omit for a full-org bundle. `rateLimit` is\n * attached alongside the wire fields for observability.\n *\n * Throws {@link AtlaSentError} on transport / auth failures — same\n * taxonomy as {@link AtlaSentClient.evaluate}.\n */\n async createAuditExport(\n filter: AuditExportRequest = {},\n ): Promise<AuditExportResult> {\n const { body: wire, rateLimit } = await this.post<AuditExport>(\n \"/v1-audit/exports\",\n filter,\n );\n\n if (\n typeof wire.export_id !== \"string\" ||\n typeof wire.chain_head_hash !== \"string\" ||\n !Array.isArray(wire.events)\n ) {\n throw new AtlaSentError(\n \"Malformed response from /v1-audit/exports: missing `export_id`, `chain_head_hash`, or `events`\",\n { code: \"bad_response\" },\n );\n }\n\n return { ...wire, rateLimit };\n }\n\n private async post<T>(\n path: string,\n body: unknown,\n ): Promise<{ body: T; rateLimit: RateLimitState | null }> {\n return this.request<T>(path, \"POST\", body, undefined);\n }\n\n private async get<T>(\n path: string,\n query?: URLSearchParams,\n ): Promise<{ body: T; rateLimit: RateLimitState | null }> {\n return this.request<T>(path, \"GET\", undefined, query);\n }\n\n private async request<T>(\n path: string,\n method: \"GET\" | \"POST\",\n body: unknown,\n query: URLSearchParams | undefined,\n ): Promise<{ body: T; rateLimit: RateLimitState | null }> {\n const qs = query && Array.from(query).length > 0 ? `?${query.toString()}` : \"\";\n const url = `${this.baseUrl}${path}${qs}`;\n const requestId = globalThis.crypto.randomUUID();\n\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n \"User-Agent\": `@atlasent/sdk/${SDK_VERSION} node/${process.version}`,\n \"X-Request-ID\": requestId,\n };\n if (method === \"POST\") headers[\"Content-Type\"] = \"application/json\";\n\n const init: RequestInit = {\n method,\n headers,\n signal: AbortSignal.timeout(this.timeoutMs),\n };\n if (method === \"POST\") init.body = JSON.stringify(body);\n\n let response: Response;\n try {\n response = await this.fetchImpl(url, init);\n } catch (err) {\n throw mapFetchError(err, requestId);\n }\n\n if (!response.ok) {\n throw await buildHttpError(response, requestId);\n }\n\n let parsed: unknown;\n try {\n parsed = await response.json();\n } catch (err) {\n throw new AtlaSentError(\"Invalid JSON response from AtlaSent API\", {\n code: \"bad_response\",\n status: response.status,\n requestId,\n cause: err,\n });\n }\n\n if (parsed === null || typeof parsed !== \"object\") {\n throw new AtlaSentError(\"Expected a JSON object from AtlaSent API\", {\n code: \"bad_response\",\n status: response.status,\n requestId,\n });\n }\n\n return {\n body: parsed as T,\n rateLimit: parseRateLimitHeaders(response.headers),\n };\n }\n}\n\n/**\n * Parse the server's `X-RateLimit-*` header triple into a typed\n * {@link RateLimitState}. Returns `null` when any of the three headers\n * is missing or unparseable — callers treat that as \"the server didn't\n * emit rate-limit state\" rather than \"the window is empty\".\n *\n * `X-RateLimit-Reset` is accepted as either unix-seconds (what the\n * AtlaSent edge functions emit today) or an ISO 8601 timestamp.\n */\nfunction parseRateLimitHeaders(headers: Headers): RateLimitState | null {\n const rawLimit = headers.get(\"x-ratelimit-limit\");\n const rawRemaining = headers.get(\"x-ratelimit-remaining\");\n const rawReset = headers.get(\"x-ratelimit-reset\");\n if (rawLimit === null || rawRemaining === null || rawReset === null) {\n return null;\n }\n const limit = Number(rawLimit);\n const remaining = Number(rawRemaining);\n if (!Number.isFinite(limit) || !Number.isFinite(remaining)) {\n return null;\n }\n const resetAt = parseResetHeader(rawReset);\n if (resetAt === null) {\n return null;\n }\n return { limit, remaining, resetAt };\n}\n\nfunction parseResetHeader(raw: string): Date | null {\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) {\n // Standard shape: unix seconds. 10-digit values are in the valid\n // range ~2001–2286 so this heuristic won't confuse a tiny\n // `remaining`-like number for an epoch.\n return new Date(seconds * 1000);\n }\n const ms = Date.parse(raw);\n if (Number.isFinite(ms)) {\n return new Date(ms);\n }\n return null;\n}\n\nfunction mapFetchError(err: unknown, requestId: string): AtlaSentError {\n if (err instanceof AtlaSentError) return err;\n if (err instanceof DOMException && err.name === \"TimeoutError\") {\n return new AtlaSentError(\"Request to AtlaSent API timed out\", {\n code: \"timeout\",\n requestId,\n cause: err,\n });\n }\n if (err instanceof Error && err.name === \"AbortError\") {\n return new AtlaSentError(\"Request to AtlaSent API timed out\", {\n code: \"timeout\",\n requestId,\n cause: err,\n });\n }\n const message = err instanceof Error ? err.message : \"network error\";\n return new AtlaSentError(`Failed to reach AtlaSent API: ${message}`, {\n code: \"network\",\n requestId,\n cause: err,\n });\n}\n\nasync function buildHttpError(\n response: Response,\n requestId: string,\n): Promise<AtlaSentError> {\n const status = response.status;\n const classified = await classifyHttpStatus(response);\n const init: AtlaSentErrorInit = {\n status,\n code: classified.code,\n requestId,\n };\n if (classified.retryAfterMs !== undefined) {\n init.retryAfterMs = classified.retryAfterMs;\n }\n return new AtlaSentError(classified.message, init);\n}\n\nasync function classifyHttpStatus(response: Response): Promise<{\n message: string;\n code: AtlaSentErrorCode;\n retryAfterMs: number | undefined;\n}> {\n const status = response.status;\n const serverMessage = await readServerMessage(response);\n\n if (status === 401) {\n return {\n message: serverMessage ?? \"Invalid API key\",\n code: \"invalid_api_key\",\n retryAfterMs: undefined,\n };\n }\n if (status === 403) {\n return {\n message:\n serverMessage ?? \"Access forbidden — check your API key permissions\",\n code: \"forbidden\",\n retryAfterMs: undefined,\n };\n }\n if (status === 429) {\n return {\n message: serverMessage ?? \"Rate limited by AtlaSent API\",\n code: \"rate_limited\",\n retryAfterMs: parseRetryAfter(response.headers.get(\"retry-after\")),\n };\n }\n if (status >= 500) {\n return {\n message: serverMessage ?? `AtlaSent API returned HTTP ${status}`,\n code: \"server_error\",\n retryAfterMs: undefined,\n };\n }\n return {\n message: serverMessage ?? `AtlaSent API returned HTTP ${status}`,\n code: \"bad_request\",\n retryAfterMs: undefined,\n };\n}\n\nasync function readServerMessage(response: Response): Promise<string | null> {\n try {\n const text = await response.text();\n if (!text) return null;\n try {\n const parsed = JSON.parse(text);\n if (parsed && typeof parsed === \"object\") {\n const msg = (parsed as Record<string, unknown>).message;\n const reason = (parsed as Record<string, unknown>).reason;\n if (typeof msg === \"string\" && msg.length > 0) return msg;\n if (typeof reason === \"string\" && reason.length > 0) return reason;\n }\n } catch {\n // Fall through — treat as plain text.\n }\n return text.length > 500 ? `${text.slice(0, 500)}…` : text;\n } catch {\n return null;\n }\n}\n\n/**\n * Translate an {@link AuditEventsQuery} into `URLSearchParams`. The\n * server expects snake_case keys (`actor_id`) and accepts\n * comma-joined values for `types`; numeric `limit` serializes via\n * `String(n)`. Undefined / empty fields are dropped so the query\n * string stays minimal.\n */\nfunction buildAuditEventsQuery(query: AuditEventsQuery): URLSearchParams {\n const params = new URLSearchParams();\n if (query.types !== undefined && query.types !== \"\") {\n params.set(\"types\", query.types);\n }\n if (query.actor_id !== undefined && query.actor_id !== \"\") {\n params.set(\"actor_id\", query.actor_id);\n }\n if (query.from !== undefined && query.from !== \"\") {\n params.set(\"from\", query.from);\n }\n if (query.to !== undefined && query.to !== \"\") {\n params.set(\"to\", query.to);\n }\n if (query.limit !== undefined) {\n params.set(\"limit\", String(query.limit));\n }\n if (query.cursor !== undefined && query.cursor !== \"\") {\n params.set(\"cursor\", query.cursor);\n }\n return params;\n}\n\nfunction parseRetryAfter(raw: string | null): number | undefined {\n if (!raw) return undefined;\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(raw);\n if (Number.isFinite(date)) {\n const delta = date - Date.now();\n return delta > 0 ? delta : 0;\n }\n return undefined;\n}\n","/**\n * `atlasent.protect(...)` — the one-call, fail-closed execution-time\n * authorization boundary.\n *\n * ```ts\n * import atlasent from \"@atlasent/sdk\";\n *\n * const permit = await atlasent.protect({\n * agent: \"deploy-bot\",\n * action: \"deploy_to_production\",\n * context: { commit, approver },\n * });\n * // …run the action. If we got here, AtlaSent authorized it\n * // end-to-end (evaluate + verifyPermit).\n * ```\n *\n * Unlike {@link AtlaSentClient.evaluate}, `protect` never returns a\n * denied decision. On deny, it throws {@link AtlaSentDeniedError};\n * on transport / auth / server failure it throws\n * {@link AtlaSentError}. The action cannot execute unless a valid\n * {@link Permit} is returned — this is the SDK's category boundary,\n * not a helper.\n */\n\nimport { AtlaSentClient } from \"./client.js\";\nimport {\n AtlaSentDeniedError,\n AtlaSentError,\n type AtlaSentDecision,\n} from \"./errors.js\";\nimport type { AtlaSentClientOptions } from \"./types.js\";\n\n/** Input to {@link protect}. Same shape as `EvaluateRequest`. */\nexport interface ProtectRequest {\n agent: string;\n action: string;\n context?: Record<string, unknown>;\n}\n\n/**\n * Success return from {@link protect}. The action is authorized\n * end-to-end — evaluation allowed AND the resulting permit verified.\n */\nexport interface Permit {\n /** Opaque permit / decision identifier. */\n permitId: string;\n /** Verification hash bound to the permit. */\n permitHash: string;\n /** Audit-trail entry associated with the decision (hash-chained). */\n auditHash: string;\n /** Human-readable reason from the policy engine. */\n reason: string;\n /** ISO 8601 timestamp of the verification. */\n timestamp: string;\n}\n\n/** Configuration for the process-wide singleton used by {@link protect}. */\nexport interface ConfigureOptions {\n /** Overrides `ATLASENT_API_KEY` env var. */\n apiKey?: string;\n /** Overrides the default `https://api.atlasent.io`. */\n baseUrl?: string;\n /** Per-request timeout in ms. */\n timeoutMs?: number;\n /** Inject a custom fetch (primarily for tests). */\n fetch?: typeof fetch;\n}\n\nlet sharedClient: AtlaSentClient | null = null;\nlet overrides: ConfigureOptions = {};\n\n/**\n * Configure the singleton client used by {@link protect}. Optional —\n * if `ATLASENT_API_KEY` is set in the environment, `protect` works\n * without any configuration. Calling `configure` again replaces the\n * singleton; subsequent `protect` calls use the new settings.\n */\nexport function configure(options: ConfigureOptions): void {\n overrides = { ...overrides, ...options };\n sharedClient = null;\n}\n\n/** Reset the singleton. Exported for tests; not part of the public API. */\nexport function __resetSharedClientForTests(): void {\n sharedClient = null;\n overrides = {};\n}\n\nfunction getClient(): AtlaSentClient {\n if (sharedClient) return sharedClient;\n\n const apiKey = overrides.apiKey ?? process.env.ATLASENT_API_KEY;\n if (!apiKey) {\n throw new AtlaSentError(\n \"AtlaSent is not configured. Set ATLASENT_API_KEY in the environment, or call atlasent.configure({ apiKey }).\",\n { code: \"invalid_api_key\" },\n );\n }\n const options: AtlaSentClientOptions = { apiKey };\n if (overrides.baseUrl !== undefined) options.baseUrl = overrides.baseUrl;\n if (overrides.timeoutMs !== undefined) options.timeoutMs = overrides.timeoutMs;\n if (overrides.fetch !== undefined) options.fetch = overrides.fetch;\n sharedClient = new AtlaSentClient(options);\n return sharedClient;\n}\n\nfunction wireDecisionToDenied(serverDecision: string): AtlaSentDecision {\n // The current /v1-evaluate contract returns \"ALLOW\" | \"DENY\". Map\n // \"DENY\" → \"deny\"; any future lowercase-or-new value passes through\n // so forthcoming \"hold\" / \"escalate\" responses don't break callers.\n const lower = serverDecision.toLowerCase();\n if (lower === \"hold\" || lower === \"escalate\") return lower;\n return \"deny\";\n}\n\n/**\n * Authorize an action end-to-end. On allow, returns a verified\n * {@link Permit}. On anything else, throws:\n *\n * - {@link AtlaSentDeniedError} — policy denied, or the permit\n * failed verification. Fail-closed: if this throws, the action\n * MUST NOT proceed.\n * - {@link AtlaSentError} — transport, timeout, auth, rate-limit,\n * or server error. Same fail-closed contract: do not proceed.\n */\nexport async function protect(request: ProtectRequest): Promise<Permit> {\n const client = getClient();\n const evaluation = await client.evaluate(request);\n\n if (evaluation.decision !== \"ALLOW\") {\n throw new AtlaSentDeniedError({\n decision: wireDecisionToDenied(evaluation.decision),\n evaluationId: evaluation.permitId,\n reason: evaluation.reason,\n auditHash: evaluation.auditHash,\n });\n }\n\n const verifyRequest: {\n permitId: string;\n agent: string;\n action: string;\n context?: Record<string, unknown>;\n } = {\n permitId: evaluation.permitId,\n agent: request.agent,\n action: request.action,\n };\n if (request.context !== undefined) verifyRequest.context = request.context;\n const verification = await client.verifyPermit(verifyRequest);\n\n if (!verification.verified) {\n throw new AtlaSentDeniedError({\n decision: \"deny\",\n evaluationId: evaluation.permitId,\n reason: `Permit failed verification (${verification.outcome})`,\n auditHash: evaluation.auditHash,\n });\n }\n\n return {\n permitId: evaluation.permitId,\n permitHash: verification.permitHash,\n auditHash: evaluation.auditHash,\n reason: evaluation.reason,\n timestamp: verification.timestamp,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACqCO,IAAM,gBAAN,cAA4B,MAAM;AAAA;AAAA;AAAA,EAG9B,OAAe;AAAA;AAAA,EAGf;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,SAAiB,OAA0B,CAAC,GAAG;AACzD;AAAA,MACE;AAAA,MACA,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI;AAAA,IACrD;AACA,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,KAAK;AACjB,SAAK,YAAY,KAAK;AACtB,SAAK,eAAe,KAAK;AAAA,EAC3B;AACF;AAoCO,IAAM,sBAAN,cAAkC,cAAc;AAAA,EAC5C,OAAe;AAAA;AAAA,EAGf;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,MAA+B;AACzC,UAAM,MAAM,KAAK,SACb,YAAY,KAAK,QAAQ,KAAK,KAAK,MAAM,KACzC,YAAY,KAAK,QAAQ;AAC7B,UAAM,UAA6B,EAAE,QAAQ,IAAI;AACjD,QAAI,KAAK,cAAc,OAAW,SAAQ,YAAY,KAAK;AAC3D,UAAM,KAAK,OAAO;AAClB,SAAK,WAAW,KAAK;AACrB,SAAK,eAAe,KAAK;AACzB,SAAK,SAAS,KAAK;AACnB,SAAK,YAAY,KAAK;AAAA,EACxB;AACF;;;ACtFA,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAC3B,IAAM,cAAc;AA+Bb,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,SAAgC;AAC1C,QAAI,CAAC,QAAQ,UAAU,OAAO,QAAQ,WAAW,UAAU;AACzD,YAAM,IAAI,cAAc,sBAAsB;AAAA,QAC5C,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,SAAK,SAAS,QAAQ;AACtB,SAAK,WAAW,QAAQ,WAAW,kBAAkB,QAAQ,QAAQ,EAAE;AACvE,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,YAAY,QAAQ,SAAS,WAAW,MAAM,KAAK,UAAU;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,SAAS,OAAmD;AAChE,UAAM,OAAO;AAAA,MACX,QAAQ,MAAM;AAAA,MACd,OAAO,MAAM;AAAA,MACb,SAAS,MAAM,WAAW,CAAC;AAAA,MAC3B,SAAS,KAAK;AAAA,IAChB;AACA,UAAM,EAAE,MAAM,MAAM,UAAU,IAAI,MAAM,KAAK,KAAmB,gBAAgB,IAAI;AAEpF,QAAI,OAAO,KAAK,cAAc,aAAa,OAAO,KAAK,gBAAgB,UAAU;AAC/E,YAAM,IAAI;AAAA,QACR;AAAA,QACA,EAAE,MAAM,eAAe;AAAA,MACzB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,UAAU,KAAK,YAAY,UAAU;AAAA,MACrC,UAAU,KAAK;AAAA,MACf,QAAQ,KAAK,UAAU;AAAA,MACvB,WAAW,KAAK,cAAc;AAAA,MAC9B,WAAW,KAAK,aAAa;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aACJ,OAC+B;AAC/B,UAAM,OAAO;AAAA,MACX,aAAa,MAAM;AAAA,MACnB,QAAQ,MAAM,UAAU;AAAA,MACxB,OAAO,MAAM,SAAS;AAAA,MACtB,SAAS,MAAM,WAAW,CAAC;AAAA,MAC3B,SAAS,KAAK;AAAA,IAChB;AACA,UAAM,EAAE,MAAM,MAAM,UAAU,IAAI,MAAM,KAAK;AAAA,MAC3C;AAAA,MACA;AAAA,IACF;AAEA,QAAI,OAAO,KAAK,aAAa,WAAW;AACtC,YAAM,IAAI;AAAA,QACR;AAAA,QACA,EAAE,MAAM,eAAe;AAAA,MACzB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,UAAU,KAAK;AAAA,MACf,SAAS,KAAK,WAAW;AAAA,MACzB,YAAY,KAAK,eAAe;AAAA,MAChC,WAAW,KAAK,aAAa;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,UAAuC;AAC3C,UAAM,EAAE,MAAM,MAAM,UAAU,IAAI,MAAM,KAAK;AAAA,MAC3C;AAAA,IACF;AAEA,QAAI,OAAO,KAAK,WAAW,YAAY,OAAO,KAAK,oBAAoB,UAAU;AAC/E,YAAM,IAAI;AAAA,QACR;AAAA,QACA,EAAE,MAAM,eAAe;AAAA,MACzB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,gBAAgB,KAAK;AAAA,MACrB,aAAa,KAAK;AAAA,MAClB,QAAQ,KAAK,UAAU,CAAC;AAAA,MACxB,cAAc,KAAK,iBAAiB;AAAA,MACpC,oBAAoB,KAAK;AAAA,MACzB,UAAU,KAAK,aAAa;AAAA,MAC5B,WAAW,KAAK,cAAc;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,gBACJ,QAA0B,CAAC,GACC;AAC5B,UAAM,EAAE,MAAM,MAAM,UAAU,IAAI,MAAM,KAAK;AAAA,MAC3C;AAAA,MACA,sBAAsB,KAAK;AAAA,IAC7B;AAEA,QAAI,CAAC,MAAM,QAAQ,KAAK,MAAM,KAAK,OAAO,KAAK,UAAU,UAAU;AACjE,YAAM,IAAI;AAAA,QACR;AAAA,QACA,EAAE,MAAM,eAAe;AAAA,MACzB;AAAA,IACF;AAEA,WAAO,EAAE,GAAG,MAAM,UAAU;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,kBACJ,SAA6B,CAAC,GACF;AAC5B,UAAM,EAAE,MAAM,MAAM,UAAU,IAAI,MAAM,KAAK;AAAA,MAC3C;AAAA,MACA;AAAA,IACF;AAEA,QACE,OAAO,KAAK,cAAc,YAC1B,OAAO,KAAK,oBAAoB,YAChC,CAAC,MAAM,QAAQ,KAAK,MAAM,GAC1B;AACA,YAAM,IAAI;AAAA,QACR;AAAA,QACA,EAAE,MAAM,eAAe;AAAA,MACzB;AAAA,IACF;AAEA,WAAO,EAAE,GAAG,MAAM,UAAU;AAAA,EAC9B;AAAA,EAEA,MAAc,KACZ,MACA,MACwD;AACxD,WAAO,KAAK,QAAW,MAAM,QAAQ,MAAM,MAAS;AAAA,EACtD;AAAA,EAEA,MAAc,IACZ,MACA,OACwD;AACxD,WAAO,KAAK,QAAW,MAAM,OAAO,QAAW,KAAK;AAAA,EACtD;AAAA,EAEA,MAAc,QACZ,MACA,QACA,MACA,OACwD;AACxD,UAAM,KAAK,SAAS,MAAM,KAAK,KAAK,EAAE,SAAS,IAAI,IAAI,MAAM,SAAS,CAAC,KAAK;AAC5E,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI,GAAG,EAAE;AACvC,UAAM,YAAY,WAAW,OAAO,WAAW;AAE/C,UAAM,UAAkC;AAAA,MACtC,QAAQ;AAAA,MACR,eAAe,UAAU,KAAK,MAAM;AAAA,MACpC,cAAc,iBAAiB,WAAW,SAAS,QAAQ,OAAO;AAAA,MAClE,gBAAgB;AAAA,IAClB;AACA,QAAI,WAAW,OAAQ,SAAQ,cAAc,IAAI;AAEjD,UAAM,OAAoB;AAAA,MACxB;AAAA,MACA;AAAA,MACA,QAAQ,YAAY,QAAQ,KAAK,SAAS;AAAA,IAC5C;AACA,QAAI,WAAW,OAAQ,MAAK,OAAO,KAAK,UAAU,IAAI;AAEtD,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,KAAK,UAAU,KAAK,IAAI;AAAA,IAC3C,SAAS,KAAK;AACZ,YAAM,cAAc,KAAK,SAAS;AAAA,IACpC;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,MAAM,eAAe,UAAU,SAAS;AAAA,IAChD;AAEA,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,SAAS,KAAK;AAAA,IAC/B,SAAS,KAAK;AACZ,YAAM,IAAI,cAAc,2CAA2C;AAAA,QACjE,MAAM;AAAA,QACN,QAAQ,SAAS;AAAA,QACjB;AAAA,QACA,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,QAAI,WAAW,QAAQ,OAAO,WAAW,UAAU;AACjD,YAAM,IAAI,cAAc,4CAA4C;AAAA,QAClE,MAAM;AAAA,QACN,QAAQ,SAAS;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,WAAW,sBAAsB,SAAS,OAAO;AAAA,IACnD;AAAA,EACF;AACF;AAWA,SAAS,sBAAsB,SAAyC;AACtE,QAAM,WAAW,QAAQ,IAAI,mBAAmB;AAChD,QAAM,eAAe,QAAQ,IAAI,uBAAuB;AACxD,QAAM,WAAW,QAAQ,IAAI,mBAAmB;AAChD,MAAI,aAAa,QAAQ,iBAAiB,QAAQ,aAAa,MAAM;AACnE,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,OAAO,QAAQ;AAC7B,QAAM,YAAY,OAAO,YAAY;AACrC,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,SAAS,SAAS,GAAG;AAC1D,WAAO;AAAA,EACT;AACA,QAAM,UAAU,iBAAiB,QAAQ;AACzC,MAAI,YAAY,MAAM;AACpB,WAAO;AAAA,EACT;AACA,SAAO,EAAE,OAAO,WAAW,QAAQ;AACrC;AAEA,SAAS,iBAAiB,KAA0B;AAClD,QAAM,UAAU,OAAO,GAAG;AAC1B,MAAI,OAAO,SAAS,OAAO,GAAG;AAI5B,WAAO,IAAI,KAAK,UAAU,GAAI;AAAA,EAChC;AACA,QAAM,KAAK,KAAK,MAAM,GAAG;AACzB,MAAI,OAAO,SAAS,EAAE,GAAG;AACvB,WAAO,IAAI,KAAK,EAAE;AAAA,EACpB;AACA,SAAO;AACT;AAEA,SAAS,cAAc,KAAc,WAAkC;AACrE,MAAI,eAAe,cAAe,QAAO;AACzC,MAAI,eAAe,gBAAgB,IAAI,SAAS,gBAAgB;AAC9D,WAAO,IAAI,cAAc,qCAAqC;AAAA,MAC5D,MAAM;AAAA,MACN;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,WAAO,IAAI,cAAc,qCAAqC;AAAA,MAC5D,MAAM;AAAA,MACN;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,QAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,SAAO,IAAI,cAAc,iCAAiC,OAAO,IAAI;AAAA,IACnE,MAAM;AAAA,IACN;AAAA,IACA,OAAO;AAAA,EACT,CAAC;AACH;AAEA,eAAe,eACb,UACA,WACwB;AACxB,QAAM,SAAS,SAAS;AACxB,QAAM,aAAa,MAAM,mBAAmB,QAAQ;AACpD,QAAM,OAA0B;AAAA,IAC9B;AAAA,IACA,MAAM,WAAW;AAAA,IACjB;AAAA,EACF;AACA,MAAI,WAAW,iBAAiB,QAAW;AACzC,SAAK,eAAe,WAAW;AAAA,EACjC;AACA,SAAO,IAAI,cAAc,WAAW,SAAS,IAAI;AACnD;AAEA,eAAe,mBAAmB,UAI/B;AACD,QAAM,SAAS,SAAS;AACxB,QAAM,gBAAgB,MAAM,kBAAkB,QAAQ;AAEtD,MAAI,WAAW,KAAK;AAClB,WAAO;AAAA,MACL,SAAS,iBAAiB;AAAA,MAC1B,MAAM;AAAA,MACN,cAAc;AAAA,IAChB;AAAA,EACF;AACA,MAAI,WAAW,KAAK;AAClB,WAAO;AAAA,MACL,SACE,iBAAiB;AAAA,MACnB,MAAM;AAAA,MACN,cAAc;AAAA,IAChB;AAAA,EACF;AACA,MAAI,WAAW,KAAK;AAClB,WAAO;AAAA,MACL,SAAS,iBAAiB;AAAA,MAC1B,MAAM;AAAA,MACN,cAAc,gBAAgB,SAAS,QAAQ,IAAI,aAAa,CAAC;AAAA,IACnE;AAAA,EACF;AACA,MAAI,UAAU,KAAK;AACjB,WAAO;AAAA,MACL,SAAS,iBAAiB,8BAA8B,MAAM;AAAA,MAC9D,MAAM;AAAA,MACN,cAAc;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS,iBAAiB,8BAA8B,MAAM;AAAA,IAC9D,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAEA,eAAe,kBAAkB,UAA4C;AAC3E,MAAI;AACF,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,cAAM,MAAO,OAAmC;AAChD,cAAM,SAAU,OAAmC;AACnD,YAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,EAAG,QAAO;AACtD,YAAI,OAAO,WAAW,YAAY,OAAO,SAAS,EAAG,QAAO;AAAA,MAC9D;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO,KAAK,SAAS,MAAM,GAAG,KAAK,MAAM,GAAG,GAAG,CAAC,WAAM;AAAA,EACxD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,SAAS,sBAAsB,OAA0C;AACvE,QAAM,SAAS,IAAI,gBAAgB;AACnC,MAAI,MAAM,UAAU,UAAa,MAAM,UAAU,IAAI;AACnD,WAAO,IAAI,SAAS,MAAM,KAAK;AAAA,EACjC;AACA,MAAI,MAAM,aAAa,UAAa,MAAM,aAAa,IAAI;AACzD,WAAO,IAAI,YAAY,MAAM,QAAQ;AAAA,EACvC;AACA,MAAI,MAAM,SAAS,UAAa,MAAM,SAAS,IAAI;AACjD,WAAO,IAAI,QAAQ,MAAM,IAAI;AAAA,EAC/B;AACA,MAAI,MAAM,OAAO,UAAa,MAAM,OAAO,IAAI;AAC7C,WAAO,IAAI,MAAM,MAAM,EAAE;AAAA,EAC3B;AACA,MAAI,MAAM,UAAU,QAAW;AAC7B,WAAO,IAAI,SAAS,OAAO,MAAM,KAAK,CAAC;AAAA,EACzC;AACA,MAAI,MAAM,WAAW,UAAa,MAAM,WAAW,IAAI;AACrD,WAAO,IAAI,UAAU,MAAM,MAAM;AAAA,EACnC;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,KAAwC;AAC/D,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,UAAU,OAAO,GAAG;AAC1B,MAAI,OAAO,SAAS,OAAO,EAAG,QAAO,KAAK,IAAI,GAAG,UAAU,GAAI;AAC/D,QAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,MAAI,OAAO,SAAS,IAAI,GAAG;AACzB,UAAM,QAAQ,OAAO,KAAK,IAAI;AAC9B,WAAO,QAAQ,IAAI,QAAQ;AAAA,EAC7B;AACA,SAAO;AACT;;;AC/cA,IAAI,eAAsC;AAC1C,IAAI,YAA8B,CAAC;AAmBnC,SAAS,YAA4B;AACnC,MAAI,aAAc,QAAO;AAEzB,QAAM,SAAS,UAAU,UAAU,QAAQ,IAAI;AAC/C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,MACA,EAAE,MAAM,kBAAkB;AAAA,IAC5B;AAAA,EACF;AACA,QAAM,UAAiC,EAAE,OAAO;AAChD,MAAI,UAAU,YAAY,OAAW,SAAQ,UAAU,UAAU;AACjE,MAAI,UAAU,cAAc,OAAW,SAAQ,YAAY,UAAU;AACrE,MAAI,UAAU,UAAU,OAAW,SAAQ,QAAQ,UAAU;AAC7D,iBAAe,IAAI,eAAe,OAAO;AACzC,SAAO;AACT;AAEA,SAAS,qBAAqB,gBAA0C;AAItE,QAAM,QAAQ,eAAe,YAAY;AACzC,MAAI,UAAU,UAAU,UAAU,WAAY,QAAO;AACrD,SAAO;AACT;AAYA,eAAsB,QAAQ,SAA0C;AACtE,QAAM,SAAS,UAAU;AACzB,QAAM,aAAa,MAAM,OAAO,SAAS,OAAO;AAEhD,MAAI,WAAW,aAAa,SAAS;AACnC,UAAM,IAAI,oBAAoB;AAAA,MAC5B,UAAU,qBAAqB,WAAW,QAAQ;AAAA,MAClD,cAAc,WAAW;AAAA,MACzB,QAAQ,WAAW;AAAA,MACnB,WAAW,WAAW;AAAA,IACxB,CAAC;AAAA,EACH;AAEA,QAAM,gBAKF;AAAA,IACF,UAAU,WAAW;AAAA,IACrB,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,EAClB;AACA,MAAI,QAAQ,YAAY,OAAW,eAAc,UAAU,QAAQ;AACnE,QAAM,eAAe,MAAM,OAAO,aAAa,aAAa;AAE5D,MAAI,CAAC,aAAa,UAAU;AAC1B,UAAM,IAAI,oBAAoB;AAAA,MAC5B,UAAU;AAAA,MACV,cAAc,WAAW;AAAA,MACzB,QAAQ,+BAA+B,aAAa,OAAO;AAAA,MAC3D,WAAW,WAAW;AAAA,IACxB,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,UAAU,WAAW;AAAA,IACrB,YAAY,aAAa;AAAA,IACzB,WAAW,WAAW;AAAA,IACtB,QAAQ,WAAW;AAAA,IACnB,WAAW,aAAa;AAAA,EAC1B;AACF;;;AHtFA,IAAM,sBAAsB;AAE5B,eAAe,QACb,OACA,GACY;AACZ,SAAO,OAAO,UAAU,aACpB,MAAO,MAAyC,CAAC,IACjD;AACN;AAQO,SAAS,cACd,SACmB;AACnB,QAAM,aAAa,QAAQ,OAAO;AAClC,SAAO,OAAO,GAAG,SAAS;AACxB,UAAM,CAAC,OAAO,QAAQ,GAAG,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC7C,QAAQ,QAAQ,OAAO,CAAC;AAAA,MACxB,QAAQ,QAAQ,QAAQ,CAAC;AAAA,MACzB,QAAQ,UAAU,QAAQ,QAAQ,CAAC,IAAI,QAAQ,QAAQ,MAAS;AAAA,IAClE,CAAC;AAED,UAAM,UAA0B,EAAE,OAAO,OAAO;AAChD,QAAI,QAAQ,OAAW,SAAQ,UAAU;AAEzC,UAAM,SAAiB,MAAM,QAAQ,OAAO;AAC5C,MAAE,IAAI,YAAY,MAAM;AACxB,UAAM,KAAK;AAAA,EACb;AACF;AAkBA,SAAS,kBAAkB,KAAmD;AAC5E,QAAM,OAAgC;AAAA,IACpC,OAAO;AAAA,IACP,UAAU,IAAI;AAAA,IACd,cAAc,IAAI;AAAA,EACpB;AACA,MAAI,IAAI,WAAW,OAAW,MAAK,SAAS,IAAI;AAChD,MAAI,IAAI,cAAc,OAAW,MAAK,YAAY,IAAI;AACtD,SAAO;AACT;AAEA,SAAS,mBAAmB,KAA6C;AACvE,QAAM,OAAgC;AAAA,IACpC,OAAO;AAAA,IACP,MAAM,IAAI,QAAQ;AAAA,EACpB;AACA,MAAI,IAAI,cAAc,OAAW,MAAK,YAAY,IAAI;AACtD,SAAO;AACT;AAaO,SAAS,qBACd,UAAuC,CAAC,GAC1B;AACd,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,cAAc,QAAQ,eAAe;AAE3C,SAAO,CAAC,KAAK,MAAM;AACjB,QAAI,eAAe,qBAAqB;AACtC,aAAO,EAAE,KAAK,WAAW,GAAG,GAAG,UAAU;AAAA,IAC3C;AACA,QAAI,eAAe,eAAe;AAChC,aAAO,EAAE,KAAK,YAAY,GAAG,GAAG,WAAW;AAAA,IAC7C;AACA,UAAM;AAAA,EACR;AACF;","names":[]}