@atlasent/sdk 1.5.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -23,22 +33,128 @@ __export(index_exports, {
23
33
  AtlaSentClient: () => AtlaSentClient,
24
34
  AtlaSentDeniedError: () => AtlaSentDeniedError,
25
35
  AtlaSentError: () => AtlaSentError,
36
+ AtlaSentEscalateError: () => AtlaSentEscalateError,
37
+ DEFAULT_INCENTIVE_CONFIG: () => DEFAULT_INCENTIVE_CONFIG,
26
38
  DEFAULT_RETRY_POLICY: () => DEFAULT_RETRY_POLICY,
39
+ DEFAULT_RISK_TIER_THRESHOLDS: () => DEFAULT_RISK_TIER_THRESHOLDS,
40
+ DEPLOYMENT_PRODUCTION_ACTION: () => DEPLOYMENT_PRODUCTION_ACTION,
41
+ DEPLOY_GATE_CODES: () => DEPLOY_GATE_CODES,
42
+ FeatureNotEnabledError: () => FeatureNotEnabledError,
43
+ GovernanceEnforcementError: () => GovernanceEnforcementError,
44
+ PRODUCTION_DEPLOY_ACTION: () => PRODUCTION_DEPLOY_ACTION,
45
+ PermitRevoked: () => PermitRevoked,
46
+ StreamParseError: () => StreamParseError,
47
+ StreamTimeoutError: () => StreamTimeoutError,
48
+ V2_BATCH_PATH: () => V2_BATCH_PATH,
49
+ V2_GRAPHQL_MAX_DEPTH: () => V2_GRAPHQL_MAX_DEPTH,
50
+ V2_GRAPHQL_PATH: () => V2_GRAPHQL_PATH,
51
+ V2_MAX_BATCH_ITEMS: () => V2_MAX_BATCH_ITEMS,
52
+ V2_MAX_BODY_BYTES: () => V2_MAX_BODY_BYTES,
53
+ V2_STREAM_PATH: () => V2_STREAM_PATH,
54
+ WebhookVerificationError: () => WebhookVerificationError,
55
+ assertWebhook: () => assertWebhook,
56
+ authorizeStream: () => authorizeStream,
57
+ budgetUtilizationSeverity: () => budgetUtilizationSeverity,
58
+ buildLiabilityChain: () => buildLiabilityChain,
59
+ buildLiabilityVisualization: () => buildLiabilityVisualization,
60
+ buildRiskTimeline: () => buildRiskTimeline,
61
+ buildSignableContent: () => buildSignableContent,
27
62
  canonicalJSON: () => canonicalJSON,
63
+ canonicalizeForEvidence: () => canonicalizeForEvidence,
64
+ checkAutonomousBounds: () => checkAutonomousBounds,
65
+ checkBudgetConstraints: () => checkBudgetConstraints,
66
+ clampTokenDuration: () => clampTokenDuration,
67
+ classifyCommand: () => classifyCommand,
68
+ classifyRiskTier: () => classifyRiskTier,
69
+ computeApprovalRiskScore: () => computeApprovalRiskScore,
28
70
  computeBackoffMs: () => computeBackoffMs,
71
+ computeEscalatedApprovalCount: () => computeEscalatedApprovalCount,
72
+ computeExposureScore: () => computeExposureScore,
73
+ computeGovernanceHealthScore: () => computeGovernanceHealthScore,
74
+ computeHHI: () => computeHHI,
75
+ computeLiabilityWeights: () => computeLiabilityWeights,
76
+ computeOverallRiskScore: () => computeOverallRiskScore,
77
+ computeOverrideScore: () => computeOverrideScore,
78
+ computeRemediationUrgency: () => computeRemediationUrgency,
79
+ computeSignalEngagementRate: () => computeSignalEngagementRate,
29
80
  configure: () => configure,
30
81
  default: () => index_default,
82
+ delegationPropagationHadEffect: () => delegationPropagationHadEffect,
83
+ deployGate: () => deployGate,
84
+ detectAutonomousAnomaly: () => detectAutonomousAnomaly,
85
+ detectMisalignedIncentives: () => detectMisalignedIncentives,
86
+ detectSelfApproval: () => detectSelfApproval,
87
+ enforceAutonomousBounds: () => enforceAutonomousBounds,
88
+ enforceBudgetConstraint: () => enforceBudgetConstraint,
89
+ enforceEconomicGovernance: () => enforceEconomicGovernance,
90
+ enforceFinancialQuorum: () => enforceFinancialQuorum,
91
+ evaluateFinancialQuorum: () => evaluateFinancialQuorum,
92
+ evaluateMany: () => evaluateMany,
93
+ evidenceRunPasses: () => evidenceRunPasses,
94
+ findPrimaryLiabilityParties: () => findPrimaryLiabilityParties,
95
+ formatPolicySyncDiff: () => formatPolicySyncDiff,
96
+ graphql: () => graphql,
31
97
  hasAttemptsLeft: () => hasAttemptsLeft,
98
+ hhiToConcentrationScore: () => hhiToConcentrationScore,
99
+ highestSeverityAction: () => highestSeverityAction,
100
+ hitlRequiredApproverCount: () => hitlRequiredApproverCount,
101
+ isBudgetExceptionActive: () => isBudgetExceptionActive,
102
+ isBudgetExceptionTerminal: () => isBudgetExceptionTerminal,
103
+ isEscalationSlaBreached: () => isEscalationSlaBreached,
104
+ isFreezeActive: () => isFreezeActive,
105
+ isImpersonationGrantUsable: () => isImpersonationGrantUsable,
106
+ isPolicySyncTerminal: () => isPolicySyncTerminal,
107
+ isRegulatoryEscalationTerminal: () => isRegulatoryEscalationTerminal,
32
108
  isRetryable: () => isRetryable,
109
+ isSandboxDiffPopulated: () => isSandboxDiffPopulated,
110
+ isSubstantiveSignalResponse: () => isSubstantiveSignalResponse,
111
+ matchAnomalyRules: () => matchAnomalyRules,
33
112
  mergePolicy: () => mergePolicy,
113
+ nonPassingControls: () => nonPassingControls,
114
+ normalizeEvaluateRequest: () => normalizeEvaluateRequest,
115
+ normalizeEvaluateResponse: () => normalizeEvaluateResponse,
116
+ normalizePermitOutcome: () => normalizePermitOutcome,
34
117
  protect: () => protect,
118
+ requirePermit: () => requirePermit,
119
+ scoreToRiskTier: () => scoreToRiskTier,
120
+ serializeSignableContent: () => serializeSignableContent,
35
121
  signedBytesFor: () => signedBytesFor,
122
+ summarizeCrossOrgPermission: () => summarizeCrossOrgPermission,
123
+ transitionDispute: () => transitionDispute,
124
+ transitionReversal: () => transitionReversal,
125
+ validateLiabilityChain: () => validateLiabilityChain,
36
126
  verifyAuditBundle: () => verifyAuditBundle,
37
- verifyBundle: () => verifyBundle
127
+ verifyBundle: () => verifyBundle,
128
+ verifyEvidenceBundleStructure: () => verifyEvidenceBundleStructure,
129
+ verifyWebhook: () => verifyWebhook,
130
+ verifyWebhookSignature: () => verifyWebhookSignature,
131
+ withPermit: () => withPermit,
132
+ withinAutonomousCeiling: () => withinAutonomousCeiling
38
133
  });
39
134
  module.exports = __toCommonJS(index_exports);
40
135
 
41
136
  // src/errors.ts
137
+ var StreamTimeoutError = class extends Error {
138
+ name = "StreamTimeoutError";
139
+ /** Timeout that was exceeded, in milliseconds. */
140
+ timeoutMs;
141
+ constructor(timeoutMs) {
142
+ super(`AtlaSent stream timed out after ${timeoutMs}ms with no event`);
143
+ this.timeoutMs = timeoutMs;
144
+ }
145
+ };
146
+ var StreamParseError = class extends Error {
147
+ name = "StreamParseError";
148
+ /** The raw data string that failed to parse. */
149
+ rawData;
150
+ constructor(rawData, cause) {
151
+ super(`AtlaSent stream received malformed JSON: ${rawData.slice(0, 200)}`);
152
+ this.rawData = rawData;
153
+ if (cause !== void 0) {
154
+ this.cause = cause;
155
+ }
156
+ }
157
+ };
42
158
  var AtlaSentError = class extends Error {
43
159
  // Subclasses override to their own literal (e.g. "AtlaSentDeniedError");
44
160
  // keep this assignable rather than pinned to a single literal.
@@ -62,6 +178,18 @@ var AtlaSentError = class extends Error {
62
178
  this.retryAfterMs = init.retryAfterMs;
63
179
  }
64
180
  };
