@atlasent/sdk 2.10.0 → 2.13.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.js CHANGED
@@ -47,7 +47,8 @@ var KNOWN_PERMIT_OUTCOMES = /* @__PURE__ */ new Set([
47
47
  "permit_consumed",
48
48
  "permit_expired",
49
49
  "permit_revoked",
50
- "permit_not_found"
50
+ "permit_not_found",
51
+ "permit_signing_key_revoked"
51
52
  ]);
52
53
  function normalizePermitOutcome(raw) {
53
54
  if (raw !== void 0 && KNOWN_PERMIT_OUTCOMES.has(raw)) {
@@ -108,7 +109,197 @@ var AtlaSentDeniedError = class extends AtlaSentError {
108
109
  get isNotFound() {
109
110
  return this.outcome === "permit_not_found";
110
111
  }
112
+ /**
113
+ * `true` when the permit's signing key KID appears in the
114
+ * trust-root revocation list (ADR-005 D3 R2/R3 key rotation).
115
+ */
116
+ get isSigningKeyRevoked() {
117
+ return this.outcome === "permit_signing_key_revoked";
118
+ }
111
119
  };
120
+ var BundleVerificationError = class extends AtlaSentError {
121
+ name = "BundleVerificationError";
122
+ reason;
123
+ snapshotValidUntil;
124
+ snapshotFetchedAt;
125
+ snapshotSource;
126
+ kid;
127
+ constructor(init) {
128
+ super(`AtlaSent audit bundle verification failed: ${init.reason}`);
129
+ this.reason = init.reason;
130
+ this.snapshotValidUntil = init.snapshotValidUntil;
131
+ this.snapshotFetchedAt = init.snapshotFetchedAt;
132
+ this.snapshotSource = init.snapshotSource;
133
+ this.kid = init.kid;
134
+ }
135
+ };
136
+
137
+ // src/trustRoot.ts
138
+ import { readFileSync } from "fs";
139
+ import { fileURLToPath } from "url";
140
+ import { resolve, dirname } from "path";
141
+ var REFRESH_INTERVAL_MS_DEFAULT = 4 * 60 * 60 * 1e3;
142
+ var REFRESH_INTERVAL_MS_FLOOR = 5 * 60 * 1e3;
143
+ var KEYS_BASE_URL = "https://keys.atlasent.io/.well-known";
144
+ var _halfLifeWarningEmitted = false;
145
+ var _expiredWarningEmitted = false;
146
+ var TrustRootManager = class {
147
+ _snapshot;
148
+ _refreshTimer = null;
149
+ _opts;
150
+ constructor(initialSnapshot, opts = {}) {
151
+ this._snapshot = initialSnapshot;
152
+ const intervalMs = Math.max(
153
+ opts.refreshIntervalMs ?? REFRESH_INTERVAL_MS_DEFAULT,
154
+ REFRESH_INTERVAL_MS_FLOOR
155
+ );
156
+ this._opts = {
157
+ refreshBaseUrl: opts.refreshBaseUrl ?? KEYS_BASE_URL,
158
+ refreshIntervalMs: intervalMs,
159
+ disableRefresh: opts.disableRefresh ?? false,
160
+ fetch: opts.fetch ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : ((_url) => Promise.reject(new Error("fetch not available"))))
161
+ };
162
+ if (!this._opts.disableRefresh) {
163
+ this._scheduleRefresh();
164
+ }
165
+ }
166
+ getSnapshot() {
167
+ return this._snapshot;
168
+ }
169
+ /**
170
+ * Check whether the snapshot is expired, emit one-time warnings at
171
+ * half-life and expiry. Returns "ok" | "half_life" | "expired".
172
+ *
173
+ * Emits console.warn once per process at half-life (ADR-005 D3).
174
+ * Emits console.warn once per process on expiry.
175
+ */
176
+ checkExpiry() {
177
+ const snap = this._snapshot;
178
+ const now = Date.now();
179
+ const issuedAt = new Date(snap.issued_at).getTime();
180
+ const validUntil = new Date(snap.valid_until).getTime();
181
+ if (now > validUntil) {
182
+ if (!_expiredWarningEmitted) {
183
+ _expiredWarningEmitted = true;
184
+ const daysAgo = Math.floor((now - validUntil) / (24 * 60 * 60 * 1e3));
185
+ console.warn(
186
+ `[atlasent] Trust snapshot expired ${daysAgo} day(s) ago (valid_until: ${snap.valid_until}). Update to a newer SDK build or enable allowExpiredSnapshot.`
187
+ );
188
+ }
189
+ return "expired";
190
+ }
191
+ const window = validUntil - issuedAt;
192
+ const halfLife = issuedAt + window / 2;
193
+ if (now > halfLife) {
194
+ if (!_halfLifeWarningEmitted) {
195
+ _halfLifeWarningEmitted = true;
196
+ const daysLeft = Math.floor((validUntil - now) / (24 * 60 * 60 * 1e3));
197
+ console.warn(
198
+ `[atlasent] Trust snapshot at half-life: expires in ${daysLeft} day(s) (valid_until: ${snap.valid_until}). Plan an SDK update.`
199
+ );
200
+ }
201
+ return "half_life";
202
+ }
203
+ return "ok";
204
+ }
205
+ /** Look up a key entry by kid. Returns undefined if not found. */
206
+ lookupKey(kid) {
207
+ return this._snapshot.keys.find((k) => k.kid === kid);
208
+ }
209
+ /** Returns true if the kid appears in revoked_keys. */
210
+ isRevoked(kid) {
211
+ return this._snapshot.revoked_keys.some((r) => r.kid === kid);
212
+ }
213
+ /** Replace the snapshot (e.g. after a successful refresh). */
214
+ replaceSnapshot(next) {
215
+ this._snapshot = next;
216
+ }
217
+ stopRefresh() {
218
+ if (this._refreshTimer !== null) {
219
+ clearInterval(this._refreshTimer);
220
+ this._refreshTimer = null;
221
+ }
222
+ }
223
+ _scheduleRefresh() {
224
+ this._refreshTimer = setInterval(() => {
225
+ void this._doRefresh();
226
+ }, this._opts.refreshIntervalMs);
227
+ if (this._refreshTimer && typeof this._refreshTimer === "object" && "unref" in this._refreshTimer) {
228
+ this._refreshTimer.unref();
229
+ }
230
+ }
231
+ async _doRefresh() {
232
+ try {
233
+ const base = this._opts.refreshBaseUrl.replace(/\/$/, "");
234
+ const [keysRes, revocRes] = await Promise.all([
235
+ this._opts.fetch(`${base}/atlasent-verifier-keys.json`),
236
+ this._opts.fetch(`${base}/atlasent-revocations.json`)
237
+ ]);
238
+ const indexRes = await this._opts.fetch(`${base}/atlasent-trust-root.json`);
239
+ if (!keysRes.ok || !revocRes.ok || !indexRes.ok) return;
240
+ const [keys, revoc, index] = await Promise.all([
241
+ keysRes.json(),
242
+ revocRes.json(),
243
+ indexRes.json()
244
+ ]);
245
+ if (!index.valid_until || !Array.isArray(keys.keys)) return;
246
+ this._snapshot = {
247
+ valid_until: index.valid_until,
248
+ issued_at: index.issued_at ?? this._snapshot.issued_at,
249
+ keys: keys.keys,
250
+ revoked_keys: revoc.revoked_keys ?? [],
251
+ revoked_identities: revoc.revoked_identities ?? []
252
+ };
253
+ } catch {
254
+ }
255
+ }
256
+ };
257
+ function _loadVendorSnapshot() {
258
+ try {
259
+ let packageRoot;
260
+ try {
261
+ const thisFile = fileURLToPath(import.meta.url);
262
+ packageRoot = resolve(dirname(thisFile), "..", "..");
263
+ } catch {
264
+ packageRoot = resolve(__dirname, "..", "..");
265
+ }
266
+ const vendorDir = resolve(packageRoot, "vendor", "trust-root");
267
+ const index = JSON.parse(
268
+ readFileSync(resolve(vendorDir, "atlasent-trust-root.json"), "utf8")
269
+ );
270
+ const verifierKeys = JSON.parse(
271
+ readFileSync(resolve(vendorDir, "atlasent-verifier-keys.json"), "utf8")
272
+ );
273
+ const revocations = JSON.parse(
274
+ readFileSync(resolve(vendorDir, "atlasent-revocations.json"), "utf8")
275
+ );
276
+ return {
277
+ valid_until: index.valid_until,
278
+ issued_at: index.issued_at,
279
+ keys: verifierKeys.keys ?? [],
280
+ revoked_keys: revocations.revoked_keys ?? [],
281
+ revoked_identities: revocations.revoked_identities ?? []
282
+ };
283
+ } catch {
284
+ return {
285
+ valid_until: "2099-01-01T00:00:00Z",
286
+ issued_at: "2026-05-26T00:00:00Z",
287
+ keys: [],
288
+ revoked_keys: [],
289
+ revoked_identities: []
290
+ };
291
+ }
292
+ }
293
+ var _globalManager = null;
294
+ function getGlobalTrustRootManager(opts) {
295
+ if (!_globalManager) {
296
+ _globalManager = new TrustRootManager(
297
+ _loadVendorSnapshot(),
298
+ opts ?? { disableRefresh: false }
299
+ );
300
+ }
301
+ return _globalManager;
302
+ }
112
303
 
113
304
  // src/types.ts
114
305
  var PRODUCTION_DEPLOY_ACTION = "production.deploy";
@@ -135,7 +326,13 @@ function normalizeEvaluateRequest(input) {
135
326
  actor_id: legacy.agent
136
327
  };
137
328
  if (legacy.context !== void 0) normalized.context = legacy.context;
138
- if (legacy.explain !== void 0) normalized.explain = legacy.explain;
329
+ const l = legacy;
330
+ if (l.explain !== void 0) normalized.explain = l.explain;
331
+ if (l.environment !== void 0) normalized.environment = l.environment;
332
+ if (l.resource !== void 0) normalized.resource = l.resource;
333
+ if (l.current_state !== void 0) normalized.current_state = l.current_state;
334
+ if (l.proposed_state !== void 0) normalized.proposed_state = l.proposed_state;
335
+ if (l.execution_binding !== void 0) normalized.execution_binding = l.execution_binding;
139
336
  return normalized;
140
337
  }
