@atlasent/sdk 2.10.0 → 2.12.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 CHANGED
@@ -86,7 +86,8 @@ var KNOWN_PERMIT_OUTCOMES = /* @__PURE__ */ new Set([
86
86
  "permit_consumed",
87
87
  "permit_expired",
88
88
  "permit_revoked",
89
- "permit_not_found"
89
+ "permit_not_found",
90
+ "permit_signing_key_revoked"
90
91
  ]);
91
92
  function normalizePermitOutcome(raw) {
92
93
  if (raw !== void 0 && KNOWN_PERMIT_OUTCOMES.has(raw)) {
@@ -147,8 +148,199 @@ var AtlaSentDeniedError = class extends AtlaSentError {
147
148
  get isNotFound() {
148
149
  return this.outcome === "permit_not_found";
149
150
  }
151
+ /**
152
+ * `true` when the permit's signing key KID appears in the
153
+ * trust-root revocation list (ADR-005 D3 R2/R3 key rotation).
154
+ */
155
+ get isSigningKeyRevoked() {
156
+ return this.outcome === "permit_signing_key_revoked";
157
+ }
158
+ };
159
+ var BundleVerificationError = class extends AtlaSentError {
160
+ name = "BundleVerificationError";
161
+ reason;
162
+ snapshotValidUntil;
163
+ snapshotFetchedAt;
164
+ snapshotSource;
165
+ kid;
166
+ constructor(init) {
167
+ super(`AtlaSent audit bundle verification failed: ${init.reason}`);
168
+ this.reason = init.reason;
169
+ this.snapshotValidUntil = init.snapshotValidUntil;
170
+ this.snapshotFetchedAt = init.snapshotFetchedAt;
171
+ this.snapshotSource = init.snapshotSource;
172
+ this.kid = init.kid;
173
+ }
150
174
  };
151
175
 
176
+ // src/trustRoot.ts
177
+ var import_node_fs = require("fs");
178
+ var import_node_url = require("url");
179
+ var import_node_path = require("path");
180
+ var import_meta = {};
181
+ var REFRESH_INTERVAL_MS_DEFAULT = 4 * 60 * 60 * 1e3;
182
+ var REFRESH_INTERVAL_MS_FLOOR = 5 * 60 * 1e3;
183
+ var KEYS_BASE_URL = "https://keys.atlasent.io/.well-known";
184
+ var _halfLifeWarningEmitted = false;
185
+ var _expiredWarningEmitted = false;
186
+ var TrustRootManager = class {
187
+ _snapshot;
188
+ _refreshTimer = null;
189
+ _opts;
190
+ constructor(initialSnapshot, opts = {}) {
191
+ this._snapshot = initialSnapshot;
192
+ const intervalMs = Math.max(
193
+ opts.refreshIntervalMs ?? REFRESH_INTERVAL_MS_DEFAULT,
194
+ REFRESH_INTERVAL_MS_FLOOR
195
+ );
196
+ this._opts = {
197
+ refreshBaseUrl: opts.refreshBaseUrl ?? KEYS_BASE_URL,
198
+ refreshIntervalMs: intervalMs,
199
+ disableRefresh: opts.disableRefresh ?? false,
200
+ fetch: opts.fetch ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : ((_url) => Promise.reject(new Error("fetch not available"))))
201
+ };
202
+ if (!this._opts.disableRefresh) {
203
+ this._scheduleRefresh();
204
+ }
205
+ }
206
+ getSnapshot() {
207
+ return this._snapshot;
208
+ }
209
+ /**
210
+ * Check whether the snapshot is expired, emit one-time warnings at
211
+ * half-life and expiry. Returns "ok" | "half_life" | "expired".
212
+ *
213
+ * Emits console.warn once per process at half-life (ADR-005 D3).
214
+ * Emits console.warn once per process on expiry.
215
+ */
216
+ checkExpiry() {
217
+ const snap = this._snapshot;
218
+ const now = Date.now();
219
+ const issuedAt = new Date(snap.issued_at).getTime();
220
+ const validUntil = new Date(snap.valid_until).getTime();
221
+ if (now > validUntil) {
222
+ if (!_expiredWarningEmitted) {
223
+ _expiredWarningEmitted = true;
224
+ const daysAgo = Math.floor((now - validUntil) / (24 * 60 * 60 * 1e3));
225
+ console.warn(
226
+ `[atlasent] Trust snapshot expired ${daysAgo} day(s) ago (valid_until: ${snap.valid_until}). Update to a newer SDK build or enable allowExpiredSnapshot.`
227
+ );
228
+ }
229
+ return "expired";
230
+ }
231
+ const window = validUntil - issuedAt;
232
+ const halfLife = issuedAt + window / 2;
233
+ if (now > halfLife) {
234
+ if (!_halfLifeWarningEmitted) {
235
+ _halfLifeWarningEmitted = true;
236
+ const daysLeft = Math.floor((validUntil - now) / (24 * 60 * 60 * 1e3));
237
+ console.warn(
238
+ `[atlasent] Trust snapshot at half-life: expires in ${daysLeft} day(s) (valid_until: ${snap.valid_until}). Plan an SDK update.`
239
+ );
240
+ }
241
+ return "half_life";
242
+ }
243
+ return "ok";
244
+ }
245
+ /** Look up a key entry by kid. Returns undefined if not found. */
246
+ lookupKey(kid) {
247
+ return this._snapshot.keys.find((k) => k.kid === kid);
248
+ }
249
+ /** Returns true if the kid appears in revoked_keys. */
250
+ isRevoked(kid) {
251
+ return this._snapshot.revoked_keys.some((r) => r.kid === kid);
252
+ }
253
+ /** Replace the snapshot (e.g. after a successful refresh). */
254
+ replaceSnapshot(next) {
255
+ this._snapshot = next;
256
+ }
257
+ stopRefresh() {
258
+ if (this._refreshTimer !== null) {
259
+ clearInterval(this._refreshTimer);
260
+ this._refreshTimer = null;
261
+ }
262
+ }
263
+ _scheduleRefresh() {
264
+ this._refreshTimer = setInterval(() => {
265
+ void this._doRefresh();
266
+ }, this._opts.refreshIntervalMs);
267
+ if (this._refreshTimer && typeof this._refreshTimer === "object" && "unref" in this._refreshTimer) {
268
+ this._refreshTimer.unref();
269
+ }
270
+ }
271
+ async _doRefresh() {
272
+ try {
273
+ const base = this._opts.refreshBaseUrl.replace(/\/$/, "");
274
+ const [keysRes, revocRes] = await Promise.all([
275
+ this._opts.fetch(`${base}/atlasent-verifier-keys.json`),
276
+ this._opts.fetch(`${base}/atlasent-revocations.json`)
277
+ ]);
278
+ const indexRes = await this._opts.fetch(`${base}/atlasent-trust-root.json`);
279
+ if (!keysRes.ok || !revocRes.ok || !indexRes.ok) return;
280
+ const [keys, revoc, index] = await Promise.all([
281
+ keysRes.json(),
282
+ revocRes.json(),
283
+ indexRes.json()
284
+ ]);
285
+ if (!index.valid_until || !Array.isArray(keys.keys)) return;
286
+ this._snapshot = {
287
+ valid_until: index.valid_until,
288
+ issued_at: index.issued_at ?? this._snapshot.issued_at,
289
+ keys: keys.keys,
290
+ revoked_keys: revoc.revoked_keys ?? [],
291
+ revoked_identities: revoc.revoked_identities ?? []
292
+ };
293
+ } catch {
294
+ }
295
+ }
296
+ };
297
+ function _loadVendorSnapshot() {
298
+ try {
299
+ let packageRoot;
300
+ try {
301
+ const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
302
+ packageRoot = (0, import_node_path.resolve)((0, import_node_path.dirname)(thisFile), "..", "..");
303
+ } catch {
304
+ packageRoot = (0, import_node_path.resolve)(__dirname, "..", "..");
305
+ }
306
+ const vendorDir = (0, import_node_path.resolve)(packageRoot, "vendor", "trust-root");
307
+ const index = JSON.parse(
308
+ (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(vendorDir, "atlasent-trust-root.json"), "utf8")
309
+ );
310
+ const verifierKeys = JSON.parse(
311
+ (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(vendorDir, "atlasent-verifier-keys.json"), "utf8")
312
+ );
313
+ const revocations = JSON.parse(
314
+ (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(vendorDir, "atlasent-revocations.json"), "utf8")
315
+ );
316
+ return {
317
+ valid_until: index.valid_until,
318
+ issued_at: index.issued_at,
319
+ keys: verifierKeys.keys ?? [],
320
+ revoked_keys: revocations.revoked_keys ?? [],
321
+ revoked_identities: revocations.revoked_identities ?? []
322
+ };
323
+ } catch {
324
+ return {
325
+ valid_until: "2099-01-01T00:00:00Z",
326
+ issued_at: "2026-05-26T00:00:00Z",
327
+ keys: [],
328
+ revoked_keys: [],
329
+ revoked_identities: []
330
+ };
331
+ }
332
+ }
333
+ var _globalManager = null;
334
+ function getGlobalTrustRootManager(opts) {
335
+ if (!_globalManager) {
336
+ _globalManager = new TrustRootManager(
337
+ _loadVendorSnapshot(),
338
+ opts ?? { disableRefresh: false }
339
+ );
340
+ }
341
+ return _globalManager;
342
+ }
343
+
152
344
  // src/types.ts
153
345
  var PRODUCTION_DEPLOY_ACTION = "production.deploy";
154
346
  var DEPLOY_GATE_CODES = Object.freeze({
@@ -174,7 +366,13 @@ function normalizeEvaluateRequest(input) {
174
366
  actor_id: legacy.agent
175
367
  };
176
368
  if (legacy.context !== void 0) normalized.context = legacy.context;
177
- if (legacy.explain !== void 0) normalized.explain = legacy.explain;
369
+ const l = legacy;
370
+ if (l.explain !== void 0) normalized.explain = l.explain;
371
+ if (l.environment !== void 0) normalized.environment = l.environment;
372
+ if (l.resource !== void 0) normalized.resource = l.resource;
373
+ if (l.current_state !== void 0) normalized.current_state = l.current_state;
374
+ if (l.proposed_state !== void 0) normalized.proposed_state = l.proposed_state;
375
+ if (l.execution_binding !== void 0) normalized.execution_binding = l.execution_binding;
178
376
  return normalized;
179
377
  }
180
378
  return input;
@@ -182,9 +380,9 @@ function normalizeEvaluateRequest(input) {
182
380
 
183
381
  // src/retry.ts
184
382
  var DEFAULT_RETRY_POLICY = {
185
- maxAttempts: 4,
186
- baseDelayMs: 2e3,
187
- maxDelayMs: 16e3
383
+ maxAttempts: 3,
384
+ baseDelayMs: 250,
385
+ maxDelayMs: 1e4
188
386
  };
189
387
  var RETRYABLE_CODES = /* @__PURE__ */ new Set([
190
388
  "network",
@@ -233,10 +431,371 @@ function clampUnit(n) {
233
431
  return n;
234
432
  }
235
433
 
434
+ // src/scim.ts
435
+ var SCIM_USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User";
436
+ var SCIM_GROUP_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Group";
437
+ function scimUsersPath(orgId) {
438
+ return `/scim/v2/${encodeURIComponent(orgId)}/Users`;
439
+ }
440
+ function scimGroupsPath(orgId) {
441
+ return `/scim/v2/${encodeURIComponent(orgId)}/Groups`;
442
+ }
443
+ function buildScimQuery(filter, startIndex, count) {
444
+ const params = new URLSearchParams();
445
+ if (filter !== void 0) params.set("filter", filter);
446
+ if (startIndex !== void 0) params.set("startIndex", String(startIndex));
447
+ if (count !== void 0) params.set("count", String(count));
448
+ return params.size > 0 ? params : void 0;
449
+ }
450
+ function makeScimClient(postFn, getFn, putFn, deleteFn) {
451
+ const users = {
452
+ async list(params) {
453
+ const qs = buildScimQuery(
454
+ params.filter,
455
+ params.startIndex,
456
+ params.count
457
+ );
458
+ const { body } = await getFn(
459
+ scimUsersPath(params.orgId),
460
+ qs
461
+ );
462
+ return body;
463
+ },
464
+ async create(orgId, user) {
465
+ const payload = user.schemas ? user : { ...user, schemas: [SCIM_USER_SCHEMA] };
466
+ const { body } = await postFn(scimUsersPath(orgId), payload);
467
+ return body;
468
+ },
469
+ async update(orgId, id, user) {
470
+ const payload = user.schemas ? user : { ...user, schemas: [SCIM_USER_SCHEMA] };
471
+ const { body } = await putFn(
472
+ `${scimUsersPath(orgId)}/${encodeURIComponent(id)}`,
473
+ payload
474
+ );
475
+ return body;
476
+ },
477
+ async delete(orgId, id) {
478
+ return deleteFn(
479
+ `${scimUsersPath(orgId)}/${encodeURIComponent(id)}`
480
+ );
481
+ }
482
+ };
483
+ const groups = {
484
+ async list(params) {
485
+ const qs = buildScimQuery(
486
+ params.filter,
487
+ params.startIndex,
488
+ params.count
489
+ );
490
+ const { body } = await getFn(
491
+ scimGroupsPath(params.orgId),
492
+ qs
493
+ );
494
+ return body;
495
+ },
496
+ async create(orgId, group) {
497
+ const payload = group["schemas"] ? group : { ...group, schemas: [SCIM_GROUP_SCHEMA] };
498
+ const { body } = await postFn(
499
+ scimGroupsPath(orgId),
500
+ payload
501
+ );
502
+ return body;
503
+ },
504
+ async delete(orgId, id) {
505
+ return deleteFn(
506
+ `${scimGroupsPath(orgId)}/${encodeURIComponent(id)}`
507
+ );
508
+ }
509
+ };
510
+ return { users, groups };
511
+ }
512
+
513
+ // src/evidence-bundle.ts
514
+ function wireToBundle(w) {
515
+ return {
516
+ bundleId: w.bundle_id,
517
+ orgId: w.org_id,
518
+ incidentId: w.incident_id,
519
+ status: w.status,
520
+ includedPermits: w.included_permits ?? [],
521
+ includeOverrides: w.include_overrides ?? false,
522
+ format: w.format,
523
+ createdAt: w.created_at,
524
+ expiresAt: w.expires_at,
525
+ ...w.download_url !== void 0 ? { downloadUrl: w.download_url } : {},
526
+ ...w.metadata !== void 0 ? { metadata: w.metadata } : {}
527
+ };
528
+ }
529
+ function makeEvidenceBundleClient(postFn, getFn, getRawFn) {
530
+ return {
531
+ async list(params = {}) {
532
+ const qs = new URLSearchParams();
533
+ if (params.executionId !== void 0) qs.set("execution_id", params.executionId);
534
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
535
+ if (params.cursor !== void 0) qs.set("cursor", params.cursor);
536
+ const { body } = await getFn("/v1/evidence-bundles", qs.size > 0 ? qs : void 0);
537
+ return {
538
+ bundles: (body.bundles ?? []).map(wireToBundle),
539
+ nextCursor: body.next_cursor ?? null
540
+ };
541
+ },
542
+ async create(params) {
543
+ const payload = {
544
+ incident_id: params.incidentId
545
+ };
546
+ if (params.includedPermits !== void 0) {
547
+ payload["included_permits"] = params.includedPermits;
548
+ }
549
+ if (params.includeOverrides !== void 0) {
550
+ payload["include_overrides"] = params.includeOverrides;
551
+ }
552
+ const { body } = await postFn(
553
+ "/v1/evidence-bundles",
554
+ payload
555
+ );
556
+ return wireToBundle(body);
557
+ },
558
+ async get(bundleId) {
559
+ const { body } = await getFn(
560
+ `/v1/evidence-bundles/${encodeURIComponent(bundleId)}`
561
+ );
562
+ return wireToBundle(body);
563
+ },
564
+ async download(bundleId, format = "json") {
565
+ const qs = new URLSearchParams({ format });
566
+ const raw = await getRawFn(
567
+ `/v1/evidence-bundles/${encodeURIComponent(bundleId)}/download?${qs}`
568
+ );
569
+ return Buffer.from(raw);
570
+ }
571
+ };
572
+ }
573
+
574
+ // src/auth.ts
575
+ function wireToTokenResponse(w) {
576
+ return {
577
+ accessToken: w.access_token,
578
+ refreshToken: w.refresh_token,
579
+ tokenType: w.token_type,
580
+ expiresIn: w.expires_in,
581
+ ...w.scope !== void 0 ? { scope: w.scope } : {},
582
+ ...w.idp_id !== void 0 ? { idpId: w.idp_id } : {}
583
+ };
584
+ }
585
+ function wireToIdpConnection(w) {
586
+ return {
587
+ id: w.id,
588
+ name: w.name,
589
+ provider: w.provider,
590
+ enabled: w.enabled,
591
+ isDefault: w.default,
592
+ ...w.domains !== void 0 ? { domains: w.domains } : {},
593
+ createdAt: w.created_at
594
+ };
595
+ }
596
+ function makeAuthClient(postFn, getFn) {
597
+ return {
598
+ async refresh(refreshToken) {
599
+ const { body } = await postFn(
600
+ "/v1/auth/token/refresh",
601
+ { refresh_token: refreshToken, grant_type: "refresh_token" }
602
+ );
603
+ return wireToTokenResponse(body);
604
+ },
605
+ async refreshWithIdp(idpId, refreshToken) {
606
+ const path = `/v1/auth/idp/${encodeURIComponent(idpId)}/token/refresh`;
607
+ const { body } = await postFn(path, {
608
+ refresh_token: refreshToken,
609
+ grant_type: "refresh_token",
610
+ idp_id: idpId
611
+ });
612
+ return wireToTokenResponse(body);
613
+ },
614
+ async listIdpConnections() {
615
+ const { body } = await getFn(
616
+ "/v1/auth/idp-connections"
617
+ );
618
+ return (body.connections ?? []).map(wireToIdpConnection);
619
+ }
620
+ };
621
+ }
622
+
623
+ // src/sso.ts
624
+ function wireToSsoConnection(w) {
625
+ return {
626
+ id: w.id,
627
+ organizationId: w.organization_id,
628
+ name: w.name,
629
+ protocol: w.protocol,
630
+ idpEntityId: w.idp_entity_id,
631
+ metadataUrl: w.metadata_url,
632
+ metadataXml: w.metadata_xml,
633
+ emailDomain: w.email_domain,
634
+ enforceForDomain: w.enforce_for_domain,
635
+ isActive: w.is_active,
636
+ supabaseProviderId: w.supabase_provider_id,
637
+ createdBy: w.created_by,
638
+ createdAt: w.created_at,
639
+ updatedAt: w.updated_at
640
+ };
641
+ }
642
+ function wireToSsoJitRule(w) {
643
+ return {
644
+ id: w.id,
645
+ connectionId: w.connection_id,
646
+ organizationId: w.organization_id,
647
+ claimAttribute: w.claim_attribute,
648
+ claimValue: w.claim_value,
649
+ grantedRole: w.granted_role,
650
+ precedence: w.precedence,
651
+ isActive: w.is_active,
652
+ createdAt: w.created_at,
653
+ updatedAt: w.updated_at
654
+ };
655
+ }
656
+ function wireToSsoReadiness(w) {
657
+ return {
658
+ connectionConfigured: w.connection_configured,
659
+ connectionTested: w.connection_tested,
660
+ breakGlassSet: w.break_glass_set,
661
+ serviceApiKeysReviewed: w.service_api_keys_reviewed
662
+ };
663
+ }
664
+ function ssoConnectionInputToWire(input) {
665
+ const w = {};
666
+ if (input.name !== void 0) w["name"] = input.name;
667
+ if (input.protocol !== void 0) w["protocol"] = input.protocol;
668
+ if (input.idpEntityId !== void 0) w["idp_entity_id"] = input.idpEntityId;
669
+ if (input.metadataUrl !== void 0) w["metadata_url"] = input.metadataUrl;
670
+ if (input.metadataXml !== void 0) w["metadata_xml"] = input.metadataXml;
671
+ if (input.emailDomain !== void 0) w["email_domain"] = input.emailDomain;
672
+ if (input.enforceForDomain !== void 0) w["enforce_for_domain"] = input.enforceForDomain;
673
+ return w;
674
+ }
675
+ function makeSsoClient(getFn, postFn, patchFn, deleteFn) {
676
+ return {
677
+ async listConnections() {
678
+ const { body } = await getFn("/v1/sso/connections");
679
+ return { connections: (body.connections ?? []).map(wireToSsoConnection) };
680
+ },
681
+ async getConnection(id) {
682
+ const { body } = await getFn(`/v1/sso/connections/${encodeURIComponent(id)}`);
683
+ return wireToSsoConnection(body);
684
+ },
685
+ async createConnection(input) {
686
+ const { body } = await postFn("/v1/sso/connections", ssoConnectionInputToWire(input));
687
+ return wireToSsoConnection(body);
688
+ },
689
+ async updateConnection(id, input) {
690
+ const { body } = await patchFn(
691
+ `/v1/sso/connections/${encodeURIComponent(id)}`,
692
+ ssoConnectionInputToWire(input)
693
+ );
694
+ return wireToSsoConnection(body);
695
+ },
696
+ async deleteConnection(id) {
697
+ await deleteFn(`/v1/sso/connections/${encodeURIComponent(id)}`);
698
+ },
699
+ async activateConnection(id) {
700
+ const { body } = await postFn(
701
+ `/v1/sso/connections/${encodeURIComponent(id)}/activate`,
702
+ {}
703
+ );
704
+ return { ok: body.ok, supabaseProviderId: body.supabase_provider_id };
705
+ },
706
+ async enforce(action) {
707
+ const { body } = await postFn("/v1/sso/enforce", { action });
708
+ return {
709
+ ok: body.ok,
710
+ action: body.action,
711
+ enforceSso: body.enforce_sso,
712
+ enforceSsoAt: body.enforce_sso_at
713
+ };
714
+ },
715
+ async getStatus() {
716
+ const { body } = await getFn("/v1/sso/status");
717
+ return wireToSsoReadiness(body.readiness);
718
+ },
719
+ async listJitRules(connectionId) {
720
+ const qs = connectionId ? new URLSearchParams({ connection_id: connectionId }) : void 0;
721
+ const { body } = await getFn("/v1/sso/jit-rules", qs);
722
+ return { rules: (body.rules ?? []).map(wireToSsoJitRule) };
723
+ },
724
+ async createJitRule(input) {
725
+ const payload = {
726
+ connection_id: input.connectionId,
727
+ claim_attribute: input.claimAttribute,
728
+ claim_value: input.claimValue,
729
+ granted_role: input.grantedRole
730
+ };
731
+ if (input.precedence !== void 0) payload["precedence"] = input.precedence;
732
+ const { body } = await postFn("/v1/sso/jit-rules", payload);
733
+ return wireToSsoJitRule(body);
734
+ },
735
+ async patchJitRule(id, patch) {
736
+ const payload = {};
737
+ if (patch.claimAttribute !== void 0) payload["claim_attribute"] = patch.claimAttribute;
738
+ if (patch.claimValue !== void 0) payload["claim_value"] = patch.claimValue;
739
+ if (patch.grantedRole !== void 0) payload["granted_role"] = patch.grantedRole;
740
+ if (patch.precedence !== void 0) payload["precedence"] = patch.precedence;
741
+ if (patch.isActive !== void 0) payload["is_active"] = patch.isActive;
742
+ const { body } = await patchFn(
743
+ `/v1/sso/jit-rules/${encodeURIComponent(id)}`,
744
+ payload
745
+ );
746
+ return wireToSsoJitRule(body);
747
+ },
748
+ async deleteJitRule(id) {
749
+ await deleteFn(`/v1/sso/jit-rules/${encodeURIComponent(id)}`);
750
+ }
751
+ };
752
+ }
753
+
754
+ // src/access-governance-log.ts
755
+ function wireToEvent(w) {
756
+ return {
757
+ id: w.id,
758
+ eventType: w.event_type,
759
+ orgId: w.org_id,
760
+ actorId: w.actor_id,
761
+ actorEmail: w.actor_email,
762
+ ipAddress: w.ip_address,
763
+ metadata: w.metadata ?? {},
764
+ createdAt: w.created_at
765
+ };
766
+ }
767
+ function makeAccessGovernanceLogClient(getFn) {
768
+ return {
769
+ async list(query = {}) {
770
+ const qs = new URLSearchParams();
771
+ if (query.limit !== void 0) qs.set("limit", String(query.limit));
772
+ if (query.cursor) qs.set("cursor", query.cursor);
773
+ if (query.eventType) qs.set("event_type", query.eventType);
774
+ if (query.actorId) qs.set("actor_id", query.actorId);
775
+ if (query.from) qs.set("from", query.from);
776
+ if (query.to) qs.set("to", query.to);
777
+ const { body } = await getFn(
778
+ "/v1/access-governance-log",
779
+ qs.size > 0 ? qs : void 0
780
+ );
781
+ return {
782
+ events: (body.events ?? []).map(wireToEvent),
783
+ nextCursor: body.next_cursor,
784
+ totalCount: body.total_count ?? 0
785
+ };
786
+ }
787
+ };
788
+ }
789
+
236
790
  // src/client.ts
237
791
  var DEFAULT_BASE_URL = "https://api.atlasent.io";
238
792
  var DEFAULT_TIMEOUT_MS = 1e4;
239
- var SDK_VERSION = "2.2.0";
793
+ var SDK_VERSION = "2.10.0";
794
+ var warnedBrowser = false;
795
+ var V1_EVALUATE_BATCH_PATH = "/v1/evaluate/batch";
796
+ var V1_EVALUATE_BATCH_LEGACY_PATH = "/v1-evaluate-batch";
797
+ var V1_EVALUATE_STREAM_PATH = "/v1/evaluate/stream";
798
+ var V1_EVALUATE_STREAM_LEGACY_PATH = "/v1-evaluate-stream";
240
799
  function _buildUserAgent() {
241
800
  const isNode2 = typeof process !== "undefined" && typeof process?.versions?.node === "string";
242
801
  return isNode2 ? `@atlasent/sdk/${SDK_VERSION} node/${process.version}` : `@atlasent/sdk/${SDK_VERSION} browser`;
@@ -300,6 +859,18 @@ var AtlaSentClient = class {
300
859
  fetchImpl;
301
860
  userAgent;
302
861
  retryPolicy;
862
+ /** SCIM 2.0 provisioning sub-client. Access as `client.scim`. */
863
+ scim;
864
+ /** Evidence bundle sub-client. Access as `client.evidenceBundles`. */
865
+ evidenceBundles;
866
+ /** Auth / token management sub-client. Access as `client.auth`. */
867
+ auth;
868
+ /** SSO administration sub-client. Access as `client.sso`. */
869
+ sso;
870
+ /** Access governance log sub-client. Access as `client.accessGovernanceLog`. */
871
+ accessGovernanceLog;
872
+ /** Trust-root snapshot manager for this client instance. */
873
+ trustRoot;
303
874
  constructor(options) {
304
875
  if (!options.apiKey || typeof options.apiKey !== "string") {
305
876
  throw new AtlaSentError("apiKey is required", {
@@ -312,6 +883,12 @@ var AtlaSentClient = class {
312
883
  { code: "network" }
313
884
  );
314
885
  }
886
+ if (!warnedBrowser && typeof globalThis["window"] !== "undefined" && typeof process === "undefined") {
887
+ warnedBrowser = true;
888
+ console.warn(
889
+ "[@atlasent/sdk] Running in a browser environment. API keys should not be exposed in client-side bundles. Use a server-side proxy instead."
890
+ );
891
+ }
315
892
  this.apiKey = _validateApiKey(options.apiKey);
316
893
  this.baseUrl = _enforceTls(options.baseUrl ?? DEFAULT_BASE_URL).replace(
317
894
  /\/+$/,
@@ -321,6 +898,44 @@ var AtlaSentClient = class {
321
898
  this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
322
899
  this.userAgent = _buildUserAgent();
323
900
  this.retryPolicy = mergePolicy(options.retryPolicy ?? {});
901
+ this.scim = makeScimClient(
902
+ (path, body, query) => this._post(path, body, query),
903
+ (path, query) => this._get(path, query),
904
+ (path, body) => this._put(path, body),
905
+ (path) => this._delete(path)
906
+ );
907
+ this.evidenceBundles = makeEvidenceBundleClient(
908
+ (path, body) => this._post(path, body),
909
+ (path, query) => this._get(path, query),
910
+ (path) => this._getRaw(path)
911
+ );
912
+ this.auth = makeAuthClient(
913
+ (path, body) => this._post(path, body),
914
+ (path) => this._get(path)
915
+ );
916
+ this.sso = makeSsoClient(
917
+ (path, query) => this._get(path, query),
918
+ (path, body) => this._post(path, body),
919
+ (path, body) => this._patch(path, body),
920
+ (path) => this._delete(path)
921
+ );
922
+ this.accessGovernanceLog = makeAccessGovernanceLogClient(
923
+ (path, query) => this._get(path, query)
924
+ );
925
+ if (options.trustRootUrl !== void 0 || options.trustSnapshotRefreshMs !== void 0) {
926
+ const globalSnap = getGlobalTrustRootManager({ disableRefresh: true }).getSnapshot();
927
+ this.trustRoot = new TrustRootManager(globalSnap, {
928
+ ...options.trustRootUrl !== void 0 && { refreshBaseUrl: options.trustRootUrl },
929
+ ...options.trustSnapshotRefreshMs !== void 0 && { refreshIntervalMs: options.trustSnapshotRefreshMs }
930
+ });
931
+ } else {
932
+ this.trustRoot = getGlobalTrustRootManager();
933
+ }
934
+ this.trustRoot.checkExpiry();
935
+ }
936
+ /** Return the current trust-root snapshot (pinned or last successful refresh). */
937
+ getTrustSnapshot() {
938
+ return this.trustRoot.getSnapshot();
324
939
  }
325
940
  /**
326
941
  * Ask the policy engine whether an agent action is permitted.
@@ -347,6 +962,11 @@ var AtlaSentClient = class {
347
962
  context: normalized.context ?? {}
348
963
  };
349
964
  if (normalized.explain !== void 0) body.explain = normalized.explain;
965
+ if (normalized.environment !== void 0) body.environment = normalized.environment;
966
+ if (normalized.resource !== void 0) body.resource = normalized.resource;
967
+ if (normalized.current_state !== void 0) body.current_state = normalized.current_state;
968
+ if (normalized.proposed_state !== void 0) body.proposed_state = normalized.proposed_state;
969
+ if (normalized.execution_binding !== void 0) body.execution_binding = normalized.execution_binding;
350
970
  const { body: wire, rateLimit } = await this.post(
351
971
  "/v1-evaluate",
352
972
  body
@@ -393,13 +1013,25 @@ var AtlaSentClient = class {
393
1013
  hardBlocks: wire.risk_envelope.hard_blocks ?? [],
394
1014
  ...wire.risk_envelope.factors && { factors: wire.risk_envelope.factors }
395
1015
  }
396
- }
1016
+ },
1017
+ ...wire.risk_class !== void 0 && { riskClass: wire.risk_class },
1018
+ ...wire.authority_basis && {
1019
+ authorityBasis: {
1020
+ kind: wire.authority_basis.kind,
1021
+ ...wire.authority_basis.reference !== void 0 && { reference: wire.authority_basis.reference },
1022
+ ...wire.authority_basis.granted_by !== void 0 && { grantedBy: wire.authority_basis.granted_by },
1023
+ ...wire.authority_basis.rationale !== void 0 && { rationale: wire.authority_basis.rationale },
1024
+ ...wire.authority_basis.expires_at !== void 0 && { expiresAt: wire.authority_basis.expires_at }
1025
+ }
1026
+ },
1027
+ ...wire.escalation_id !== void 0 && { escalationId: wire.escalation_id }
397
1028
  };
398
1029
  }
399
1030
  /**
400
1031
  * Batch evaluate — send up to 100 decisions in a single round-trip.
401
1032
  *
402
- * Wraps `POST /v1-evaluate-batch`. The server evaluates each item
1033
+ * Wraps `POST /v1/evaluate/batch` (with fallback to
1034
+ * `POST /v1-evaluate-batch` on older runtimes). The server evaluates each item
403
1035
  * against the active policy bundle and returns results in the same
404
1036
  * order as the input. One rate-limit token is consumed for the
405
1037
  * whole batch, and one audit-chain entry lists every included
@@ -437,8 +1069,9 @@ var AtlaSentClient = class {
437
1069
  }));
438
1070
  const wireBody = { items: wireItems };
439
1071
  if (batchId) wireBody.batch_id = batchId;
440
- const { body: wire, rateLimit } = await this.post(
441
- "/v1-evaluate-batch",
1072
+ const { body: wire, rateLimit } = await this.postWithPathFallback(
1073
+ V1_EVALUATE_BATCH_PATH,
1074
+ V1_EVALUATE_BATCH_LEGACY_PATH,
442
1075
  wireBody
443
1076
  );
444
1077
  const items = (wire.items ?? []).map(
@@ -731,6 +1364,7 @@ var AtlaSentClient = class {
731
1364
  const agent = input.agent ?? "ci-deploy-bot";
732
1365
  const action = input.action ?? PRODUCTION_DEPLOY_ACTION;
733
1366
  const context = input.context ?? {};
1367
+ const environment = typeof context.environment === "string" ? context.environment : typeof context.environment_name === "string" ? context.environment_name : void 0;
734
1368
  const evaluation = await this.evaluate({ agent, action, context });
735
1369
  if (evaluation.decision !== "allow") {
736
1370
  return {
@@ -747,7 +1381,8 @@ var AtlaSentClient = class {
747
1381
  permitId: evaluation.permitId,
748
1382
  agent,
749
1383
  action,
750
- context
1384
+ context,
1385
+ ...environment !== void 0 ? { environment } : {}
751
1386
  });
752
1387
  if (!verification.verified) {
753
1388
  return {
@@ -961,15 +1596,15 @@ var AtlaSentClient = class {
961
1596
  */
962
1597
  async keySelf() {
963
1598
  const { body: wire, rateLimit } = await this.get("/v1-api-key-self");
964
- if (typeof wire.key_id !== "string" || typeof wire.organization_id !== "string") {
1599
+ if (typeof wire.key_id !== "string" || typeof wire.org_id !== "string") {
965
1600
  throw new AtlaSentError(
966
- "Malformed response from /v1-api-key-self: missing `key_id` or `organization_id`",
1601
+ "Malformed response from /v1-api-key-self: missing `key_id` or `org_id`",
967
1602
  { code: "bad_response" }
968
1603
  );
969
1604
  }
970
1605
  return {
971
1606
  keyId: wire.key_id,
972
- organizationId: wire.organization_id,
1607
+ orgId: wire.org_id,
973
1608
  environment: wire.environment,
974
1609
  scopes: wire.scopes ?? [],
975
1610
  allowedCidrs: wire.allowed_cidrs ?? null,
@@ -1181,7 +1816,8 @@ var AtlaSentClient = class {
1181
1816
  return response;
1182
1817
  }
1183
1818
  /**
1184
- * Open a streaming evaluation session against `POST /v1-evaluate-stream`.
1819
+ * Open a streaming evaluation session against `POST /v1/evaluate/stream`
1820
+ * (with fallback to `POST /v1-evaluate-stream` on older runtimes).
1185
1821
  *
1186
1822
  * Yields {@link StreamDecisionEvent} and {@link StreamProgressEvent} objects
1187
1823
  * as the server emits them. The iterator ends cleanly when the server sends
@@ -1219,7 +1855,7 @@ var AtlaSentClient = class {
1219
1855
  api_key: this.apiKey
1220
1856
  };
1221
1857
  const requestId = globalThis.crypto.randomUUID();
1222
- const url = `${this.baseUrl}/v1-evaluate-stream`;
1858
+ let streamPath = V1_EVALUATE_STREAM_PATH;
1223
1859
  let lastEventId;
1224
1860
  let retryCount = 0;
1225
1861
  while (true) {
@@ -1239,7 +1875,7 @@ var AtlaSentClient = class {
1239
1875
  const signal = opts.signal ? AbortSignal.any([connectionTimeoutSignal, opts.signal]) : connectionTimeoutSignal;
1240
1876
  let response;
1241
1877
  try {
1242
- response = await this.fetchImpl(url, {
1878
+ response = await this.fetchImpl(`${this.baseUrl}${streamPath}`, {
1243
1879
  method: "POST",
1244
1880
  headers,
1245
1881
  body: JSON.stringify(body),
@@ -1255,6 +1891,10 @@ var AtlaSentClient = class {
1255
1891
  throw mapped;
1256
1892
  }
1257
1893
  if (!response.ok) {
1894
+ if (streamPath === V1_EVALUATE_STREAM_PATH && (response.status === 404 || response.status === 405)) {
1895
+ streamPath = V1_EVALUATE_STREAM_LEGACY_PATH;
1896
+ continue;
1897
+ }
1258
1898
  throw await buildHttpError(response, requestId);
1259
1899
  }
1260
1900
  if (!response.body) {
@@ -1306,6 +1946,16 @@ var AtlaSentClient = class {
1306
1946
  async post(path, body, query) {
1307
1947
  return this.request(path, "POST", body, query);
1308
1948
  }
1949
+ async postWithPathFallback(primaryPath, fallbackPath, body, query) {
1950
+ try {
1951
+ return await this.post(primaryPath, body, query);
1952
+ } catch (err) {
1953
+ if (err instanceof AtlaSentError && (err.status === 404 || err.status === 405)) {
1954
+ return this.post(fallbackPath, body, query);
1955
+ }
1956
+ throw err;
1957
+ }
1958
+ }
1309
1959
  async get(path, query) {
1310
1960
  return this.request(path, "GET", void 0, query);
1311
1961
  }
@@ -1998,6 +2648,82 @@ var AtlaSentClient = class {
1998
2648
  );
1999
2649
  return [...body.evaluations ?? []];
2000
2650
  }
2651
+ // ── Private adapters for sub-client factories ──────────────────────────────
2652
+ // Thin wrappers that expose the private request infrastructure to sub-client
2653
+ // factories (scim, evidenceBundles, auth) without widening the public API.
2654
+ async _post(path, body, query) {
2655
+ const { body: b } = await this.post(path, body, query);
2656
+ return { body: b };
2657
+ }
2658
+ async _get(path, query) {
2659
+ const { body: b } = await this.get(path, query);
2660
+ return { body: b };
2661
+ }
2662
+ async _put(path, body) {
2663
+ return this._requestRaw(path, "PUT", body, void 0);
2664
+ }
2665
+ async _patch(path, body) {
2666
+ return this._requestRaw(path, "PATCH", body, void 0);
2667
+ }
2668
+ async _delete(path) {
2669
+ await this._requestRaw(path, "DELETE", void 0, void 0);
2670
+ }
2671
+ async _getRaw(path) {
2672
+ const url = `${this.baseUrl}${path}`;
2673
+ const requestId = globalThis.crypto.randomUUID();
2674
+ const headers = {
2675
+ Authorization: `Bearer ${this.apiKey}`,
2676
+ "User-Agent": this.userAgent,
2677
+ "X-Request-ID": requestId,
2678
+ "X-AtlaSent-Protocol-Version": "1"
2679
+ };
2680
+ const response = await this.fetchImpl(url, {
2681
+ method: "GET",
2682
+ headers,
2683
+ signal: AbortSignal.timeout(this.timeoutMs)
2684
+ });
2685
+ if (!response.ok) {
2686
+ const text = await response.text().catch(() => "");
2687
+ throw new AtlaSentError(`GET ${path} returned ${response.status}`, {
2688
+ code: response.status >= 500 ? "server_error" : "bad_request",
2689
+ status: response.status,
2690
+ requestId
2691
+ });
2692
+ }
2693
+ return response.arrayBuffer();
2694
+ }
2695
+ async _requestRaw(path, method, body, query) {
2696
+ const qs = query && Array.from(query).length > 0 ? `?${query.toString()}` : "";
2697
+ const url = `${this.baseUrl}${path}${qs}`;
2698
+ const requestId = globalThis.crypto.randomUUID();
2699
+ const headers = {
2700
+ Accept: "application/json",
2701
+ Authorization: `Bearer ${this.apiKey}`,
2702
+ "User-Agent": this.userAgent,
2703
+ "X-Request-ID": requestId,
2704
+ "X-AtlaSent-Protocol-Version": "1"
2705
+ };
2706
+ if (method === "PUT" && body !== void 0) {
2707
+ headers["Content-Type"] = "application/json";
2708
+ }
2709
+ const init = { method, headers, signal: AbortSignal.timeout(this.timeoutMs) };
2710
+ if (method === "PUT" && body !== void 0) {
2711
+ init.body = JSON.stringify(body);
2712
+ }
2713
+ const response = await this.fetchImpl(url, init);
2714
+ if (!response.ok) {
2715
+ const text = await response.text().catch(() => "");
2716
+ throw new AtlaSentError(`${method} ${path} returned ${response.status}`, {
2717
+ code: response.status >= 500 ? "server_error" : "bad_request",
2718
+ status: response.status,
2719
+ requestId
2720
+ });
2721
+ }
2722
+ if (method === "DELETE") {
2723
+ return { body: {} };
2724
+ }
2725
+ return { body: await response.json() };
2726
+ }
2001
2727
  };
2002
2728
  function parseRateLimitHeaders(headers) {
2003
2729
  const rawLimit = headers.get("x-ratelimit-limit");
@@ -2143,7 +2869,7 @@ function buildAuditEventsQuery(query) {
2143
2869
  return params;
2144
2870
  }
2145
2871
  function sleep(ms) {
2146
- return new Promise((resolve2) => setTimeout(resolve2, ms));
2872
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
2147
2873
  }
2148
2874
  function parseRetryAfter(raw) {
2149
2875
  if (!raw) return void 0;
@@ -2164,14 +2890,14 @@ async function* parseSseStream(body, requestId, timeoutMs, onEventId) {
2164
2890
  if (timeoutMs <= 0) {
2165
2891
  return reader.read();
2166
2892
  }
2167
- return new Promise((resolve2, reject) => {
2893
+ return new Promise((resolve3, reject) => {
2168
2894
  const timer = setTimeout(() => {
2169
2895
  reject(new StreamTimeoutError(timeoutMs));
2170
2896
  }, timeoutMs);
2171
2897
  reader.read().then(
2172
2898
  (result) => {
2173
2899
  clearTimeout(timer);
2174
- resolve2(result);
2900
+ resolve3(result);
2175
2901
  },
2176
2902
  (err) => {
2177
2903
  clearTimeout(timer);
@@ -2344,6 +3070,15 @@ async function protect(request) {
2344
3070
  { code: "bad_request" }
2345
3071
  );
2346
3072
  }
3073
+ const trustMgr = getGlobalTrustRootManager({ disableRefresh: false });
3074
+ if (trustMgr.checkExpiry() === "expired") {
3075
+ const snap = trustMgr.getSnapshot();
3076
+ throw new BundleVerificationError({
3077
+ reason: "trust_snapshot_expired",
3078
+ snapshotValidUntil: snap.valid_until,
3079
+ snapshotFetchedAt: snap.issued_at
3080
+ });
3081
+ }
2347
3082
  const client = getClient();
2348
3083
  const evaluation = await client.evaluate(request);
2349
3084
  if (evaluation.decision !== "allow") {
@@ -2398,15 +3133,15 @@ async function protect(request) {
2398
3133
 
2399
3134
  // src/hono.ts
2400
3135
  var DEFAULT_CONTEXT_KEY = "atlasent";
2401
- async function resolve(value, c) {
3136
+ async function resolve2(value, c) {
2402
3137
  return typeof value === "function" ? await value(c) : value;
2403
3138
  }
2404
3139
  function atlaSentGuard(options) {
2405
3140
  const contextKey = options.key ?? DEFAULT_CONTEXT_KEY;
2406
3141
  return async (c, next) => {
2407
3142
  const [agent, action, ctx] = await Promise.all([
2408
- resolve(options.agent, c),
2409
- resolve(options.action, c),
3143
+ resolve2(options.agent, c),
3144
+ resolve2(options.action, c),
2410
3145
  options.context ? options.context(c) : Promise.resolve(void 0)
2411
3146
  ]);
2412
3147
  const request = { agent, action };