181
+ var KNOWN_PERMIT_OUTCOMES = /* @__PURE__ */ new Set([
182
+ "permit_consumed",
183
+ "permit_expired",
184
+ "permit_revoked",
185
+ "permit_not_found"
186
+ ]);
187
+ function normalizePermitOutcome(raw) {
188
+ if (raw !== void 0 && KNOWN_PERMIT_OUTCOMES.has(raw)) {
189
+ return raw;
190
+ }
191
+ return void 0;
192
+ }
65
193
  var AtlaSentDeniedError = class extends AtlaSentError {
66
194
  name = "AtlaSentDeniedError";
67
195
  /** Policy decision — `"deny"` today; `"hold"` / `"escalate"` reserved. */
@@ -72,6 +200,12 @@ var AtlaSentDeniedError = class extends AtlaSentError {
72
200
  reason;
73
201
  /** Hash-chained audit-trail entry associated with the decision. */
74
202
  auditHash;
203
+ /**
204
+ * Discriminator for permit-side denial reasons. Populated only
205
+ * when the server reported `verified=false` from `/v1-verify-permit`;
206
+ * `undefined` for evaluate-time denials. See {@link PermitOutcome}.
207
+ */
208
+ outcome;
75
209
  constructor(init) {
76
210
  const msg = init.reason ? `AtlaSent ${init.decision}: ${init.reason}` : `AtlaSent ${init.decision}`;
77
211
  const errInit = { status: 200 };
@@ -81,92 +215,665 @@ var AtlaSentDeniedError = class extends AtlaSentError {
81
215
  this.evaluationId = init.evaluationId;
82
216
  this.reason = init.reason;
83
217
  this.auditHash = init.auditHash;
218
+ this.outcome = init.outcome;
219
+ }
220
+ // ── Outcome discriminators ───────────────────────────────────────
221
+ // Convenience predicates that mirror the operator runbook's matrix.
222
+ // Callers can compare `outcome` directly; these are sugar so the
223
+ // common cases are explicit at the call site.
224
+ /** `true` when the permit was explicitly revoked (D3 endpoint). */
225
+ get isRevoked() {
226
+ return this.outcome === "permit_revoked";
227
+ }
228
+ /** `true` when the permit's TTL passed before verification. */
229
+ get isExpired() {
230
+ return this.outcome === "permit_expired";
231
+ }
232
+ /**
233
+ * `true` when the permit was already consumed by a prior verify
234
+ * (v1 single-use replay protection).
235
+ */
236
+ get isConsumed() {
237
+ return this.outcome === "permit_consumed";
238
+ }
239
+ /**
240
+ * `true` when the permit id wasn't recognized server-side
241
+ * (typo, cross-tenant lookup, or pre-issuance race).
242
+ */
243
+ get isNotFound() {
244
+ return this.outcome === "permit_not_found";
245
+ }
246
+ };
247
+ var AtlaSentEscalateError = class extends AtlaSentError {
248
+ name = "AtlaSentEscalateError";
249
+ /** Always `"escalate"` — discriminates this error from other AtlaSent errors. */
250
+ decision = "escalate";
251
+ /** The user whose action triggered the escalation, if available. */
252
+ userId;
253
+ constructor(message, opts) {
254
+ super(message, {
255
+ ...opts?.requestId !== void 0 ? { requestId: opts.requestId } : {},
256
+ cause: opts?.cause
257
+ });
258
+ this.userId = opts?.userId;
259
+ }
260
+ };
261
+ var PermitRevoked = class extends AtlaSentError {
262
+ name = "PermitRevoked";
263
+ /** The id of the permit that was revoked mid-execution. */
264
+ permitId;
265
+ /** The `scope_revocations.id` that triggered the revocation, when available. */
266
+ revocationId;
267
+ constructor(permitId, revocationId) {
268
+ super(
269
+ revocationId ? `AtlaSent: permit ${permitId} revoked (revocation: ${revocationId}) \u2014 guard heartbeat halted execution` : `AtlaSent: permit ${permitId} revoked \u2014 guard heartbeat halted execution`
270
+ );
271
+ this.permitId = permitId;
272
+ this.revocationId = revocationId;
273
+ }
274
+ };
275
+
276
+ // src/types.ts
277
+ var PRODUCTION_DEPLOY_ACTION = "production.deploy";
278
+ var DEPLOYMENT_PRODUCTION_ACTION = "deployment.production";
279
+ var DEPLOY_GATE_CODES = Object.freeze({
280
+ ALLOW: "ALLOW",
281
+ DENY_POLICY: "DENY_POLICY",
282
+ DENY_AUTHORITY: "DENY_AUTHORITY",
283
+ DENY_ENVIRONMENT: "DENY_ENVIRONMENT",
284
+ PERMIT_EXPIRED: "PERMIT_EXPIRED",
285
+ VERIFY_FAILED: "VERIFY_FAILED",
286
+ ESCALATE_REQUIRED: "ESCALATE_REQUIRED",
287
+ OVERRIDE_APPROVED: "OVERRIDE_APPROVED"
288
+ });
289
+
290
+ // src/compat.ts
291
+ function normalizeEvaluateRequest(input) {
292
+ if ("action" in input && !("action_type" in input)) {
293
+ console.warn(
294
+ "[atlasent] Deprecation: action/agent request shape is deprecated. Use action_type/actor_id instead. This compatibility shim will be removed in v3.0.0."
295
+ );
296
+ const legacy = input;
297
+ const normalized = {
298
+ action_type: legacy.action,
299
+ actor_id: legacy.agent
300
+ };
301
+ if (legacy.context !== void 0) {
302
+ normalized.context = legacy.context;
303
+ }
304
+ return normalized;
305
+ }
306
+ return input;
307
+ }
308
+ function normalizeEvaluateResponse(wire) {
309
+ if (!("decision" in wire) && "permitted" in wire) {
310
+ const legacy = wire;
311
+ const normalized = {
312
+ decision: legacy.permitted ? "allow" : "deny"
313
+ };
314
+ if (legacy.decision_id !== void 0) {
315
+ normalized.permit_token = legacy.decision_id;
316
+ }
317
+ if (!legacy.permitted && legacy.reason) {
318
+ normalized.denial = { reason: legacy.reason };
319
+ }
320
+ return normalized;
84
321
  }
322
+ return wire;
323
+ }
324
+
325
+ // src/retry.ts
326
+ var DEFAULT_RETRY_POLICY = {
327
+ maxAttempts: 4,
328
+ baseDelayMs: 2e3,
329
+ maxDelayMs: 16e3
85
330
  };
331
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set([
332
+ "network",
333
+ "timeout",
334
+ "rate_limited",
335
+ "server_error",
336
+ "bad_response"
337
+ ]);
338
+ function isRetryable(err) {
339
+ if (!(err instanceof AtlaSentError)) return false;
340
+ if (err.code === void 0) return false;
341
+ return RETRYABLE_CODES.has(err.code);
342
+ }
343
+ function computeBackoffMs(attempt, policy = {}, err, random = Math.random) {
344
+ const merged = mergePolicy(policy);
345
+ const safeAttempt = Math.max(0, Math.floor(attempt));
346
+ const exp = Math.min(safeAttempt, 30);
347
+ const ceiling = Math.min(merged.maxDelayMs, merged.baseDelayMs * 2 ** exp);
348
+ const jittered = Math.floor(ceiling * clampUnit(random()));
349
+ const retryAfterMs = err instanceof AtlaSentError && typeof err.retryAfterMs === "number" ? Math.max(0, err.retryAfterMs) : 0;
350
+ return Math.max(retryAfterMs, jittered);
351
+ }
352
+ function hasAttemptsLeft(attempt, policy = {}) {
353
+ const merged = mergePolicy(policy);
354
+ return attempt + 1 < merged.maxAttempts;
355
+ }
356
+ function mergePolicy(policy) {
357
+ const maxAttempts = Math.max(
358
+ 1,
359
+ Math.floor(policy.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts)
360
+ );
361
+ const baseDelayMs = Math.max(
362
+ 0,
363
+ policy.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs
364
+ );
365
+ const maxDelayMs = Math.max(
366
+ baseDelayMs,
367
+ policy.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs
368
+ );
369
+ return { maxAttempts, baseDelayMs, maxDelayMs };
370
+ }
371
+ function clampUnit(n) {
372
+ if (!Number.isFinite(n)) return 0;
373
+ if (n < 0) return 0;
374
+ if (n >= 1) return 0.999999999;
375
+ return n;
376
+ }
86
377
 
87
378
  // src/client.ts
88
379
  var DEFAULT_BASE_URL = "https://api.atlasent.io";
89
380
  var DEFAULT_TIMEOUT_MS = 1e4;
90
- var SDK_VERSION = "0.1.0";
381
+ var SDK_VERSION = "2.2.0";
382
+ function _buildUserAgent() {
383
+ const isNode2 = typeof process !== "undefined" && typeof process?.versions?.node === "string";
384
+ return isNode2 ? `@atlasent/sdk/${SDK_VERSION} node/${process.version}` : `@atlasent/sdk/${SDK_VERSION} browser`;
385
+ }
386
+ var CONTEXT_PROPERTIES_SOFT_CAP = 64;
387
+ function _warnOversizeContext(context) {
388
+ if (context && Object.keys(context).length > CONTEXT_PROPERTIES_SOFT_CAP) {
389
+ console.warn(
390
+ `[atlasent] context has ${Object.keys(context).length} top-level keys (soft cap ${CONTEXT_PROPERTIES_SOFT_CAP}); the server may reject this. Pack richer payloads under a single top-level key.`
391
+ );
392
+ }
393
+ }
394
+ function _enforceTls(baseUrl) {
395
+ const nodeEnvValue = typeof process !== "undefined" && process.env ? process.env.ATLASENT_ALLOW_INSECURE_HTTP : void 0;
396
+ const allow = nodeEnvValue === "1" || globalThis.ATLASENT_ALLOW_INSECURE_HTTP === "1";
397
+ if (allow) return baseUrl;
398
+ let parsed;
399
+ try {
400
+ parsed = new URL(baseUrl);
401
+ } catch {
402
+ throw new AtlaSentError(`Invalid baseUrl: ${baseUrl}`, {
403
+ code: "bad_request"
404
+ });
405
+ }
406
+ if (parsed.protocol !== "https:") {
407
+ throw new AtlaSentError(
408
+ `AtlaSent baseUrl must use https:// (got ${parsed.protocol}). For local development, set ATLASENT_ALLOW_INSECURE_HTTP=1.`,
409
+ { code: "bad_request" }
410
+ );
411
+ }
412
+ return baseUrl;
413
+ }
414
+ var API_KEY_PATTERN = /^ask_(?:live|test)_[A-Za-z0-9_-]+$/;
415
+ function _validateApiKey(apiKey) {
416
+ if (typeof apiKey !== "string" || apiKey.length === 0) {
417
+ throw new AtlaSentError("apiKey is required", { code: "invalid_api_key" });
418
+ }
419
+ if (!API_KEY_PATTERN.test(apiKey)) {
420
+ const head = apiKey.slice(0, 8);
421
+ throw new AtlaSentError(
422
+ `AtlaSent apiKey does not match expected shape \`ask_(live|test)_<entropy>\` (got prefix=${JSON.stringify(head)}). Check for whitespace, quotes, or trailing characters.`,
423
+ { code: "invalid_api_key" }
424
+ );
425
+ }
426
+ return apiKey;
427
+ }
428
+ var isNode = typeof process !== "undefined" && typeof process.versions?.node === "string";
429
+ var NODE_VERSION = isNode ? process.version : null;
430
+ function deployGateEvidence(input) {
431
+ const evidence = {};
432
+ if (input.permitId) evidence.permitId = input.permitId;
433
+ if (input.permitHash) evidence.permitHash = input.permitHash;
434
+ if (input.auditHash) evidence.auditHash = input.auditHash;
435
+ if (input.verifiedAt) evidence.verifiedAt = input.verifiedAt;
436
+ return evidence;
437
+ }
91
438
  var AtlaSentClient = class {
92
439
  apiKey;
93
440
  baseUrl;
94
441
  timeoutMs;
95
442
  fetchImpl;
443
+ userAgent;
444
+ retryPolicy;
96
445
  constructor(options) {
97
446
  if (!options.apiKey || typeof options.apiKey !== "string") {
98
447
  throw new AtlaSentError("apiKey is required", {
99
448
  code: "invalid_api_key"
100
449
  });
101
450
  }
102
- this.apiKey = options.apiKey;
103
- this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
451
+ if (typeof AbortSignal.timeout !== "function") {
452
+ throw new AtlaSentError(
453
+ "@atlasent/sdk requires AbortSignal.timeout, which is not available in this runtime. Minimum supported browsers: Chrome 103+, Firefox 100+, Safari 16+. Upgrade your browser or add an AbortSignal.timeout polyfill.",
454
+ { code: "network" }
455
+ );
456
+ }
457
+ this.apiKey = _validateApiKey(options.apiKey);
458
+ this.baseUrl = _enforceTls(options.baseUrl ?? DEFAULT_BASE_URL).replace(
459
+ /\/+$/,
460
+ ""
461
+ );
104
462
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
105
463
  this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
464
+ this.userAgent = _buildUserAgent();
465
+ this.retryPolicy = mergePolicy(options.retryPolicy ?? {});
106
466
  }
107
467
  /**
108
468
  * Ask the policy engine whether an agent action is permitted.
109
469
  *
110
- * A "DENY" is **not** thrown it is returned in
470
+ * Accepts either the current v2.0 shape (`action_type` / `actor_id`)
471
+ * or the legacy v1.x shape (`action` / `agent`). Legacy callers
472
+ * receive a deprecation warning via `console.warn`; the shim is
473
+ * handled by {@link normalizeEvaluateRequest} and will be removed
474
+ * in v3.0.0.
475
+ *
476
+ * A "deny" is **not** thrown — it is returned in
111
477
  * `response.decision`. Network errors, invalid API key, rate
112
478
  * limits, timeouts, and malformed responses throw
113
479
  * {@link AtlaSentError}.
114
480
  */
115
481
  async evaluate(input) {
482
+ _warnOversizeContext(input.context);
483
+ const normalized = normalizeEvaluateRequest(
484
+ input
485
+ );
116
486
  const body = {
117
- action: input.action,
118
- agent: input.agent,
119
- context: input.context ?? {},
120
- api_key: this.apiKey
487
+ action_type: normalized.action_type,
488
+ actor_id: normalized.actor_id,
489
+ context: normalized.context ?? {}
121
490
  };
122
- const { body: wire, rateLimit } = await this.post("/v1-evaluate", body);
123
- if (typeof wire.permitted !== "boolean" || typeof wire.decision_id !== "string") {
491
+ const { body: wire, rateLimit } = await this.post(
492
+ "/v1-evaluate",
493
+ body
494
+ );
495
+ let decision = typeof wire.decision === "string" ? wire.decision.toLowerCase() : wire.decision;
496
+ if (decision === void 0 && typeof wire.permitted === "boolean") {
497
+ decision = wire.permitted ? "allow" : "deny";
498
+ }
499
+ const permitToken = wire.permit_token ?? wire.decision_id;
500
+ if (decision !== "allow" && decision !== "deny" && decision !== "hold" && decision !== "escalate") {
501
+ throw new AtlaSentError(
502
+ "Malformed response from /v1-evaluate: missing `decision` (or legacy `permitted`)",
503
+ { code: "bad_response" }
504
+ );
505
+ }
506
+ if (decision === "allow" && (typeof permitToken !== "string" || permitToken.length === 0)) {
124
507
  throw new AtlaSentError(
125
- "Malformed response from /v1-evaluate: missing `permitted` or `decision_id`",
508
+ "Malformed response from /v1-evaluate: decision='allow' but no `permit_token` (or legacy `decision_id`)",
126
509
  { code: "bad_response" }
127
510
  );
128
511
  }
512
+ const reason = wire.denial?.reason ?? wire.reason ?? "";
513
+ const permitId = permitToken ?? "";
129
514
  return {
130
- decision: wire.permitted ? "ALLOW" : "DENY",
131
- permitId: wire.decision_id,
132
- reason: wire.reason ?? "",
515
+ decision,
516
+ decision_canonical: decision,
517
+ evaluationId: permitId,
518
+ permitId,
519
+ // /v1-evaluate does not return a control-plane-shaped Permit body;
520
+ // callers needing the full record fetch GET /v1/permits/:id.
521
+ permit: null,
522
+ permitToken: decision === "allow" ? permitToken ?? null : null,
523
+ reasons: reason ? [reason] : [],
524
+ reason,
133
525
  auditHash: wire.audit_hash ?? "",
134
526
  timestamp: wire.timestamp ?? "",
135
527
  rateLimit
136
528
  };
137
529
  }
530
+ /**
531
+ * Pre-flight evaluation that always returns the constraint trace.
532
+ *
533
+ * Wraps `POST /v1-evaluate?include=constraint_trace`. Use this from
534
+ * a workflow's submission step to surface trivial defects (missing
535
+ * fields, wrong roles, mis-set context) BEFORE pushing the request
536
+ * onto an approval queue — only requests that would actually pass
537
+ * make it through to a human reviewer.
538
+ *
539
+ * Returns an {@link EvaluatePreflightResponse} carrying the regular
540
+ * {@link EvaluateResponse} plus the {@link ConstraintTrace}. Unlike
541
+ * {@link evaluate}, this method does NOT mark a non-allow as a
542
+ * thrown condition — the whole point is to inspect both the outcome
543
+ * AND the per-policy trace, so the caller branches on
544
+ * `result.evaluation.decision` and reads `result.constraintTrace`
545
+ * to render the failing stages.
546
+ *
547
+ * The constraint-trace shape mirrors `ConstraintTraceResponse` in
548
+ * atlasent-api (`packages/types/src/index.ts`). On older
549
+ * atlasent-api deployments that omit the trace, `constraintTrace`
550
+ * is `null` rather than throwing — forward-compatible degradation.
551
+ *
552
+ * Performance: one extra round-trip on submission. Latency is
553
+ * comparable to {@link evaluate}; the response body is fuller
554
+ * (includes the per-stage trace) so the wire payload is larger.
555
+ * If the caller does not need the trace, prefer {@link evaluate}.
556
+ */
557
+ async evaluatePreflight(input) {
558
+ _warnOversizeContext(input.context);
559
+ const body = {
560
+ action_type: input.action,
561
+ actor_id: input.agent,
562
+ context: input.context ?? {}
563
+ };
564
+ const query = new URLSearchParams({ include: "constraint_trace" });
565
+ const { body: wire, rateLimit } = await this.post(
566
+ "/v1-evaluate",
567
+ body,
568
+ query
569
+ );
570
+ let decision = typeof wire.decision === "string" ? wire.decision.toLowerCase() : wire.decision;
571
+ if (decision === void 0 && typeof wire.permitted === "boolean") {
572
+ decision = wire.permitted ? "allow" : "deny";
573
+ }
574
+ if (decision !== "allow" && decision !== "deny" && decision !== "hold" && decision !== "escalate") {
575
+ throw new AtlaSentError(
576
+ "Malformed response from /v1-evaluate: missing `decision` (or legacy `permitted`)",
577
+ { code: "bad_response" }
578
+ );
579
+ }
580
+ const permitToken = wire.permit_token ?? wire.decision_id;
581
+ const reason = wire.denial?.reason ?? wire.reason ?? "";
582
+ const permitId = permitToken ?? "";
583
+ const evaluation = {
584
+ decision,
585
+ decision_canonical: decision,
586
+ evaluationId: permitId,
587
+ permitId,
588
+ // /v1-evaluate does not return a control-plane-shaped Permit body;
589
+ // callers needing the full record fetch GET /v1/permits/:id.
590
+ permit: null,
591
+ permitToken: decision === "allow" ? permitToken ?? null : null,
592
+ reasons: reason ? [reason] : [],
593
+ reason,
594
+ auditHash: wire.audit_hash ?? "",
595
+ timestamp: wire.timestamp ?? "",
596
+ rateLimit
597
+ };
598
+ let constraintTrace = null;
599
+ if (wire.constraint_trace !== void 0 && wire.constraint_trace !== null && typeof wire.constraint_trace === "object") {
600
+ constraintTrace = wire.constraint_trace;
601
+ }
602
+ return { evaluation, constraintTrace };
603
+ }
138
604
  /**
139
605
  * Verify that a previously issued permit is still valid.
140
606
  *
607
+ * @deprecated Use {@link verifyPermitById} — the canonical REST
608
+ * surface (`POST /v1/permits/{id}/verify`) returns the unified
609
+ * verification envelope plus the full {@link PermitRecord}, instead
610
+ * of the legacy `{verified, outcome, permitHash}` shape this method
611
+ * emits. Will be removed in `@atlasent/sdk@3`.
612
+ *
141
613
  * A `verified: false` response is **not** thrown — inspect the
142
614
  * returned object. Only transport / server errors throw.
143
615
  */
144
616
  async verifyPermit(input) {
617
+ _warnOversizeContext(input.context);
145
618
  const body = {
146
- decision_id: input.permitId,
147
- action: input.action ?? "",
148
- agent: input.agent ?? "",
149
- context: input.context ?? {},
150
- api_key: this.apiKey
619
+ permit_token: input.permitId,
620
+ action_type: input.action ?? "",
621
+ actor_id: input.agent ?? ""
151
622
  };
623
+ if (input.environment !== void 0) {
624
+ body.environment = input.environment;
625
+ }
626
+ if (input.execution_hash !== void 0) {
627
+ body.execution_hash = input.execution_hash;
628
+ }
152
629
  const { body: wire, rateLimit } = await this.post(
153
630
  "/v1-verify-permit",
154
631
  body
155
632
  );
156
- if (typeof wire.verified !== "boolean") {
633
+ const valid = typeof wire.valid === "boolean" ? wire.valid : wire.verified;
634
+ if (typeof valid !== "boolean") {
157
635
  throw new AtlaSentError(
158
- "Malformed response from /v1-verify-permit: missing `verified`",
636
+ "Malformed response from /v1-verify-permit: missing `valid` (or legacy `verified`)",
159
637
  { code: "bad_response" }
160
638
  );
161
639
  }
162
640
  return {
163
- verified: wire.verified,
641
+ verified: valid,
164
642
  outcome: wire.outcome ?? "",
165
643
  permitHash: wire.permit_hash ?? "",
166
644
  timestamp: wire.timestamp ?? "",
167
645
  rateLimit
168
646
  };
169
647
  }
648
+ /**
649
+ * Run the canonical Deploy Gate V1 flow:
650
+ * evaluate `production.deploy`, verify the issued permit server-side,
651
+ * and return allow/block plus audit/evidence metadata.
652
+ *
653
+ * This helper never treats a signed/offline permit artifact as sufficient
654
+ * authorization. Execution is allowed only when `POST /v1-evaluate` returns
655
+ * `decision: "allow"` with a permit AND `POST /v1-verify-permit` returns
656
+ * `verified: true` / `valid: true`.
657
+ */
658
+ async deployGate(input = {}) {
659
+ const agent = input.agent ?? "ci-deploy-bot";
660
+ const action = input.action ?? PRODUCTION_DEPLOY_ACTION;
661
+ const context = input.context ?? {};
662
+ const evaluation = await this.evaluate({ agent, action, context });
663
+ if (evaluation.decision !== "allow") {
664
+ return {
665
+ allowed: false,
666
+ evaluation,
667
+ reason: evaluation.reason || `Deploy Gate blocked by decision=${evaluation.decision}`,
668
+ evidence: deployGateEvidence({
669
+ permitId: evaluation.permitId,
670
+ auditHash: evaluation.auditHash
671
+ })
672
+ };
673
+ }
674
+ const verification = await this.verifyPermit({
675
+ permitId: evaluation.permitId,
676
+ agent,
677
+ action,
678
+ context
679
+ });
680
+ if (!verification.verified) {
681
+ return {
682
+ allowed: false,
683
+ evaluation,
684
+ verification,
685
+ reason: verification.outcome ? `Deploy Gate blocked by permit verification outcome=${verification.outcome}` : "Deploy Gate blocked because permit verification failed",
686
+ evidence: deployGateEvidence({
687
+ permitId: evaluation.permitId,
688
+ permitHash: verification.permitHash,
689
+ auditHash: evaluation.auditHash,
690
+ verifiedAt: verification.timestamp
691
+ })
692
+ };
693
+ }
694
+ return {
695
+ allowed: true,
696
+ evaluation,
697
+ verification,
698
+ reason: evaluation.reason || "Deploy Gate permit verified",
699
+ evidence: deployGateEvidence({
700
+ permitId: evaluation.permitId,
701
+ permitHash: verification.permitHash,
702
+ auditHash: evaluation.auditHash,
703
+ verifiedAt: verification.timestamp
704
+ })
705
+ };
706
+ }
707
+ /**
708
+ * Revoke a previously-issued permit so it can no longer pass
709
+ * {@link verifyPermit}.
710
+ *
711
+ * @deprecated Use {@link revokePermitById} — the canonical REST
712
+ * surface (`POST /v1/permits/{id}/revoke`) returns the full updated
713
+ * {@link PermitRecord} with `revoked_at`/`revoked_by`/`revoke_reason`
714
+ * populated, instead of the legacy `{revoked, permitId}` envelope
715
+ * this method emits. Will be removed in `@atlasent/sdk@3`.
716
+ *
717
+ * Use this when an agent's action is cancelled, superseded, or
718
+ * determined to be unauthorized after the fact. The revocation is
719
+ * recorded in the audit log with the optional `reason`.
720
+ *
721
+ * Throws {@link AtlaSentError} on transport / auth failures.
722
+ */
723
+ async revokePermit(input) {
724
+ const body = {
725
+ decision_id: input.permitId,
726
+ reason: input.reason ?? "",
727
+ api_key: this.apiKey
728
+ };
729
+ const { body: wire, rateLimit } = await this.post("/v1-revoke-permit", body);
730
+ if (typeof wire.revoked !== "boolean" || typeof wire.decision_id !== "string") {
731
+ throw new AtlaSentError(
732
+ "Malformed response from /v1-revoke-permit: missing `revoked` or `decision_id`",
733
+ { code: "bad_response" }
734
+ );
735
+ }
736
+ return {
737
+ revoked: wire.revoked,
738
+ permitId: wire.decision_id,
739
+ revokedAt: wire.revoked_at,
740
+ auditHash: wire.audit_hash,
741
+ rateLimit
742
+ };
743
+ }
744
+ /**
745
+ * Revoke a permit through the canonical REST surface
746
+ * (`POST /v1/permits/{permitId}/revoke`).
747
+ *
748
+ * Returns the full updated {@link PermitRecord} with `status === 'revoked'`
749
+ * and `revoked_at` / `revoked_by` / `revoke_reason` populated. After
750
+ * revocation, subsequent verify calls return `410 PERMIT_REVOKED`.
751
+ *
752
+ * Idempotent on `409 permit_revoked` for already-revoked permits;
753
+ * server returns the existing revoked row in that case.
754
+ *
755
+ * Throws {@link AtlaSentError} on `404` (permit not in calling org),
756
+ * `409` (already in a terminal state), `410` (expired before revoke),
757
+ * or `429` (rate limited).
758
+ */
759
+ async revokePermitById(permitId, input = {}) {
760
+ if (!permitId) {
761
+ throw new AtlaSentError("permitId is required", { code: "bad_request" });
762
+ }
763
+ const body = {};
764
+ if (input.reason !== void 0) body.reason = input.reason;
765
+ const { body: wire, rateLimit } = await this.post(
766
+ `/v1/permits/${encodeURIComponent(permitId)}/revoke`,
767
+ body
768
+ );
769
+ return { permit: wire, rateLimit };
770
+ }
771
+ /**
772
+ * Verify a permit through the canonical REST surface
773
+ * (`POST /v1/permits/{permitId}/verify`).
774
+ *
775
+ * Returns the unified verification envelope (`valid`,
776
+ * `verification_type: 'permit'`, `reason`, `verified_at`, `evidence`)
777
+ * plus the full {@link PermitRecord} fields preserved at the top
778
+ * level. The `valid` field is the contract — pin to it.
779
+ *
780
+ * A `valid: false` is **not** thrown when the server returns 200 with
781
+ * a denial reason (matches the verify-shape unification on the wire);
782
+ * it is thrown on 4xx (`404` not found, `410` expired/consumed).
783
+ */
784
+ async verifyPermitById(permitId) {
785
+ if (!permitId) {
786
+ throw new AtlaSentError("permitId is required", { code: "bad_request" });
787
+ }
788
+ const { body: wire, rateLimit } = await this.post(`/v1/permits/${encodeURIComponent(permitId)}/verify`, {});
789
+ const { valid, verification_type, reason, verified_at, evidence, ...row } = wire;
790
+ return {
791
+ valid,
792
+ verification_type,
793
+ reason,
794
+ verified_at,
795
+ evidence,
796
+ permit: row,
797
+ rateLimit
798
+ };
799
+ }
800
+ /**
801
+ * Get a single permit's full lifecycle state.
802
+ *
803
+ * Calls `GET /v1/permits/{permitId}` (the canonical REST surface).
804
+ * Returns `status`, all timestamps, `revoked_at` / `revoked_by` /
805
+ * `revoke_reason` (when applicable), and the bound `payload_hash`
806
+ * / `decision_id`.
807
+ *
808
+ * Operator-facing introspection — answers "what state is this permit
809
+ * in, and why?" without reading audit logs.
810
+ *
811
+ * Throws {@link AtlaSentError} on `404` (permit not in calling org)
812
+ * or `410` (expired before retrieval).
813
+ */
814
+ async getPermit(permitId) {
815
+ if (!permitId) {
816
+ throw new AtlaSentError("permitId is required", { code: "bad_request" });
817
+ }
818
+ const { body: wire, rateLimit } = await this.get(
819
+ `/v1/permits/${encodeURIComponent(permitId)}`
820
+ );
821
+ return { permit: wire, rateLimit };
822
+ }
823
+ /**
824
+ * Poll whether a permit is currently valid.
825
+ *
826
+ * Calls `GET /v1/permits/{permitId}/valid` — a lightweight read
827
+ * returning only the status snapshot optimised for guard heartbeat
828
+ * polling. Guards with `permitRevalidationIntervalMs` set race this
829
+ * against `tool.execute()` and throw {@link PermitRevoked} when
830
+ * `status === "revoked"` arrives.
831
+ *
832
+ * Throws {@link AtlaSentError} on transport / auth failures.
833
+ */
834
+ async checkPermitValid(permitId) {
835
+ if (!permitId) {
836
+ throw new AtlaSentError("permitId is required", { code: "bad_request" });
837
+ }
838
+ const { body } = await this.get(
839
+ `/v1/permits/${encodeURIComponent(permitId)}/valid`
840
+ );
841
+ return body;
842
+ }
843
+ /**
844
+ * List permits issued to the calling org, most-recently-issued first.
845
+ *
846
+ * Calls `GET /v1/permits` (the canonical REST surface). Cursor-paged.
847
+ * Filters narrow on server side; pagination uses the `created_at`
848
+ * timestamp opaquely (`nextCursor`).
849
+ *
850
+ * Designed for incident review, debugging, and compliance
851
+ * reconstruction.
852
+ */
853
+ async listPermits(input = {}) {
854
+ const params = new URLSearchParams();
855
+ if (input.status) params.set("status", input.status);
856
+ if (input.actorId) params.set("actor_id", input.actorId);
857
+ if (input.actionType) params.set("action_type", input.actionType);
858
+ if (input.from) params.set("from", input.from);
859
+ if (input.to) params.set("to", input.to);
860
+ if (input.limit !== void 0) params.set("limit", String(input.limit));
861
+ if (input.cursor) params.set("cursor", input.cursor);
862
+ const { body: wire, rateLimit } = await this.get("/v1/permits", params);
863
+ if (!Array.isArray(wire.permits)) {
864
+ throw new AtlaSentError(
865
+ "Malformed response from /v1/permits: missing `permits` array",
866
+ { code: "bad_response" }
867
+ );
868
+ }
869
+ const result = {
870
+ permits: wire.permits,
871
+ total: typeof wire.total === "number" ? wire.total : wire.permits.length,
872
+ rateLimit
873
+ };
874
+ if (wire.next_cursor !== void 0) result.nextCursor = wire.next_cursor;
875
+ return result;
876
+ }
170
877
  /**
171
878
  * Self-introspection: ask the server to describe the API key this
172
879
  * client was constructed with. Returns the key's ID, organization,
@@ -181,9 +888,7 @@ var AtlaSentClient = class {
181
888
  * taxonomy as {@link AtlaSentClient.evaluate}.
182
889
  */
183
890
  async keySelf() {
184
- const { body: wire, rateLimit } = await this.get(
185
- "/v1-api-key-self"
186
- );
891
+ const { body: wire, rateLimit } = await this.get("/v1-api-key-self");
187
892
  if (typeof wire.key_id !== "string" || typeof wire.organization_id !== "string") {
188
893
  throw new AtlaSentError(
189
894
  "Malformed response from /v1-api-key-self: missing `key_id` or `organization_id`",
@@ -258,60 +963,756 @@ var AtlaSentClient = class {
258
963
  }
259
964
  return { ...wire, rateLimit };
260
965
  }
261
- async post(path, body) {
262
- return this.request(path, "POST", body, void 0);
263
- }
264
- async get(path, query) {
265
- return this.request(path, "GET", void 0, query);
266
- }
267
- async request(path, method, body, query) {
268
- const qs = query && Array.from(query).length > 0 ? `?${query.toString()}` : "";
269
- const url = `${this.baseUrl}${path}${qs}`;
270
- const requestId = globalThis.crypto.randomUUID();
271
- const headers = {
272
- Accept: "application/json",
273
- Authorization: `Bearer ${this.apiKey}`,
274
- "User-Agent": `@atlasent/sdk/${SDK_VERSION} node/${process.version}`,
275
- "X-Request-ID": requestId
276
- };
277
- if (method === "POST") headers["Content-Type"] = "application/json";
278
- const init = {
279
- method,
280
- headers,
281
- signal: AbortSignal.timeout(this.timeoutMs)
966
+ /**
967
+ * Open a streaming evaluation session against `POST /v1-evaluate-stream`.
968
+ *
969
+ * Yields {@link StreamDecisionEvent} and {@link StreamProgressEvent} objects
970
+ * as the server emits them. The iterator ends cleanly when the server sends
971
+ * `event: done`; it throws {@link AtlaSentError} on transport errors or when
972
+ * the server sends `event: error`.
973
+ *
974
+ * The final {@link StreamDecisionEvent} (isFinal: true) carries a `permitId`
975
+ * suitable for passing to {@link verifyPermit} after the stream closes.
976
+ *
977
+ * Hardening:
978
+ * - Throws {@link StreamTimeoutError} when no event arrives within
979
+ * `opts.timeoutMs` (default 30 s). Pass `0` to disable.
980
+ * - Retries up to `opts.maxRetries` times (default 3) with 1 s / 2 s / 4 s
981
+ * delays on network drop (before a terminal event). Sends `Last-Event-ID`
982
+ * on reconnect when the server has emitted event IDs.
983
+ * - Throws {@link StreamParseError} on partial / malformed JSON rather than
984
+ * crashing with a raw `SyntaxError`.
985
+ * - Closes cleanly on `event: done` or a decision event with `done: true`.
986
+ *
987
+ * ```ts
988
+ * for await (const event of client.protectStream({ agent, action })) {
989
+ * if (event.type === "decision" && event.isFinal) {
990
+ * await client.verifyPermit({ permitId: event.permitId });
991
+ * }
992
+ * }
993
+ * ```
994
+ */
995
+ async *protectStream(input, opts = {}) {
996
+ const streamTimeoutMs = opts.timeoutMs ?? 3e4;
997
+ const maxRetries = opts.maxRetries ?? 3;
998
+ const body = {
999
+ action: input.action,
1000
+ agent: input.agent,
1001
+ context: input.context ?? {},
1002
+ api_key: this.apiKey
282
1003
  };
283
- if (method === "POST") init.body = JSON.stringify(body);
284
- let response;
285
- try {
286
- response = await this.fetchImpl(url, init);
287
- } catch (err) {
288
- throw mapFetchError(err, requestId);
1004
+ const requestId = globalThis.crypto.randomUUID();
1005
+ const url = `${this.baseUrl}/v1-evaluate-stream`;
1006
+ let lastEventId;
1007
+ let retryCount = 0;
1008
+ while (true) {
1009
+ const headers = {
1010
+ Accept: "text/event-stream",
1011
+ "Content-Type": "application/json",
1012
+ Authorization: `Bearer ${this.apiKey}`,
1013
+ "User-Agent": this.userAgent,
1014
+ "X-Request-ID": requestId
1015
+ };
1016
+ if (lastEventId !== void 0) {
1017
+ headers["Last-Event-ID"] = lastEventId;
1018
+ }
1019
+ const connectionTimeoutSignal = AbortSignal.timeout(this.timeoutMs);
1020
+ const signal = opts.signal ? AbortSignal.any([connectionTimeoutSignal, opts.signal]) : connectionTimeoutSignal;
1021
+ let response;
1022
+ try {
1023
+ response = await this.fetchImpl(url, {
1024
+ method: "POST",
1025
+ headers,
1026
+ body: JSON.stringify(body),
1027
+ signal
1028
+ });
1029
+ } catch (err) {
1030
+ const mapped = mapFetchError(err, requestId);
1031
+ if (mapped.code === "network" && retryCount < maxRetries) {
1032
+ retryCount++;
1033
+ await sleep(1e3 * Math.pow(2, retryCount - 1));
1034
+ continue;
1035
+ }
1036
+ throw mapped;
1037
+ }
1038
+ if (!response.ok) {
1039
+ throw await buildHttpError(response, requestId);
1040
+ }
1041
+ if (!response.body) {
1042
+ throw new AtlaSentError("Expected streaming body from AtlaSent API", {
1043
+ code: "bad_response",
1044
+ status: response.status,
1045
+ requestId
1046
+ });
1047
+ }
1048
+ let streamDone = false;
1049
+ let networkDrop = false;
1050
+ try {
1051
+ for await (const event of parseSseStream(
1052
+ response.body,
1053
+ requestId,
1054
+ streamTimeoutMs,
1055
+ (id) => {
1056
+ lastEventId = id;
1057
+ }
1058
+ )) {
1059
+ yield event;
1060
+ if (event.type === "decision" && event.isFinal) {
1061
+ streamDone = true;
1062
+ }
1063
+ }
1064
+ streamDone = true;
1065
+ } catch (err) {
1066
+ if (err instanceof AtlaSentError && err.code === "network") {
1067
+ networkDrop = true;
1068
+ } else {
1069
+ throw err;
1070
+ }
1071
+ }
1072
+ if (streamDone) break;
1073
+ if (networkDrop && retryCount < maxRetries) {
1074
+ retryCount++;
1075
+ await sleep(1e3 * Math.pow(2, retryCount - 1));
1076
+ continue;
1077
+ }
1078
+ if (networkDrop) {
1079
+ throw new AtlaSentError(
1080
+ `AtlaSent stream dropped after ${retryCount} reconnection attempts`,
1081
+ { code: "network", requestId }
1082
+ );
1083
+ }
1084
+ break;
1085
+ }
1086
+ }
1087
+ async post(path, body, query) {
1088
+ return this.request(path, "POST", body, query);
1089
+ }
1090
+ async get(path, query) {
1091
+ return this.request(path, "GET", void 0, query);
1092
+ }
1093
+ async request(path, method, body, query) {
1094
+ const qs = query && Array.from(query).length > 0 ? `?${query.toString()}` : "";
1095
+ const url = `${this.baseUrl}${path}${qs}`;
1096
+ const requestId = globalThis.crypto.randomUUID();
1097
+ const headers = {
1098
+ Accept: "application/json",
1099
+ Authorization: `Bearer ${this.apiKey}`,
1100
+ "User-Agent": this.userAgent,
1101
+ "X-Request-ID": requestId
1102
+ };
1103
+ if (method === "POST") headers["Content-Type"] = "application/json";
1104
+ const bodyStr = method === "POST" ? JSON.stringify(body) : void 0;
1105
+ for (let attempt = 0; ; attempt++) {
1106
+ const init = {
1107
+ method,
1108
+ headers,
1109
+ signal: AbortSignal.timeout(this.timeoutMs)
1110
+ };
1111
+ if (bodyStr !== void 0) init.body = bodyStr;
1112
+ let response;
1113
+ try {
1114
+ response = await this.fetchImpl(url, init);
1115
+ } catch (err) {
1116
+ const mapped = mapFetchError(err, requestId);
1117
+ if (isRetryable(mapped) && hasAttemptsLeft(attempt, this.retryPolicy)) {
1118
+ await sleep(computeBackoffMs(attempt, this.retryPolicy, mapped));
1119
+ continue;
1120
+ }
1121
+ throw mapped;
1122
+ }
1123
+ if (!response.ok) {
1124
+ const httpErr = await buildHttpError(response, requestId);
1125
+ if (isRetryable(httpErr) && hasAttemptsLeft(attempt, this.retryPolicy)) {
1126
+ await sleep(computeBackoffMs(attempt, this.retryPolicy, httpErr));
1127
+ continue;
1128
+ }
1129
+ throw httpErr;
1130
+ }
1131
+ let parsed;
1132
+ try {
1133
+ parsed = await response.json();
1134
+ } catch (err) {
1135
+ const jsonErr = new AtlaSentError(
1136
+ "Invalid JSON response from AtlaSent API",
1137
+ {
1138
+ code: "bad_response",
1139
+ status: response.status,
1140
+ requestId,
1141
+ cause: err
1142
+ }
1143
+ );
1144
+ if (isRetryable(jsonErr) && hasAttemptsLeft(attempt, this.retryPolicy)) {
1145
+ await sleep(computeBackoffMs(attempt, this.retryPolicy, jsonErr));
1146
+ continue;
1147
+ }
1148
+ throw jsonErr;
1149
+ }
1150
+ if (parsed === null || typeof parsed !== "object") {
1151
+ const shapeErr = new AtlaSentError(
1152
+ "Expected a JSON object from AtlaSent API",
1153
+ {
1154
+ code: "bad_response",
1155
+ status: response.status,
1156
+ requestId
1157
+ }
1158
+ );
1159
+ if (isRetryable(shapeErr) && hasAttemptsLeft(attempt, this.retryPolicy)) {
1160
+ await sleep(computeBackoffMs(attempt, this.retryPolicy, shapeErr));
1161
+ continue;
1162
+ }
1163
+ throw shapeErr;
1164
+ }
1165
+ return {
1166
+ body: parsed,
1167
+ rateLimit: parseRateLimitHeaders(response.headers)
1168
+ };
289
1169
  }
290
- if (!response.ok) {
291
- throw await buildHttpError(response, requestId);
1170
+ }
1171
+ /**
1172
+ * Open a new HITL escalation. Bridges a `hold` outcome from
1173
+ * `protect()` to the approval queue: an agent that receives a
1174
+ * `hold` decision calls this to enroll the proposed action for
1175
+ * human review. The returned escalation can then be polled with
1176
+ * `getHitlEscalation()` or driven to terminal by
1177
+ * `approveHitlEscalation()` / `rejectHitlEscalation()`.
1178
+ *
1179
+ * Quorum, pool size, fallback decision and routing inherit from
1180
+ * the server-side policy when omitted from `input`.
1181
+ *
1182
+ * Calls `POST /v1/hitl`.
1183
+ */
1184
+ async createHitlEscalation(input) {
1185
+ const { body, rateLimit } = await this.post(
1186
+ "/v1/hitl",
1187
+ input
1188
+ );
1189
+ return { escalation: body, rateLimit };
1190
+ }
1191
+ /**
1192
+ * List HITL escalations for the calling org. Defaults to
1193
+ * `status=pending`; pass `status` to query other queues
1194
+ * (`escalated`, `approved`, `rejected`, `auto_approved`,
1195
+ * `timed_out`).
1196
+ *
1197
+ * Calls `GET /v1/hitl`.
1198
+ */
1199
+ async listHitlEscalations(input = {}) {
1200
+ const params = new URLSearchParams();
1201
+ if (input.status) params.set("status", input.status);
1202
+ if (input.agentId) params.set("agent_id", input.agentId);
1203
+ if (input.assignedToUserId)
1204
+ params.set("assigned_to_user_id", input.assignedToUserId);
1205
+ if (input.limit !== void 0) params.set("limit", String(input.limit));
1206
+ if (input.cursor) params.set("cursor", input.cursor);
1207
+ const { body, rateLimit } = await this.get(
1208
+ "/v1/hitl",
1209
+ params
1210
+ );
1211
+ return { data: body, rateLimit };
1212
+ }
1213
+ /**
1214
+ * Get a HITL escalation. The server payload includes a live
1215
+ * `quorum_progress` snapshot when the escalation is still open.
1216
+ *
1217
+ * Calls `GET /v1/hitl/:id`.
1218
+ */
1219
+ async getHitlEscalation(escalationId) {
1220
+ if (!escalationId) {
1221
+ throw new AtlaSentError("escalationId is required", {
1222
+ code: "bad_request"
1223
+ });
292
1224
  }
293
- let parsed;
294
- try {
295
- parsed = await response.json();
296
- } catch (err) {
297
- throw new AtlaSentError("Invalid JSON response from AtlaSent API", {
298
- code: "bad_response",
299
- status: response.status,
300
- requestId,
301
- cause: err
1225
+ const { body, rateLimit } = await this.get(
1226
+ `/v1/hitl/${encodeURIComponent(escalationId)}`
1227
+ );
1228
+ return { escalation: body, rateLimit };
1229
+ }
1230
+ /**
1231
+ * List per-approver vote rows for an escalation.
1232
+ * Calls `GET /v1/hitl/:id/approvals`.
1233
+ */
1234
+ async listHitlApprovals(escalationId) {
1235
+ const { body, rateLimit } = await this.get(`/v1/hitl/${encodeURIComponent(escalationId)}/approvals`);
1236
+ return { approvals: body.approvals ?? [], rateLimit };
1237
+ }
1238
+ /**
1239
+ * List the escalation chain hops for an escalation. Each `/escalate`
1240
+ * call appends one row.
1241
+ * Calls `GET /v1/hitl/:id/chain`.
1242
+ */
1243
+ async getHitlChain(escalationId) {
1244
+ const { body, rateLimit } = await this.get(
1245
+ `/v1/hitl/${encodeURIComponent(escalationId)}/chain`
1246
+ );
1247
+ return { chain: body.chain ?? [], rateLimit };
1248
+ }
1249
+ /**
1250
+ * Record an approve vote. Resolves the escalation only once the
1251
+ * server-side quorum count is satisfied; before that the response
1252
+ * carries a refreshed escalation row with the latest
1253
+ * `quorum_progress`.
1254
+ *
1255
+ * Calls `POST /v1/hitl/:id/approve`. The server returns 409
1256
+ * `duplicate_vote` if the same principal has already voted, and
1257
+ * 409 `already_rejected` if a concurrent reject crossed the line.
1258
+ */
1259
+ async approveHitlEscalation(escalationId, input = {}) {
1260
+ const { body, rateLimit } = await this.post(
1261
+ `/v1/hitl/${encodeURIComponent(escalationId)}/approve`,
1262
+ input
1263
+ );
1264
+ return { escalation: body, rateLimit };
1265
+ }
1266
+ /**
1267
+ * Record a reject vote. Reject is short-circuit terminal — a single
1268
+ * reject closes the escalation regardless of how many approves have
1269
+ * accumulated.
1270
+ *
1271
+ * Calls `POST /v1/hitl/:id/reject`.
1272
+ */
1273
+ async rejectHitlEscalation(escalationId, input = {}) {
1274
+ const { body, rateLimit } = await this.post(
1275
+ `/v1/hitl/${encodeURIComponent(escalationId)}/reject`,
1276
+ input
1277
+ );
1278
+ return { escalation: body, rateLimit };
1279
+ }
1280
+ /**
1281
+ * Re-route an open escalation to a higher tier. Bounded by the
1282
+ * escalation's `max_escalation_depth` — the server returns 409
1283
+ * `chain_exhausted` and applies the configured fallback decision
1284
+ * once the ceiling is hit.
1285
+ *
1286
+ * Calls `POST /v1/hitl/:id/escalate`.
1287
+ */
1288
+ async escalateHitlEscalation(escalationId, input) {
1289
+ const { body, rateLimit } = await this.post(
1290
+ `/v1/hitl/${encodeURIComponent(escalationId)}/escalate`,
1291
+ input
1292
+ );
1293
+ return { escalation: body, rateLimit };
1294
+ }
1295
+ /**
1296
+ * Manually apply the escalation's `fallback_decision`. Useful for
1297
+ * admin recovery of a hung escalation when the cron sweeper hasn't
1298
+ * run yet, or to short-circuit a stuck flow during incident
1299
+ * response.
1300
+ *
1301
+ * Calls `POST /v1/hitl/:id/timeout`.
1302
+ */
1303
+ async timeoutHitlEscalation(escalationId) {
1304
+ const { body, rateLimit } = await this.post(
1305
+ `/v1/hitl/${encodeURIComponent(escalationId)}/timeout`,
1306
+ {}
1307
+ );
1308
+ return { escalation: body, rateLimit };
1309
+ }
1310
+ /**
1311
+ * Run a named governance graph traversal query.
1312
+ *
1313
+ * Dispatches to `GET /v1/governance/graph/query?type=<queryType>`.
1314
+ * Each query type returns a different row shape — the return type
1315
+ * narrows automatically based on the literal `queryType` argument.
1316
+ *
1317
+ * `"user_approvals"` requires `params.actor_id` — the server returns
1318
+ * a 400 if it is absent.
1319
+ */
1320
+ async queryGovernanceGraph(queryType, params = {}) {
1321
+ const qs = new URLSearchParams({ type: queryType });
1322
+ if (params.actor_id) qs.set("actor_id", params.actor_id);
1323
+ const { body, rateLimit } = await this.get("/v1/governance/graph/query", qs);
1324
+ return { ...body, rateLimit };
1325
+ }
1326
+ /**
1327
+ * Reconstruct the multi-system execution timeline for a specific incident.
1328
+ *
1329
+ * Calls `GET /v1/governance/timeline/incident/{incidentId}`. Backed
1330
+ * server-side by `reconstruct_incident_chains_v2()`, which fixes the
1331
+ * `executor_id → actor_id` bug that silently produced empty timelines
1332
+ * in the original function.
1333
+ *
1334
+ * Returns full execution rows including the §13.1 columns
1335
+ * (`delegation_chain_id`, `replay_of_execution_id`, `incident_id`,
1336
+ * `policy_version_id`, `bundle_version_id`) alongside the actor
1337
+ * timeline and evidence rows.
1338
+ */
1339
+ async getIncidentTimeline(incidentId) {
1340
+ if (!incidentId) {
1341
+ throw new AtlaSentError("incidentId is required", {
1342
+ code: "bad_request"
302
1343
  });
303
1344
  }
304
- if (parsed === null || typeof parsed !== "object") {
305
- throw new AtlaSentError("Expected a JSON object from AtlaSent API", {
306
- code: "bad_response",
307
- status: response.status,
308
- requestId
1345
+ const { body, rateLimit } = await this.get(`/v1/governance/timeline/incident/${encodeURIComponent(incidentId)}`);
1346
+ return { ...body, rateLimit };
1347
+ }
1348
+ // ── Connector Management ─────────────────────────────────────────────────
1349
+ /**
1350
+ * List connectors registered for the calling org.
1351
+ * Calls `GET /v1/governance/connectors`.
1352
+ */
1353
+ async listConnectors(options = {}) {
1354
+ const params = new URLSearchParams();
1355
+ if (options.cursor) params.set("cursor", options.cursor);
1356
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
1357
+ const { body, rateLimit } = await this.get("/v1/governance/connectors", params);
1358
+ const result = {
1359
+ connectors: body.connectors ?? [],
1360
+ total: body.total,
1361
+ rateLimit
1362
+ };
1363
+ if (body.next_cursor) result.nextCursor = body.next_cursor;
1364
+ return result;
1365
+ }
1366
+ /**
1367
+ * Register and install a new connector for the calling org.
1368
+ * Calls `POST /v1/governance/connectors`.
1369
+ */
1370
+ async installConnector(input) {
1371
+ const { body, rateLimit } = await this.post("/v1/governance/connectors", input);
1372
+ return { connector: body, rateLimit };
1373
+ }
1374
+ /**
1375
+ * Store encrypted credentials for a connector.
1376
+ * Calls `POST /v1/governance/connectors/{id}/authenticate`.
1377
+ */
1378
+ async authenticateConnector(connectorId, input) {
1379
+ if (!connectorId) {
1380
+ throw new AtlaSentError("connectorId is required", {
1381
+ code: "bad_request"
309
1382
  });
310
1383
  }
1384
+ const { body, rateLimit } = await this.post(
1385
+ `/v1/governance/connectors/${encodeURIComponent(connectorId)}/authenticate`,
1386
+ input
1387
+ );
311
1388
  return {
312
- body: parsed,
313
- rateLimit: parseRateLimitHeaders(response.headers)
1389
+ credential_id: body.credential_id,
1390
+ version: body.version,
1391
+ rateLimit
1392
+ };
1393
+ }
1394
+ /**
1395
+ * Trigger an incremental sync for a connector.
1396
+ * Calls `POST /v1/governance/connectors/{id}/sync`.
1397
+ */
1398
+ async syncConnector(connectorId) {
1399
+ if (!connectorId) {
1400
+ throw new AtlaSentError("connectorId is required", {
1401
+ code: "bad_request"
1402
+ });
1403
+ }
1404
+ const { body, rateLimit } = await this.post(`/v1/governance/connectors/${encodeURIComponent(connectorId)}/sync`, {});
1405
+ return { ...body, rateLimit };
1406
+ }
1407
+ /**
1408
+ * Revoke a connector and all its associated credentials.
1409
+ * Calls `POST /v1/governance/connectors/{id}/revoke`.
1410
+ */
1411
+ async revokeConnector(connectorId, reason) {
1412
+ if (!connectorId) {
1413
+ throw new AtlaSentError("connectorId is required", {
1414
+ code: "bad_request"
1415
+ });
1416
+ }
1417
+ const body = {};
1418
+ if (reason !== void 0) body.reason = reason;
1419
+ const { body: wire, rateLimit } = await this.post(
1420
+ `/v1/governance/connectors/${encodeURIComponent(connectorId)}/revoke`,
1421
+ body
1422
+ );
1423
+ return { ...wire, rateLimit };
1424
+ }
1425
+ /**
1426
+ * Rotate the credentials for a connector.
1427
+ * Calls `POST /v1/governance/connectors/{id}/rotate-credentials`.
1428
+ */
1429
+ async rotateConnectorCredentials(connectorId) {
1430
+ if (!connectorId) {
1431
+ throw new AtlaSentError("connectorId is required", {
1432
+ code: "bad_request"
1433
+ });
1434
+ }
1435
+ const { body, rateLimit } = await this.post(
1436
+ `/v1/governance/connectors/${encodeURIComponent(connectorId)}/rotate-credentials`,
1437
+ {}
1438
+ );
1439
+ return { ...body, rateLimit };
1440
+ }
1441
+ /**
1442
+ * List enforcement policies for the calling org, optionally filtered by connector type.
1443
+ * Calls `GET /v1/governance/enforcement-policies`.
1444
+ */
1445
+ async listEnforcementPolicies(connectorType) {
1446
+ const params = new URLSearchParams();
1447
+ if (connectorType) params.set("connector_type", connectorType);
1448
+ const { body, rateLimit } = await this.get("/v1/governance/enforcement-policies", params);
1449
+ return { policies: body.policies ?? [], total: body.total, rateLimit };
1450
+ }
1451
+ /**
1452
+ * Create or update a connector enforcement policy.
1453
+ * Calls `POST /v1/governance/enforcement-policies`.
1454
+ */
1455
+ async upsertEnforcementPolicy(input) {
1456
+ const { body, rateLimit } = await this.post("/v1/governance/enforcement-policies", input);
1457
+ return { policy: body, rateLimit };
1458
+ }
1459
+ // ── Organizational Risk Graph ─────────────────────────────────────────────
1460
+ /**
1461
+ * Trigger a fresh org-level risk score computation.
1462
+ * Calls `POST /v1/governance/risk/compute`.
1463
+ */
1464
+ async computeOrgRisk(options = {}) {
1465
+ const { body, rateLimit } = await this.post("/v1/governance/risk/compute", options);
1466
+ return { score: body, rateLimit };
1467
+ }
1468
+ /**
1469
+ * Retrieve the most recently computed risk score for the calling org.
1470
+ * Calls `GET /v1/governance/risk/latest`.
1471
+ */
1472
+ async getLatestOrgRisk() {
1473
+ const { body, rateLimit } = await this.get("/v1/governance/risk/latest");
1474
+ return { score: body.score ?? null, rateLimit };
1475
+ }
1476
+ /**
1477
+ * Page through historical org risk scores, most-recent first.
1478
+ * Calls `GET /v1/governance/risk/history`.
1479
+ */
1480
+ async listOrgRiskHistory(options = {}) {
1481
+ const params = new URLSearchParams();
1482
+ if (options.cursor) params.set("cursor", options.cursor);
1483
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
1484
+ const { body, rateLimit } = await this.get("/v1/governance/risk/history", params);
1485
+ const result = {
1486
+ scores: body.scores ?? [],
1487
+ total: body.total,
1488
+ rateLimit
314
1489
  };
1490
+ if (body.next_cursor) result.nextCursor = body.next_cursor;
1491
+ return result;
1492
+ }
1493
+ // ── Cross-Org Permission Negotiation ──────────────────────────────────────
1494
+ async checkCrossOrgPermission(req) {
1495
+ const { body } = await this.post(
1496
+ "/v1/cross-org/permissions/check",
1497
+ req
1498
+ );
1499
+ return body;
1500
+ }
1501
+ async listCrossOrgPermissionChecks(params) {
1502
+ const qs = new URLSearchParams();
1503
+ if (params?.source_org_id) qs.set("source_org_id", params.source_org_id);
1504
+ if (params?.target_org_id) qs.set("target_org_id", params.target_org_id);
1505
+ if (params?.allowed !== void 0)
1506
+ qs.set("allowed", String(params.allowed));
1507
+ if (params?.limit !== void 0) qs.set("limit", String(params.limit));
1508
+ const { body } = await this.get("/v1/cross-org/permissions/checks", qs);
1509
+ return body.checks ?? [];
1510
+ }
1511
+ // ── Anomaly Response Automation ───────────────────────────────────────────
1512
+ async listAnomalyResponseRules() {
1513
+ const { body } = await this.get(
1514
+ "/v1/anomaly-response/rules"
1515
+ );
1516
+ return body.rules ?? [];
1517
+ }
1518
+ async createAnomalyResponseRule(req) {
1519
+ const { body } = await this.post(
1520
+ "/v1/anomaly-response/rules",
1521
+ req
1522
+ );
1523
+ return body;
1524
+ }
1525
+ async updateAnomalyResponseRule(id, updates) {
1526
+ const { body } = await this.post(
1527
+ `/v1/anomaly-response/rules/${encodeURIComponent(id)}/update`,
1528
+ updates
1529
+ );
1530
+ return body;
1531
+ }
1532
+ async deleteAnomalyResponseRule(id) {
1533
+ await this.post(
1534
+ `/v1/anomaly-response/rules/${encodeURIComponent(id)}/delete`,
1535
+ {}
1536
+ );
1537
+ }
1538
+ async triggerAnomalyResponse(req) {
1539
+ const { body } = await this.post(
1540
+ "/v1/anomaly-response/trigger",
1541
+ req
1542
+ );
1543
+ return body.events ?? [];
1544
+ }
1545
+ async listAnomalyResponseEvents(params) {
1546
+ const qs = new URLSearchParams();
1547
+ if (params?.execution_id) qs.set("execution_id", params.execution_id);
1548
+ if (params?.limit !== void 0) qs.set("limit", String(params.limit));
1549
+ const { body } = await this.get(
1550
+ "/v1/anomaly-response/events",
1551
+ qs
1552
+ );
1553
+ return body.events ?? [];
1554
+ }
1555
+ // ── Budget Exception Workflows ────────────────────────────────────────────
1556
+ async listBudgetExceptions(params) {
1557
+ const qs = new URLSearchParams();
1558
+ if (params?.status) qs.set("status", params.status);
1559
+ if (params?.budget_policy_id)
1560
+ qs.set("budget_policy_id", params.budget_policy_id);
1561
+ if (params?.limit !== void 0) qs.set("limit", String(params.limit));
1562
+ if (params?.offset !== void 0) qs.set("offset", String(params.offset));
1563
+ const { body } = await this.get(
1564
+ "/v1/budget-exceptions",
1565
+ qs
1566
+ );
1567
+ return body.exceptions ?? [];
1568
+ }
1569
+ async getBudgetException(id) {
1570
+ const { body } = await this.get(
1571
+ `/v1/budget-exceptions/${encodeURIComponent(id)}`
1572
+ );
1573
+ return body;
1574
+ }
1575
+ async createBudgetException(req) {
1576
+ const { body } = await this.post(
1577
+ "/v1/budget-exceptions",
1578
+ req
1579
+ );
1580
+ return body;
1581
+ }
1582
+ async approveBudgetException(id, req) {
1583
+ const { body } = await this.post(
1584
+ `/v1/budget-exceptions/${encodeURIComponent(id)}/approve`,
1585
+ req
1586
+ );
1587
+ return body;
1588
+ }
1589
+ async rejectBudgetException(id, review_notes) {
1590
+ const { body } = await this.post(
1591
+ `/v1/budget-exceptions/${encodeURIComponent(id)}/reject`,
1592
+ { review_notes }
1593
+ );
1594
+ return body;
1595
+ }
1596
+ async cancelBudgetException(id) {
1597
+ const { body } = await this.post(
1598
+ `/v1/budget-exceptions/${encodeURIComponent(id)}/cancel`,
1599
+ {}
1600
+ );
1601
+ return body;
1602
+ }
1603
+ // ── Regulatory Escalation Chain ───────────────────────────────────────────
1604
+ async listRegulatoryAuthorityLevels() {
1605
+ const { body } = await this.get(
1606
+ "/v1/regulatory/authority-levels"
1607
+ );
1608
+ return body.levels ?? [];
1609
+ }
1610
+ async createRegulatoryAuthorityLevel(req) {
1611
+ const { body } = await this.post(
1612
+ "/v1/regulatory/authority-levels",
1613
+ req
1614
+ );
1615
+ return body;
1616
+ }
1617
+ async listRegulatoryEscalations(params) {
1618
+ const qs = new URLSearchParams();
1619
+ if (params?.status) qs.set("status", params.status);
1620
+ if (params?.subject_type) qs.set("subject_type", params.subject_type);
1621
+ if (params?.subject_id) qs.set("subject_id", params.subject_id);
1622
+ const { body } = await this.get(
1623
+ "/v1/regulatory/escalations",
1624
+ qs
1625
+ );
1626
+ return body.escalations ?? [];
1627
+ }
1628
+ async createRegulatoryEscalation(req) {
1629
+ const { body } = await this.post(
1630
+ "/v1/regulatory/escalations",
1631
+ req
1632
+ );
1633
+ return body;
1634
+ }
1635
+ async acknowledgeRegulatoryEscalation(id) {
1636
+ const { body } = await this.post(
1637
+ `/v1/regulatory/escalations/${encodeURIComponent(id)}/acknowledge`,
1638
+ {}
1639
+ );
1640
+ return body;
1641
+ }
1642
+ async resolveRegulatoryEscalation(id, resolution, resolution_details) {
1643
+ const { body } = await this.post(
1644
+ `/v1/regulatory/escalations/${encodeURIComponent(id)}/resolve`,
1645
+ { resolution, resolution_details }
1646
+ );
1647
+ return body;
1648
+ }
1649
+ async overrideRegulatoryEscalation(id, reason) {
1650
+ const { body } = await this.post(
1651
+ `/v1/regulatory/escalations/${encodeURIComponent(id)}/override`,
1652
+ { reason }
1653
+ );
1654
+ return body;
1655
+ }
1656
+ // ── Incentive Signal Feedback Loop ────────────────────────────────────────
1657
+ async listSignalActions(signal_id) {
1658
+ const { body } = await this.get(
1659
+ `/v1/governance/signals/${encodeURIComponent(signal_id)}/actions`
1660
+ );
1661
+ return body.actions ?? [];
1662
+ }
1663
+ async recordSignalAction(signal_id, req) {
1664
+ const { body } = await this.post(
1665
+ `/v1/governance/signals/${encodeURIComponent(signal_id)}/actions`,
1666
+ req
1667
+ );
1668
+ return body;
1669
+ }
1670
+ async recordSignalOutcome(signal_id, action_id, req) {
1671
+ const { body } = await this.post(
1672
+ `/v1/governance/signals/${encodeURIComponent(signal_id)}/actions/${encodeURIComponent(action_id)}/outcome`,
1673
+ req
1674
+ );
1675
+ return body;
1676
+ }
1677
+ async getSignalActionSummary() {
1678
+ const { body } = await this.get(
1679
+ "/v1/governance/signals/actions/summary"
1680
+ );
1681
+ return body;
1682
+ }
1683
+ // ── Cross-Org Impersonation ───────────────────────────────────────────────
1684
+ async listImpersonationGrants() {
1685
+ const { body } = await this.get(
1686
+ "/v1/cross-org/impersonation/grants"
1687
+ );
1688
+ return body.grants ?? [];
1689
+ }
1690
+ async createImpersonationGrant(req) {
1691
+ const { body } = await this.post(
1692
+ "/v1/cross-org/impersonation/grants",
1693
+ req
1694
+ );
1695
+ return body;
1696
+ }
1697
+ async revokeImpersonationGrant(id) {
1698
+ await this.post(
1699
+ `/v1/cross-org/impersonation/grants/${encodeURIComponent(id)}/revoke`,
1700
+ {}
1701
+ );
1702
+ }
1703
+ async issueImpersonationToken(grant_id, requested_duration_seconds) {
1704
+ const { body } = await this.post(
1705
+ `/v1/cross-org/impersonation/grants/${encodeURIComponent(grant_id)}/token`,
1706
+ { requested_duration_seconds }
1707
+ );
1708
+ return body;
1709
+ }
1710
+ async validateImpersonationToken(token) {
1711
+ const { body } = await this.post(
1712
+ "/v1/cross-org/impersonation/validate",
1713
+ { token }
1714
+ );
1715
+ return body;
315
1716
  }
316
1717
  };
317
1718
  function parseRateLimitHeaders(headers) {
@@ -457,6 +1858,9 @@ function buildAuditEventsQuery(query) {
457
1858
  }
458
1859
  return params;
459
1860
  }
1861
+ function sleep(ms) {
1862
+ return new Promise((resolve) => setTimeout(resolve, ms));
1863
+ }
460
1864
  function parseRetryAfter(raw) {
461
1865
  if (!raw) return void 0;
462
1866
  const seconds = Number(raw);
@@ -468,6 +1872,125 @@ function parseRetryAfter(raw) {
468
1872
  }
469
1873
  return void 0;
470
1874
  }
1875
+ async function* parseSseStream(body, requestId, timeoutMs, onEventId) {
1876
+ const reader = body.getReader();
1877
+ const decoder = new TextDecoder("utf-8");
1878
+ let buf = "";
1879
+ async function readChunk() {
1880
+ if (timeoutMs <= 0) {
1881
+ return reader.read();
1882
+ }
1883
+ return new Promise((resolve, reject) => {
1884
+ const timer = setTimeout(() => {
1885
+ reject(new StreamTimeoutError(timeoutMs));
1886
+ }, timeoutMs);
1887
+ reader.read().then(
1888
+ (result) => {
1889
+ clearTimeout(timer);
1890
+ resolve(result);
1891
+ },
1892
+ (err) => {
1893
+ clearTimeout(timer);
1894
+ reject(err);
1895
+ }
1896
+ );
1897
+ });
1898
+ }
1899
+ try {
1900
+ for (; ; ) {
1901
+ let done;
1902
+ let value;
1903
+ try {
1904
+ const result = await readChunk();
1905
+ done = result.done;
1906
+ value = result.value;
1907
+ } catch (err) {
1908
+ if (err instanceof StreamTimeoutError) throw err;
1909
+ throw new AtlaSentError(
1910
+ `AtlaSent stream read failed: ${err instanceof Error ? err.message : String(err)}`,
1911
+ { code: "network", requestId, cause: err }
1912
+ );
1913
+ }
1914
+ if (done) break;
1915
+ buf += decoder.decode(value, { stream: true });
1916
+ let boundary;
1917
+ while ((boundary = buf.indexOf("\n\n")) !== -1) {
1918
+ const block = buf.slice(0, boundary);
1919
+ buf = buf.slice(boundary + 2);
1920
+ let eventType = "message";
1921
+ let data = "";
1922
+ let eventId;
1923
+ for (const line of block.split("\n")) {
1924
+ if (line.startsWith("event: ")) eventType = line.slice(7).trim();
1925
+ else if (line.startsWith("data: ")) data = line.slice(6);
1926
+ else if (line.startsWith("id: ")) eventId = line.slice(4).trim();
1927
+ else if (line.startsWith("id:")) eventId = line.slice(3).trim();
1928
+ }
1929
+ if (eventId !== void 0) onEventId(eventId);
1930
+ if (!data) continue;
1931
+ if (eventType === "done") return;
1932
+ let parsed;
1933
+ try {
1934
+ parsed = JSON.parse(data);
1935
+ } catch (err) {
1936
+ throw new StreamParseError(data, err);
1937
+ }
1938
+ if (eventType === "error") {
1939
+ const e = parsed;
1940
+ throw new AtlaSentError(
1941
+ e.message ?? "Stream error from AtlaSent API",
1942
+ {
1943
+ code: e.code ?? "server_error",
1944
+ requestId: e.request_id ?? requestId
1945
+ }
1946
+ );
1947
+ }
1948
+ if (eventType === "decision") {
1949
+ const d = parsed;
1950
+ if (typeof d.permitted !== "boolean" || typeof d.decision_id !== "string") {
1951
+ throw new AtlaSentError(
1952
+ "Malformed decision event from AtlaSent API",
1953
+ {
1954
+ code: "bad_response",
1955
+ requestId
1956
+ }
1957
+ );
1958
+ }
1959
+ const streamDecision = d.permitted ? "allow" : "deny";
1960
+ const isFinal = d.is_final ?? false;
1961
+ yield {
1962
+ type: "decision",
1963
+ decision: streamDecision,
1964
+ decision_canonical: streamDecision,
1965
+ permitId: d.decision_id,
1966
+ reason: d.reason ?? "",
1967
+ auditHash: d.audit_hash ?? "",
1968
+ timestamp: d.timestamp ?? "",
1969
+ isFinal
1970
+ };
1971
+ if (isFinal || d.done === true) return;
1972
+ } else if (eventType === "progress") {
1973
+ const p = parsed;
1974
+ yield {
1975
+ type: "progress",
1976
+ stage: String(p["stage"] ?? ""),
1977
+ ...p
1978
+ };
1979
+ if (p.done === true) return;
1980
+ } else {
1981
+ if (parsed !== null && typeof parsed === "object" && parsed.done === true) {
1982
+ return;
1983
+ }
1984
+ }
1985
+ }
1986
+ }
1987
+ if (buf.trim().length > 0) {
1988
+ throw new StreamParseError(buf);
1989
+ }
1990
+ } finally {
1991
+ reader.releaseLock();
1992
+ }
1993
+ }
471
1994
 
472
1995
  // src/auditBundle.ts
473
1996
  var import_promises = require("fs/promises");
@@ -624,9 +2147,13 @@ function configure(options) {
624
2147
  overrides = { ...overrides, ...options };
625
2148
  sharedClient = null;
626
2149
  }
2150
+ async function deployGate(request = {}) {
2151
+ return getClient().deployGate(request);
2152
+ }
627
2153
  function getClient() {
628
2154
  if (sharedClient) return sharedClient;
629
- const apiKey = overrides.apiKey ?? process.env.ATLASENT_API_KEY;
2155
+ const envApiKey = typeof process !== "undefined" && process.env ? process.env.ATLASENT_API_KEY : void 0;
2156
+ const apiKey = overrides.apiKey ?? envApiKey;
630
2157
  if (!apiKey) {
631
2158
  throw new AtlaSentError(
632
2159
  "AtlaSent is not configured. Set ATLASENT_API_KEY in the environment, or call atlasent.configure({ apiKey }).",
@@ -635,8 +2162,11 @@ function getClient() {
635
2162
  }
636
2163
  const options = { apiKey };
637
2164
  if (overrides.baseUrl !== void 0) options.baseUrl = overrides.baseUrl;
638
- if (overrides.timeoutMs !== void 0) options.timeoutMs = overrides.timeoutMs;
2165
+ if (overrides.timeoutMs !== void 0)
2166
+ options.timeoutMs = overrides.timeoutMs;
639
2167
  if (overrides.fetch !== void 0) options.fetch = overrides.fetch;
2168
+ if (overrides.retryPolicy !== void 0)
2169
+ options.retryPolicy = overrides.retryPolicy;
640
2170
  sharedClient = new AtlaSentClient(options);
641
2171
  return sharedClient;
642
2172
  }
@@ -645,10 +2175,42 @@ function wireDecisionToDenied(serverDecision) {
645
2175
  if (lower === "hold" || lower === "escalate") return lower;
646
2176
  return "deny";
647
2177
  }
2178
+ function sortKeysDeep(val) {
2179
+ if (Array.isArray(val)) return val.map(sortKeysDeep);
2180
+ if (val !== null && typeof val === "object") {
2181
+ return Object.keys(val).sort().reduce((acc, k) => {
2182
+ acc[k] = sortKeysDeep(val[k]);
2183
+ return acc;
2184
+ }, {});
2185
+ }
2186
+ return val;
2187
+ }
2188
+ async function computeExecutionHash(payload) {
2189
+ const sorted = sortKeysDeep(payload);
2190
+ const canonical = JSON.stringify(sorted);
2191
+ if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle?.digest) {
2192
+ const bytes = new TextEncoder().encode(canonical);
2193
+ const buf = await globalThis.crypto.subtle.digest("SHA-256", bytes);
2194
+ return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
2195
+ }
2196
+ try {
2197
+ const { createHash } = await import(
2198
+ /* @vite-ignore */
2199
+ /* webpackIgnore: true */
2200
+ "crypto"
2201
+ );
2202
+ return createHash("sha256").update(canonical, "utf8").digest("hex");
2203
+ } catch {
2204
+ console.warn(
2205
+ "[atlasent] Could not compute execution_hash: neither crypto.subtle nor node:crypto is available in this runtime."
2206
+ );
2207
+ return "";
2208
+ }
2209
+ }
648
2210
  async function protect(request) {
649
2211
  const client = getClient();
650
2212
  const evaluation = await client.evaluate(request);
651
- if (evaluation.decision !== "ALLOW") {
2213
+ if (evaluation.decision !== "allow") {
652
2214
  throw new AtlaSentDeniedError({
653
2215
  decision: wireDecisionToDenied(evaluation.decision),
654
2216
  evaluationId: evaluation.permitId,
@@ -656,19 +2218,35 @@ async function protect(request) {
656
2218
  auditHash: evaluation.auditHash
657
2219
  });
658
2220
  }
2221
+ const environment = request.context?.environment ?? (() => {
2222
+ console.warn(
2223
+ "[atlasent] environment not set on evaluate request \u2014 defaulting to 'production'. Set context.environment explicitly to suppress."
2224
+ );
2225
+ return "production";
2226
+ })();
2227
+ const evaluatePayload = {
2228
+ action_type: request.action,
2229
+ actor_id: request.agent,
2230
+ context: request.context ?? {}
2231
+ };
2232
+ const execution_hash = await computeExecutionHash(evaluatePayload);
659
2233
  const verifyRequest = {
660
2234
  permitId: evaluation.permitId,
661
2235
  agent: request.agent,
662
- action: request.action
2236
+ action: request.action,
2237
+ environment,
2238
+ ...execution_hash ? { execution_hash } : {}
663
2239
  };
664
2240
  if (request.context !== void 0) verifyRequest.context = request.context;
665
2241
  const verification = await client.verifyPermit(verifyRequest);
666
2242
  if (!verification.verified) {
2243
+ const outcome = normalizePermitOutcome(verification.outcome);
667
2244
  throw new AtlaSentDeniedError({
668
2245
  decision: "deny",
669
2246
  evaluationId: evaluation.permitId,
670
2247
  reason: `Permit failed verification (${verification.outcome})`,
671
- auditHash: evaluation.auditHash
2248
+ auditHash: evaluation.auditHash,
2249
+ ...outcome !== void 0 && { outcome }
672
2250
  });
673
2251
  }
674
2252
  return {
@@ -680,64 +2258,1318 @@ async function protect(request) {
680
2258
  };
681
2259
  }
682
2260
 
683
- // src/retry.ts
684
- var DEFAULT_RETRY_POLICY = {
685
- maxAttempts: 3,
686
- baseDelayMs: 250,
687
- maxDelayMs: 7e3
688
- };
689
- var RETRYABLE_CODES = /* @__PURE__ */ new Set([
690
- "network",
691
- "timeout",
692
- "rate_limited",
693
- "server_error",
694
- "bad_response"
695
- ]);
696
- function isRetryable(err) {
697
- if (!(err instanceof AtlaSentError)) return false;
698
- if (err.code === void 0) return false;
699
- return RETRYABLE_CODES.has(err.code);
2261
+ // src/requirePermit.ts
2262
+ async function requirePermit(action, execute) {
2263
+ await protect({
2264
+ agent: action.actor_id,
2265
+ action: action.action_type,
2266
+ context: {
2267
+ resource_id: action.resource_id,
2268
+ environment: action.environment,
2269
+ ...action.context
2270
+ }
2271
+ });
2272
+ return execute();
700
2273
  }
701
- function computeBackoffMs(attempt, policy = {}, err, random = Math.random) {
702
- const merged = mergePolicy(policy);
703
- const safeAttempt = Math.max(0, Math.floor(attempt));
704
- const exp = Math.min(safeAttempt, 30);
705
- const ceiling = Math.min(merged.maxDelayMs, merged.baseDelayMs * 2 ** exp);
706
- const jittered = Math.floor(ceiling * clampUnit(random()));
707
- const retryAfterMs = err instanceof AtlaSentError && typeof err.retryAfterMs === "number" ? Math.max(0, err.retryAfterMs) : 0;
708
- return Math.max(retryAfterMs, jittered);
2274
+ var DESTRUCTIVE_PATTERNS = [
2275
+ /rm\s+-rf/,
2276
+ /DROP\s+TABLE/i,
2277
+ /DROP\s+DATABASE/i,
2278
+ /DELETE\s+FROM/i,
2279
+ /TRUNCATE\s+TABLE/i,
2280
+ /railway\s+volume\s+delete/i,
2281
+ /kubectl\s+delete/i,
2282
+ /terraform\s+destroy/i
2283
+ ];
2284
+ function classifyCommand(command) {
2285
+ return DESTRUCTIVE_PATTERNS.some((p) => p.test(command)) ? "destructive.command" : null;
709
2286
  }
710
- function hasAttemptsLeft(attempt, policy = {}) {
711
- const merged = mergePolicy(policy);
712
- return attempt + 1 < merged.maxAttempts;
2287
+
2288
+ // src/withPermit.ts
2289
+ async function withPermit(request, fn) {
2290
+ const permit = await protect(request);
2291
+ return await fn(permit);
713
2292
  }
714
- function mergePolicy(policy) {
715
- const maxAttempts = Math.max(
716
- 1,
717
- Math.floor(policy.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts)
2293
+
2294
+ // src/hitl.ts
2295
+ function hitlRequiredApproverCount(quorum, poolSize) {
2296
+ const n = Number.isFinite(poolSize) && poolSize >= 1 ? Math.floor(poolSize) : 1;
2297
+ switch (quorum) {
2298
+ case "single_approver":
2299
+ return 1;
2300
+ case "simple_majority":
2301
+ return Math.floor(n / 2) + 1;
2302
+ case "two_thirds":
2303
+ return Math.ceil(2 * n / 3);
2304
+ case "unanimous":
2305
+ return n;
2306
+ }
2307
+ }
2308
+
2309
+ // src/sandboxDiff.ts
2310
+ function isSandboxDiffPopulated(r) {
2311
+ return r.total_writes > 0 || r.org_id !== void 0;
2312
+ }
2313
+
2314
+ // src/delegationPropagation.ts
2315
+ function delegationPropagationHadEffect(s) {
2316
+ return s.hitl_reassigned > 0 || s.financial_invalidated > 0 || s.policies_flagged > 0;
2317
+ }
2318
+
2319
+ // src/financialAction.ts
2320
+ var DEFAULT_RISK_TIER_THRESHOLDS = [
2321
+ { tier: "low", lower_bound: 0, upper_bound: 1e3, reference_currency: "USD" },
2322
+ { tier: "medium", lower_bound: 1e3, upper_bound: 5e4, reference_currency: "USD" },
2323
+ { tier: "high", lower_bound: 5e4, upper_bound: 1e6, reference_currency: "USD" },
2324
+ { tier: "critical", lower_bound: 1e6, upper_bound: null, reference_currency: "USD" }
2325
+ ];
2326
+ function classifyRiskTier(value, thresholds = DEFAULT_RISK_TIER_THRESHOLDS) {
2327
+ for (const t of thresholds) {
2328
+ if (value >= t.lower_bound && (t.upper_bound === null || value < t.upper_bound)) {
2329
+ return t.tier;
2330
+ }
2331
+ }
2332
+ return "critical";
2333
+ }
2334
+ function withinAutonomousCeiling(actionValue, ceiling) {
2335
+ if (ceiling === null) return true;
2336
+ return actionValue <= ceiling;
2337
+ }
2338
+
2339
+ // src/liabilityAttribution.ts
2340
+ var ROLE_WEIGHTS = {
2341
+ authorizer: 0.3,
2342
+ delegator: 0.15,
2343
+ delegate: 0.15,
2344
+ executor: 0.25,
2345
+ approver: 0.05,
2346
+ override_actor: 0.4,
2347
+ supervisor: 0.1,
2348
+ exception_approver: 0.05
2349
+ };
2350
+ function computeLiabilityWeights(parties, distribution = "role_weighted") {
2351
+ if (parties.length === 0) return [];
2352
+ let raw;
2353
+ if (distribution === "equal") {
2354
+ raw = parties.map(() => 1);
2355
+ } else {
2356
+ raw = parties.map((p) => ROLE_WEIGHTS[p.role] ?? 0.05);
2357
+ }
2358
+ const total = raw.reduce((s, w) => s + w, 0);
2359
+ if (total <= 0) return parties.map(() => 1 / parties.length);
2360
+ return raw.map((w) => w / total);
2361
+ }
2362
+ function buildLiabilityChain(input, distribution = "role_weighted") {
2363
+ const raw = [];
2364
+ raw.push({ ...input.authorizer, role: "authorizer" });
2365
+ for (const d of input.delegations ?? []) {
2366
+ raw.push({
2367
+ party_id: d.delegator_id,
2368
+ party_label: d.delegator_label,
2369
+ party_type: d.delegator_type,
2370
+ role: "delegator",
2371
+ acted_at: d.acted_at,
2372
+ permit_id: d.permit_id
2373
+ });
2374
+ raw.push({
2375
+ party_id: d.delegate_id,
2376
+ party_label: d.delegate_label,
2377
+ party_type: d.delegate_type,
2378
+ role: "delegate",
2379
+ acted_at: d.acted_at,
2380
+ permit_id: d.permit_id
2381
+ });
2382
+ }
2383
+ for (const a of input.approvers) {
2384
+ raw.push({ ...a, role: "approver" });
2385
+ }
2386
+ for (const s of input.supervisors ?? []) {
2387
+ raw.push({ ...s, role: "supervisor" });
2388
+ }
2389
+ raw.push({ ...input.executor, role: "executor" });
2390
+ if (input.override) {
2391
+ raw.push({
2392
+ party_id: input.override.actor_id,
2393
+ party_label: input.override.actor_label,
2394
+ party_type: input.override.actor_type,
2395
+ role: "override_actor",
2396
+ acted_at: input.override.acted_at,
2397
+ permit_id: input.override.permit_id
2398
+ });
2399
+ }
2400
+ const weights = computeLiabilityWeights(raw, distribution);
2401
+ return raw.map((p, i) => ({ ...p, liability_weight: weights[i] ?? 0 }));
2402
+ }
2403
+ function findPrimaryLiabilityParties(chain, threshold = 0.2) {
2404
+ return chain.filter((p) => p.liability_weight >= threshold);
2405
+ }
2406
+ function validateLiabilityChain(chain, hasEmergencyOverride) {
2407
+ const errors = [];
2408
+ if (chain.length === 0) {
2409
+ errors.push("liability chain must have at least one party");
2410
+ }
2411
+ const weightSum = chain.reduce((s, p) => s + p.liability_weight, 0);
2412
+ if (Math.abs(weightSum - 1) > 0.01) {
2413
+ errors.push(`liability weights sum to ${weightSum.toFixed(4)}, expected 1.0`);
2414
+ }
2415
+ const seen = /* @__PURE__ */ new Set();
2416
+ for (const p of chain) {
2417
+ const key = `${p.party_id}:${p.role}`;
2418
+ if (seen.has(key)) errors.push(`duplicate party+role: ${key}`);
2419
+ seen.add(key);
2420
+ }
2421
+ if (hasEmergencyOverride && !chain.some((p) => p.role === "override_actor")) {
2422
+ errors.push("emergency_override is true but no override_actor in chain");
2423
+ }
2424
+ return { valid: errors.length === 0, errors };
2425
+ }
2426
+
2427
+ // src/economicRisk.ts
2428
+ var SCORE_WEIGHTS = {
2429
+ exposure: 0.25,
2430
+ concentration: 0.25,
2431
+ override: 0.2,
2432
+ drift: 0.15,
2433
+ anomaly: 0.15
2434
+ };
2435
+ function computeOverallRiskScore(subScores) {
2436
+ return Math.min(
2437
+ 100,
2438
+ Math.max(
2439
+ 0,
2440
+ subScores.exposure * SCORE_WEIGHTS.exposure + subScores.concentration * SCORE_WEIGHTS.concentration + subScores.override * SCORE_WEIGHTS.override + subScores.drift * SCORE_WEIGHTS.drift + subScores.anomaly * SCORE_WEIGHTS.anomaly
2441
+ )
718
2442
  );
719
- const baseDelayMs = Math.max(
720
- 0,
721
- policy.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs
2443
+ }
2444
+ function scoreToRiskTier(score) {
2445
+ if (score <= 25) return "low";
2446
+ if (score <= 55) return "medium";
2447
+ if (score <= 80) return "high";
2448
+ return "critical";
2449
+ }
2450
+ function computeHHI(shares) {
2451
+ return shares.reduce((acc, s) => acc + s * s, 0);
2452
+ }
2453
+ function hhiToConcentrationScore(hhi) {
2454
+ return Math.min(100, hhi / 1e4 * 100);
2455
+ }
2456
+ function computeExposureScore(records, exposureCeilingUSD = 1e7) {
2457
+ const activeStatuses = /* @__PURE__ */ new Set(
2458
+ ["pending_approval", "approved", "executing"]
722
2459
  );
723
- const maxDelayMs = Math.max(
724
- baseDelayMs,
725
- policy.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs
2460
+ const totalExposure = records.filter((r) => activeStatuses.has(r.status)).reduce((acc, r) => acc + r.action_value, 0);
2461
+ return Math.min(100, totalExposure / exposureCeilingUSD * 100);
2462
+ }
2463
+ function computeOverrideScore(totalExecutions, overriddenExecutions) {
2464
+ if (totalExecutions === 0) return 0;
2465
+ const rate = overriddenExecutions / totalExecutions;
2466
+ return Math.min(100, rate * 1e3);
2467
+ }
2468
+ function detectSelfApproval(initiatorId, approverIds) {
2469
+ return approverIds.includes(initiatorId);
2470
+ }
2471
+ function computeApprovalRiskScore(analysis) {
2472
+ return hhiToConcentrationScore(analysis.concentration_hhi);
2473
+ }
2474
+
2475
+ // src/financialQuorum.ts
2476
+ function evaluateFinancialQuorum(input) {
2477
+ const unmet = [];
2478
+ const activeFreeze = input.active_freezes.find((f) => !f.lifted);
2479
+ if (activeFreeze) {
2480
+ return {
2481
+ passed: false,
2482
+ base_quorum_passed: false,
2483
+ amount_threshold_satisfied: false,
2484
+ financial_roles_satisfied: false,
2485
+ regulator_approval_missing: false,
2486
+ blocked_by_freeze: true,
2487
+ base_quorum_proof: null,
2488
+ denial_reason: `action blocked by emergency freeze (${activeFreeze.freeze_id}): ${activeFreeze.reason}`,
2489
+ unmet_requirements: [`emergency_freeze:${activeFreeze.freeze_id}`]
2490
+ };
2491
+ }
2492
+ const baseQuorumPassed = input.base_quorum_proof !== null || input.approval_count >= input.policy.required_count;
2493
+ if (!baseQuorumPassed) {
2494
+ unmet.push(
2495
+ `base quorum requires ${input.policy.required_count} approvals, have ${input.approval_count}`
2496
+ );
2497
+ }
2498
+ let amountThresholdSatisfied = true;
2499
+ for (const threshold of input.policy.amount_thresholds) {
2500
+ if (input.action_value >= threshold.value) {
2501
+ const needed = input.policy.required_count + threshold.additional_approvals;
2502
+ if (input.approval_count < needed) {
2503
+ amountThresholdSatisfied = false;
2504
+ unmet.push(
2505
+ `amount threshold ${threshold.value} ${threshold.currency} requires ${needed} approvals`
2506
+ );
2507
+ }
2508
+ for (const req of threshold.additional_roles) {
2509
+ const present = input.present_roles[req.role] ?? 0;
2510
+ if (present < req.min) {
2511
+ amountThresholdSatisfied = false;
2512
+ unmet.push(`amount threshold requires ${req.min} ${req.role} approver(s), have ${present}`);
2513
+ }
2514
+ }
2515
+ if (threshold.senior_review_required && !(input.present_roles["senior_finance"] ?? 0)) {
2516
+ amountThresholdSatisfied = false;
2517
+ unmet.push("amount threshold requires senior_finance review");
2518
+ }
2519
+ }
2520
+ }
2521
+ let financialRolesSatisfied = true;
2522
+ for (const req of input.policy.financial_role_requirements) {
2523
+ if (req.applies_to_tiers && !req.applies_to_tiers.includes(input.risk_tier)) continue;
2524
+ if (req.applies_above !== void 0 && input.action_value < req.applies_above) continue;
2525
+ const present = input.present_roles[req.role] ?? 0;
2526
+ if (present < req.min) {
2527
+ financialRolesSatisfied = false;
2528
+ unmet.push(`financial role ${req.role} requires ${req.min} approver(s), have ${present}`);
2529
+ }
2530
+ }
2531
+ const regulatorMissing = input.policy.regulator_approval_threshold !== null && input.action_value >= input.policy.regulator_approval_threshold && !input.regulator_approval_present;
2532
+ if (regulatorMissing) {
2533
+ unmet.push("regulator approval required for this action value");
2534
+ }
2535
+ const passed = baseQuorumPassed && amountThresholdSatisfied && financialRolesSatisfied && !regulatorMissing;
2536
+ return {
2537
+ passed,
2538
+ base_quorum_passed: baseQuorumPassed,
2539
+ amount_threshold_satisfied: amountThresholdSatisfied,
2540
+ financial_roles_satisfied: financialRolesSatisfied,
2541
+ regulator_approval_missing: regulatorMissing,
2542
+ blocked_by_freeze: false,
2543
+ base_quorum_proof: input.base_quorum_proof,
2544
+ denial_reason: passed ? null : unmet[0] ?? "financial quorum not satisfied",
2545
+ unmet_requirements: unmet
2546
+ };
2547
+ }
2548
+ function computeEscalatedApprovalCount(baseCount, actionValue, thresholds) {
2549
+ let additional = 0;
2550
+ for (const t of thresholds) {
2551
+ if (actionValue >= t.value) {
2552
+ additional = Math.max(additional, t.additional_approvals);
2553
+ }
2554
+ }
2555
+ return baseCount + additional;
2556
+ }
2557
+
2558
+ // src/budgetaryGovernance.ts
2559
+ function checkBudgetConstraints(params) {
2560
+ const hardBlocks = [];
2561
+ const softWarnings = [];
2562
+ const limitsChecked = [];
2563
+ const constraintsChecked = [];
2564
+ const nowIso = (params.now ?? /* @__PURE__ */ new Date()).toISOString();
2565
+ for (const limit of params.applicableLimits) {
2566
+ limitsChecked.push(limit.limit_id);
2567
+ if (limit.period_end && nowIso > limit.period_end) {
2568
+ const v = {
2569
+ violation_type: "period_expired",
2570
+ limit_id: limit.limit_id,
2571
+ description: `Budget limit ${limit.limit_id} period expired at ${limit.period_end}`
2572
+ };
2573
+ if (limit.enforcement === "hard") hardBlocks.push(v);
2574
+ else softWarnings.push(v);
2575
+ continue;
2576
+ }
2577
+ const projected = limit.spending.spent_amount + params.actionValue;
2578
+ if (projected > limit.limit_amount) {
2579
+ const v = {
2580
+ violation_type: "limit_exceeded",
2581
+ limit_id: limit.limit_id,
2582
+ description: `Action would exceed ${limit.scope_type} limit (${limit.limit_amount} ${limit.currency})`,
2583
+ overage_amount: projected - limit.limit_amount
2584
+ };
2585
+ if (limit.enforcement === "hard") hardBlocks.push(v);
2586
+ else softWarnings.push(v);
2587
+ }
2588
+ }
2589
+ for (const constraint of params.applicableConstraints) {
2590
+ constraintsChecked.push(constraint.constraint_id);
2591
+ if (constraint.action_type !== "*" && constraint.action_type !== params.actionType) continue;
2592
+ if (!constraint.allow_anonymous_agents && params.isAnonymousAgent) {
2593
+ hardBlocks.push({
2594
+ violation_type: "anonymous_agent_blocked",
2595
+ constraint_id: constraint.constraint_id,
2596
+ description: `Anonymous agents are not permitted to execute ${params.actionType}`
2597
+ });
2598
+ }
2599
+ if (params.actionValue > constraint.max_single_transaction) {
2600
+ hardBlocks.push({
2601
+ violation_type: "single_transaction_exceeds",
2602
+ constraint_id: constraint.constraint_id,
2603
+ description: `Value ${params.actionValue} exceeds single-transaction limit ${constraint.max_single_transaction} ${constraint.currency}`,
2604
+ overage_amount: params.actionValue - constraint.max_single_transaction
2605
+ });
2606
+ }
2607
+ if (constraint.max_daily_aggregate !== null && params.currentDailySpend + params.actionValue > constraint.max_daily_aggregate) {
2608
+ hardBlocks.push({
2609
+ violation_type: "daily_aggregate_exceeds",
2610
+ constraint_id: constraint.constraint_id,
2611
+ description: `Action would exceed daily aggregate limit ${constraint.max_daily_aggregate} ${constraint.currency}`,
2612
+ overage_amount: params.currentDailySpend + params.actionValue - constraint.max_daily_aggregate
2613
+ });
2614
+ }
2615
+ if (constraint.max_monthly_aggregate !== null && params.currentMonthlySpend + params.actionValue > constraint.max_monthly_aggregate) {
2616
+ hardBlocks.push({
2617
+ violation_type: "monthly_aggregate_exceeds",
2618
+ constraint_id: constraint.constraint_id,
2619
+ description: `Action would exceed monthly aggregate limit ${constraint.max_monthly_aggregate} ${constraint.currency}`,
2620
+ overage_amount: params.currentMonthlySpend + params.actionValue - constraint.max_monthly_aggregate
2621
+ });
2622
+ }
2623
+ }
2624
+ return {
2625
+ permitted: hardBlocks.length === 0,
2626
+ hard_blocks: hardBlocks,
2627
+ soft_warnings: softWarnings,
2628
+ limits_checked: limitsChecked,
2629
+ constraints_checked: constraintsChecked
2630
+ };
2631
+ }
2632
+ function budgetUtilizationSeverity(utilizationPct) {
2633
+ if (utilizationPct >= 100) return "critical";
2634
+ if (utilizationPct >= 80) return "warn";
2635
+ return "normal";
2636
+ }
2637
+
2638
+ // src/autonomousFinancial.ts
2639
+ var RISK_TIER_ORDER = {
2640
+ low: 1,
2641
+ medium: 2,
2642
+ high: 3,
2643
+ critical: 4
2644
+ };
2645
+ function checkAutonomousBounds(params) {
2646
+ const violations = [];
2647
+ const nowIso = (params.now ?? /* @__PURE__ */ new Date()).toISOString();
2648
+ const boundsActive = params.bounds.active;
2649
+ if (!boundsActive) violations.push("agent execution bounds are inactive");
2650
+ const boundsNotExpired = params.bounds.expires_at === null || params.bounds.expires_at > nowIso;
2651
+ if (!boundsNotExpired) {
2652
+ violations.push(`agent bounds expired at ${params.bounds.expires_at}`);
2653
+ }
2654
+ const actionTypePermitted = params.bounds.permitted_action_types.includes(
2655
+ params.actionType
726
2656
  );
727
- return { maxAttempts, baseDelayMs, maxDelayMs };
2657
+ if (!actionTypePermitted) {
2658
+ violations.push(`action type ${params.actionType} not in agent's permitted set`);
2659
+ }
2660
+ const applicableCeiling = params.bounds.ceilings.find((c) => c.action_type === params.actionType) ?? null;
2661
+ let withinExecutionCeiling = true;
2662
+ if (applicableCeiling !== null) {
2663
+ if (params.actionValue > applicableCeiling.per_execution_max) {
2664
+ withinExecutionCeiling = false;
2665
+ violations.push(
2666
+ `value ${params.actionValue} exceeds per-execution ceiling ${applicableCeiling.per_execution_max} ${applicableCeiling.currency}`
2667
+ );
2668
+ }
2669
+ if (applicableCeiling.max_daily_count !== null) {
2670
+ const todayCount = params.currentDailyCount[params.actionType] ?? 0;
2671
+ if (todayCount >= applicableCeiling.max_daily_count) {
2672
+ withinExecutionCeiling = false;
2673
+ violations.push(
2674
+ `daily count ${todayCount} at or exceeds limit ${applicableCeiling.max_daily_count} for ${params.actionType}`
2675
+ );
2676
+ }
2677
+ }
2678
+ }
2679
+ const withinDailyAggregate = params.currentDailyAggregate + params.actionValue <= params.bounds.daily_aggregate_ceiling;
2680
+ if (!withinDailyAggregate) {
2681
+ violations.push(
2682
+ `daily aggregate ${params.currentDailyAggregate + params.actionValue} would exceed ceiling ${params.bounds.daily_aggregate_ceiling} ${params.bounds.aggregate_currency}`
2683
+ );
2684
+ }
2685
+ const withinRiskTier = RISK_TIER_ORDER[params.riskTier] <= RISK_TIER_ORDER[params.bounds.max_risk_tier];
2686
+ if (!withinRiskTier) {
2687
+ violations.push(
2688
+ `action risk tier ${params.riskTier} exceeds agent max ${params.bounds.max_risk_tier}`
2689
+ );
2690
+ }
2691
+ const permitted = boundsActive && boundsNotExpired && actionTypePermitted && withinExecutionCeiling && withinDailyAggregate && withinRiskTier;
2692
+ return {
2693
+ permitted,
2694
+ action_type_permitted: actionTypePermitted,
2695
+ within_execution_ceiling: withinExecutionCeiling,
2696
+ within_daily_aggregate: withinDailyAggregate,
2697
+ within_risk_tier: withinRiskTier,
2698
+ bounds_active: boundsActive,
2699
+ bounds_not_expired: boundsNotExpired,
2700
+ applicable_ceiling: applicableCeiling,
2701
+ denial_reason: permitted ? null : violations[0] ?? "execution out of bounds",
2702
+ violations
2703
+ };
728
2704
  }
729
- function clampUnit(n) {
730
- if (!Number.isFinite(n)) return 0;
731
- if (n < 0) return 0;
732
- if (n >= 1) return 0.999999999;
733
- return n;
2705
+ function detectAutonomousAnomaly(params) {
2706
+ const zScore = params.historicalStdDev > 0 ? Math.abs(params.actionValue - params.historicalMeanValue) / params.historicalStdDev : 0;
2707
+ if (zScore > 3) {
2708
+ return {
2709
+ anomalyDetected: true,
2710
+ description: `action value ${params.actionValue} is ${zScore.toFixed(1)}\u03C3 from mean (${params.historicalMeanValue})`
2711
+ };
2712
+ }
2713
+ if (params.recentExecutionCount > params.burstThreshold) {
2714
+ return {
2715
+ anomalyDetected: true,
2716
+ description: `execution burst: ${params.recentExecutionCount} in window (threshold: ${params.burstThreshold})`
2717
+ };
2718
+ }
2719
+ if (params.isOffHours && params.actionValue > params.historicalMeanValue * 2) {
2720
+ return {
2721
+ anomalyDetected: true,
2722
+ description: `off-hours execution with above-average value ${params.actionValue}`
2723
+ };
2724
+ }
2725
+ return { anomalyDetected: false, description: null };
2726
+ }
2727
+
2728
+ // src/incentiveAlignment.ts
2729
+ var DEFAULT_INCENTIVE_CONFIG = {
2730
+ max_override_rate: 0.05,
2731
+ min_approval_latency_seconds: 30,
2732
+ max_emergency_bypasses_30d: 3,
2733
+ max_concentration_share: 0.4,
2734
+ max_delegation_depth: 3
2735
+ };
2736
+ function detectMisalignedIncentives(params) {
2737
+ const config = params.config ?? DEFAULT_INCENTIVE_CONFIG;
2738
+ const signals = [];
2739
+ const now = (params.now ?? /* @__PURE__ */ new Date()).toISOString();
2740
+ let signalIdx = 0;
2741
+ const makeId = () => `signal_${params.partyId}_${signalIdx++}`;
2742
+ const overrideRate = params.totalActions > 0 ? params.overrideCount / params.totalActions : 0;
2743
+ if (overrideRate > config.max_override_rate) {
2744
+ signals.push({
2745
+ signal_id: makeId(),
2746
+ signal_type: "excessive_overrides",
2747
+ party_id: params.partyId,
2748
+ party_label: params.partyLabel,
2749
+ severity: Math.min(100, overrideRate / config.max_override_rate * 50),
2750
+ description: `Override rate ${(overrideRate * 100).toFixed(1)}% exceeds threshold ${(config.max_override_rate * 100).toFixed(1)}%`,
2751
+ evidence: [`override_count:${params.overrideCount}`, `total_actions:${params.totalActions}`],
2752
+ detected_at: now,
2753
+ reviewed: false,
2754
+ reviewed_by: null
2755
+ });
2756
+ }
2757
+ if (params.emergencyBypassCount > config.max_emergency_bypasses_30d) {
2758
+ signals.push({
2759
+ signal_id: makeId(),
2760
+ signal_type: "emergency_bypass_repeat",
2761
+ party_id: params.partyId,
2762
+ party_label: params.partyLabel,
2763
+ severity: Math.min(100, params.emergencyBypassCount / config.max_emergency_bypasses_30d * 60),
2764
+ description: `${params.emergencyBypassCount} emergency bypasses in ${params.windowDays}d (threshold: ${config.max_emergency_bypasses_30d})`,
2765
+ evidence: [`bypass_count:${params.emergencyBypassCount}`],
2766
+ detected_at: now,
2767
+ reviewed: false,
2768
+ reviewed_by: null
2769
+ });
2770
+ }
2771
+ const rushCount = params.approvalLatencies.filter((l) => l < config.min_approval_latency_seconds).length;
2772
+ if (rushCount > 0 && params.approvalLatencies.length > 0) {
2773
+ const minLatency = Math.min(...params.approvalLatencies);
2774
+ const rushRate = rushCount / params.approvalLatencies.length;
2775
+ signals.push({
2776
+ signal_id: makeId(),
2777
+ signal_type: "rushed_approval",
2778
+ party_id: params.partyId,
2779
+ party_label: params.partyLabel,
2780
+ severity: Math.min(100, rushRate * 80),
2781
+ description: `${rushCount} approvals in under ${config.min_approval_latency_seconds}s (min observed: ${minLatency.toFixed(0)}s)`,
2782
+ evidence: [`rushed_count:${rushCount}`, `total_approvals:${params.approvalLatencies.length}`],
2783
+ detected_at: now,
2784
+ reviewed: false,
2785
+ reviewed_by: null
2786
+ });
2787
+ }
2788
+ if (params.approvalShare > config.max_concentration_share) {
2789
+ signals.push({
2790
+ signal_id: makeId(),
2791
+ signal_type: "authority_concentration",
2792
+ party_id: params.partyId,
2793
+ party_label: params.partyLabel,
2794
+ severity: Math.min(100, params.approvalShare / config.max_concentration_share * 50),
2795
+ description: `Party controls ${(params.approvalShare * 100).toFixed(1)}% of approvals (threshold: ${(config.max_concentration_share * 100).toFixed(1)}%)`,
2796
+ evidence: [`approval_share:${params.approvalShare.toFixed(3)}`],
2797
+ detected_at: now,
2798
+ reviewed: false,
2799
+ reviewed_by: null
2800
+ });
2801
+ }
2802
+ if (params.delegationDepthMax > config.max_delegation_depth) {
2803
+ signals.push({
2804
+ signal_id: makeId(),
2805
+ signal_type: "delegation_chain_depth",
2806
+ party_id: params.partyId,
2807
+ party_label: params.partyLabel,
2808
+ severity: Math.min(100, (params.delegationDepthMax - config.max_delegation_depth) / config.max_delegation_depth * 60),
2809
+ description: `Delegation depth ${params.delegationDepthMax} exceeds threshold ${config.max_delegation_depth}`,
2810
+ evidence: [`depth:${params.delegationDepthMax}`],
2811
+ detected_at: now,
2812
+ reviewed: false,
2813
+ reviewed_by: null
2814
+ });
2815
+ }
2816
+ return signals.sort((a, b) => b.severity - a.severity);
2817
+ }
2818
+ function computeGovernanceHealthScore(signals) {
2819
+ if (signals.length === 0) return 100;
2820
+ const totalPenalty = signals.reduce((acc, s) => acc + s.severity * 0.5, 0);
2821
+ return Math.max(0, 100 - totalPenalty);
2822
+ }
2823
+
2824
+ // src/economicEvidence.ts
2825
+ function canonicalizeForEvidence(value) {
2826
+ if (value === null || value === void 0) return "null";
2827
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
2828
+ if (typeof value === "boolean") return value ? "true" : "false";
2829
+ if (typeof value === "string") return JSON.stringify(value);
2830
+ if (Array.isArray(value)) return "[" + value.map(canonicalizeForEvidence).join(",") + "]";
2831
+ if (typeof value === "object") {
2832
+ const obj = value;
2833
+ const keys = Object.keys(obj).sort();
2834
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalizeForEvidence(obj[k])).join(",") + "}";
2835
+ }
2836
+ return "null";
2837
+ }
2838
+ function buildSignableContent(bundle) {
2839
+ return {
2840
+ bundle_id: bundle.bundle_id,
2841
+ org_id: bundle.org_id,
2842
+ purpose: bundle.purpose,
2843
+ execution_id: bundle.execution_record.execution_id,
2844
+ attribution_id: bundle.liability_attribution.attribution_id,
2845
+ liability_chain_hash: bundle.liability_attribution.chain_hash,
2846
+ approval_count: bundle.approval_provenance.length,
2847
+ permit_ids: bundle.approval_provenance.map((a) => a.permit_id),
2848
+ policy_compliant: bundle.policy_compliant,
2849
+ generated_at: bundle.generated_at
2850
+ };
2851
+ }
2852
+ function serializeSignableContent(content) {
2853
+ return new TextEncoder().encode(canonicalizeForEvidence(content));
2854
+ }
2855
+ function verifyEvidenceBundleStructure(bundle) {
2856
+ const errors = [];
2857
+ const bundlePermitIds = new Set(bundle.execution_record.permit_ids);
2858
+ const provenancePermitIds = bundle.approval_provenance.map((a) => a.permit_id);
2859
+ const permitIdsMatch = provenancePermitIds.every((id) => bundlePermitIds.has(id));
2860
+ if (!permitIdsMatch) {
2861
+ errors.push("permit IDs in approval provenance do not all appear in execution record");
2862
+ }
2863
+ const contentHashValid = typeof bundle.content_hash === "string" && bundle.content_hash.length === 64;
2864
+ if (!contentHashValid) errors.push("content_hash appears invalid (expected 64-char hex)");
2865
+ const liabilityChainHashMatches = typeof bundle.liability_attribution.chain_hash === "string" && bundle.liability_attribution.chain_hash.length > 0;
2866
+ if (!liabilityChainHashMatches) errors.push("liability_attribution.chain_hash is missing or empty");
2867
+ const signatureValid = bundle.signature !== null && bundle.signature.length > 0;
2868
+ return {
2869
+ valid: errors.length === 0 && contentHashValid,
2870
+ content_hash_valid: contentHashValid,
2871
+ signature_valid: signatureValid,
2872
+ liability_chain_hash_matches: liabilityChainHashMatches,
2873
+ permit_ids_match: permitIdsMatch,
2874
+ reason: errors.length === 0 ? null : errors[0] ?? "bundle integrity check failed"
2875
+ };
2876
+ }
2877
+
2878
+ // src/disputeReversal.ts
2879
+ var VALID_DISPUTE_TRANSITIONS = {
2880
+ open: ["under_review", "withdrawn"],
2881
+ under_review: ["escalated", "resolved_in_favor", "resolved_against", "reversed"],
2882
+ escalated: ["resolved_in_favor", "resolved_against", "reversed"],
2883
+ resolved_in_favor: [],
2884
+ resolved_against: [],
2885
+ reversed: [],
2886
+ withdrawn: []
2887
+ };
2888
+ function transitionDispute(current, next) {
2889
+ const allowed = VALID_DISPUTE_TRANSITIONS[current];
2890
+ if (!allowed.includes(next)) {
2891
+ return { success: false, new_status: null, error: `invalid dispute transition: ${current} \u2192 ${next}` };
2892
+ }
2893
+ return { success: true, new_status: next, error: null };
2894
+ }
2895
+ var VALID_REVERSAL_TRANSITIONS = {
2896
+ initiated: ["authorization_pending", "cancelled"],
2897
+ authorization_pending: ["authorized", "cancelled"],
2898
+ authorized: ["executing", "cancelled"],
2899
+ executing: ["completed", "failed"],
2900
+ completed: [],
2901
+ failed: ["initiated"],
2902
+ // Allow retry
2903
+ cancelled: []
2904
+ };
2905
+ function transitionReversal(current, next) {
2906
+ const allowed = VALID_REVERSAL_TRANSITIONS[current];
2907
+ if (!allowed.includes(next)) {
2908
+ return { success: false, error: `invalid reversal transition: ${current} \u2192 ${next}` };
2909
+ }
2910
+ return { success: true, error: null };
2911
+ }
2912
+ function isFreezeActive(freeze, now) {
2913
+ if (freeze.lifted) return false;
2914
+ if (freeze.expires_at !== null) {
2915
+ const nowIso = (now ?? /* @__PURE__ */ new Date()).toISOString();
2916
+ if (nowIso >= freeze.expires_at) return false;
2917
+ }
2918
+ return true;
2919
+ }
2920
+ function computeRemediationUrgency(_openedAt, deadline, now) {
2921
+ if (deadline === null) return "normal";
2922
+ const nowMs = (now ?? /* @__PURE__ */ new Date()).getTime();
2923
+ const deadlineMs = new Date(deadline).getTime();
2924
+ const remaining = deadlineMs - nowMs;
2925
+ if (remaining < 0) return "overdue";
2926
+ if (remaining < 24 * 60 * 60 * 1e3) return "urgent";
2927
+ return "normal";
2928
+ }
2929
+
2930
+ // src/financialDashboard.ts
2931
+ function buildLiabilityVisualization(executionId, orgId, chain) {
2932
+ const nodes = chain.map((p) => ({
2933
+ id: p.party_id,
2934
+ label: p.party_label,
2935
+ party_type: p.party_type,
2936
+ role: p.role,
2937
+ liability_weight: p.liability_weight,
2938
+ acted_at: p.acted_at
2939
+ }));
2940
+ const edges = [];
2941
+ for (let i = 0; i < chain.length - 1; i++) {
2942
+ const from = chain[i];
2943
+ const to = chain[i + 1];
2944
+ let relationship = "authorized";
2945
+ if (from.role === "delegator" && to.role === "delegate") relationship = "delegated_to";
2946
+ else if (from.role === "supervisor") relationship = "supervised";
2947
+ else if (to.role === "approver") relationship = "approved_for";
2948
+ else if (to.role === "override_actor") relationship = "overrode";
2949
+ edges.push({
2950
+ from_id: from.party_id,
2951
+ to_id: to.party_id,
2952
+ relationship,
2953
+ weight: Math.min(from.liability_weight, to.liability_weight)
2954
+ });
2955
+ }
2956
+ return {
2957
+ execution_id: executionId,
2958
+ org_id: orgId,
2959
+ nodes,
2960
+ edges,
2961
+ total_weight: chain.reduce((s, p) => s + p.liability_weight, 0)
2962
+ };
2963
+ }
2964
+ function buildRiskTimeline(snapshots) {
2965
+ return snapshots.map((s) => {
2966
+ let tier = "low";
2967
+ if (s.riskScore > 80) tier = "critical";
2968
+ else if (s.riskScore > 55) tier = "high";
2969
+ else if (s.riskScore > 25) tier = "medium";
2970
+ return {
2971
+ date: s.date,
2972
+ risk_score: s.riskScore,
2973
+ risk_tier: tier,
2974
+ action_count: s.actionCount,
2975
+ total_value: s.totalValue,
2976
+ override_count: s.overrideCount,
2977
+ anomaly_count: s.anomalyCount
2978
+ };
2979
+ });
2980
+ }
2981
+
2982
+ // src/governanceEnforcement.ts
2983
+ var GovernanceEnforcementError = class extends AtlaSentError {
2984
+ name = "GovernanceEnforcementError";
2985
+ /** Which gate fired (`financial_quorum` / `budget` / `autonomous_bounds`). */
2986
+ gate;
2987
+ /** Stable taxonomy code; maps to a row in `docs/APPROVAL_DENY_REASONS.md`. */
2988
+ denyCode;
2989
+ /** Human-readable explanation. Do NOT branch on this string — branch on `denyCode`. */
2990
+ reason;
2991
+ /** The structured advisory result that produced the denial. */
2992
+ details;
2993
+ constructor(init) {
2994
+ const errInit = { code: "forbidden" };
2995
+ if (init.requestId !== void 0) errInit.requestId = init.requestId;
2996
+ super(`[${init.gate}/${init.denyCode}] ${init.reason}`, errInit);
2997
+ this.gate = init.gate;
2998
+ this.denyCode = init.denyCode;
2999
+ this.reason = init.reason;
3000
+ this.details = init.details;
3001
+ }
3002
+ /** Combined `<gate>/<denyCode>` string used in audit records. */
3003
+ get fullyQualifiedCode() {
3004
+ return `${this.gate}/${this.denyCode}`;
3005
+ }
3006
+ };
3007
+ function financialQuorumDenyCode(result) {
3008
+ if (result.blocked_by_freeze) return "blocked_by_emergency_freeze";
3009
+ if (!result.base_quorum_passed) return "base_count_unmet";
3010
+ if (!result.amount_threshold_satisfied) return "amount_threshold_unmet";
3011
+ if (!result.financial_roles_satisfied) return "financial_role_unmet";
3012
+ if (result.regulator_approval_missing) return "regulator_approval_missing";
3013
+ return "base_count_unmet";
3014
+ }
3015
+ function enforceFinancialQuorum(result) {
3016
+ if (result.passed) return;
3017
+ const denyCode = financialQuorumDenyCode(result);
3018
+ throw new GovernanceEnforcementError({
3019
+ gate: "financial_quorum",
3020
+ denyCode,
3021
+ reason: result.denial_reason ?? `financial quorum failed: ${denyCode}`,
3022
+ details: result
3023
+ });
3024
+ }
3025
+ function enforceBudgetConstraint(result) {
3026
+ if (result.permitted) return;
3027
+ if (result.hard_blocks.length === 0) {
3028
+ throw new GovernanceEnforcementError({
3029
+ gate: "budget",
3030
+ denyCode: "limit_exceeded",
3031
+ reason: "budget enforcement failed without a structured violation",
3032
+ details: result
3033
+ });
3034
+ }
3035
+ const first = result.hard_blocks[0];
3036
+ throw new GovernanceEnforcementError({
3037
+ gate: "budget",
3038
+ denyCode: first.violation_type,
3039
+ reason: first.description,
3040
+ details: result
3041
+ });
3042
+ }
3043
+ function autonomousBoundsDenyCode(result) {
3044
+ if (!result.bounds_active) return "inactive";
3045
+ if (!result.bounds_not_expired) return "expired";
3046
+ if (!result.action_type_permitted) return "action_type_not_permitted";
3047
+ if (!result.within_execution_ceiling) return "execution_ceiling_exceeded";
3048
+ if (!result.within_daily_aggregate) return "daily_aggregate_exceeded";
3049
+ if (!result.within_risk_tier) return "risk_tier_exceeded";
3050
+ return "inactive";
3051
+ }
3052
+ function enforceAutonomousBounds(result) {
3053
+ if (result.permitted) return;
3054
+ const denyCode = autonomousBoundsDenyCode(result);
3055
+ throw new GovernanceEnforcementError({
3056
+ gate: "autonomous_bounds",
3057
+ denyCode,
3058
+ reason: result.denial_reason ?? `autonomous execution out of bounds: ${denyCode}`,
3059
+ details: result
3060
+ });
3061
+ }
3062
+ function enforceEconomicGovernance(params) {
3063
+ if (params.quorum !== void 0) enforceFinancialQuorum(params.quorum);
3064
+ if (params.budget !== void 0) enforceBudgetConstraint(params.budget);
3065
+ if (params.autonomous !== void 0) enforceAutonomousBounds(params.autonomous);
3066
+ }
3067
+
3068
+ // src/governanceWebhooks.ts
3069
+ async function verifyWebhookSignature(payload, signature, secret) {
3070
+ const prefix = "sha256=";
3071
+ if (!signature.startsWith(prefix)) return false;
3072
+ const receivedHex = signature.slice(prefix.length);
3073
+ const encoder = new TextEncoder();
3074
+ const key = await crypto.subtle.importKey(
3075
+ "raw",
3076
+ encoder.encode(secret),
3077
+ { name: "HMAC", hash: "SHA-256" },
3078
+ false,
3079
+ ["sign"]
3080
+ );
3081
+ const sigBuffer = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
3082
+ const expectedHex = Array.from(new Uint8Array(sigBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
3083
+ if (receivedHex.length !== expectedHex.length) return false;
3084
+ let diff = 0;
3085
+ for (let i = 0; i < expectedHex.length; i++) {
3086
+ diff |= receivedHex.charCodeAt(i) ^ expectedHex.charCodeAt(i);
3087
+ }
3088
+ return diff === 0;
3089
+ }
3090
+
3091
+ // src/complianceEvidence.ts
3092
+ function evidenceRunPasses(run) {
3093
+ return (run.controls ?? []).every((c) => c.status !== "finding");
3094
+ }
3095
+ function nonPassingControls(run) {
3096
+ return (run.controls ?? []).filter((c) => c.status !== "pass").sort((a, b) => {
3097
+ if (a.status === "finding" && b.status !== "finding") return -1;
3098
+ if (b.status === "finding" && a.status !== "finding") return 1;
3099
+ return 0;
3100
+ });
3101
+ }
3102
+
3103
+ // src/policySync.ts
3104
+ function formatPolicySyncDiff(run) {
3105
+ const parts = [];
3106
+ if (run.policies_added > 0) parts.push(`+${run.policies_added} added`);
3107
+ if (run.policies_updated > 0) parts.push(`~${run.policies_updated} updated`);
3108
+ if (run.policies_removed > 0) parts.push(`-${run.policies_removed} removed`);
3109
+ return parts.length > 0 ? parts.join(", ") : "no changes";
3110
+ }
3111
+ function isPolicySyncTerminal(run) {
3112
+ return run.status === "completed" || run.status === "failed" || run.status === "rejected";
3113
+ }
3114
+
3115
+ // src/webhook.ts
3116
+ var WebhookVerificationError = class extends Error {
3117
+ name = "WebhookVerificationError";
3118
+ constructor(message) {
3119
+ super(message);
3120
+ }
3121
+ };
3122
+ var _hasWebCrypto = (() => {
3123
+ try {
3124
+ return typeof crypto !== "undefined" && typeof crypto.subtle === "object" && crypto.subtle !== null;
3125
+ } catch {
3126
+ return false;
3127
+ }
3128
+ })();
3129
+ function _extractHex(signature) {
3130
+ if (typeof signature !== "string") return null;
3131
+ const hex = signature.startsWith("sha256=") ? signature.slice(7) : signature;
3132
+ if (hex.length !== 64) return null;
3133
+ if (!/^[0-9a-f]+$/i.test(hex)) return null;
3134
+ return hex.toLowerCase();
3135
+ }
3136
+ async function _timingSafeEqual(a, b) {
3137
+ if (a.length !== b.length) return false;
3138
+ if (!_hasWebCrypto) {
3139
+ const { timingSafeEqual, createHmac: _c } = await import("crypto");
3140
+ const aBuf = Buffer.from(a, "hex");
3141
+ const bBuf = Buffer.from(b, "hex");
3142
+ return timingSafeEqual(aBuf, bBuf);
3143
+ }
3144
+ let acc = 0;
3145
+ for (let i = 0; i < a.length; i++) {
3146
+ acc |= a.charCodeAt(i) ^ b.charCodeAt(i);
3147
+ }
3148
+ return acc === 0;
3149
+ }
3150
+ async function _hmacSha256Hex(payload, secret) {
3151
+ if (_hasWebCrypto) {
3152
+ const enc = new TextEncoder();
3153
+ const key = await crypto.subtle.importKey(
3154
+ "raw",
3155
+ enc.encode(secret),
3156
+ { name: "HMAC", hash: "SHA-256" },
3157
+ false,
3158
+ ["sign"]
3159
+ );
3160
+ const sig = await crypto.subtle.sign("HMAC", key, enc.encode(payload));
3161
+ return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
3162
+ }
3163
+ const { createHmac } = await import("crypto");
3164
+ return createHmac("sha256", secret).update(payload).digest("hex");
3165
+ }
3166
+ async function verifyWebhook(payload, signature, secret) {
3167
+ try {
3168
+ const expectedHex = _extractHex(signature);
3169
+ if (expectedHex === null) return false;
3170
+ const actualHex = await _hmacSha256Hex(payload, secret);
3171
+ return await _timingSafeEqual(actualHex, expectedHex);
3172
+ } catch {
3173
+ return false;
3174
+ }
3175
+ }
3176
+ async function assertWebhook(payload, signature, secret) {
3177
+ const valid = await verifyWebhook(payload, signature, secret);
3178
+ if (!valid) {
3179
+ throw new WebhookVerificationError(
3180
+ "Webhook signature verification failed: the signature does not match the payload."
3181
+ );
3182
+ }
3183
+ }
3184
+
3185
+ // src/crossOrgPermission.ts
3186
+ function summarizeCrossOrgPermission(result) {
3187
+ if (!result.allowed) return "denied";
3188
+ if (result.conditions.length > 0) return "conditional";
3189
+ return "allowed";
3190
+ }
3191
+
3192
+ // src/anomalyResponse.ts
3193
+ function matchAnomalyRules(rules, anomalyScore) {
3194
+ return rules.filter((r) => r.is_active && anomalyScore >= r.anomaly_score_threshold).sort((a, b) => b.anomaly_score_threshold - a.anomaly_score_threshold);
3195
+ }
3196
+ function highestSeverityAction(rules) {
3197
+ const ORDER = [
3198
+ "escalate_to_regulator",
3199
+ "rollback_execution",
3200
+ "freeze_agent",
3201
+ "require_hitl",
3202
+ "create_incident",
3203
+ "notify_admin"
3204
+ ];
3205
+ for (const action of ORDER) {
3206
+ if (rules.some((r) => r.action_type === action)) return action;
3207
+ }
3208
+ return null;
3209
+ }
3210
+
3211
+ // src/budgetExceptions.ts
3212
+ function isBudgetExceptionActive(exception, now) {
3213
+ if (exception.status !== "approved") return false;
3214
+ const ts = (now ?? /* @__PURE__ */ new Date()).toISOString();
3215
+ if (exception.effective_from && ts < exception.effective_from) return false;
3216
+ if (exception.effective_until && ts > exception.effective_until) return false;
3217
+ return true;
3218
+ }
3219
+ function isBudgetExceptionTerminal(status) {
3220
+ return status === "approved" || status === "rejected" || status === "expired" || status === "cancelled";
3221
+ }
3222
+
3223
+ // src/regulatoryEscalation.ts
3224
+ function isRegulatoryEscalationTerminal(status) {
3225
+ return status === "resolved" || status === "overridden";
3226
+ }
3227
+ function isEscalationSlaBreached(escalation, targetLevel, now) {
3228
+ if (isRegulatoryEscalationTerminal(escalation.status)) return false;
3229
+ const createdMs = new Date(escalation.created_at).getTime();
3230
+ const nowMs = (now ?? /* @__PURE__ */ new Date()).getTime();
3231
+ const elapsedHours = (nowMs - createdMs) / (1e3 * 60 * 60);
3232
+ return elapsedHours > targetLevel.escalation_sla_hours;
3233
+ }
3234
+
3235
+ // src/incentiveSignalFeedback.ts
3236
+ function computeSignalEngagementRate(summary) {
3237
+ if (summary.total_signals === 0) return 0;
3238
+ return summary.acted_on / summary.total_signals;
3239
+ }
3240
+ function isSubstantiveSignalResponse(actionType) {
3241
+ return actionType === "policy_updated" || actionType === "training_initiated" || actionType === "process_changed" || actionType === "monitoring_increased" || actionType === "escalated";
3242
+ }
3243
+
3244
+ // src/crossOrgImpersonation.ts
3245
+ function isImpersonationGrantUsable(grant, now) {
3246
+ if (!grant.is_active) return false;
3247
+ if (grant.revoked_at) return false;
3248
+ const ts = (now ?? /* @__PURE__ */ new Date()).toISOString();
3249
+ if (grant.expires_at && ts > grant.expires_at) return false;
3250
+ return true;
3251
+ }
3252
+ function clampTokenDuration(grant, requestedSeconds) {
3253
+ return Math.min(requestedSeconds, grant.max_token_duration_seconds);
3254
+ }
3255
+
3256
+ // src/v2.ts
3257
+ var V2_BATCH_PATH = "/v1/evaluate/batch";
3258
+ var V2_STREAM_PATH = "/v1/evaluate/stream";
3259
+ var V2_GRAPHQL_PATH = "/v1/graphql";
3260
+ var V2_MAX_BATCH_ITEMS = 100;
3261
+ var V2_MAX_BODY_BYTES = 1e6;
3262
+ var V2_GRAPHQL_MAX_DEPTH = 8;
3263
+ var FeatureNotEnabledError = class extends AtlaSentError {
3264
+ name = "FeatureNotEnabledError";
3265
+ /** Which tenant flag is gating the endpoint. */
3266
+ feature;
3267
+ /** The wire path the SDK attempted (for diagnostics). */
3268
+ endpoint;
3269
+ constructor(init) {
3270
+ const message = `AtlaSent V2 feature '${init.feature}' is not enabled for this tenant (POST ${init.endpoint} returned 404). Enable the v2_${init.feature} flag or fall back to the v1 per-item /v1-evaluate loop.`;
3271
+ const errInit = { status: 404, code: "feature_disabled" };
3272
+ if (init.requestId !== void 0) errInit.requestId = init.requestId;
3273
+ super(message, errInit);
3274
+ this.feature = init.feature;
3275
+ this.endpoint = init.endpoint;
3276
+ }
3277
+ };
3278
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3279
+ function assertValidBatchId(batchId) {
3280
+ if (batchId === void 0) return;
3281
+ if (typeof batchId !== "string" || !UUID_RE.test(batchId)) {
3282
+ throw new TypeError(`batchId must be a valid UUID: ${String(batchId)}`);
3283
+ }
3284
+ }
3285
+ function assertItemsShape(items) {
3286
+ if (!Array.isArray(items) || items.length === 0) {
3287
+ throw new TypeError("items must be a non-empty array");
3288
+ }
3289
+ if (items.length > V2_MAX_BATCH_ITEMS) {
3290
+ throw new TypeError(
3291
+ `items length ${items.length} exceeds maximum of ${V2_MAX_BATCH_ITEMS}`
3292
+ );
3293
+ }
3294
+ }
3295
+ function assertBodyWithinCap(raw) {
3296
+ const bytes = new TextEncoder().encode(raw).length;
3297
+ if (bytes > V2_MAX_BODY_BYTES) {
3298
+ throw new TypeError(
3299
+ `request body ${bytes} bytes exceeds maximum of ${V2_MAX_BODY_BYTES}`
3300
+ );
3301
+ }
3302
+ }
3303
+ function commonHeaders(apiKey) {
3304
+ return {
3305
+ "Content-Type": "application/json",
3306
+ Accept: "application/json",
3307
+ Authorization: `Bearer ${apiKey}`
3308
+ };
3309
+ }
3310
+ function pickFetch(t) {
3311
+ if (t.fetch !== void 0) return t.fetch;
3312
+ if (typeof fetch !== "undefined") return fetch;
3313
+ throw new Error(
3314
+ "v2: no fetch implementation available (set transport.fetch or run on Node \u2265 18)"
3315
+ );
3316
+ }
3317
+ async function evaluateMany(transport, req) {
3318
+ assertItemsShape(req.items);
3319
+ assertValidBatchId(req.batchId);
3320
+ const body = { items: req.items };
3321
+ if (req.batchId !== void 0) body["batch_id"] = req.batchId;
3322
+ const raw = JSON.stringify(body);
3323
+ assertBodyWithinCap(raw);
3324
+ const url = `${transport.baseUrl}${V2_BATCH_PATH}`;
3325
+ const fetchImpl = pickFetch(transport);
3326
+ const response = await fetchImpl(url, {
3327
+ method: "POST",
3328
+ headers: commonHeaders(transport.apiKey),
3329
+ body: raw
3330
+ });
3331
+ const requestId = response.headers.get("X-Request-ID") ?? void 0;
3332
+ if (response.status === 404) {
3333
+ const init = {
3334
+ feature: "batch",
3335
+ endpoint: V2_BATCH_PATH
3336
+ };
3337
+ if (requestId !== void 0) init.requestId = requestId;
3338
+ throw new FeatureNotEnabledError(init);
3339
+ }
3340
+ if (!response.ok) {
3341
+ throwHttpError(V2_BATCH_PATH, response.status, requestId);
3342
+ }
3343
+ const parsed = await safeJson(response, V2_BATCH_PATH, requestId);
3344
+ return parseBatchResponse(parsed);
3345
+ }
3346
+ function parseBatchResponse(data) {
3347
+ const root = data ?? {};
3348
+ const rawItems = root["items"] ?? [];
3349
+ const items = rawItems.map((it) => {
3350
+ const out = {
3351
+ index: typeof it["index"] === "number" ? it["index"] : -1
3352
+ };
3353
+ const decision = it["decision"];
3354
+ if (typeof decision === "string") out.decision = decision;
3355
+ const decisionId = it["decision_id"];
3356
+ if (typeof decisionId === "string") out.decisionId = decisionId;
3357
+ const permitToken = it["permit_token"];
3358
+ if (typeof permitToken === "string") out.permitToken = permitToken;
3359
+ const reason = it["reason"];
3360
+ if (typeof reason === "string") out.reason = reason;
3361
+ const errorCode = it["error_code"];
3362
+ if (typeof errorCode === "string") out.errorCode = errorCode;
3363
+ const errorMessage = it["error_message"];
3364
+ if (typeof errorMessage === "string") out.errorMessage = errorMessage;
3365
+ return out;
3366
+ });
3367
+ const batchId = root["batch_id"];
3368
+ return {
3369
+ batchId: typeof batchId === "string" ? batchId : "",
3370
+ items,
3371
+ partial: Boolean(root["partial"])
3372
+ };
3373
+ }
3374
+ async function authorizeStream(transport, req, handlers = {}) {
3375
+ assertItemsShape(req.items);
3376
+ assertValidBatchId(req.batchId);
3377
+ const body = { items: req.items };
3378
+ if (req.batchId !== void 0) body["batch_id"] = req.batchId;
3379
+ const raw = JSON.stringify(body);
3380
+ assertBodyWithinCap(raw);
3381
+ const url = `${transport.baseUrl}${V2_STREAM_PATH}`;
3382
+ const fetchImpl = pickFetch(transport);
3383
+ const response = await fetchImpl(url, {
3384
+ method: "POST",
3385
+ headers: {
3386
+ ...commonHeaders(transport.apiKey),
3387
+ Accept: "text/event-stream"
3388
+ },
3389
+ body: raw
3390
+ });
3391
+ const requestId = response.headers.get("X-Request-ID") ?? void 0;
3392
+ if (response.status === 404) {
3393
+ const init = {
3394
+ feature: "streaming",
3395
+ endpoint: V2_STREAM_PATH
3396
+ };
3397
+ if (requestId !== void 0) init.requestId = requestId;
3398
+ throw new FeatureNotEnabledError(init);
3399
+ }
3400
+ if (!response.ok) {
3401
+ throwHttpError(V2_STREAM_PATH, response.status, requestId);
3402
+ }
3403
+ if (response.body === null) {
3404
+ throw new AtlaSentError(
3405
+ `POST ${V2_STREAM_PATH} returned no response body`,
3406
+ { code: "bad_response", status: response.status }
3407
+ );
3408
+ }
3409
+ const complete = await consumeSseStream(response.body, handlers);
3410
+ if (complete === null) {
3411
+ throw new AtlaSentError(
3412
+ "authorizeStream: stream closed without a `complete` event",
3413
+ { code: "bad_response" }
3414
+ );
3415
+ }
3416
+ return complete;
3417
+ }
3418
+ async function consumeSseStream(body, handlers) {
3419
+ const reader = body.getReader();
3420
+ const decoder = new TextDecoder("utf-8");
3421
+ let buffer = "";
3422
+ let eventName = void 0;
3423
+ let complete = null;
3424
+ while (true) {
3425
+ const { value, done } = await reader.read();
3426
+ if (value !== void 0) {
3427
+ buffer += decoder.decode(value, { stream: !done });
3428
+ }
3429
+ if (done) {
3430
+ buffer += decoder.decode();
3431
+ }
3432
+ let nl;
3433
+ while ((nl = buffer.indexOf("\n")) !== -1) {
3434
+ let line = buffer.slice(0, nl);
3435
+ buffer = buffer.slice(nl + 1);
3436
+ if (line.endsWith("\r")) line = line.slice(0, -1);
3437
+ if (line === "") {
3438
+ eventName = void 0;
3439
+ continue;
3440
+ }
3441
+ if (line.startsWith(":")) continue;
3442
+ if (line.startsWith("event:")) {
3443
+ eventName = line.slice("event:".length).trim();
3444
+ continue;
3445
+ }
3446
+ if (!line.startsWith("data:")) continue;
3447
+ const dataText = line.slice("data:".length).trim();
3448
+ let payload;
3449
+ try {
3450
+ payload = JSON.parse(dataText);
3451
+ } catch {
3452
+ continue;
3453
+ }
3454
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
3455
+ continue;
3456
+ }
3457
+ const p = payload;
3458
+ if (eventName === "decision" && handlers.onDecision !== void 0) {
3459
+ const dIndex = p["index"];
3460
+ const dDecision = p["decision"];
3461
+ const frame = {
3462
+ index: typeof dIndex === "number" ? dIndex : -1,
3463
+ decision: typeof dDecision === "string" ? dDecision : ""
3464
+ };
3465
+ const dId = p["decision_id"];
3466
+ if (typeof dId === "string") frame.decisionId = dId;
3467
+ const pt = p["permit_token"];
3468
+ if (typeof pt === "string") frame.permitToken = pt;
3469
+ const r = p["reason"];
3470
+ if (typeof r === "string") frame.reason = r;
3471
+ handlers.onDecision(frame);
3472
+ } else if (eventName === "error" && handlers.onError !== void 0) {
3473
+ const eIndex = p["index"];
3474
+ const eCode = p["error_code"];
3475
+ const eMsg = p["message"];
3476
+ handlers.onError({
3477
+ index: typeof eIndex === "number" ? eIndex : -1,
3478
+ errorCode: typeof eCode === "string" ? eCode : "",
3479
+ message: typeof eMsg === "string" ? eMsg : ""
3480
+ });
3481
+ } else if (eventName === "complete") {
3482
+ const cBatchId = p["batch_id"];
3483
+ const cCount = p["count"];
3484
+ complete = {
3485
+ batchId: typeof cBatchId === "string" ? cBatchId : "",
3486
+ count: typeof cCount === "number" ? cCount : 0,
3487
+ partial: Boolean(p["partial"])
3488
+ };
3489
+ try {
3490
+ await reader.cancel();
3491
+ } catch {
3492
+ }
3493
+ return complete;
3494
+ }
3495
+ }
3496
+ if (done) break;
3497
+ }
3498
+ return complete;
3499
+ }
3500
+ async function graphql(transport, req) {
3501
+ if (typeof req.query !== "string" || req.query.trim() === "") {
3502
+ throw new TypeError("query must be a non-empty string");
3503
+ }
3504
+ const body = { query: req.query };
3505
+ if (req.variables !== void 0) body["variables"] = req.variables;
3506
+ if (req.operationName !== void 0)
3507
+ body["operationName"] = req.operationName;
3508
+ const raw = JSON.stringify(body);
3509
+ assertBodyWithinCap(raw);
3510
+ const url = `${transport.baseUrl}${V2_GRAPHQL_PATH}`;
3511
+ const fetchImpl = pickFetch(transport);
3512
+ const response = await fetchImpl(url, {
3513
+ method: "POST",
3514
+ headers: commonHeaders(transport.apiKey),
3515
+ body: raw
3516
+ });
3517
+ const requestId = response.headers.get("X-Request-ID") ?? void 0;
3518
+ if (response.status === 404) {
3519
+ const init = {
3520
+ feature: "graphql",
3521
+ endpoint: V2_GRAPHQL_PATH
3522
+ };
3523
+ if (requestId !== void 0) init.requestId = requestId;
3524
+ throw new FeatureNotEnabledError(init);
3525
+ }
3526
+ if (!response.ok) {
3527
+ throwHttpError(V2_GRAPHQL_PATH, response.status, requestId);
3528
+ }
3529
+ const parsed = await safeJson(response, V2_GRAPHQL_PATH, requestId);
3530
+ const root = parsed ?? {};
3531
+ const out = {
3532
+ data: root["data"] ?? null
3533
+ };
3534
+ const errs = root["errors"];
3535
+ if (Array.isArray(errs) && errs.length > 0) {
3536
+ out.errors = errs;
3537
+ }
3538
+ return out;
3539
+ }
3540
+ function throwHttpError(path, status, requestId) {
3541
+ const errInit = {
3542
+ status,
3543
+ code: status >= 500 ? "server_error" : "bad_request"
3544
+ };
3545
+ if (requestId !== void 0) errInit.requestId = requestId;
3546
+ throw new AtlaSentError(`POST ${path} returned ${status}`, errInit);
3547
+ }
3548
+ async function safeJson(response, path, requestId) {
3549
+ try {
3550
+ return await response.json();
3551
+ } catch (cause) {
3552
+ const errInit = {
3553
+ status: response.status,
3554
+ code: "bad_response",
3555
+ cause
3556
+ };
3557
+ if (requestId !== void 0) errInit.requestId = requestId;
3558
+ throw new AtlaSentError(`${path}: malformed JSON response`, errInit);
3559
+ }
734
3560
  }
735
3561
 
736
3562
  // src/index.ts
737
3563
  var atlasent = {
738
3564
  protect,
3565
+ withPermit,
3566
+ deployGate,
739
3567
  configure,
3568
+ requirePermit,
3569
+ classifyCommand,
740
3570
  verifyBundle,
3571
+ PRODUCTION_DEPLOY_ACTION,
3572
+ DEPLOYMENT_PRODUCTION_ACTION,
741
3573
  AtlaSentClient,
742
3574
  AtlaSentError,
743
3575
  AtlaSentDeniedError
@@ -748,16 +3580,101 @@ var index_default = atlasent;
748
3580
  AtlaSentClient,
749
3581
  AtlaSentDeniedError,
750
3582
  AtlaSentError,
3583
+ AtlaSentEscalateError,
3584
+ DEFAULT_INCENTIVE_CONFIG,
751
3585
  DEFAULT_RETRY_POLICY,
3586
+ DEFAULT_RISK_TIER_THRESHOLDS,
3587
+ DEPLOYMENT_PRODUCTION_ACTION,
3588
+ DEPLOY_GATE_CODES,
3589
+ FeatureNotEnabledError,
3590
+ GovernanceEnforcementError,
3591
+ PRODUCTION_DEPLOY_ACTION,
3592
+ PermitRevoked,
3593
+ StreamParseError,
3594
+ StreamTimeoutError,
3595
+ V2_BATCH_PATH,
3596
+ V2_GRAPHQL_MAX_DEPTH,
3597
+ V2_GRAPHQL_PATH,
3598
+ V2_MAX_BATCH_ITEMS,
3599
+ V2_MAX_BODY_BYTES,
3600
+ V2_STREAM_PATH,
3601
+ WebhookVerificationError,
3602
+ assertWebhook,
3603
+ authorizeStream,
3604
+ budgetUtilizationSeverity,
3605
+ buildLiabilityChain,
3606
+ buildLiabilityVisualization,
3607
+ buildRiskTimeline,
3608
+ buildSignableContent,
752
3609
  canonicalJSON,
3610
+ canonicalizeForEvidence,
3611
+ checkAutonomousBounds,
3612
+ checkBudgetConstraints,
3613
+ clampTokenDuration,
3614
+ classifyCommand,
3615
+ classifyRiskTier,
3616
+ computeApprovalRiskScore,
753
3617
  computeBackoffMs,
3618
+ computeEscalatedApprovalCount,
3619
+ computeExposureScore,
3620
+ computeGovernanceHealthScore,
3621
+ computeHHI,
3622
+ computeLiabilityWeights,
3623
+ computeOverallRiskScore,
3624
+ computeOverrideScore,
3625
+ computeRemediationUrgency,
3626
+ computeSignalEngagementRate,
754
3627
  configure,
3628
+ delegationPropagationHadEffect,
3629
+ deployGate,
3630
+ detectAutonomousAnomaly,
3631
+ detectMisalignedIncentives,
3632
+ detectSelfApproval,
3633
+ enforceAutonomousBounds,
3634
+ enforceBudgetConstraint,
3635
+ enforceEconomicGovernance,
3636
+ enforceFinancialQuorum,
3637
+ evaluateFinancialQuorum,
3638
+ evaluateMany,
3639
+ evidenceRunPasses,
3640
+ findPrimaryLiabilityParties,
3641
+ formatPolicySyncDiff,
3642
+ graphql,
755
3643
  hasAttemptsLeft,
3644
+ hhiToConcentrationScore,
3645
+ highestSeverityAction,
3646
+ hitlRequiredApproverCount,
3647
+ isBudgetExceptionActive,
3648
+ isBudgetExceptionTerminal,
3649
+ isEscalationSlaBreached,
3650
+ isFreezeActive,
3651
+ isImpersonationGrantUsable,
3652
+ isPolicySyncTerminal,
3653
+ isRegulatoryEscalationTerminal,
756
3654
  isRetryable,
3655
+ isSandboxDiffPopulated,
3656
+ isSubstantiveSignalResponse,
3657
+ matchAnomalyRules,
757
3658
  mergePolicy,
3659
+ nonPassingControls,
3660
+ normalizeEvaluateRequest,
3661
+ normalizeEvaluateResponse,
3662
+ normalizePermitOutcome,
758
3663
  protect,
3664
+ requirePermit,
3665
+ scoreToRiskTier,
3666
+ serializeSignableContent,
759
3667
  signedBytesFor,
3668
+ summarizeCrossOrgPermission,
3669
+ transitionDispute,
3670
+ transitionReversal,
3671
+ validateLiabilityChain,
760
3672
  verifyAuditBundle,
761
- verifyBundle
3673
+ verifyBundle,
3674
+ verifyEvidenceBundleStructure,
3675
+ verifyWebhook,
3676
+ verifyWebhookSignature,
3677
+ withPermit,
3678
+ withinAutonomousCeiling
762
3679
  });
763
3680
  //# sourceMappingURL=index.cjs.map