141
338
  return input;
@@ -143,9 +340,9 @@ function normalizeEvaluateRequest(input) {
143
340
 
144
341
  // src/retry.ts
145
342
  var DEFAULT_RETRY_POLICY = {
146
- maxAttempts: 4,
147
- baseDelayMs: 2e3,
148
- maxDelayMs: 16e3
343
+ maxAttempts: 3,
344
+ baseDelayMs: 250,
345
+ maxDelayMs: 1e4
149
346
  };
150
347
  var RETRYABLE_CODES = /* @__PURE__ */ new Set([
151
348
  "network",
@@ -194,10 +391,371 @@ function clampUnit(n) {
194
391
  return n;
195
392
  }
196
393
 
394
+ // src/scim.ts
395
+ var SCIM_USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User";
396
+ var SCIM_GROUP_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Group";
397
+ function scimUsersPath(orgId) {
398
+ return `/scim/v2/${encodeURIComponent(orgId)}/Users`;
399
+ }
400
+ function scimGroupsPath(orgId) {
401
+ return `/scim/v2/${encodeURIComponent(orgId)}/Groups`;
402
+ }
403
+ function buildScimQuery(filter, startIndex, count) {
404
+ const params = new URLSearchParams();
405
+ if (filter !== void 0) params.set("filter", filter);
406
+ if (startIndex !== void 0) params.set("startIndex", String(startIndex));
407
+ if (count !== void 0) params.set("count", String(count));
408
+ return params.size > 0 ? params : void 0;
409
+ }
410
+ function makeScimClient(postFn, getFn, putFn, deleteFn) {
411
+ const users = {
412
+ async list(params) {
413
+ const qs = buildScimQuery(
414
+ params.filter,
415
+ params.startIndex,
416
+ params.count
417
+ );
418
+ const { body } = await getFn(
419
+ scimUsersPath(params.orgId),
420
+ qs
421
+ );
422
+ return body;
423
+ },
424
+ async create(orgId, user) {
425
+ const payload = user.schemas ? user : { ...user, schemas: [SCIM_USER_SCHEMA] };
426
+ const { body } = await postFn(scimUsersPath(orgId), payload);
427
+ return body;
428
+ },
429
+ async update(orgId, id, user) {
430
+ const payload = user.schemas ? user : { ...user, schemas: [SCIM_USER_SCHEMA] };
431
+ const { body } = await putFn(
432
+ `${scimUsersPath(orgId)}/${encodeURIComponent(id)}`,
433
+ payload
434
+ );
435
+ return body;
436
+ },
437
+ async delete(orgId, id) {
438
+ return deleteFn(
439
+ `${scimUsersPath(orgId)}/${encodeURIComponent(id)}`
440
+ );
441
+ }
442
+ };
443
+ const groups = {
444
+ async list(params) {
445
+ const qs = buildScimQuery(
446
+ params.filter,
447
+ params.startIndex,
448
+ params.count
449
+ );
450
+ const { body } = await getFn(
451
+ scimGroupsPath(params.orgId),
452
+ qs
453
+ );
454
+ return body;
455
+ },
456
+ async create(orgId, group) {
457
+ const payload = group["schemas"] ? group : { ...group, schemas: [SCIM_GROUP_SCHEMA] };
458
+ const { body } = await postFn(
459
+ scimGroupsPath(orgId),
460
+ payload
461
+ );
462
+ return body;
463
+ },
464
+ async delete(orgId, id) {
465
+ return deleteFn(
466
+ `${scimGroupsPath(orgId)}/${encodeURIComponent(id)}`
467
+ );
468
+ }
469
+ };
470
+ return { users, groups };
471
+ }
472
+
473
+ // src/evidence-bundle.ts
474
+ function wireToBundle(w) {
475
+ return {
476
+ bundleId: w.bundle_id,
477
+ orgId: w.org_id,
478
+ incidentId: w.incident_id,
479
+ status: w.status,
480
+ includedPermits: w.included_permits ?? [],
481
+ includeOverrides: w.include_overrides ?? false,
482
+ format: w.format,
483
+ createdAt: w.created_at,
484
+ expiresAt: w.expires_at,
485
+ ...w.download_url !== void 0 ? { downloadUrl: w.download_url } : {},
486
+ ...w.metadata !== void 0 ? { metadata: w.metadata } : {}
487
+ };
488
+ }
489
+ function makeEvidenceBundleClient(postFn, getFn, getRawFn) {
490
+ return {
491
+ async list(params = {}) {
492
+ const qs = new URLSearchParams();
493
+ if (params.executionId !== void 0) qs.set("execution_id", params.executionId);
494
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
495
+ if (params.cursor !== void 0) qs.set("cursor", params.cursor);
496
+ const { body } = await getFn("/v1/evidence-bundles", qs.size > 0 ? qs : void 0);
497
+ return {
498
+ bundles: (body.bundles ?? []).map(wireToBundle),
499
+ nextCursor: body.next_cursor ?? null
500
+ };
501
+ },
502
+ async create(params) {
503
+ const payload = {
504
+ incident_id: params.incidentId
505
+ };
506
+ if (params.includedPermits !== void 0) {
507
+ payload["included_permits"] = params.includedPermits;
508
+ }
509
+ if (params.includeOverrides !== void 0) {
510
+ payload["include_overrides"] = params.includeOverrides;
511
+ }
512
+ const { body } = await postFn(
513
+ "/v1/evidence-bundles",
514
+ payload
515
+ );
516
+ return wireToBundle(body);
517
+ },
518
+ async get(bundleId) {
519
+ const { body } = await getFn(
520
+ `/v1/evidence-bundles/${encodeURIComponent(bundleId)}`
521
+ );
522
+ return wireToBundle(body);
523
+ },
524
+ async download(bundleId, format = "json") {
525
+ const qs = new URLSearchParams({ format });
526
+ const raw = await getRawFn(
527
+ `/v1/evidence-bundles/${encodeURIComponent(bundleId)}/download?${qs}`
528
+ );
529
+ return Buffer.from(raw);
530
+ }
531
+ };
532
+ }
533
+
534
+ // src/auth.ts
535
+ function wireToTokenResponse(w) {
536
+ return {
537
+ accessToken: w.access_token,
538
+ refreshToken: w.refresh_token,
539
+ tokenType: w.token_type,
540
+ expiresIn: w.expires_in,
541
+ ...w.scope !== void 0 ? { scope: w.scope } : {},
542
+ ...w.idp_id !== void 0 ? { idpId: w.idp_id } : {}
543
+ };
544
+ }
545
+ function wireToIdpConnection(w) {
546
+ return {
547
+ id: w.id,
548
+ name: w.name,
549
+ provider: w.provider,
550
+ enabled: w.enabled,
551
+ isDefault: w.default,
552
+ ...w.domains !== void 0 ? { domains: w.domains } : {},
553
+ createdAt: w.created_at
554
+ };
555
+ }
556
+ function makeAuthClient(postFn, getFn) {
557
+ return {
558
+ async refresh(refreshToken) {
559
+ const { body } = await postFn(
560
+ "/v1/auth/token/refresh",
561
+ { refresh_token: refreshToken, grant_type: "refresh_token" }
562
+ );
563
+ return wireToTokenResponse(body);
564
+ },
565
+ async refreshWithIdp(idpId, refreshToken) {
566
+ const path = `/v1/auth/idp/${encodeURIComponent(idpId)}/token/refresh`;
567
+ const { body } = await postFn(path, {
568
+ refresh_token: refreshToken,
569
+ grant_type: "refresh_token",
570
+ idp_id: idpId
571
+ });
572
+ return wireToTokenResponse(body);
573
+ },
574
+ async listIdpConnections() {
575
+ const { body } = await getFn(
576
+ "/v1/auth/idp-connections"
577
+ );
578
+ return (body.connections ?? []).map(wireToIdpConnection);
579
+ }
580
+ };
581
+ }
582
+
583
+ // src/sso.ts
584
+ function wireToSsoConnection(w) {
585
+ return {
586
+ id: w.id,
587
+ organizationId: w.organization_id,
588
+ name: w.name,
589
+ protocol: w.protocol,
590
+ idpEntityId: w.idp_entity_id,
591
+ metadataUrl: w.metadata_url,
592
+ metadataXml: w.metadata_xml,
593
+ emailDomain: w.email_domain,
594
+ enforceForDomain: w.enforce_for_domain,
595
+ isActive: w.is_active,
596
+ supabaseProviderId: w.supabase_provider_id,
597
+ createdBy: w.created_by,
598
+ createdAt: w.created_at,
599
+ updatedAt: w.updated_at
600
+ };
601
+ }
602
+ function wireToSsoJitRule(w) {
603
+ return {
604
+ id: w.id,
605
+ connectionId: w.connection_id,
606
+ organizationId: w.organization_id,
607
+ claimAttribute: w.claim_attribute,
608
+ claimValue: w.claim_value,
609
+ grantedRole: w.granted_role,
610
+ precedence: w.precedence,
611
+ isActive: w.is_active,
612
+ createdAt: w.created_at,
613
+ updatedAt: w.updated_at
614
+ };
615
+ }
616
+ function wireToSsoReadiness(w) {
617
+ return {
618
+ connectionConfigured: w.connection_configured,
619
+ connectionTested: w.connection_tested,
620
+ breakGlassSet: w.break_glass_set,
621
+ serviceApiKeysReviewed: w.service_api_keys_reviewed
622
+ };
623
+ }
624
+ function ssoConnectionInputToWire(input) {
625
+ const w = {};
626
+ if (input.name !== void 0) w["name"] = input.name;
627
+ if (input.protocol !== void 0) w["protocol"] = input.protocol;
628
+ if (input.idpEntityId !== void 0) w["idp_entity_id"] = input.idpEntityId;
629
+ if (input.metadataUrl !== void 0) w["metadata_url"] = input.metadataUrl;
630
+ if (input.metadataXml !== void 0) w["metadata_xml"] = input.metadataXml;
631
+ if (input.emailDomain !== void 0) w["email_domain"] = input.emailDomain;
632
+ if (input.enforceForDomain !== void 0) w["enforce_for_domain"] = input.enforceForDomain;
633
+ return w;
634
+ }
635
+ function makeSsoClient(getFn, postFn, patchFn, deleteFn) {
636
+ return {
637
+ async listConnections() {
638
+ const { body } = await getFn("/v1/sso/connections");
639
+ return { connections: (body.connections ?? []).map(wireToSsoConnection) };
640
+ },
641
+ async getConnection(id) {
642
+ const { body } = await getFn(`/v1/sso/connections/${encodeURIComponent(id)}`);
643
+ return wireToSsoConnection(body);
644
+ },
645
+ async createConnection(input) {
646
+ const { body } = await postFn("/v1/sso/connections", ssoConnectionInputToWire(input));
647
+ return wireToSsoConnection(body);
648
+ },
649
+ async updateConnection(id, input) {
650
+ const { body } = await patchFn(
651
+ `/v1/sso/connections/${encodeURIComponent(id)}`,
652
+ ssoConnectionInputToWire(input)
653
+ );
654
+ return wireToSsoConnection(body);
655
+ },
656
+ async deleteConnection(id) {
657
+ await deleteFn(`/v1/sso/connections/${encodeURIComponent(id)}`);
658
+ },
659
+ async activateConnection(id) {
660
+ const { body } = await postFn(
661
+ `/v1/sso/connections/${encodeURIComponent(id)}/activate`,
662
+ {}
663
+ );
664
+ return { ok: body.ok, supabaseProviderId: body.supabase_provider_id };
665
+ },
666
+ async enforce(action) {
667
+ const { body } = await postFn("/v1/sso/enforce", { action });
668
+ return {
669
+ ok: body.ok,
670
+ action: body.action,
671
+ enforceSso: body.enforce_sso,
672
+ enforceSsoAt: body.enforce_sso_at
673
+ };
674
+ },
675
+ async getStatus() {
676
+ const { body } = await getFn("/v1/sso/status");
677
+ return wireToSsoReadiness(body.readiness);
678
+ },
679
+ async listJitRules(connectionId) {
680
+ const qs = connectionId ? new URLSearchParams({ connection_id: connectionId }) : void 0;
681
+ const { body } = await getFn("/v1/sso/jit-rules", qs);
682
+ return { rules: (body.rules ?? []).map(wireToSsoJitRule) };
683
+ },
684
+ async createJitRule(input) {
685
+ const payload = {
686
+ connection_id: input.connectionId,
687
+ claim_attribute: input.claimAttribute,
688
+ claim_value: input.claimValue,
689
+ granted_role: input.grantedRole
690
+ };
691
+ if (input.precedence !== void 0) payload["precedence"] = input.precedence;
692
+ const { body } = await postFn("/v1/sso/jit-rules", payload);
693
+ return wireToSsoJitRule(body);
694
+ },
695
+ async patchJitRule(id, patch) {
696
+ const payload = {};
697
+ if (patch.claimAttribute !== void 0) payload["claim_attribute"] = patch.claimAttribute;
698
+ if (patch.claimValue !== void 0) payload["claim_value"] = patch.claimValue;
699
+ if (patch.grantedRole !== void 0) payload["granted_role"] = patch.grantedRole;
700
+ if (patch.precedence !== void 0) payload["precedence"] = patch.precedence;
701
+ if (patch.isActive !== void 0) payload["is_active"] = patch.isActive;
702
+ const { body } = await patchFn(
703
+ `/v1/sso/jit-rules/${encodeURIComponent(id)}`,
704
+ payload
705
+ );
706
+ return wireToSsoJitRule(body);
707
+ },
708
+ async deleteJitRule(id) {
709
+ await deleteFn(`/v1/sso/jit-rules/${encodeURIComponent(id)}`);
710
+ }
711
+ };
712
+ }
713
+
714
+ // src/access-governance-log.ts
715
+ function wireToEvent(w) {
716
+ return {
717
+ id: w.id,
718
+ eventType: w.event_type,
719
+ orgId: w.org_id,
720
+ actorId: w.actor_id,
721
+ actorEmail: w.actor_email,
722
+ ipAddress: w.ip_address,
723
+ metadata: w.metadata ?? {},
724
+ createdAt: w.created_at
725
+ };
726
+ }
727
+ function makeAccessGovernanceLogClient(getFn) {
728
+ return {
729
+ async list(query = {}) {
730
+ const qs = new URLSearchParams();
731
+ if (query.limit !== void 0) qs.set("limit", String(query.limit));
732
+ if (query.cursor) qs.set("cursor", query.cursor);
733
+ if (query.eventType) qs.set("event_type", query.eventType);
734
+ if (query.actorId) qs.set("actor_id", query.actorId);
735
+ if (query.from) qs.set("from", query.from);
736
+ if (query.to) qs.set("to", query.to);
737
+ const { body } = await getFn(
738
+ "/v1/access-governance-log",
739
+ qs.size > 0 ? qs : void 0
740
+ );
741
+ return {
742
+ events: (body.events ?? []).map(wireToEvent),
743
+ nextCursor: body.next_cursor,
744
+ totalCount: body.total_count ?? 0
745
+ };
746
+ }
747
+ };
748
+ }
749
+
197
750
  // src/client.ts
198
751
  var DEFAULT_BASE_URL = "https://api.atlasent.io";
199
752
  var DEFAULT_TIMEOUT_MS = 1e4;
200
- var SDK_VERSION = "2.2.0";
753
+ var SDK_VERSION = "2.10.0";
754
+ var warnedBrowser = false;
755
+ var V1_EVALUATE_BATCH_PATH = "/v1/evaluate/batch";
756
+ var V1_EVALUATE_BATCH_LEGACY_PATH = "/v1-evaluate-batch";
757
+ var V1_EVALUATE_STREAM_PATH = "/v1/evaluate/stream";
758
+ var V1_EVALUATE_STREAM_LEGACY_PATH = "/v1-evaluate-stream";
201
759
  function _buildUserAgent() {
202
760
  const isNode2 = typeof process !== "undefined" && typeof process?.versions?.node === "string";
203
761
  return isNode2 ? `@atlasent/sdk/${SDK_VERSION} node/${process.version}` : `@atlasent/sdk/${SDK_VERSION} browser`;
@@ -261,6 +819,18 @@ var AtlaSentClient = class {
261
819
  fetchImpl;
262
820
  userAgent;
263
821
  retryPolicy;
822
+ /** SCIM 2.0 provisioning sub-client. Access as `client.scim`. */
823
+ scim;
824
+ /** Evidence bundle sub-client. Access as `client.evidenceBundles`. */
825
+ evidenceBundles;
826
+ /** Auth / token management sub-client. Access as `client.auth`. */
827
+ auth;
828
+ /** SSO administration sub-client. Access as `client.sso`. */
829
+ sso;
830
+ /** Access governance log sub-client. Access as `client.accessGovernanceLog`. */
831
+ accessGovernanceLog;
832
+ /** Trust-root snapshot manager for this client instance. */
833
+ trustRoot;
264
834
  constructor(options) {
265
835
  if (!options.apiKey || typeof options.apiKey !== "string") {
266
836
  throw new AtlaSentError("apiKey is required", {
@@ -273,6 +843,12 @@ var AtlaSentClient = class {
273
843
  { code: "network" }
274
844
  );
275
845
  }
846
+ if (!warnedBrowser && typeof globalThis["window"] !== "undefined" && typeof process === "undefined") {
847
+ warnedBrowser = true;
848
+ console.warn(
849
+ "[@atlasent/sdk] Running in a browser environment. API keys should not be exposed in client-side bundles. Use a server-side proxy instead."
850
+ );
851
+ }
276
852
  this.apiKey = _validateApiKey(options.apiKey);
277
853
  this.baseUrl = _enforceTls(options.baseUrl ?? DEFAULT_BASE_URL).replace(
278
854
  /\/+$/,
@@ -282,6 +858,44 @@ var AtlaSentClient = class {
282
858
  this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
283
859
  this.userAgent = _buildUserAgent();
284
860
  this.retryPolicy = mergePolicy(options.retryPolicy ?? {});
861
+ this.scim = makeScimClient(
862
+ (path, body, query) => this._post(path, body, query),
863
+ (path, query) => this._get(path, query),
864
+ (path, body) => this._put(path, body),
865
+ (path) => this._delete(path)
866
+ );
867
+ this.evidenceBundles = makeEvidenceBundleClient(
868
+ (path, body) => this._post(path, body),
869
+ (path, query) => this._get(path, query),
870
+ (path) => this._getRaw(path)
871
+ );
872
+ this.auth = makeAuthClient(
873
+ (path, body) => this._post(path, body),
874
+ (path) => this._get(path)
875
+ );
876
+ this.sso = makeSsoClient(
877
+ (path, query) => this._get(path, query),
878
+ (path, body) => this._post(path, body),
879
+ (path, body) => this._patch(path, body),
880
+ (path) => this._delete(path)
881
+ );
882
+ this.accessGovernanceLog = makeAccessGovernanceLogClient(
883
+ (path, query) => this._get(path, query)
884
+ );
885
+ if (options.trustRootUrl !== void 0 || options.trustSnapshotRefreshMs !== void 0) {
886
+ const globalSnap = getGlobalTrustRootManager({ disableRefresh: true }).getSnapshot();
887
+ this.trustRoot = new TrustRootManager(globalSnap, {
888
+ ...options.trustRootUrl !== void 0 && { refreshBaseUrl: options.trustRootUrl },
889
+ ...options.trustSnapshotRefreshMs !== void 0 && { refreshIntervalMs: options.trustSnapshotRefreshMs }
890
+ });
891
+ } else {
892
+ this.trustRoot = getGlobalTrustRootManager();
893
+ }
894
+ this.trustRoot.checkExpiry();
895
+ }
896
+ /** Return the current trust-root snapshot (pinned or last successful refresh). */
897
+ getTrustSnapshot() {
898
+ return this.trustRoot.getSnapshot();
285
899
  }
286
900
  /**
287
901
  * Ask the policy engine whether an agent action is permitted.
@@ -308,6 +922,11 @@ var AtlaSentClient = class {
308
922
  context: normalized.context ?? {}
309
923
  };
310
924
  if (normalized.explain !== void 0) body.explain = normalized.explain;
925
+ if (normalized.environment !== void 0) body.environment = normalized.environment;
926
+ if (normalized.resource !== void 0) body.resource = normalized.resource;
927
+ if (normalized.current_state !== void 0) body.current_state = normalized.current_state;
928
+ if (normalized.proposed_state !== void 0) body.proposed_state = normalized.proposed_state;
929
+ if (normalized.execution_binding !== void 0) body.execution_binding = normalized.execution_binding;
311
930
  const { body: wire, rateLimit } = await this.post(
312
931
  "/v1-evaluate",
313
932
  body
@@ -354,13 +973,25 @@ var AtlaSentClient = class {
354
973
  hardBlocks: wire.risk_envelope.hard_blocks ?? [],
355
974
  ...wire.risk_envelope.factors && { factors: wire.risk_envelope.factors }
356
975
  }
357
- }
976
+ },
977
+ ...wire.risk_class !== void 0 && { riskClass: wire.risk_class },
978
+ ...wire.authority_basis && {
979
+ authorityBasis: {
980
+ kind: wire.authority_basis.kind,
981
+ ...wire.authority_basis.reference !== void 0 && { reference: wire.authority_basis.reference },
982
+ ...wire.authority_basis.granted_by !== void 0 && { grantedBy: wire.authority_basis.granted_by },
983
+ ...wire.authority_basis.rationale !== void 0 && { rationale: wire.authority_basis.rationale },
984
+ ...wire.authority_basis.expires_at !== void 0 && { expiresAt: wire.authority_basis.expires_at }
985
+ }
986
+ },
987
+ ...wire.escalation_id !== void 0 && { escalationId: wire.escalation_id }
358
988
  };
359
989
  }
360
990
  /**
361
991
  * Batch evaluate — send up to 100 decisions in a single round-trip.
362
992
  *
363
- * Wraps `POST /v1-evaluate-batch`. The server evaluates each item
993
+ * Wraps `POST /v1/evaluate/batch` (with fallback to
994
+ * `POST /v1-evaluate-batch` on older runtimes). The server evaluates each item
364
995
  * against the active policy bundle and returns results in the same
365
996
  * order as the input. One rate-limit token is consumed for the
366
997
  * whole batch, and one audit-chain entry lists every included
@@ -398,8 +1029,9 @@ var AtlaSentClient = class {
398
1029
  }));
399
1030
  const wireBody = { items: wireItems };
400
1031
  if (batchId) wireBody.batch_id = batchId;
401
- const { body: wire, rateLimit } = await this.post(
402
- "/v1-evaluate-batch",
1032
+ const { body: wire, rateLimit } = await this.postWithPathFallback(
1033
+ V1_EVALUATE_BATCH_PATH,
1034
+ V1_EVALUATE_BATCH_LEGACY_PATH,
403
1035
  wireBody
404
1036
  );
405
1037
  const items = (wire.items ?? []).map(
@@ -692,6 +1324,7 @@ var AtlaSentClient = class {
692
1324
  const agent = input.agent ?? "ci-deploy-bot";
693
1325
  const action = input.action ?? PRODUCTION_DEPLOY_ACTION;
694
1326
  const context = input.context ?? {};
1327
+ const environment = typeof context.environment === "string" ? context.environment : typeof context.environment_name === "string" ? context.environment_name : void 0;
695
1328
  const evaluation = await this.evaluate({ agent, action, context });
696
1329
  if (evaluation.decision !== "allow") {
697
1330
  return {
@@ -708,7 +1341,8 @@ var AtlaSentClient = class {
708
1341
  permitId: evaluation.permitId,
709
1342
  agent,
710
1343
  action,
711
- context
1344
+ context,
1345
+ ...environment !== void 0 ? { environment } : {}
712
1346
  });
713
1347
  if (!verification.verified) {
714
1348
  return {
@@ -922,15 +1556,15 @@ var AtlaSentClient = class {
922
1556
  */
923
1557
  async keySelf() {
924
1558
  const { body: wire, rateLimit } = await this.get("/v1-api-key-self");
925
- if (typeof wire.key_id !== "string" || typeof wire.organization_id !== "string") {
1559
+ if (typeof wire.key_id !== "string" || typeof wire.org_id !== "string") {
926
1560
  throw new AtlaSentError(
927
- "Malformed response from /v1-api-key-self: missing `key_id` or `organization_id`",
1561
+ "Malformed response from /v1-api-key-self: missing `key_id` or `org_id`",
928
1562
  { code: "bad_response" }
929
1563
  );
930
1564
  }
931
1565
  return {
932
1566
  keyId: wire.key_id,
933
- organizationId: wire.organization_id,
1567
+ orgId: wire.org_id,
934
1568
  environment: wire.environment,
935
1569
  scopes: wire.scopes ?? [],
936
1570
  allowedCidrs: wire.allowed_cidrs ?? null,
@@ -1142,7 +1776,8 @@ var AtlaSentClient = class {
1142
1776
  return response;
1143
1777
  }
1144
1778
  /**
1145
- * Open a streaming evaluation session against `POST /v1-evaluate-stream`.
1779
+ * Open a streaming evaluation session against `POST /v1/evaluate/stream`
1780
+ * (with fallback to `POST /v1-evaluate-stream` on older runtimes).
1146
1781
  *
1147
1782
  * Yields {@link StreamDecisionEvent} and {@link StreamProgressEvent} objects
1148
1783
  * as the server emits them. The iterator ends cleanly when the server sends
@@ -1180,7 +1815,7 @@ var AtlaSentClient = class {
1180
1815
  api_key: this.apiKey
1181
1816
  };
1182
1817
  const requestId = globalThis.crypto.randomUUID();
1183
- const url = `${this.baseUrl}/v1-evaluate-stream`;
1818
+ let streamPath = V1_EVALUATE_STREAM_PATH;
1184
1819
  let lastEventId;
1185
1820
  let retryCount = 0;
1186
1821
  while (true) {
@@ -1200,7 +1835,7 @@ var AtlaSentClient = class {
1200
1835
  const signal = opts.signal ? AbortSignal.any([connectionTimeoutSignal, opts.signal]) : connectionTimeoutSignal;
1201
1836
  let response;
1202
1837
  try {
1203
- response = await this.fetchImpl(url, {
1838
+ response = await this.fetchImpl(`${this.baseUrl}${streamPath}`, {
1204
1839
  method: "POST",
1205
1840
  headers,
1206
1841
  body: JSON.stringify(body),
@@ -1216,6 +1851,10 @@ var AtlaSentClient = class {
1216
1851
  throw mapped;
1217
1852
  }
1218
1853
  if (!response.ok) {
1854
+ if (streamPath === V1_EVALUATE_STREAM_PATH && (response.status === 404 || response.status === 405)) {
1855
+ streamPath = V1_EVALUATE_STREAM_LEGACY_PATH;
1856
+ continue;
1857
+ }
1219
1858
  throw await buildHttpError(response, requestId);
1220
1859
  }
1221
1860
  if (!response.body) {
@@ -1264,9 +1903,61 @@ var AtlaSentClient = class {
1264
1903
  break;
1265
1904
  }
1266
1905
  }
1906
+ // ── License verification (self-hosted / air-gapped) ──────────────────────
1907
+ /**
1908
+ * Retrieve the license status of this self-hosted or air-gapped deployment.
1909
+ *
1910
+ * Calls `GET /v1/license`. Returns the current validity state, expiry,
1911
+ * enabled feature flags, and optional capacity limits for the installed
1912
+ * license key.
1913
+ *
1914
+ * Callers should check `result.status === "active"` before proceeding.
1915
+ * A `"grace"` status means the license has lapsed but a grace window
1916
+ * (`grace_until`) is still open — the deployment continues to function
1917
+ * but the license should be renewed immediately.
1918
+ *
1919
+ * Throws {@link AtlaSentError} on transport / auth failures.
1920
+ */
1921
+ async getLicense() {
1922
+ const { body, rateLimit } = await this.get("/v1/license");
1923
+ return { ...body, rateLimit };
1924
+ }
1925
+ /**
1926
+ * Validate a signed license blob against this deployment's installed
1927
+ * public key.
1928
+ *
1929
+ * Calls `POST /v1/license/verify`. Use this when onboarding a new license
1930
+ * key or rotating an expiring one — submit the blob received from AtlaSent
1931
+ * and check `result.valid` before applying the new license.
1932
+ *
1933
+ * A `valid: false` response is **not** thrown — inspect the returned
1934
+ * object. Only transport / server errors throw {@link AtlaSentError}.
1935
+ *
1936
+ * @param blob — The signed license blob string provided by AtlaSent.
1937
+ */
1938
+ async verifyLicense(blob) {
1939
+ if (!blob || typeof blob !== "string") {
1940
+ throw new AtlaSentError("blob is required", { code: "bad_request" });
1941
+ }
1942
+ const { body, rateLimit } = await this.post(
1943
+ "/v1/license/verify",
1944
+ { blob }
1945
+ );
1946
+ return { ...body, rateLimit };
1947
+ }
1267
1948
  async post(path, body, query) {
1268
1949
  return this.request(path, "POST", body, query);
1269
1950
  }
1951
+ async postWithPathFallback(primaryPath, fallbackPath, body, query) {
1952
+ try {
1953
+ return await this.post(primaryPath, body, query);
1954
+ } catch (err) {
1955
+ if (err instanceof AtlaSentError && (err.status === 404 || err.status === 405)) {
1956
+ return this.post(fallbackPath, body, query);
1957
+ }
1958
+ throw err;
1959
+ }
1960
+ }
1270
1961
  async get(path, query) {
1271
1962
  return this.request(path, "GET", void 0, query);
1272
1963
  }
@@ -1959,6 +2650,82 @@ var AtlaSentClient = class {
1959
2650
  );
1960
2651
  return [...body.evaluations ?? []];
1961
2652
  }
2653
+ // ── Private adapters for sub-client factories ──────────────────────────────
2654
+ // Thin wrappers that expose the private request infrastructure to sub-client
2655
+ // factories (scim, evidenceBundles, auth) without widening the public API.
2656
+ async _post(path, body, query) {
2657
+ const { body: b } = await this.post(path, body, query);
2658
+ return { body: b };
2659
+ }
2660
+ async _get(path, query) {
2661
+ const { body: b } = await this.get(path, query);
2662
+ return { body: b };
2663
+ }
2664
+ async _put(path, body) {
2665
+ return this._requestRaw(path, "PUT", body, void 0);
2666
+ }
2667
+ async _patch(path, body) {
2668
+ return this._requestRaw(path, "PATCH", body, void 0);
2669
+ }
2670
+ async _delete(path) {
2671
+ await this._requestRaw(path, "DELETE", void 0, void 0);
2672
+ }
2673
+ async _getRaw(path) {
2674
+ const url = `${this.baseUrl}${path}`;
2675
+ const requestId = globalThis.crypto.randomUUID();
2676
+ const headers = {
2677
+ Authorization: `Bearer ${this.apiKey}`,
2678
+ "User-Agent": this.userAgent,
2679
+ "X-Request-ID": requestId,
2680
+ "X-AtlaSent-Protocol-Version": "1"
2681
+ };
2682
+ const response = await this.fetchImpl(url, {
2683
+ method: "GET",
2684
+ headers,
2685
+ signal: AbortSignal.timeout(this.timeoutMs)
2686
+ });
2687
+ if (!response.ok) {
2688
+ const text = await response.text().catch(() => "");
2689
+ throw new AtlaSentError(`GET ${path} returned ${response.status}`, {
2690
+ code: response.status >= 500 ? "server_error" : "bad_request",
2691
+ status: response.status,
2692
+ requestId
2693
+ });
2694
+ }
2695
+ return response.arrayBuffer();
2696
+ }
2697
+ async _requestRaw(path, method, body, query) {
2698
+ const qs = query && Array.from(query).length > 0 ? `?${query.toString()}` : "";
2699
+ const url = `${this.baseUrl}${path}${qs}`;
2700
+ const requestId = globalThis.crypto.randomUUID();
2701
+ const headers = {
2702
+ Accept: "application/json",
2703
+ Authorization: `Bearer ${this.apiKey}`,
2704
+ "User-Agent": this.userAgent,
2705
+ "X-Request-ID": requestId,
2706
+ "X-AtlaSent-Protocol-Version": "1"
2707
+ };
2708
+ if (method === "PUT" && body !== void 0) {
2709
+ headers["Content-Type"] = "application/json";
2710
+ }
2711
+ const init = { method, headers, signal: AbortSignal.timeout(this.timeoutMs) };
2712
+ if (method === "PUT" && body !== void 0) {
2713
+ init.body = JSON.stringify(body);
2714
+ }
2715
+ const response = await this.fetchImpl(url, init);
2716
+ if (!response.ok) {
2717
+ const text = await response.text().catch(() => "");
2718
+ throw new AtlaSentError(`${method} ${path} returned ${response.status}`, {
2719
+ code: response.status >= 500 ? "server_error" : "bad_request",
2720
+ status: response.status,
2721
+ requestId
2722
+ });
2723
+ }
2724
+ if (method === "DELETE") {
2725
+ return { body: {} };
2726
+ }
2727
+ return { body: await response.json() };
2728
+ }
1962
2729
  };
1963
2730
  function parseRateLimitHeaders(headers) {
1964
2731
  const rawLimit = headers.get("x-ratelimit-limit");
@@ -2104,7 +2871,7 @@ function buildAuditEventsQuery(query) {
2104
2871
  return params;
2105
2872
  }
2106
2873
  function sleep(ms) {
2107
- return new Promise((resolve2) => setTimeout(resolve2, ms));
2874
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
2108
2875
  }
2109
2876
  function parseRetryAfter(raw) {
2110
2877
  if (!raw) return void 0;
@@ -2125,14 +2892,14 @@ async function* parseSseStream(body, requestId, timeoutMs, onEventId) {
2125
2892
  if (timeoutMs <= 0) {
2126
2893
  return reader.read();
2127
2894
  }
2128
- return new Promise((resolve2, reject) => {
2895
+ return new Promise((resolve3, reject) => {
2129
2896
  const timer = setTimeout(() => {
2130
2897
  reject(new StreamTimeoutError(timeoutMs));
2131
2898
  }, timeoutMs);
2132
2899
  reader.read().then(
2133
2900
  (result) => {
2134
2901
  clearTimeout(timer);
2135
- resolve2(result);
2902
+ resolve3(result);
2136
2903
  },
2137
2904
  (err) => {
2138
2905
  clearTimeout(timer);
@@ -2305,6 +3072,15 @@ async function protect(request) {
2305
3072
  { code: "bad_request" }
2306
3073
  );
2307
3074
  }
3075
+ const trustMgr = getGlobalTrustRootManager({ disableRefresh: false });
3076
+ if (trustMgr.checkExpiry() === "expired") {
3077
+ const snap = trustMgr.getSnapshot();
3078
+ throw new BundleVerificationError({
3079
+ reason: "trust_snapshot_expired",
3080
+ snapshotValidUntil: snap.valid_until,
3081
+ snapshotFetchedAt: snap.issued_at
3082
+ });
3083
+ }
2308
3084
  const client = getClient();
2309
3085
  const evaluation = await client.evaluate(request);
2310
3086
  if (evaluation.decision !== "allow") {
@@ -2359,15 +3135,15 @@ async function protect(request) {
2359
3135
 
2360
3136
  // src/hono.ts
2361
3137
  var DEFAULT_CONTEXT_KEY = "atlasent";
2362
- async function resolve(value, c) {
3138
+ async function resolve2(value, c) {
2363
3139
  return typeof value === "function" ? await value(c) : value;
2364
3140
  }
2365
3141
  function atlaSentGuard(options) {
2366
3142
  const contextKey = options.key ?? DEFAULT_CONTEXT_KEY;
2367
3143
  return async (c, next) => {
2368
3144
  const [agent, action, ctx] = await Promise.all([
2369
- resolve(options.agent, c),
2370
- resolve(options.action, c),
3145
+ resolve2(options.agent, c),
3146
+ resolve2(options.action, c),
2371
3147
  options.context ? options.context(c) : Promise.resolve(void 0)
2372
3148
  ]);
2373
3149
  const request = { agent, action };