@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.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,8 +109,198 @@ 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
+ }
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
+ }
111
135
  };
112
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
+ }
303
+
113
304
  // src/types.ts
114
305
  var PRODUCTION_DEPLOY_ACTION = "production.deploy";
115
306
  var DEPLOY_GATE_CODES = Object.freeze({
@@ -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) {
@@ -1267,6 +1906,16 @@ var AtlaSentClient = class {
1267
1906
  async post(path, body, query) {
1268
1907
  return this.request(path, "POST", body, query);
1269
1908
  }
1909
+ async postWithPathFallback(primaryPath, fallbackPath, body, query) {
1910
+ try {
1911
+ return await this.post(primaryPath, body, query);
1912
+ } catch (err) {
1913
+ if (err instanceof AtlaSentError && (err.status === 404 || err.status === 405)) {
1914
+ return this.post(fallbackPath, body, query);
1915
+ }
1916
+ throw err;
1917
+ }
1918
+ }
1270
1919
  async get(path, query) {
1271
1920
  return this.request(path, "GET", void 0, query);
1272
1921
  }
@@ -1959,6 +2608,82 @@ var AtlaSentClient = class {
1959
2608
  );
1960
2609
  return [...body.evaluations ?? []];
1961
2610
  }
2611
+ // ── Private adapters for sub-client factories ──────────────────────────────
2612
+ // Thin wrappers that expose the private request infrastructure to sub-client
2613
+ // factories (scim, evidenceBundles, auth) without widening the public API.
2614
+ async _post(path, body, query) {
2615
+ const { body: b } = await this.post(path, body, query);
2616
+ return { body: b };
2617
+ }
2618
+ async _get(path, query) {
2619
+ const { body: b } = await this.get(path, query);
2620
+ return { body: b };
2621
+ }
2622
+ async _put(path, body) {
2623
+ return this._requestRaw(path, "PUT", body, void 0);
2624
+ }
2625
+ async _patch(path, body) {
2626
+ return this._requestRaw(path, "PATCH", body, void 0);
2627
+ }
2628
+ async _delete(path) {
2629
+ await this._requestRaw(path, "DELETE", void 0, void 0);
2630
+ }
2631
+ async _getRaw(path) {
2632
+ const url = `${this.baseUrl}${path}`;
2633
+ const requestId = globalThis.crypto.randomUUID();
2634
+ const headers = {
2635
+ Authorization: `Bearer ${this.apiKey}`,
2636
+ "User-Agent": this.userAgent,
2637
+ "X-Request-ID": requestId,
2638
+ "X-AtlaSent-Protocol-Version": "1"
2639
+ };
2640
+ const response = await this.fetchImpl(url, {
2641
+ method: "GET",
2642
+ headers,
2643
+ signal: AbortSignal.timeout(this.timeoutMs)
2644
+ });
2645
+ if (!response.ok) {
2646
+ const text = await response.text().catch(() => "");
2647
+ throw new AtlaSentError(`GET ${path} returned ${response.status}`, {
2648
+ code: response.status >= 500 ? "server_error" : "bad_request",
2649
+ status: response.status,
2650
+ requestId
2651
+ });
2652
+ }
2653
+ return response.arrayBuffer();
2654
+ }
2655
+ async _requestRaw(path, method, body, query) {
2656
+ const qs = query && Array.from(query).length > 0 ? `?${query.toString()}` : "";
2657
+ const url = `${this.baseUrl}${path}${qs}`;
2658
+ const requestId = globalThis.crypto.randomUUID();
2659
+ const headers = {
2660
+ Accept: "application/json",
2661
+ Authorization: `Bearer ${this.apiKey}`,
2662
+ "User-Agent": this.userAgent,
2663
+ "X-Request-ID": requestId,
2664
+ "X-AtlaSent-Protocol-Version": "1"
2665
+ };
2666
+ if (method === "PUT" && body !== void 0) {
2667
+ headers["Content-Type"] = "application/json";
2668
+ }
2669
+ const init = { method, headers, signal: AbortSignal.timeout(this.timeoutMs) };
2670
+ if (method === "PUT" && body !== void 0) {
2671
+ init.body = JSON.stringify(body);
2672
+ }
2673
+ const response = await this.fetchImpl(url, init);
2674
+ if (!response.ok) {
2675
+ const text = await response.text().catch(() => "");
2676
+ throw new AtlaSentError(`${method} ${path} returned ${response.status}`, {
2677
+ code: response.status >= 500 ? "server_error" : "bad_request",
2678
+ status: response.status,
2679
+ requestId
2680
+ });
2681
+ }
2682
+ if (method === "DELETE") {
2683
+ return { body: {} };
2684
+ }
2685
+ return { body: await response.json() };
2686
+ }
1962
2687
  };
1963
2688
  function parseRateLimitHeaders(headers) {
1964
2689
  const rawLimit = headers.get("x-ratelimit-limit");
@@ -2104,7 +2829,7 @@ function buildAuditEventsQuery(query) {
2104
2829
  return params;
2105
2830
  }
2106
2831
  function sleep(ms) {
2107
- return new Promise((resolve2) => setTimeout(resolve2, ms));
2832
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
2108
2833
  }
2109
2834
  function parseRetryAfter(raw) {
2110
2835
  if (!raw) return void 0;
@@ -2125,14 +2850,14 @@ async function* parseSseStream(body, requestId, timeoutMs, onEventId) {
2125
2850
  if (timeoutMs <= 0) {
2126
2851
  return reader.read();
2127
2852
  }
2128
- return new Promise((resolve2, reject) => {
2853
+ return new Promise((resolve3, reject) => {
2129
2854
  const timer = setTimeout(() => {
2130
2855
  reject(new StreamTimeoutError(timeoutMs));
2131
2856
  }, timeoutMs);
2132
2857
  reader.read().then(
2133
2858
  (result) => {
2134
2859
  clearTimeout(timer);
2135
- resolve2(result);
2860
+ resolve3(result);
2136
2861
  },
2137
2862
  (err) => {
2138
2863
  clearTimeout(timer);
@@ -2305,6 +3030,15 @@ async function protect(request) {
2305
3030
  { code: "bad_request" }
2306
3031
  );
2307
3032
  }
3033
+ const trustMgr = getGlobalTrustRootManager({ disableRefresh: false });
3034
+ if (trustMgr.checkExpiry() === "expired") {
3035
+ const snap = trustMgr.getSnapshot();
3036
+ throw new BundleVerificationError({
3037
+ reason: "trust_snapshot_expired",
3038
+ snapshotValidUntil: snap.valid_until,
3039
+ snapshotFetchedAt: snap.issued_at
3040
+ });
3041
+ }
2308
3042
  const client = getClient();
2309
3043
  const evaluation = await client.evaluate(request);
2310
3044
  if (evaluation.decision !== "allow") {
@@ -2359,15 +3093,15 @@ async function protect(request) {
2359
3093
 
2360
3094
  // src/hono.ts
2361
3095
  var DEFAULT_CONTEXT_KEY = "atlasent";
2362
- async function resolve(value, c) {
3096
+ async function resolve2(value, c) {
2363
3097
  return typeof value === "function" ? await value(c) : value;
2364
3098
  }
2365
3099
  function atlaSentGuard(options) {
2366
3100
  const contextKey = options.key ?? DEFAULT_CONTEXT_KEY;
2367
3101
  return async (c, next) => {
2368
3102
  const [agent, action, ctx] = await Promise.all([
2369
- resolve(options.agent, c),
2370
- resolve(options.action, c),
3103
+ resolve2(options.agent, c),
3104
+ resolve2(options.action, c),
2371
3105
  options.context ? options.context(c) : Promise.resolve(void 0)
2372
3106
  ]);
2373
3107
  const request = { agent, action };