@atlasent/sdk 2.5.0 → 2.10.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
@@ -34,13 +34,18 @@ __export(index_exports, {
34
34
  AtlaSentDeniedError: () => AtlaSentDeniedError,
35
35
  AtlaSentError: () => AtlaSentError,
36
36
  AtlaSentEscalateError: () => AtlaSentEscalateError,
37
+ BCCAEClient: () => BCCAEClient,
37
38
  DEFAULT_INCENTIVE_CONFIG: () => DEFAULT_INCENTIVE_CONFIG,
39
+ DEFAULT_REDACTION_RULES: () => DEFAULT_REDACTION_RULES,
38
40
  DEFAULT_RETRY_POLICY: () => DEFAULT_RETRY_POLICY,
39
41
  DEFAULT_RISK_TIER_THRESHOLDS: () => DEFAULT_RISK_TIER_THRESHOLDS,
40
42
  DEPLOYMENT_PRODUCTION_ACTION: () => DEPLOYMENT_PRODUCTION_ACTION,
41
43
  DEPLOY_GATE_CODES: () => DEPLOY_GATE_CODES,
44
+ EscalationDeniedError: () => EscalationDeniedError,
45
+ EscalationTimeoutError: () => EscalationTimeoutError,
42
46
  FeatureNotEnabledError: () => FeatureNotEnabledError,
43
47
  GovernanceEnforcementError: () => GovernanceEnforcementError,
48
+ NOT_APPLICABLE: () => NOT_APPLICABLE,
44
49
  PRODUCTION_DEPLOY_ACTION: () => PRODUCTION_DEPLOY_ACTION,
45
50
  PermitRevoked: () => PermitRevoked,
46
51
  StreamParseError: () => StreamParseError,
@@ -55,6 +60,9 @@ __export(index_exports, {
55
60
  assertWebhook: () => assertWebhook,
56
61
  authorizeStream: () => authorizeStream,
57
62
  budgetUtilizationSeverity: () => budgetUtilizationSeverity,
63
+ buildActionContext: () => buildActionContext,
64
+ buildClaimEvidenceLink: () => buildClaimEvidenceLink,
65
+ buildClaimEvidenceLinkFromActionBundle: () => buildClaimEvidenceLinkFromActionBundle,
58
66
  buildLiabilityChain: () => buildLiabilityChain,
59
67
  buildLiabilityVisualization: () => buildLiabilityVisualization,
60
68
  buildRiskTimeline: () => buildRiskTimeline,
@@ -63,9 +71,11 @@ __export(index_exports, {
63
71
  canonicalizeForEvidence: () => canonicalizeForEvidence,
64
72
  checkAutonomousBounds: () => checkAutonomousBounds,
65
73
  checkBudgetConstraints: () => checkBudgetConstraints,
74
+ checkIntegrationHealth: () => checkIntegrationHealth,
66
75
  clampTokenDuration: () => clampTokenDuration,
67
76
  classifyCommand: () => classifyCommand,
68
77
  classifyRiskTier: () => classifyRiskTier,
78
+ classifyToolRisk: () => classifyToolRisk,
69
79
  computeApprovalRiskScore: () => computeApprovalRiskScore,
70
80
  computeBackoffMs: () => computeBackoffMs,
71
81
  computeEscalatedApprovalCount: () => computeEscalatedApprovalCount,
@@ -78,6 +88,10 @@ __export(index_exports, {
78
88
  computeRemediationUrgency: () => computeRemediationUrgency,
79
89
  computeSignalEngagementRate: () => computeSignalEngagementRate,
80
90
  configure: () => configure,
91
+ configureApprovalRuntime: () => configureApprovalRuntime,
92
+ configureControlSurface: () => configureControlSurface,
93
+ configureShadow: () => configureShadow,
94
+ createEscalation: () => createEscalation,
81
95
  default: () => index_default,
82
96
  delegationPropagationHadEffect: () => delegationPropagationHadEffect,
83
97
  deployGate: () => deployGate,
@@ -92,10 +106,15 @@ __export(index_exports, {
92
106
  evaluateMany: () => evaluateMany,
93
107
  evidenceRunPasses: () => evidenceRunPasses,
94
108
  findPrimaryLiabilityParties: () => findPrimaryLiabilityParties,
109
+ flattenActionContext: () => flattenActionContext,
95
110
  formatPolicySyncDiff: () => formatPolicySyncDiff,
111
+ generateBccaeNonce: () => generateBccaeNonce,
112
+ getEnforcementStatus: () => getEnforcementStatus,
113
+ getOrgSummary: () => getOrgSummary,
96
114
  graphql: () => graphql,
97
115
  hasAttemptsLeft: () => hasAttemptsLeft,
98
116
  hhiToConcentrationScore: () => hhiToConcentrationScore,
117
+ highestAgentFindingSeverity: () => highestAgentFindingSeverity,
99
118
  highestSeverityAction: () => highestSeverityAction,
100
119
  hitlRequiredApproverCount: () => hitlRequiredApproverCount,
101
120
  isBudgetExceptionActive: () => isBudgetExceptionActive,
@@ -115,6 +134,17 @@ __export(index_exports, {
115
134
  normalizeEvaluateResponse: () => normalizeEvaluateResponse,
116
135
  normalizePermitOutcome: () => normalizePermitOutcome,
117
136
  protect: () => protect,
137
+ protectCloseAction: () => protectCloseAction,
138
+ protectDeploy: () => protectDeploy,
139
+ protectOrEscalate: () => protectOrEscalate,
140
+ protectPaymentRelease: () => protectPaymentRelease,
141
+ protectShadow: () => protectShadow,
142
+ protectToolCall: () => protectToolCall,
143
+ protectWithEvidence: () => protectWithEvidence,
144
+ redactContext: () => redactContext,
145
+ reportProtectedAction: () => reportProtectedAction,
146
+ reportShadowEvent: () => reportShadowEvent,
147
+ requestOverride: () => requestOverride,
118
148
  requirePermit: () => requirePermit,
119
149
  scoreToRiskTier: () => scoreToRiskTier,
120
150
  serializeSignableContent: () => serializeSignableContent,
@@ -122,12 +152,15 @@ __export(index_exports, {
122
152
  summarizeCrossOrgPermission: () => summarizeCrossOrgPermission,
123
153
  transitionDispute: () => transitionDispute,
124
154
  transitionReversal: () => transitionReversal,
155
+ validateActionContext: () => validateActionContext,
125
156
  validateLiabilityChain: () => validateLiabilityChain,
126
157
  verifyAuditBundle: () => verifyAuditBundle,
127
158
  verifyBundle: () => verifyBundle,
159
+ verifyClaimEvidenceLink: () => verifyClaimEvidenceLink,
128
160
  verifyEvidenceBundleStructure: () => verifyEvidenceBundleStructure,
129
161
  verifyWebhook: () => verifyWebhook,
130
162
  verifyWebhookSignature: () => verifyWebhookSignature,
163
+ waitForEscalationApproval: () => waitForEscalationApproval,
131
164
  withPermit: () => withPermit,
132
165
  withinAutonomousCeiling: () => withinAutonomousCeiling
133
166
  });
@@ -298,9 +331,8 @@ function normalizeEvaluateRequest(input) {
298
331
  action_type: legacy.action,
299
332
  actor_id: legacy.agent
300
333
  };
301
- if (legacy.context !== void 0) {
302
- normalized.context = legacy.context;
303
- }
334
+ if (legacy.context !== void 0) normalized.context = legacy.context;
335
+ if (legacy.explain !== void 0) normalized.explain = legacy.explain;
304
336
  return normalized;
305
337
  }
306
338
  return input;
@@ -488,6 +520,7 @@ var AtlaSentClient = class {
488
520
  actor_id: normalized.actor_id,
489
521
  context: normalized.context ?? {}
490
522
  };
523
+ if (normalized.explain !== void 0) body.explain = normalized.explain;
491
524
  const { body: wire, rateLimit } = await this.post(
492
525
  "/v1-evaluate",
493
526
  body
@@ -524,9 +557,211 @@ var AtlaSentClient = class {
524
557
  reason,
525
558
  auditHash: wire.audit_hash ?? "",
526
559
  timestamp: wire.timestamp ?? "",
560
+ rateLimit,
561
+ ...wire.risk_envelope && {
562
+ riskEnvelope: {
563
+ weightedScore: wire.risk_envelope.weighted_score,
564
+ engineDecision: wire.risk_envelope.engine_decision,
565
+ envelopeDecision: wire.risk_envelope.envelope_decision,
566
+ promoted: wire.risk_envelope.promoted,
567
+ hardBlocks: wire.risk_envelope.hard_blocks ?? [],
568
+ ...wire.risk_envelope.factors && { factors: wire.risk_envelope.factors }
569
+ }
570
+ }
571
+ };
572
+ }
573
+ /**
574
+ * Batch evaluate — send up to 100 decisions in a single round-trip.
575
+ *
576
+ * Wraps `POST /v1-evaluate-batch`. The server evaluates each item
577
+ * against the active policy bundle and returns results in the same
578
+ * order as the input. One rate-limit token is consumed for the
579
+ * whole batch, and one audit-chain entry lists every included
580
+ * decision id.
581
+ *
582
+ * A per-item policy `deny` is **not** thrown — it appears as
583
+ * `item.decision === "deny"` in the returned items. A whole-batch
584
+ * network error, 4xx, or 5xx throws {@link AtlaSentError}.
585
+ *
586
+ * Requires the `v2_batch` tenant feature flag to be enabled on the
587
+ * org (returns 404 when off). Requires scope `evaluate:write`.
588
+ *
589
+ * @param requests - 1–100 evaluate items.
590
+ * @param batchId - Optional caller-supplied UUID for idempotency.
591
+ * A retried call with the same `batchId` and identical items
592
+ * returns the cached response within 24 h (`replayed: true`).
593
+ */
594
+ async evaluateBatch(requests, batchId) {
595
+ if (!Array.isArray(requests) || requests.length === 0) {
596
+ throw new AtlaSentError(
597
+ "evaluateBatch: requests must be a non-empty array",
598
+ { code: "bad_request" }
599
+ );
600
+ }
601
+ if (requests.length > 100) {
602
+ throw new AtlaSentError(
603
+ `evaluateBatch: requests.length ${requests.length} exceeds the 100-item cap`,
604
+ { code: "bad_request" }
605
+ );
606
+ }
607
+ const wireItems = requests.map((r) => ({
608
+ action_type: r.action,
609
+ actor_id: r.agent,
610
+ context: r.context ?? {}
611
+ }));
612
+ const wireBody = { items: wireItems };
613
+ if (batchId) wireBody.batch_id = batchId;
614
+ const { body: wire, rateLimit } = await this.post(
615
+ "/v1-evaluate-batch",
616
+ wireBody
617
+ );
618
+ const items = (wire.items ?? []).map(
619
+ (item) => {
620
+ const rawDecision = typeof item.decision === "string" ? item.decision.toLowerCase() : void 0;
621
+ const decision = rawDecision === "allow" || rawDecision === "deny" || rawDecision === "hold" || rawDecision === "escalate" ? rawDecision : void 0;
622
+ return {
623
+ index: item.index,
624
+ ...decision !== void 0 ? { decision } : {},
625
+ ...item.decision_id ? { decisionId: item.decision_id } : {},
626
+ ...item.permit_token != null ? { permitToken: item.permit_token } : {},
627
+ ...item.reason != null ? { reason: item.reason } : {},
628
+ ...item.audit_entry_hash ? { auditHash: item.audit_entry_hash } : {},
629
+ ...item.timestamp ? { timestamp: item.timestamp } : {},
630
+ ...item.error ? { error: item.error } : {},
631
+ ...item.message ? { message: item.message } : {}
632
+ };
633
+ }
634
+ );
635
+ return {
636
+ batchId: wire.batch_id,
637
+ items,
638
+ partial: wire.partial ?? false,
639
+ ...wire.replayed ? { replayed: wire.replayed } : {},
527
640
  rateLimit
528
641
  };
529
642
  }
643
+ /**
644
+ * Subscribe to a live stream of decisions for this org.
645
+ *
646
+ * Wraps `GET /v1-decisions-stream`. The server emits one SSE frame
647
+ * per audit event and sends a heartbeat every 15 s. The session
648
+ * auto-closes after `maxSeconds` (default 30 min); reconnect with
649
+ * the last received `event.id` to resume without replaying history.
650
+ *
651
+ * ```ts
652
+ * const controller = new AbortController();
653
+ * for await (const event of client.subscribeDecisions({ signal: controller.signal })) {
654
+ * if (event.type === "heartbeat") continue;
655
+ * console.log(event.type, event.decision, event.actorId);
656
+ * if (event.type === "session_end") break; // reconnect
657
+ * }
658
+ * ```
659
+ *
660
+ * Requires scope `audit:read`. Requires the `v2_decisions_stream`
661
+ * tenant feature flag (returns 404 when off).
662
+ */
663
+ async *subscribeDecisions(opts = {}) {
664
+ const url = new URL(`${this.baseUrl}/v1-decisions-stream`);
665
+ if (opts.types?.length) url.searchParams.set("types", opts.types.join(","));
666
+ if (opts.actorId) url.searchParams.set("actor_id", opts.actorId);
667
+ if (opts.maxSeconds !== void 0) url.searchParams.set("max_seconds", String(opts.maxSeconds));
668
+ const headers = {
669
+ Accept: "text/event-stream",
670
+ Authorization: `Bearer ${this.apiKey}`,
671
+ "User-Agent": this.userAgent,
672
+ // ADR-025: declare the wire-protocol version we were built
673
+ // against. Runtime serves this version's response shape; older
674
+ // versions outside the compatibility window get 426.
675
+ "X-AtlaSent-Protocol-Version": "1"
676
+ };
677
+ if (opts.lastEventId) headers["Last-Event-ID"] = opts.lastEventId;
678
+ let response;
679
+ try {
680
+ response = await this.fetchImpl(url.toString(), {
681
+ method: "GET",
682
+ headers,
683
+ ...opts.signal ? { signal: opts.signal } : {}
684
+ });
685
+ } catch (err) {
686
+ if (err instanceof Error && err.name === "AbortError") return;
687
+ throw new AtlaSentError(
688
+ `Failed to connect to decisions stream: ${err instanceof Error ? err.message : String(err)}`,
689
+ { code: "network" }
690
+ );
691
+ }
692
+ if (!response.ok) {
693
+ const code = response.status === 401 ? "invalid_api_key" : "server_error";
694
+ throw new AtlaSentError(
695
+ `Decisions stream returned ${response.status}`,
696
+ { code, status: response.status }
697
+ );
698
+ }
699
+ if (!response.body) {
700
+ throw new AtlaSentError("Decisions stream response has no body", { code: "bad_response" });
701
+ }
702
+ const reader = response.body.getReader();
703
+ const decoder = new TextDecoder("utf-8");
704
+ let buf = "";
705
+ try {
706
+ while (true) {
707
+ let chunk;
708
+ try {
709
+ chunk = await reader.read();
710
+ } catch (err) {
711
+ if (err instanceof Error && err.name === "AbortError") return;
712
+ throw new AtlaSentError(
713
+ `Decisions stream read error: ${err instanceof Error ? err.message : String(err)}`,
714
+ { code: "network" }
715
+ );
716
+ }
717
+ if (chunk.done) break;
718
+ buf += decoder.decode(chunk.value, { stream: true });
719
+ const rawBlocks = buf.split("\n\n");
720
+ buf = rawBlocks.pop() ?? "";
721
+ for (const block of rawBlocks) {
722
+ if (!block.trim()) continue;
723
+ if (block.trimStart().startsWith(":")) {
724
+ yield { type: "heartbeat" };
725
+ continue;
726
+ }
727
+ let id;
728
+ let eventType = "audit_event";
729
+ let dataLine = "";
730
+ for (const line of block.split("\n")) {
731
+ if (line.startsWith("id:")) id = line.slice(3).trim();
732
+ else if (line.startsWith("event:")) eventType = line.slice(6).trim();
733
+ else if (line.startsWith("data:")) dataLine = line.slice(5).trim();
734
+ }
735
+ if (!dataLine) continue;
736
+ let parsed;
737
+ try {
738
+ parsed = JSON.parse(dataLine);
739
+ } catch {
740
+ continue;
741
+ }
742
+ if (eventType === "session_end") {
743
+ yield { ...id !== void 0 ? { id } : {}, type: "session_end", payload: parsed };
744
+ return;
745
+ }
746
+ const decision = typeof parsed.decision === "string" ? parsed.decision.toLowerCase() : void 0;
747
+ yield {
748
+ ...id !== void 0 ? { id } : {},
749
+ type: eventType,
750
+ ...decision ? { decision } : {},
751
+ ...typeof parsed.actor_id === "string" ? { actorId: parsed.actor_id } : {},
752
+ ...typeof parsed.resource_type === "string" ? { resourceType: parsed.resource_type } : {},
753
+ ...typeof parsed.resource_id === "string" ? { resourceId: parsed.resource_id } : {},
754
+ ...parsed.payload && typeof parsed.payload === "object" ? { payload: parsed.payload } : {},
755
+ ...typeof parsed.hash === "string" ? { hash: parsed.hash } : {},
756
+ ...typeof parsed.previous_hash === "string" ? { previousHash: parsed.previous_hash } : {},
757
+ ...typeof parsed.occurred_at === "string" ? { occurredAt: parsed.occurred_at } : {}
758
+ };
759
+ }
760
+ }
761
+ } finally {
762
+ reader.releaseLock();
763
+ }
764
+ }
530
765
  /**
531
766
  * Pre-flight evaluation that always returns the constraint trace.
532
767
  *
@@ -593,7 +828,17 @@ var AtlaSentClient = class {
593
828
  reason,
594
829
  auditHash: wire.audit_hash ?? "",
595
830
  timestamp: wire.timestamp ?? "",
596
- rateLimit
831
+ rateLimit,
832
+ ...wire.risk_envelope && {
833
+ riskEnvelope: {
834
+ weightedScore: wire.risk_envelope.weighted_score,
835
+ engineDecision: wire.risk_envelope.engine_decision,
836
+ envelopeDecision: wire.risk_envelope.envelope_decision,
837
+ promoted: wire.risk_envelope.promoted,
838
+ hardBlocks: wire.risk_envelope.hard_blocks ?? [],
839
+ ...wire.risk_envelope.factors && { factors: wire.risk_envelope.factors }
840
+ }
841
+ }
597
842
  };
598
843
  let constraintTrace = null;
599
844
  if (wire.constraint_trace !== void 0 && wire.constraint_trace !== null && typeof wire.constraint_trace === "object") {
@@ -642,6 +887,7 @@ var AtlaSentClient = class {
642
887
  outcome: wire.outcome ?? "",
643
888
  permitHash: wire.permit_hash ?? "",
644
889
  timestamp: wire.timestamp ?? "",
890
+ expiresAt: wire.expires_at ?? null,
645
891
  rateLimit
646
892
  };
647
893
  }
@@ -963,6 +1209,151 @@ var AtlaSentClient = class {
963
1209
  }
964
1210
  return { ...wire, rateLimit };
965
1211
  }
1212
+ /**
1213
+ * Re-evaluate a recorded decision against its originally-pinned policy
1214
+ * bundle and engine version, and report whether the result agrees with
1215
+ * what was recorded.
1216
+ *
1217
+ * Wraps `POST /v1-decisions-replay/:id/replay`. **Side-effect-free** — no
1218
+ * audit chain row is written and no permit is issued (per ADR-016).
1219
+ * Useful for compliance review, regression testing of bundle changes,
1220
+ * and post-incident investigation.
1221
+ *
1222
+ * Outcomes encoded in the response:
1223
+ * - `variance: "NONE"` — replay agrees with the original decision.
1224
+ * - `variance: "DECISION_CHANGED"` — same envelope, same bundle, different
1225
+ * decision. Almost always indicates non-determinism in a rule
1226
+ * (e.g. wall-clock comparison) and warrants investigation.
1227
+ * - `variance: "ENVELOPE_DRIFT"` — the recorded request envelope no longer
1228
+ * hashes to the recorded value. The replay short-circuits without
1229
+ * running the engine; `replay_decision` is absent. Treat as evidence
1230
+ * of substrate tamper or a recorder bug.
1231
+ *
1232
+ * Server-side 409 responses (replay refused because the engine version
1233
+ * does not accept replay, or because no bundle was pinned) surface as
1234
+ * `AtlaSentError` with `code: "replay_not_eligible"` — callers should
1235
+ * treat them as expected for old / un-pinned decisions, not as bugs.
1236
+ *
1237
+ * Requires the `evaluate:write` API key scope.
1238
+ *
1239
+ * @param decisionId The UUID of the recorded decision to replay.
1240
+ * Matches `execution_evaluations.request_id`.
1241
+ *
1242
+ * @example
1243
+ * ```ts
1244
+ * const result = await client.replayDecision("dec_abc123");
1245
+ * if (result.variance === "DECISION_CHANGED") {
1246
+ * console.warn(
1247
+ * `Decision ${result.decision_id} changed on replay: ` +
1248
+ * `${result.original_decision} → ${result.replay_decision}`,
1249
+ * );
1250
+ * }
1251
+ * ```
1252
+ */
1253
+ async replayDecision(decisionId) {
1254
+ if (typeof decisionId !== "string" || decisionId.length === 0) {
1255
+ throw new AtlaSentError("decisionId is required", {
1256
+ code: "bad_request"
1257
+ });
1258
+ }
1259
+ const path = `/v1-decisions-replay/${encodeURIComponent(decisionId)}/replay`;
1260
+ const { body: wire, rateLimit } = await this.post(
1261
+ path,
1262
+ {}
1263
+ );
1264
+ if (typeof wire.decision_id !== "string" || typeof wire.original_decision !== "string" || typeof wire.engine_version_kind !== "string" || typeof wire.accepts_replay !== "boolean" || typeof wire.variance !== "string" || typeof wire.envelope_verification !== "string" || typeof wire.replayed_at !== "string") {
1265
+ throw new AtlaSentError(
1266
+ "Malformed response from /v1-decisions-replay/:id/replay: missing required fields",
1267
+ { code: "bad_response" }
1268
+ );
1269
+ }
1270
+ return { ...wire, rateLimit };
1271
+ }
1272
+ /**
1273
+ * ADR-015 Phase C — SDK-canonical replay runtime.
1274
+ *
1275
+ * Re-evaluates a recorded decision against its originally-pinned policy
1276
+ * bundle and engine version via `POST /v1/decisions/:id/replay`.
1277
+ * Side-effect-free server-side: no audit chain row is written and no
1278
+ * permit is issued (ADR-016 `mode: "replay"` sentinel).
1279
+ *
1280
+ * Differences from {@link replayDecision} (the 2.7.0 raw-wire surface):
1281
+ *
1282
+ * | | `replayDecision()` | `replay()` |
1283
+ * | --- | --- | --- |
1284
+ * | Path | `/v1-decisions-replay/:id/replay` | `/v1/decisions/:id/replay` |
1285
+ * | Variance | raw wire (`DECISION_CHANGED`) | SDK-canonical (`POLICY_DRIFT`) |
1286
+ * | 409 handling | throws `AtlaSentError` | returns `ENGINE_DRIFT` / `BUNDLE_MISSING` |
1287
+ * | Input shape | `decisionId: string` | `{ evaluationId }` |
1288
+ *
1289
+ * **Never throws on `409 replay_not_eligible`** — instead returns a
1290
+ * `ReplayResponse` with `varianceKind: "ENGINE_DRIFT"` (engine retired
1291
+ * beyond archival window) or `"BUNDLE_MISSING"` (no bundle pinned on
1292
+ * the original evaluation). Callers can always `switch` on
1293
+ * `result.varianceKind` without a try/catch.
1294
+ *
1295
+ * Fix-forward note: this method was originally landed in PR #275 but
1296
+ * dropped from the squash merge. The TS types (`ReplayResponse`,
1297
+ * `ReplayRequest`) and CHANGELOG made it through; the method itself
1298
+ * did not. Restored here to match the Python {@link
1299
+ * AtlaSentClient}.replay() that landed in atlasent-sdk@2.6.0 (Python).
1300
+ */
1301
+ async replay(input) {
1302
+ if (!input || typeof input.evaluationId !== "string" || input.evaluationId.length === 0) {
1303
+ throw new AtlaSentError("evaluationId is required", {
1304
+ code: "bad_request"
1305
+ });
1306
+ }
1307
+ const path = `/v1/decisions/${encodeURIComponent(input.evaluationId)}/replay`;
1308
+ let wire;
1309
+ let rateLimit;
1310
+ try {
1311
+ const result = await this.post(path, {});
1312
+ wire = result.body;
1313
+ rateLimit = result.rateLimit;
1314
+ } catch (err) {
1315
+ if (err instanceof AtlaSentError && err.status === 409) {
1316
+ const msg = (err.message ?? "").toLowerCase();
1317
+ const varianceKind2 = msg.includes("bundle") ? "BUNDLE_MISSING" : "ENGINE_DRIFT";
1318
+ return {
1319
+ decisionId: input.evaluationId,
1320
+ varianceKind: varianceKind2,
1321
+ originalDecision: "deny",
1322
+ acceptsReplay: false,
1323
+ replayedAt: (/* @__PURE__ */ new Date()).toISOString(),
1324
+ rateLimit: null
1325
+ };
1326
+ }
1327
+ throw err;
1328
+ }
1329
+ const VARIANCE_MAP = {
1330
+ NONE: "NONE",
1331
+ DECISION_CHANGED: "POLICY_DRIFT",
1332
+ ENVELOPE_DRIFT: "ENVELOPE_DRIFT",
1333
+ CHAIN_TAMPER: "CHAIN_TAMPER",
1334
+ BUNDLE_MISSING: "BUNDLE_MISSING",
1335
+ ENGINE_DRIFT: "ENGINE_DRIFT"
1336
+ };
1337
+ const rawVariance = typeof wire.variance === "string" ? wire.variance : "";
1338
+ const varianceKind = VARIANCE_MAP[rawVariance] ?? "NONE";
1339
+ const replayDec = typeof wire.replay_decision === "string" ? wire.replay_decision.toLowerCase() : void 0;
1340
+ const originalDec = typeof wire.original_decision === "string" ? wire.original_decision.toLowerCase() : "deny";
1341
+ const response = {
1342
+ decisionId: typeof wire.decision_id === "string" ? wire.decision_id : input.evaluationId,
1343
+ varianceKind,
1344
+ originalDecision: originalDec,
1345
+ acceptsReplay: typeof wire.accepts_replay === "boolean" ? wire.accepts_replay : true,
1346
+ replayedAt: typeof wire.replayed_at === "string" ? wire.replayed_at : (/* @__PURE__ */ new Date()).toISOString(),
1347
+ rateLimit
1348
+ };
1349
+ if (typeof wire.original_deny_code === "string") response.originalDenyCode = wire.original_deny_code;
1350
+ if (replayDec !== void 0) response.replayedDecision = replayDec;
1351
+ if (typeof wire.replay_deny_code === "string") response.replayedDenyCode = wire.replay_deny_code;
1352
+ if (typeof wire.engine_version === "string") response.engineVersion = wire.engine_version;
1353
+ if (typeof wire.engine_version_kind === "string") response.engineVersionKind = wire.engine_version_kind;
1354
+ if (typeof wire.envelope_verification === "string") response.envelopeVerification = wire.envelope_verification;
1355
+ return response;
1356
+ }
966
1357
  /**
967
1358
  * Open a streaming evaluation session against `POST /v1-evaluate-stream`.
968
1359
  *
@@ -1011,6 +1402,8 @@ var AtlaSentClient = class {
1011
1402
  "Content-Type": "application/json",
1012
1403
  Authorization: `Bearer ${this.apiKey}`,
1013
1404
  "User-Agent": this.userAgent,
1405
+ // ADR-025: wire-protocol version declared on every request.
1406
+ "X-AtlaSent-Protocol-Version": "1",
1014
1407
  "X-Request-ID": requestId
1015
1408
  };
1016
1409
  if (lastEventId !== void 0) {
@@ -1098,7 +1491,9 @@ var AtlaSentClient = class {
1098
1491
  Accept: "application/json",
1099
1492
  Authorization: `Bearer ${this.apiKey}`,
1100
1493
  "User-Agent": this.userAgent,
1101
- "X-Request-ID": requestId
1494
+ "X-Request-ID": requestId,
1495
+ // ADR-025: wire-protocol version declared on every request.
1496
+ "X-AtlaSent-Protocol-Version": "1"
1102
1497
  };
1103
1498
  if (method === "POST") headers["Content-Type"] = "application/json";
1104
1499
  const bodyStr = method === "POST" ? JSON.stringify(body) : void 0;
@@ -1714,6 +2109,69 @@ var AtlaSentClient = class {
1714
2109
  );
1715
2110
  return body;
1716
2111
  }
2112
+ // ── Constrained governance agents (read surface) ──────────────────────────
2113
+ //
2114
+ // Three GETs onto the v1-governance-agents edge function. Doctrine:
2115
+ // findings produced by these endpoints are advisory signal, never
2116
+ // authority. There is no `runGovernanceAgent` method on this client —
2117
+ // invocation belongs in CI (atlasent-action `governance-agents` mode),
2118
+ // not in application code.
2119
+ /**
2120
+ * List the advisory governance-agent registry for the calling org.
2121
+ *
2122
+ * Calls `GET /v1/governance/agents`. The registry is reference data
2123
+ * seeded at runtime-DB migration time; every row has
2124
+ * `authority_class = "advisory"` and `can_authorize = false` —
2125
+ * structural invariants enforced by the schema, not policy.
2126
+ */
2127
+ async listGovernanceAgents() {
2128
+ const { body } = await this.get(
2129
+ "/v1/governance/agents"
2130
+ );
2131
+ return [...body.agents ?? []];
2132
+ }
2133
+ /**
2134
+ * List advisory findings emitted against one governed change.
2135
+ *
2136
+ * Calls `GET /v1/governance/findings?change_id=…[&agent_slug=…]`.
2137
+ * Returns the typed-finding rows in `created_at DESC` order, including
2138
+ * `routed_gate_id` when the finding→gate trigger linked them. Findings
2139
+ * with `can_authorize === false` (always) are advisory; rendering them
2140
+ * never satisfies a gate.
2141
+ */
2142
+ async listGovernanceFindings(query) {
2143
+ if (!query?.change_id) {
2144
+ throw new AtlaSentError("change_id is required", { code: "bad_request" });
2145
+ }
2146
+ const params = new URLSearchParams({ change_id: query.change_id });
2147
+ if (query.agent_slug) params.set("agent_slug", query.agent_slug);
2148
+ const { body } = await this.get(
2149
+ "/v1/governance/findings",
2150
+ params
2151
+ );
2152
+ return [...body.findings ?? []];
2153
+ }
2154
+ /**
2155
+ * List agent run records against one governed change.
2156
+ *
2157
+ * Calls `GET /v1/governance/evaluations?change_id=…[&agent_slug=…]`.
2158
+ * Returns every persisted evaluation, including `failed` / `timeout`
2159
+ * runs and `completed` runs with zero findings — the latter is the
2160
+ * positive signal "the agent ran and found nothing", which the UI
2161
+ * surfaces as `clear`.
2162
+ */
2163
+ async listGovernanceEvaluations(query) {
2164
+ if (!query?.change_id) {
2165
+ throw new AtlaSentError("change_id is required", { code: "bad_request" });
2166
+ }
2167
+ const params = new URLSearchParams({ change_id: query.change_id });
2168
+ if (query.agent_slug) params.set("agent_slug", query.agent_slug);
2169
+ const { body } = await this.get(
2170
+ "/v1/governance/evaluations",
2171
+ params
2172
+ );
2173
+ return [...body.evaluations ?? []];
2174
+ }
1717
2175
  };
1718
2176
  function parseRateLimitHeaders(headers) {
1719
2177
  const rawLimit = headers.get("x-ratelimit-limit");
@@ -2140,6 +2598,174 @@ async function verifyBundle(pathOrBundle, options) {
2140
2598
  return verifyAuditBundle(bundle, keys);
2141
2599
  }
2142
2600
 
2601
+ // src/evidenceEngine.ts
2602
+ function buildWhyTrace(decision, reasons, trace) {
2603
+ if (!trace) {
2604
+ return {
2605
+ decision,
2606
+ summary: formatSummary(decision, reasons, void 0, void 0),
2607
+ policy_evaluations: [],
2608
+ total_stages_evaluated: 0
2609
+ };
2610
+ }
2611
+ const matchedPolicyId = typeof trace.matching_policy_id === "string" ? trace.matching_policy_id : void 0;
2612
+ let terminalStage;
2613
+ let totalStages = 0;
2614
+ const policyEvaluations = (trace.rules_evaluated ?? []).map((policy) => {
2615
+ const wasDecisive = matchedPolicyId === policy.policy_id;
2616
+ let foundTerminal = false;
2617
+ const stages = (policy.stages ?? []).map(
2618
+ (s, idx) => {
2619
+ totalStages++;
2620
+ const isLast = idx === (policy.stages?.length ?? 1) - 1;
2621
+ const candidateForTerminal = wasDecisive && !foundTerminal && (s.matched || isLast);
2622
+ let impact = "passing";
2623
+ if (candidateForTerminal) {
2624
+ impact = "terminal";
2625
+ foundTerminal = true;
2626
+ terminalStage = {
2627
+ stage: s.stage,
2628
+ ...s.rule !== void 0 ? { rule: s.rule } : {},
2629
+ matched: s.matched,
2630
+ ...s.detail !== void 0 ? { detail: s.detail } : {},
2631
+ impact: "terminal"
2632
+ };
2633
+ } else if (s.matched) {
2634
+ impact = "contributing";
2635
+ }
2636
+ return {
2637
+ stage: s.stage,
2638
+ ...s.rule !== void 0 ? { rule: s.rule } : {},
2639
+ matched: s.matched,
2640
+ ...s.detail !== void 0 ? { detail: s.detail } : {},
2641
+ impact
2642
+ };
2643
+ }
2644
+ );
2645
+ return {
2646
+ policy_id: policy.policy_id,
2647
+ decision: policy.decision,
2648
+ fingerprint: policy.fingerprint,
2649
+ ...policy.risk_score !== void 0 ? { risk_score: policy.risk_score } : {},
2650
+ stages,
2651
+ was_decisive: wasDecisive
2652
+ };
2653
+ });
2654
+ return {
2655
+ decision,
2656
+ summary: formatSummary(decision, reasons, matchedPolicyId, terminalStage),
2657
+ ...matchedPolicyId !== void 0 ? { matched_policy_id: matchedPolicyId } : {},
2658
+ policy_evaluations: policyEvaluations,
2659
+ ...terminalStage !== void 0 ? { terminal_stage: terminalStage } : {},
2660
+ total_stages_evaluated: totalStages
2661
+ };
2662
+ }
2663
+ function formatSummary(decision, reasons, matchedPolicyId, terminalStage) {
2664
+ const reason0 = reasons.length > 0 ? reasons[0] : void 0;
2665
+ switch (decision) {
2666
+ case "allow":
2667
+ return reason0 ? `Allowed: ${reason0}` : "Allowed: all policy checks passed.";
2668
+ case "deny":
2669
+ if (reason0) return `Denied: ${reason0}`;
2670
+ if (terminalStage?.detail)
2671
+ return `Denied at stage "${terminalStage.stage}": ${terminalStage.detail}`;
2672
+ if (terminalStage)
2673
+ return `Denied at stage "${terminalStage.stage}".`;
2674
+ if (matchedPolicyId)
2675
+ return `Denied by policy ${matchedPolicyId}.`;
2676
+ return "Denied: policy check failed.";
2677
+ case "hold":
2678
+ return reason0 ? `Held for review: ${reason0}` : "Held pending human review.";
2679
+ case "escalate":
2680
+ return reason0 ? `Escalated: ${reason0}` : "Escalated to a human reviewer.";
2681
+ }
2682
+ }
2683
+ function sortedJSON(val) {
2684
+ if (val === null || val === void 0) return "null";
2685
+ if (typeof val === "number")
2686
+ return Number.isFinite(val) ? String(val) : "null";
2687
+ if (typeof val === "boolean") return val ? "true" : "false";
2688
+ if (typeof val === "string") return JSON.stringify(val);
2689
+ if (Array.isArray(val)) return "[" + val.map(sortedJSON).join(",") + "]";
2690
+ if (typeof val === "object") {
2691
+ const obj = val;
2692
+ return "{" + Object.keys(obj).sort().map((k) => JSON.stringify(k) + ":" + sortedJSON(obj[k])).join(",") + "}";
2693
+ }
2694
+ return "null";
2695
+ }
2696
+ function hexEncode(bytes) {
2697
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
2698
+ }
2699
+ async function sha256Hex2(input) {
2700
+ const bytes = new TextEncoder().encode(input);
2701
+ if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle?.digest) {
2702
+ const buf = await globalThis.crypto.subtle.digest("SHA-256", bytes);
2703
+ return hexEncode(new Uint8Array(buf));
2704
+ }
2705
+ try {
2706
+ const { createHash } = await import(
2707
+ /* @vite-ignore */
2708
+ /* webpackIgnore: true */
2709
+ "crypto"
2710
+ );
2711
+ return createHash("sha256").update(input, "utf8").digest("hex");
2712
+ } catch {
2713
+ return "";
2714
+ }
2715
+ }
2716
+ async function computeContextHash(context) {
2717
+ return sha256Hex2(sortedJSON(context));
2718
+ }
2719
+ function buildDecisionReceiptPayload(args) {
2720
+ return {
2721
+ receipt_id: args.receipt_id,
2722
+ evaluation_id: args.evaluation_id,
2723
+ org_id: args.org_id,
2724
+ decision: args.decision,
2725
+ action: args.action,
2726
+ actor: args.actor,
2727
+ resource_type: args.resource_type ?? null,
2728
+ resource_id: args.resource_id ?? null,
2729
+ reasons: Array.from(args.reasons),
2730
+ why_summary: args.why_summary,
2731
+ permit_id: args.permit_id ?? null,
2732
+ permit_hash: args.permit_hash ?? null,
2733
+ audit_hash: args.audit_hash,
2734
+ context_hash: args.context_hash,
2735
+ issued_at: args.issued_at,
2736
+ expires_at: args.expires_at ?? null
2737
+ };
2738
+ }
2739
+ function receiptSigningInput(payload) {
2740
+ return payload.receipt_id + "\n" + payload.issued_at + "\n" + JSON.stringify(payload);
2741
+ }
2742
+ async function signDecisionReceiptHmac(payload, secret) {
2743
+ const input = receiptSigningInput(payload);
2744
+ const keyBytes = new TextEncoder().encode(secret);
2745
+ const msgBytes = new TextEncoder().encode(input);
2746
+ if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle) {
2747
+ const key = await globalThis.crypto.subtle.importKey(
2748
+ "raw",
2749
+ keyBytes,
2750
+ { name: "HMAC", hash: "SHA-256" },
2751
+ false,
2752
+ ["sign"]
2753
+ );
2754
+ const sig = await globalThis.crypto.subtle.sign("HMAC", key, msgBytes);
2755
+ return hexEncode(new Uint8Array(sig));
2756
+ }
2757
+ try {
2758
+ const { createHmac: createHmac2 } = await import(
2759
+ /* @vite-ignore */
2760
+ /* webpackIgnore: true */
2761
+ "crypto"
2762
+ );
2763
+ return createHmac2("sha256", secret).update(input).digest("hex");
2764
+ } catch {
2765
+ return "";
2766
+ }
2767
+ }
2768
+
2143
2769
  // src/protect.ts
2144
2770
  var sharedClient = null;
2145
2771
  var overrides = {};
@@ -2170,6 +2796,7 @@ function getClient() {
2170
2796
  sharedClient = new AtlaSentClient(options);
2171
2797
  return sharedClient;
2172
2798
  }
2799
+ var ACTION_TYPE_RE = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
2173
2800
  function wireDecisionToDenied(serverDecision) {
2174
2801
  const lower = serverDecision.toLowerCase();
2175
2802
  if (lower === "hold" || lower === "escalate") return lower;
@@ -2207,7 +2834,19 @@ async function computeExecutionHash(payload) {
2207
2834
  return "";
2208
2835
  }
2209
2836
  }
2837
+ function generateReceiptId() {
2838
+ if (typeof globalThis !== "undefined" && typeof globalThis.crypto?.randomUUID === "function") {
2839
+ return globalThis.crypto.randomUUID();
2840
+ }
2841
+ return `rcpt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
2842
+ }
2210
2843
  async function protect(request) {
2844
+ if (!ACTION_TYPE_RE.test(request.action)) {
2845
+ throw new AtlaSentError(
2846
+ `action must be in dot-notation format (e.g. "production.deploy"). Got: ${JSON.stringify(request.action)}`,
2847
+ { code: "bad_request" }
2848
+ );
2849
+ }
2211
2850
  const client = getClient();
2212
2851
  const evaluation = await client.evaluate(request);
2213
2852
  if (evaluation.decision !== "allow") {
@@ -2218,12 +2857,71 @@ async function protect(request) {
2218
2857
  auditHash: evaluation.auditHash
2219
2858
  });
2220
2859
  }
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."
2860
+ const environment = request.context?.environment;
2861
+ if (!environment) {
2862
+ throw new AtlaSentError(
2863
+ 'context.environment is required. Pass the environment where this action executes (e.g. "production", "staging").',
2864
+ { code: "bad_request" }
2865
+ );
2866
+ }
2867
+ const evaluatePayload = {
2868
+ action_type: request.action,
2869
+ actor_id: request.agent,
2870
+ context: request.context ?? {}
2871
+ };
2872
+ const execution_hash = await computeExecutionHash(evaluatePayload);
2873
+ const verifyRequest = {
2874
+ permitId: evaluation.permitId,
2875
+ agent: request.agent,
2876
+ action: request.action,
2877
+ environment,
2878
+ ...execution_hash ? { execution_hash } : {}
2879
+ };
2880
+ if (request.context !== void 0) verifyRequest.context = request.context;
2881
+ const verification = await client.verifyPermit(verifyRequest);
2882
+ if (!verification.verified) {
2883
+ const outcome = normalizePermitOutcome(verification.outcome);
2884
+ throw new AtlaSentDeniedError({
2885
+ decision: "deny",
2886
+ evaluationId: evaluation.permitId,
2887
+ reason: `Permit failed verification (${verification.outcome})`,
2888
+ auditHash: evaluation.auditHash,
2889
+ ...outcome !== void 0 && { outcome }
2890
+ });
2891
+ }
2892
+ return {
2893
+ permitId: evaluation.permitId,
2894
+ permitHash: verification.permitHash,
2895
+ auditHash: evaluation.auditHash,
2896
+ reason: evaluation.reason,
2897
+ timestamp: verification.timestamp,
2898
+ permitExpiresAt: verification.expiresAt ?? null
2899
+ };
2900
+ }
2901
+ async function protectWithEvidence(request, opts = {}) {
2902
+ if (!ACTION_TYPE_RE.test(request.action)) {
2903
+ throw new AtlaSentError(
2904
+ `action must be in dot-notation format (e.g. "production.deploy"). Got: ${JSON.stringify(request.action)}`,
2905
+ { code: "bad_request" }
2906
+ );
2907
+ }
2908
+ const client = getClient();
2909
+ const evaluation = await client.evaluate(request);
2910
+ if (evaluation.decision !== "allow") {
2911
+ throw new AtlaSentDeniedError({
2912
+ decision: wireDecisionToDenied(evaluation.decision),
2913
+ evaluationId: evaluation.permitId,
2914
+ reason: evaluation.reason,
2915
+ auditHash: evaluation.auditHash
2916
+ });
2917
+ }
2918
+ const environment = request.context?.environment;
2919
+ if (!environment) {
2920
+ throw new AtlaSentError(
2921
+ 'context.environment is required. Pass the environment where this action executes (e.g. "production", "staging").',
2922
+ { code: "bad_request" }
2224
2923
  );
2225
- return "production";
2226
- })();
2924
+ }
2227
2925
  const evaluatePayload = {
2228
2926
  action_type: request.action,
2229
2927
  actor_id: request.agent,
@@ -2249,12 +2947,68 @@ async function protect(request) {
2249
2947
  ...outcome !== void 0 && { outcome }
2250
2948
  });
2251
2949
  }
2950
+ const contextHash = await computeContextHash(request.context ?? {});
2951
+ const whyTrace = buildWhyTrace(
2952
+ "allow",
2953
+ evaluation.reasons,
2954
+ opts.constraintTrace ?? null
2955
+ );
2956
+ const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
2957
+ const receiptId = generateReceiptId();
2958
+ const orgId = evaluation.permit?.orgId ?? "";
2959
+ const payload = buildDecisionReceiptPayload({
2960
+ receipt_id: receiptId,
2961
+ evaluation_id: evaluation.evaluationId,
2962
+ org_id: orgId,
2963
+ decision: "allow",
2964
+ action: request.action,
2965
+ actor: request.agent,
2966
+ resource_type: request.context?.resource_type ?? null,
2967
+ resource_id: request.context?.resource_id ?? null,
2968
+ reasons: evaluation.reasons,
2969
+ why_summary: whyTrace.summary,
2970
+ permit_id: evaluation.permitId,
2971
+ permit_hash: verification.permitHash,
2972
+ audit_hash: evaluation.auditHash,
2973
+ context_hash: contextHash,
2974
+ issued_at: issuedAt
2975
+ });
2976
+ let signature = null;
2977
+ let algorithm = "none";
2978
+ if (opts.signingSecret) {
2979
+ signature = await signDecisionReceiptHmac(payload, opts.signingSecret);
2980
+ algorithm = "hmac-sha256";
2981
+ }
2982
+ const receipt = {
2983
+ receipt_id: receiptId,
2984
+ evaluation_id: evaluation.evaluationId,
2985
+ org_id: orgId,
2986
+ decision: "allow",
2987
+ action: request.action,
2988
+ actor: request.agent,
2989
+ resource_type: request.context?.resource_type ?? null,
2990
+ resource_id: request.context?.resource_id ?? null,
2991
+ reasons: evaluation.reasons,
2992
+ why_trace: opts.constraintTrace !== void 0 ? whyTrace : null,
2993
+ permit_id: evaluation.permitId,
2994
+ permit_hash: verification.permitHash,
2995
+ audit_hash: evaluation.auditHash,
2996
+ context_hash: contextHash,
2997
+ issued_at: issuedAt,
2998
+ expires_at: null,
2999
+ algorithm,
3000
+ signature,
3001
+ signing_key_id: opts.signingKeyId ?? null,
3002
+ payload
3003
+ };
2252
3004
  return {
2253
3005
  permitId: evaluation.permitId,
2254
3006
  permitHash: verification.permitHash,
2255
3007
  auditHash: evaluation.auditHash,
2256
3008
  reason: evaluation.reason,
2257
- timestamp: verification.timestamp
3009
+ timestamp: verification.timestamp,
3010
+ permitExpiresAt: verification.expiresAt ?? null,
3011
+ receipt
2258
3012
  };
2259
3013
  }
2260
3014
 
@@ -3160,8 +3914,8 @@ async function _hmacSha256Hex(payload, secret) {
3160
3914
  const sig = await crypto.subtle.sign("HMAC", key, enc.encode(payload));
3161
3915
  return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
3162
3916
  }
3163
- const { createHmac } = await import("crypto");
3164
- return createHmac("sha256", secret).update(payload).digest("hex");
3917
+ const { createHmac: createHmac2 } = await import("crypto");
3918
+ return createHmac2("sha256", secret).update(payload).digest("hex");
3165
3919
  }
3166
3920
  async function verifyWebhook(payload, signature, secret) {
3167
3921
  try {
@@ -3559,6 +4313,1391 @@ async function safeJson(response, path, requestId) {
3559
4313
  }
3560
4314
  }
3561
4315
 
4316
+ // src/approvalRuntime.ts
4317
+ var _runtimeConfig = {};
4318
+ function configureApprovalRuntime(config) {
4319
+ _runtimeConfig = { ..._runtimeConfig, ...config };
4320
+ }
4321
+ function resolveConfig(overrides2) {
4322
+ const apiKey = overrides2?.apiKey ?? _runtimeConfig.apiKey ?? (typeof process !== "undefined" && process.env ? process.env["ATLASENT_API_KEY"] : void 0);
4323
+ if (!apiKey) {
4324
+ throw new AtlaSentError(
4325
+ "ApprovalRuntime: no API key configured. Set ATLASENT_API_KEY or call configureApprovalRuntime({ apiKey }).",
4326
+ { code: "invalid_api_key" }
4327
+ );
4328
+ }
4329
+ return {
4330
+ apiKey,
4331
+ baseUrl: overrides2?.baseUrl ?? _runtimeConfig.baseUrl ?? "https://api.atlasent.io",
4332
+ requestTimeoutMs: _runtimeConfig.timeoutMs ?? 3e4
4333
+ };
4334
+ }
4335
+ async function apiPost(path, body, cfg) {
4336
+ const url = `${cfg.baseUrl}${path}`;
4337
+ let resp;
4338
+ try {
4339
+ resp = await fetch(url, {
4340
+ method: "POST",
4341
+ headers: {
4342
+ Accept: "application/json",
4343
+ Authorization: `Bearer ${cfg.apiKey}`,
4344
+ "Content-Type": "application/json"
4345
+ },
4346
+ body: JSON.stringify(body),
4347
+ signal: AbortSignal.timeout(cfg.requestTimeoutMs)
4348
+ });
4349
+ } catch (err) {
4350
+ throw new AtlaSentError(
4351
+ `ApprovalRuntime: network error calling ${path}`,
4352
+ { code: "network", cause: err }
4353
+ );
4354
+ }
4355
+ if (!resp.ok) {
4356
+ const text = await resp.text().catch(() => "");
4357
+ const code = resp.status === 401 ? "invalid_api_key" : resp.status === 403 ? "forbidden" : resp.status === 429 ? "rate_limited" : "server_error";
4358
+ throw new AtlaSentError(
4359
+ `ApprovalRuntime: API error ${resp.status} at ${path}: ${text.slice(0, 200)}`,
4360
+ { code, status: resp.status }
4361
+ );
4362
+ }
4363
+ return resp.json();
4364
+ }
4365
+ async function apiGet(path, cfg) {
4366
+ const url = `${cfg.baseUrl}${path}`;
4367
+ let resp;
4368
+ try {
4369
+ resp = await fetch(url, {
4370
+ method: "GET",
4371
+ headers: {
4372
+ Accept: "application/json",
4373
+ Authorization: `Bearer ${cfg.apiKey}`
4374
+ },
4375
+ signal: AbortSignal.timeout(cfg.requestTimeoutMs)
4376
+ });
4377
+ } catch (err) {
4378
+ throw new AtlaSentError(
4379
+ `ApprovalRuntime: network error calling ${path}`,
4380
+ { code: "network", cause: err }
4381
+ );
4382
+ }
4383
+ if (!resp.ok) {
4384
+ const text = await resp.text().catch(() => "");
4385
+ const code = resp.status === 401 ? "invalid_api_key" : resp.status === 403 ? "forbidden" : resp.status === 429 ? "rate_limited" : "server_error";
4386
+ throw new AtlaSentError(
4387
+ `ApprovalRuntime: API error ${resp.status} at ${path}: ${text.slice(0, 200)}`,
4388
+ { code, status: resp.status }
4389
+ );
4390
+ }
4391
+ return resp.json();
4392
+ }
4393
+ function sleep2(ms) {
4394
+ return new Promise((resolve) => setTimeout(resolve, ms));
4395
+ }
4396
+ var EscalationDeniedError = class extends Error {
4397
+ name = "EscalationDeniedError";
4398
+ escalationId;
4399
+ outcome;
4400
+ constructor(outcome) {
4401
+ super(
4402
+ `Escalation ${outcome.escalation.id} was rejected` + (outcome.resolutionNote ? `: ${outcome.resolutionNote}` : "")
4403
+ );
4404
+ this.escalationId = outcome.escalation.id;
4405
+ this.outcome = outcome;
4406
+ }
4407
+ };
4408
+ var EscalationTimeoutError = class extends Error {
4409
+ name = "EscalationTimeoutError";
4410
+ escalationId;
4411
+ outcome;
4412
+ constructor(outcome) {
4413
+ super(
4414
+ `Escalation ${outcome.escalation.id} timed out waiting for approval`
4415
+ );
4416
+ this.escalationId = outcome.escalation.id;
4417
+ this.outcome = outcome;
4418
+ }
4419
+ };
4420
+ async function createEscalation(opts) {
4421
+ const { apiKey, baseUrl, ...hitlBody } = opts;
4422
+ const cfg = resolveConfig({
4423
+ ...apiKey !== void 0 ? { apiKey } : {},
4424
+ ...baseUrl !== void 0 ? { baseUrl } : {}
4425
+ });
4426
+ const body = {
4427
+ agent_id: hitlBody.agent_id ?? "unknown",
4428
+ escalation_reason: hitlBody.escalation_reason ?? "Policy hold \u2014 awaiting human approval",
4429
+ ...hitlBody
4430
+ };
4431
+ const escalation = await apiPost("/v1/hitl", body, cfg);
4432
+ return {
4433
+ escalationId: escalation.id,
4434
+ createdAt: escalation.created_at,
4435
+ timeoutAt: escalation.timeout_at ?? null,
4436
+ assignedToRole: escalation.assigned_to_role ?? null
4437
+ };
4438
+ }
4439
+ async function waitForEscalationApproval(opts) {
4440
+ const cfg = resolveConfig({
4441
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4442
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4443
+ });
4444
+ const waitMs = opts.waitMs ?? 6e5;
4445
+ const pollIntervalMs = Math.max(opts.pollIntervalMs ?? 5e3, 1e3);
4446
+ const deadline = Date.now() + waitMs;
4447
+ const toOutcome = (escalation2) => {
4448
+ const terminal = escalation2.status === "approved" || escalation2.status === "rejected" || escalation2.status === "auto_approved" || escalation2.status === "timed_out";
4449
+ if (!terminal) return null;
4450
+ const status = escalation2.status === "approved" || escalation2.status === "auto_approved" ? "approved" : escalation2.status === "timed_out" ? "timed_out" : "rejected";
4451
+ return {
4452
+ status,
4453
+ escalation: escalation2,
4454
+ resolvedBy: escalation2.resolved_by ?? null,
4455
+ resolutionNote: escalation2.resolution_note ?? null,
4456
+ resolvedAt: escalation2.resolved_at ?? null
4457
+ };
4458
+ };
4459
+ while (Date.now() < deadline) {
4460
+ const escalation2 = await apiGet(
4461
+ `/v1/escalations/${opts.escalationId}`,
4462
+ cfg
4463
+ );
4464
+ const outcome2 = toOutcome(escalation2);
4465
+ if (outcome2) return outcome2;
4466
+ const remaining = deadline - Date.now();
4467
+ if (remaining <= 0) break;
4468
+ await sleep2(Math.min(pollIntervalMs, remaining));
4469
+ }
4470
+ const escalation = await apiGet(
4471
+ `/v1/escalations/${opts.escalationId}`,
4472
+ cfg
4473
+ );
4474
+ const outcome = toOutcome(escalation);
4475
+ if (outcome) return outcome;
4476
+ return {
4477
+ status: "timed_out",
4478
+ escalation,
4479
+ resolvedBy: null,
4480
+ resolutionNote: "Client-side wait timeout elapsed",
4481
+ resolvedAt: null
4482
+ };
4483
+ }
4484
+ async function protectOrEscalate(request, opts = {}) {
4485
+ try {
4486
+ const permit = await protect(request);
4487
+ return {
4488
+ ...permit,
4489
+ escalationId: "",
4490
+ resolvedBy: null,
4491
+ resolutionNote: null,
4492
+ resolvedAt: permit.timestamp,
4493
+ approvalBasis: "direct_policy"
4494
+ };
4495
+ } catch (err) {
4496
+ if (!(err instanceof AtlaSentDeniedError) || err.decision !== "hold" && err.decision !== "escalate") {
4497
+ throw err;
4498
+ }
4499
+ }
4500
+ const proposedAction = opts.proposedAction ?? request.context;
4501
+ const handle = await createEscalation({
4502
+ agent_id: opts.agentId ?? request.agent,
4503
+ escalation_reason: opts.escalationReason ?? `Policy hold for "${request.action}" by "${request.agent}"`,
4504
+ ...proposedAction !== void 0 ? { proposed_action: proposedAction } : {},
4505
+ ...opts.riskScore !== void 0 ? { risk_score: opts.riskScore } : {},
4506
+ ...opts.assignedToRole !== void 0 ? { assigned_to_role: opts.assignedToRole } : {},
4507
+ ...opts.quorumRequired !== void 0 ? { quorum_required: opts.quorumRequired } : {},
4508
+ ...opts.fallbackDecision !== void 0 ? { fallback_decision: opts.fallbackDecision } : {},
4509
+ ...opts.timeoutAt !== void 0 ? { timeout_at: opts.timeoutAt } : {},
4510
+ ...opts.metadata !== void 0 ? { metadata: opts.metadata } : {},
4511
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4512
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4513
+ });
4514
+ opts.onEscalationCreated?.(handle);
4515
+ const outcome = await waitForEscalationApproval({
4516
+ escalationId: handle.escalationId,
4517
+ ...opts.waitMs !== void 0 ? { waitMs: opts.waitMs } : {},
4518
+ ...opts.pollIntervalMs !== void 0 ? { pollIntervalMs: opts.pollIntervalMs } : {},
4519
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4520
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4521
+ });
4522
+ if (outcome.status === "rejected") throw new EscalationDeniedError(outcome);
4523
+ if (outcome.status === "timed_out") throw new EscalationTimeoutError(outcome);
4524
+ return {
4525
+ permitId: `escl_${handle.escalationId}`,
4526
+ permitHash: "",
4527
+ auditHash: outcome.escalation.id,
4528
+ reason: outcome.resolutionNote ?? "Approved by human reviewer",
4529
+ timestamp: outcome.resolvedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4530
+ permitExpiresAt: null,
4531
+ escalationId: handle.escalationId,
4532
+ resolvedBy: outcome.resolvedBy,
4533
+ resolutionNote: outcome.resolutionNote,
4534
+ resolvedAt: outcome.resolvedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4535
+ approvalBasis: "human_approval"
4536
+ };
4537
+ }
4538
+ async function requestOverride(opts) {
4539
+ const cfg = resolveConfig({
4540
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4541
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4542
+ });
4543
+ const body = {
4544
+ reason: opts.reason,
4545
+ evaluationId: opts.evaluationId,
4546
+ ...opts.ttlSeconds !== void 0 && { ttlSeconds: opts.ttlSeconds },
4547
+ ...opts.metadata !== void 0 && { metadata: opts.metadata }
4548
+ };
4549
+ return apiPost("/v1/overrides", body, cfg);
4550
+ }
4551
+
4552
+ // src/actionContext.ts
4553
+ function buildActionContext(input) {
4554
+ const env = typeof input.environment === "string" ? { name: input.environment } : input.environment;
4555
+ const ctx = {
4556
+ actor: input.actor,
4557
+ ...input.resource !== void 0 && { resource: input.resource },
4558
+ ...env !== void 0 && { environment: env },
4559
+ ...input.action_meta !== void 0 && { action_meta: input.action_meta },
4560
+ ...input.history !== void 0 && { history: input.history },
4561
+ ...input.extra ?? {}
4562
+ };
4563
+ if (env?.name !== void 0) ctx.environment_name = env.name;
4564
+ if (input.resource?.type !== void 0)
4565
+ ctx.resource_type = input.resource.type;
4566
+ if (input.resource?.id !== void 0) ctx.resource_id = input.resource.id;
4567
+ return ctx;
4568
+ }
4569
+ function getNestedValue(obj, path) {
4570
+ return path.split(".").reduce((cur, key) => {
4571
+ if (cur !== null && typeof cur === "object") {
4572
+ return cur[key];
4573
+ }
4574
+ return void 0;
4575
+ }, obj);
4576
+ }
4577
+ function validateActionContext(ctx, opts = {}) {
4578
+ const errors = [];
4579
+ const warnings = [];
4580
+ const ctxObj = ctx;
4581
+ if (ctx.actor !== void 0) {
4582
+ if (!ctx.actor.id || typeof ctx.actor.id !== "string") {
4583
+ errors.push({
4584
+ field: "actor.id",
4585
+ code: "required",
4586
+ message: "actor.id is required when actor is provided"
4587
+ });
4588
+ }
4589
+ }
4590
+ const hasEnvName = ctx.environment?.name !== void 0 || ctx.environment_name !== void 0;
4591
+ if (!hasEnvName) {
4592
+ warnings.push({
4593
+ field: "environment.name",
4594
+ code: "recommended",
4595
+ message: "environment.name is not set; protect() will default to 'production' with a console warning"
4596
+ });
4597
+ }
4598
+ if (!opts.skipCrossFieldChecks) {
4599
+ const amount = ctx.action_meta?.estimated_amount;
4600
+ if (typeof amount === "number" && amount > 0) {
4601
+ if (!ctx.action_meta?.currency) {
4602
+ errors.push({
4603
+ field: "action_meta.currency",
4604
+ code: "cross_field",
4605
+ message: "action_meta.currency is required when action_meta.estimated_amount > 0"
4606
+ });
4607
+ } else if (!/^[A-Z]{3}$/.test(ctx.action_meta.currency)) {
4608
+ errors.push({
4609
+ field: "action_meta.currency",
4610
+ code: "invalid_value",
4611
+ message: `action_meta.currency '${ctx.action_meta.currency}' is not a valid ISO 4217 code (expected 3 uppercase letters)`
4612
+ });
4613
+ }
4614
+ }
4615
+ }
4616
+ if (ctx.history?.last_action_at !== void 0) {
4617
+ const ts = new Date(ctx.history.last_action_at);
4618
+ if (isNaN(ts.getTime())) {
4619
+ errors.push({
4620
+ field: "history.last_action_at",
4621
+ code: "invalid_type",
4622
+ message: `history.last_action_at '${ctx.history.last_action_at}' is not a valid ISO-8601 timestamp`
4623
+ });
4624
+ }
4625
+ }
4626
+ const knownSensitivities = /* @__PURE__ */ new Set([
4627
+ "public",
4628
+ "internal",
4629
+ "confidential",
4630
+ "restricted"
4631
+ ]);
4632
+ if (ctx.resource?.sensitivity !== void 0 && !knownSensitivities.has(ctx.resource.sensitivity)) {
4633
+ errors.push({
4634
+ field: "resource.sensitivity",
4635
+ code: "invalid_value",
4636
+ message: `resource.sensitivity '${ctx.resource.sensitivity}' is not one of: public, internal, confidential, restricted`
4637
+ });
4638
+ }
4639
+ for (const fieldPath of opts.requiredFields ?? []) {
4640
+ const value = getNestedValue(ctxObj, fieldPath);
4641
+ if (value === void 0 || value === null || value === "") {
4642
+ errors.push({
4643
+ field: fieldPath,
4644
+ code: "required",
4645
+ message: `${fieldPath} is required by the caller's validation rules`
4646
+ });
4647
+ }
4648
+ }
4649
+ return { valid: errors.length === 0, errors, warnings };
4650
+ }
4651
+ var DEFAULT_REDACTION_RULES = [
4652
+ {
4653
+ field: /password|passwd|passphrase/i,
4654
+ mode: "remove"
4655
+ },
4656
+ {
4657
+ field: /secret|private_key|client_secret|signing_secret/i,
4658
+ mode: "remove"
4659
+ },
4660
+ {
4661
+ field: /api_key|apikey|access_key|access_token/i,
4662
+ mode: "remove"
4663
+ },
4664
+ {
4665
+ field: /\btoken\b|auth_token|bearer/i,
4666
+ mode: "mask"
4667
+ },
4668
+ {
4669
+ field: /\bssn\b|social_security|tax_id|\bsin\b/i,
4670
+ mode: "remove"
4671
+ },
4672
+ {
4673
+ field: /credit_card|card_number|pan\b|cvv|cvc|expiry/i,
4674
+ mode: "remove"
4675
+ },
4676
+ {
4677
+ field: /\bemail\b/i,
4678
+ mode: "mask"
4679
+ },
4680
+ {
4681
+ field: /phone|mobile|cell\b/i,
4682
+ mode: "mask"
4683
+ },
4684
+ {
4685
+ field: /\bip\b|ip_address|remote_addr/i,
4686
+ mode: "mask"
4687
+ },
4688
+ {
4689
+ field: /dob|date_of_birth|birth_date|birthdate/i,
4690
+ mode: "remove"
4691
+ }
4692
+ ];
4693
+ var MASK_PLACEHOLDER = "[REDACTED]";
4694
+ function matchesRule(key, rule) {
4695
+ if (typeof rule.field === "string") {
4696
+ return key.toLowerCase() === rule.field.toLowerCase();
4697
+ }
4698
+ return rule.field.test(key);
4699
+ }
4700
+ function redactValue(value, mode) {
4701
+ if (mode === "remove") return void 0;
4702
+ if (mode === "mask") return MASK_PLACEHOLDER;
4703
+ return "[HASHED]";
4704
+ }
4705
+ function redactObject(obj, rules, currentPath) {
4706
+ const result = {};
4707
+ for (const [key, value] of Object.entries(obj)) {
4708
+ const fieldPath = currentPath ? `${currentPath}.${key}` : key;
4709
+ const matchingRule = rules.find(
4710
+ (r) => matchesRule(key, r) && (r.path === void 0 || r.path === fieldPath)
4711
+ );
4712
+ if (matchingRule) {
4713
+ const redacted = redactValue(value, matchingRule.mode);
4714
+ if (redacted !== void 0) result[key] = redacted;
4715
+ } else if (Array.isArray(value)) {
4716
+ result[key] = value.map(
4717
+ (item) => item !== null && typeof item === "object" && !Array.isArray(item) ? redactObject(item, rules, fieldPath) : item
4718
+ );
4719
+ } else if (value !== null && typeof value === "object") {
4720
+ result[key] = redactObject(
4721
+ value,
4722
+ rules,
4723
+ fieldPath
4724
+ );
4725
+ } else {
4726
+ result[key] = value;
4727
+ }
4728
+ }
4729
+ return result;
4730
+ }
4731
+ function redactContext(ctx, rules = DEFAULT_REDACTION_RULES) {
4732
+ return redactObject(
4733
+ ctx,
4734
+ rules,
4735
+ ""
4736
+ );
4737
+ }
4738
+ function flattenActionContext(ctx) {
4739
+ const flat = {};
4740
+ for (const [key, value] of Object.entries(ctx)) {
4741
+ flat[key] = value;
4742
+ }
4743
+ const envName = ctx.environment?.name ?? ctx.environment_name;
4744
+ if (envName !== void 0) {
4745
+ flat["environment_name"] = envName;
4746
+ }
4747
+ return flat;
4748
+ }
4749
+
4750
+ // src/shadow.ts
4751
+ var _defaultConfig = {};
4752
+ function configureShadow(config) {
4753
+ _defaultConfig = { ..._defaultConfig, ...config };
4754
+ }
4755
+ async function protectShadow(request, opts) {
4756
+ const merged = { ..._defaultConfig, ...opts };
4757
+ const mode = merged.mode ?? "observe";
4758
+ if (mode === "enforce") {
4759
+ const start2 = Date.now();
4760
+ const permit = await protect(request);
4761
+ const outcome = {
4762
+ decision: "permit",
4763
+ permit,
4764
+ error: null,
4765
+ would_have_blocked: false,
4766
+ latencyMs: Date.now() - start2,
4767
+ evaluationId: permit.permitId,
4768
+ request,
4769
+ mode
4770
+ };
4771
+ await _notify(outcome, merged);
4772
+ return outcome;
4773
+ }
4774
+ const start = Date.now();
4775
+ try {
4776
+ const permit = await protect(request);
4777
+ const outcome = {
4778
+ decision: "permit",
4779
+ permit,
4780
+ error: null,
4781
+ would_have_blocked: false,
4782
+ latencyMs: Date.now() - start,
4783
+ evaluationId: permit.permitId,
4784
+ request,
4785
+ mode
4786
+ };
4787
+ await _notify(outcome, merged);
4788
+ if (merged.reportToApi) {
4789
+ void reportShadowEvent(outcome, merged).catch(() => void 0);
4790
+ }
4791
+ return outcome;
4792
+ } catch (err) {
4793
+ if (err instanceof AtlaSentDeniedError) {
4794
+ const outcome = {
4795
+ decision: err.decision,
4796
+ permit: null,
4797
+ error: err,
4798
+ would_have_blocked: true,
4799
+ latencyMs: Date.now() - start,
4800
+ evaluationId: err.evaluationId ?? null,
4801
+ request,
4802
+ mode
4803
+ };
4804
+ if (mode === "warn") {
4805
+ console.warn(
4806
+ `[AtlaSent shadow:warn] Action '${request.action}' would have been blocked (decision=${err.decision}, evaluationId=${err.evaluationId ?? "unknown"})`
4807
+ );
4808
+ }
4809
+ await _notify(outcome, merged);
4810
+ if (merged.reportToApi) {
4811
+ void reportShadowEvent(outcome, merged).catch(() => void 0);
4812
+ }
4813
+ return outcome;
4814
+ }
4815
+ throw err;
4816
+ }
4817
+ }
4818
+ async function _notify(outcome, config) {
4819
+ if (config.onOutcome) {
4820
+ try {
4821
+ await config.onOutcome(outcome);
4822
+ } catch {
4823
+ }
4824
+ }
4825
+ }
4826
+ async function reportShadowEvent(outcome, opts) {
4827
+ const apiKey = opts?.apiKey ?? _defaultConfig.apiKey ?? process.env["ATLASENT_API_KEY"] ?? "";
4828
+ const baseUrl = opts?.baseUrl ?? _defaultConfig.baseUrl ?? process.env["ATLASENT_BASE_URL"] ?? "https://api.atlasent.ai";
4829
+ const payload = {
4830
+ action: outcome.request.action,
4831
+ agentId: outcome.request.agent ?? null,
4832
+ decision: outcome.decision,
4833
+ would_have_blocked: outcome.would_have_blocked,
4834
+ latencyMs: outcome.latencyMs,
4835
+ evaluationId: outcome.evaluationId,
4836
+ mode: outcome.mode,
4837
+ ...outcome.error ? { deniedReason: outcome.error.message } : {},
4838
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4839
+ };
4840
+ const response = await fetch(`${baseUrl}/v1/shadow-events`, {
4841
+ method: "POST",
4842
+ headers: {
4843
+ "Content-Type": "application/json",
4844
+ Authorization: `Bearer ${apiKey}`
4845
+ },
4846
+ body: JSON.stringify(payload)
4847
+ });
4848
+ if (!response.ok && response.status >= 500) {
4849
+ throw new Error(`Shadow event reporting failed: ${response.status}`);
4850
+ }
4851
+ }
4852
+
4853
+ // src/controlSurface.ts
4854
+ var _config = {};
4855
+ function configureControlSurface(config) {
4856
+ _config = { ..._config, ...config };
4857
+ }
4858
+ function resolveConfig2(opts) {
4859
+ return {
4860
+ apiKey: opts?.apiKey ?? _config.apiKey ?? process.env["ATLASENT_API_KEY"] ?? "",
4861
+ baseUrl: opts?.baseUrl ?? _config.baseUrl ?? process.env["ATLASENT_BASE_URL"] ?? "https://api.atlasent.ai",
4862
+ timeoutMs: opts?.timeoutMs ?? _config.timeoutMs ?? 1e4
4863
+ };
4864
+ }
4865
+ async function apiGet2(path, config) {
4866
+ const controller = new AbortController();
4867
+ const timer = setTimeout(() => controller.abort(), config.timeoutMs);
4868
+ try {
4869
+ const res = await fetch(`${config.baseUrl}${path}`, {
4870
+ method: "GET",
4871
+ headers: {
4872
+ Authorization: `Bearer ${config.apiKey}`,
4873
+ Accept: "application/json"
4874
+ },
4875
+ signal: controller.signal
4876
+ });
4877
+ if (!res.ok) {
4878
+ throw new Error(`HTTP ${res.status}`);
4879
+ }
4880
+ return res.json();
4881
+ } finally {
4882
+ clearTimeout(timer);
4883
+ }
4884
+ }
4885
+ async function apiPost2(path, body, config) {
4886
+ const controller = new AbortController();
4887
+ const timer = setTimeout(() => controller.abort(), config.timeoutMs);
4888
+ try {
4889
+ const res = await fetch(`${config.baseUrl}${path}`, {
4890
+ method: "POST",
4891
+ headers: {
4892
+ "Content-Type": "application/json",
4893
+ Authorization: `Bearer ${config.apiKey}`
4894
+ },
4895
+ body: JSON.stringify(body),
4896
+ signal: controller.signal
4897
+ });
4898
+ if (!res.ok) {
4899
+ throw new Error(`HTTP ${res.status}`);
4900
+ }
4901
+ return res.json();
4902
+ } finally {
4903
+ clearTimeout(timer);
4904
+ }
4905
+ }
4906
+ async function checkIntegrationHealth(opts) {
4907
+ const config = resolveConfig2(opts);
4908
+ const errors = [];
4909
+ let apiReachable = false;
4910
+ let authenticated = false;
4911
+ let latencyMs = null;
4912
+ let apiVersion = null;
4913
+ if (!config.apiKey) {
4914
+ errors.push("ATLASENT_API_KEY is not configured");
4915
+ }
4916
+ const start = Date.now();
4917
+ try {
4918
+ const data = await apiGet2("/v1/health", config);
4919
+ latencyMs = Date.now() - start;
4920
+ apiReachable = true;
4921
+ apiVersion = data.version ?? null;
4922
+ if (data.status === "ok" || data.status === "healthy") {
4923
+ authenticated = true;
4924
+ } else {
4925
+ errors.push(`API health status: ${data.status ?? "unknown"}`);
4926
+ }
4927
+ } catch (err) {
4928
+ latencyMs = Date.now() - start;
4929
+ const message = err instanceof Error ? err.message : String(err);
4930
+ if (message.includes("401") || message.includes("403")) {
4931
+ apiReachable = true;
4932
+ errors.push("API key is invalid or lacks required permissions");
4933
+ } else {
4934
+ errors.push(`API unreachable: ${message}`);
4935
+ }
4936
+ }
4937
+ return {
4938
+ healthy: apiReachable && authenticated && errors.length === 0,
4939
+ apiReachable,
4940
+ authenticated,
4941
+ latencyMs,
4942
+ apiVersion,
4943
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
4944
+ errors
4945
+ };
4946
+ }
4947
+ async function reportProtectedAction(opts) {
4948
+ const config = resolveConfig2(opts);
4949
+ return apiPost2(
4950
+ "/v1/control-surface/actions",
4951
+ {
4952
+ action_class: opts.actionClass,
4953
+ enforcement_mode: opts.enforcementMode ?? "observe",
4954
+ schema_id: opts.schemaId ?? null,
4955
+ tags: opts.tags ?? []
4956
+ },
4957
+ config
4958
+ );
4959
+ }
4960
+ async function getEnforcementStatus(opts) {
4961
+ const config = resolveConfig2(opts);
4962
+ return apiGet2(
4963
+ `/v1/control-surface/actions/${encodeURIComponent(opts.actionClass)}/status`,
4964
+ config
4965
+ );
4966
+ }
4967
+ async function getOrgSummary(opts) {
4968
+ const config = resolveConfig2(opts);
4969
+ return apiGet2("/v1/control-surface/summary", config);
4970
+ }
4971
+
4972
+ // src/verticals/deployGate.ts
4973
+ function resolveEnvActor() {
4974
+ return process.env["GITHUB_ACTOR"] ?? process.env["GITLAB_USER_LOGIN"] ?? process.env["CIRCLE_USERNAME"] ?? process.env["BITBUCKET_STEP_TRIGGERER_UUID"] ?? void 0;
4975
+ }
4976
+ function resolveEnvSha() {
4977
+ return process.env["GITHUB_SHA"] ?? process.env["CI_COMMIT_SHA"] ?? process.env["CIRCLE_SHA1"] ?? process.env["BITBUCKET_COMMIT"] ?? void 0;
4978
+ }
4979
+ function resolveEnvWorkflow() {
4980
+ return process.env["GITHUB_WORKFLOW"] ?? process.env["CI_PIPELINE_NAME"] ?? process.env["CIRCLE_WORKFLOW_NAME"] ?? void 0;
4981
+ }
4982
+ async function protectDeploy(opts) {
4983
+ const actorId = opts.actorId ?? resolveEnvActor() ?? "ci-system";
4984
+ const sha = opts.sha ?? resolveEnvSha();
4985
+ const workflow = opts.workflow ?? resolveEnvWorkflow();
4986
+ const environment = opts.environment ?? "production";
4987
+ const isProduction = environment === "production";
4988
+ const ctx = buildActionContext({
4989
+ actor: {
4990
+ id: actorId,
4991
+ type: "service_account",
4992
+ ...opts.actorLabel !== void 0 ? { label: opts.actorLabel } : {}
4993
+ },
4994
+ resource: {
4995
+ id: opts.service,
4996
+ type: opts.resourceType ?? "service"
4997
+ },
4998
+ environment,
4999
+ action_meta: {
5000
+ risk_level: isProduction ? "critical" : "medium",
5001
+ reversibility: "partial",
5002
+ ...opts.description !== void 0 ? { description: opts.description } : sha !== void 0 ? { description: `Deploy ${sha.slice(0, 8)} to ${environment}` } : {}
5003
+ },
5004
+ extra: {
5005
+ sha,
5006
+ workflow
5007
+ }
5008
+ });
5009
+ const request = {
5010
+ action: "production.deploy",
5011
+ agent: actorId,
5012
+ context: flattenActionContext(ctx)
5013
+ };
5014
+ if (opts.requireApproval || isProduction) {
5015
+ return protectOrEscalate(request, {
5016
+ escalationReason: `Production deployment of ${opts.service} requires human approval`,
5017
+ assignedToRole: opts.assignedToRole ?? "release-manager",
5018
+ waitMs: opts.waitMs ?? 30 * 60 * 1e3,
5019
+ ...opts.onEscalationCreated !== void 0 ? { onEscalationCreated: opts.onEscalationCreated } : {},
5020
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
5021
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
5022
+ });
5023
+ }
5024
+ return protect(request);
5025
+ }
5026
+
5027
+ // src/verticals/closeGovernance.ts
5028
+ var ACTION_RISK = {
5029
+ "period.close": "critical",
5030
+ "period.reopen": "critical",
5031
+ "reconciliation.lock": "high",
5032
+ "data.export": "high"
5033
+ };
5034
+ var ACTION_REVERSIBILITY = {
5035
+ "period.close": "partial",
5036
+ "period.reopen": "partial",
5037
+ "reconciliation.lock": "reversible",
5038
+ "data.export": "irreversible"
5039
+ };
5040
+ async function protectCloseAction(opts) {
5041
+ const ctx = buildActionContext({
5042
+ actor: {
5043
+ id: opts.closedBy,
5044
+ type: "human",
5045
+ trust_level: "medium"
5046
+ },
5047
+ resource: {
5048
+ id: opts.entityId,
5049
+ type: "accounting_entity",
5050
+ sensitivity: opts.dataClassification ?? "confidential",
5051
+ ...opts.entityName !== void 0 ? { name: opts.entityName } : {}
5052
+ },
5053
+ environment: "production",
5054
+ action_meta: {
5055
+ risk_level: ACTION_RISK[opts.action],
5056
+ reversibility: ACTION_REVERSIBILITY[opts.action],
5057
+ description: opts.description ?? `${opts.action} for period ${opts.periodLabel} on entity ${opts.entityId}`
5058
+ },
5059
+ extra: {
5060
+ period_label: opts.periodLabel,
5061
+ close_action: opts.action
5062
+ }
5063
+ });
5064
+ return protectOrEscalate(
5065
+ {
5066
+ action: opts.action,
5067
+ agent: opts.closedBy,
5068
+ context: flattenActionContext(ctx)
5069
+ },
5070
+ {
5071
+ escalationReason: `Accounting ${opts.action} for period '${opts.periodLabel}' requires approval`,
5072
+ assignedToRole: opts.assignedToRole ?? "controller",
5073
+ quorumRequired: opts.requireDualApproval ?? opts.action === "period.close" ? "simple_majority" : "single_approver",
5074
+ waitMs: opts.waitMs ?? 24 * 60 * 60 * 1e3,
5075
+ ...opts.onEscalationCreated !== void 0 ? { onEscalationCreated: opts.onEscalationCreated } : {},
5076
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
5077
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
5078
+ }
5079
+ );
5080
+ }
5081
+
5082
+ // src/verticals/paymentRelease.ts
5083
+ var ISO_4217 = /^[A-Z]{3}$/;
5084
+ async function protectPaymentRelease(opts) {
5085
+ if (!ISO_4217.test(opts.currency)) {
5086
+ throw new TypeError(
5087
+ `Invalid currency code '${opts.currency}': must be a 3-letter ISO 4217 code (e.g. USD, EUR, GBP)`
5088
+ );
5089
+ }
5090
+ if (opts.amount <= 0) {
5091
+ throw new RangeError(`Payment amount must be greater than 0, got ${opts.amount}`);
5092
+ }
5093
+ const escalateThreshold = opts.autoEscalateAbove ?? 1e4;
5094
+ const dualThreshold = opts.requireDualApprovalAbove ?? 1e5;
5095
+ const needsEscalation = opts.amount > escalateThreshold;
5096
+ const needsDual = opts.amount > dualThreshold;
5097
+ const ctx = buildActionContext({
5098
+ actor: {
5099
+ id: opts.authorizedBy,
5100
+ type: "human"
5101
+ },
5102
+ resource: {
5103
+ id: opts.vendorId,
5104
+ type: "vendor",
5105
+ ...opts.vendorName !== void 0 ? { name: opts.vendorName } : {}
5106
+ },
5107
+ environment: "production",
5108
+ action_meta: {
5109
+ risk_level: opts.amount > dualThreshold ? "critical" : opts.amount > escalateThreshold ? "high" : "medium",
5110
+ reversibility: "irreversible",
5111
+ estimated_amount: opts.amount,
5112
+ currency: opts.currency,
5113
+ description: opts.description ?? `Release ${opts.currency} ${opts.amount.toLocaleString()} to ${opts.vendorName ?? opts.vendorId}`
5114
+ },
5115
+ extra: {
5116
+ reference: opts.reference
5117
+ }
5118
+ });
5119
+ const request = {
5120
+ action: "payment.release",
5121
+ agent: opts.authorizedBy,
5122
+ context: flattenActionContext(ctx)
5123
+ };
5124
+ if (needsEscalation) {
5125
+ return protectOrEscalate(request, {
5126
+ escalationReason: `Payment of ${opts.currency} ${opts.amount.toLocaleString()} to ${opts.vendorName ?? opts.vendorId} exceeds auto-approval threshold of ${opts.currency} ${escalateThreshold.toLocaleString()}`,
5127
+ assignedToRole: opts.assignedToRole ?? "finance-approver",
5128
+ quorumRequired: needsDual ? "simple_majority" : "single_approver",
5129
+ waitMs: opts.waitMs ?? 4 * 60 * 60 * 1e3,
5130
+ ...opts.onEscalationCreated !== void 0 ? { onEscalationCreated: opts.onEscalationCreated } : {},
5131
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
5132
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
5133
+ });
5134
+ }
5135
+ return protect(request);
5136
+ }
5137
+
5138
+ // src/verticals/agentTools.ts
5139
+ var HIGH_RISK_TOOLS = /* @__PURE__ */ new Set([
5140
+ "bash",
5141
+ "shell",
5142
+ "exec",
5143
+ "execute_code",
5144
+ "run_command",
5145
+ "write_file",
5146
+ "delete_file",
5147
+ "overwrite_file",
5148
+ "sql_execute",
5149
+ "db_write",
5150
+ "db_delete",
5151
+ "send_email",
5152
+ "send_message",
5153
+ "post_to_slack",
5154
+ "create_pr",
5155
+ "merge_pr",
5156
+ "push_code",
5157
+ "deploy",
5158
+ "release",
5159
+ "make_payment",
5160
+ "transfer_funds",
5161
+ "create_user",
5162
+ "delete_user",
5163
+ "modify_permissions",
5164
+ "aws_cli",
5165
+ "gcloud",
5166
+ "kubectl"
5167
+ ]);
5168
+ var CRITICAL_TOOLS = /* @__PURE__ */ new Set([
5169
+ "bash",
5170
+ "shell",
5171
+ "exec",
5172
+ "execute_code",
5173
+ "run_command",
5174
+ "delete_file",
5175
+ "db_delete",
5176
+ "make_payment",
5177
+ "transfer_funds",
5178
+ "delete_user",
5179
+ "modify_permissions",
5180
+ "deploy",
5181
+ "release"
5182
+ ]);
5183
+ function classifyToolRisk(toolName) {
5184
+ const normalized = toolName.toLowerCase().replace(/[^a-z0-9_]/g, "_");
5185
+ if (CRITICAL_TOOLS.has(normalized)) return "critical";
5186
+ if (HIGH_RISK_TOOLS.has(normalized)) return "high";
5187
+ if (normalized.includes("write") || normalized.includes("create") || normalized.includes("update")) return "medium";
5188
+ return "low";
5189
+ }
5190
+ async function protectToolCall(opts) {
5191
+ const inferredRisk = opts.riskLevel ?? classifyToolRisk(opts.toolName);
5192
+ const mode = opts.mode ?? (inferredRisk === "low" ? "enforce" : "escalate");
5193
+ const ctx = buildActionContext({
5194
+ actor: {
5195
+ id: opts.agentId,
5196
+ type: "agent",
5197
+ ...opts.sessionId !== void 0 ? { session_id: opts.sessionId } : {}
5198
+ },
5199
+ resource: {
5200
+ type: "agent_tool",
5201
+ id: opts.toolName
5202
+ },
5203
+ environment: "production",
5204
+ action_meta: {
5205
+ risk_level: inferredRisk,
5206
+ reversibility: inferredRisk === "critical" || inferredRisk === "high" ? "irreversible" : "reversible",
5207
+ description: opts.description ?? `Agent ${opts.agentId} calling tool '${opts.toolName}'`
5208
+ },
5209
+ extra: {
5210
+ tool_args_keys: Object.keys(opts.toolArgs),
5211
+ session_id: opts.sessionId
5212
+ }
5213
+ });
5214
+ const request = {
5215
+ action: `agent_tool.${opts.toolName}`,
5216
+ agent: opts.agentId,
5217
+ context: flattenActionContext(ctx)
5218
+ };
5219
+ if (mode === "observe") {
5220
+ return protectShadow(request, { mode: "observe" });
5221
+ }
5222
+ if (mode === "escalate" || inferredRisk === "critical") {
5223
+ return protectOrEscalate(request, {
5224
+ escalationReason: `Agent '${opts.agentId}' is calling ${inferredRisk}-risk tool '${opts.toolName}'`,
5225
+ assignedToRole: opts.assignedToRole ?? "agent-supervisor",
5226
+ riskScore: inferredRisk === "critical" ? 1 : inferredRisk === "high" ? 0.75 : 0.5,
5227
+ waitMs: opts.waitMs ?? 15 * 60 * 1e3,
5228
+ ...opts.onEscalationCreated !== void 0 ? { onEscalationCreated: opts.onEscalationCreated } : {},
5229
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
5230
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
5231
+ });
5232
+ }
5233
+ return protect(request);
5234
+ }
5235
+
5236
+ // src/claimLineage.ts
5237
+ var import_node_crypto2 = require("crypto");
5238
+ var NOT_APPLICABLE = { notApplicable: true };
5239
+ function isNotApplicable(v) {
5240
+ return typeof v === "object" && v !== null && v.notApplicable === true;
5241
+ }
5242
+ var SDK_VERSION2 = "@atlasent/sdk@1.4.2";
5243
+ function canonicalize(value) {
5244
+ if (value === null || value === void 0) return "null";
5245
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
5246
+ if (typeof value === "boolean") return value ? "true" : "false";
5247
+ if (typeof value === "string") return JSON.stringify(value);
5248
+ if (Array.isArray(value)) return "[" + value.map(canonicalize).join(",") + "]";
5249
+ if (typeof value === "object") {
5250
+ const obj = value;
5251
+ const keys = Object.keys(obj).sort();
5252
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalize(obj[k])).join(",") + "}";
5253
+ }
5254
+ return "null";
5255
+ }
5256
+ function sha256Hex3(input) {
5257
+ const { createHash } = require("crypto");
5258
+ return createHash("sha256").update(input).digest("hex");
5259
+ }
5260
+ function hmacSha256Base64url(payload, secret) {
5261
+ return (0, import_node_crypto2.createHmac)("sha256", secret).update(payload).digest("base64url");
5262
+ }
5263
+ function computeLinkHash(link) {
5264
+ return sha256Hex3(canonicalize(link));
5265
+ }
5266
+ function slotStatus(input, slot) {
5267
+ if (isNotApplicable(input)) return "not_applicable";
5268
+ if (slot !== null) return "present";
5269
+ return "missing";
5270
+ }
5271
+ function toDeploySlot(input) {
5272
+ if (input === void 0 || isNotApplicable(input)) return null;
5273
+ return {
5274
+ deploy_id: input.deploy_id,
5275
+ environment: input.environment,
5276
+ sha: input.sha,
5277
+ actor_id: input.actor_id,
5278
+ deployed_at: input.deployed_at,
5279
+ gate_permit_token: input.gate_permit_token
5280
+ };
5281
+ }
5282
+ function toIntegrationSlot(input) {
5283
+ if (input === void 0 || isNotApplicable(input)) return null;
5284
+ const run = input;
5285
+ return {
5286
+ run_id: run.id,
5287
+ framework: run.framework,
5288
+ period_start: run.period_start,
5289
+ period_end: run.period_end,
5290
+ status: run.status,
5291
+ passing_control_count: (run.controls ?? []).filter((c) => c.status === "pass").length,
5292
+ failing_control_count: (run.controls ?? []).filter((c) => c.status !== "pass").length,
5293
+ run_completed_at: run.created_at
5294
+ };
5295
+ }
5296
+ function toApprovalSlot(input) {
5297
+ if (input === void 0 || isNotApplicable(input)) return null;
5298
+ if ("escalation" in input) {
5299
+ const chain = input;
5300
+ const approvals = chain.approvals;
5301
+ const lastApproved = approvals.filter((a) => a.decision === "approve").map((a) => a.created_at).sort().at(-1) ?? chain.escalation.created_at;
5302
+ return {
5303
+ approval_id: chain.escalation.id,
5304
+ approval_kind: "hitl_chain",
5305
+ quorum_type: hitlQuorumToSlotQuorum(chain.escalation.quorum_required),
5306
+ approver_count: approvals.filter((a) => a.decision === "approve").length,
5307
+ approver_ids: approvals.filter((a) => a.decision === "approve").map((a) => a.user_id ?? a.actor_label ?? "unknown"),
5308
+ approved_at: lastApproved,
5309
+ artifact_hash: chain.artifact_hash
5310
+ };
5311
+ }
5312
+ const artifact = input;
5313
+ return {
5314
+ approval_id: artifact.approval_id,
5315
+ approval_kind: "approval_artifact",
5316
+ quorum_type: artifact.quorum_type,
5317
+ approver_count: artifact.approver_ids.length,
5318
+ approver_ids: artifact.approver_ids,
5319
+ approved_at: artifact.approved_at,
5320
+ artifact_hash: artifact.artifact_hash
5321
+ };
5322
+ }
5323
+ function hitlQuorumToSlotQuorum(tier) {
5324
+ switch (tier) {
5325
+ case "single_approver":
5326
+ return "single_approver";
5327
+ case "two_thirds":
5328
+ return "two_thirds";
5329
+ case "unanimous":
5330
+ return "unanimous";
5331
+ default:
5332
+ return "simple_majority";
5333
+ }
5334
+ }
5335
+ function toRuntimeSlot(receipt, verifiedAtCreation) {
5336
+ return {
5337
+ permit_token: receipt.permit_id ?? receipt.receipt_id,
5338
+ audit_hash: receipt.audit_hash,
5339
+ decision: receipt.decision === "allow" ? "allow" : receipt.decision === "escalate" ? "escalate" : "deny",
5340
+ decision_id: receipt.evaluation_id,
5341
+ evaluated_at: receipt.issued_at,
5342
+ algorithm: receipt.algorithm,
5343
+ signature: receipt.signature,
5344
+ permit_revoked_at: null,
5345
+ verified_at_claim_time: receipt.decision === "allow",
5346
+ verified_at_link_creation: verifiedAtCreation
5347
+ };
5348
+ }
5349
+ function buildChecklist(runtime, deployStatus, integrationStatus, approvalStatus, delta, lastVerifiedAt, now) {
5350
+ const deltaComputed = delta.status === "computed";
5351
+ const policyDriftClean = deltaComputed ? !delta.policy_drift_detected : null;
5352
+ const schemaDriftClean = !delta.schema_drift_detected;
5353
+ const allPass = runtime.verified_at_claim_time && runtime.verified_at_link_creation && deltaComputed && policyDriftClean === true && schemaDriftClean && deployStatus !== "missing" && integrationStatus !== "missing" && approvalStatus !== "missing";
5354
+ return {
5355
+ runtime_evidence_present: true,
5356
+ verified_at_claim_time: runtime.verified_at_claim_time,
5357
+ verified_at_link_creation: runtime.verified_at_link_creation,
5358
+ deploy_evidence_status: deployStatus,
5359
+ integration_evidence_status: integrationStatus,
5360
+ approval_artifact_status: approvalStatus,
5361
+ delta_computed: deltaComputed,
5362
+ policy_drift_clean: policyDriftClean,
5363
+ schema_drift_clean: schemaDriftClean,
5364
+ all_pass: allPass,
5365
+ last_verified_at: lastVerifiedAt,
5366
+ computed_at: now
5367
+ };
5368
+ }
5369
+ function buildClaimEvidenceLink(opts) {
5370
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5371
+ const linkId = `cel_${(0, import_node_crypto2.randomUUID)().replace(/-/g, "")}`;
5372
+ const orgId = opts.orgId ?? opts.runtimeEvidence.org_id;
5373
+ const schemaVersion = opts.schemaVersion ?? SDK_VERSION2;
5374
+ const deploySlot = toDeploySlot(opts.deployEvidence);
5375
+ const integrationSlot = toIntegrationSlot(opts.integrationEvidence);
5376
+ const approvalSlot = toApprovalSlot(opts.approvalArtifact);
5377
+ const deployStatus = slotStatus(opts.deployEvidence, deploySlot);
5378
+ const integrationStatus = slotStatus(opts.integrationEvidence, integrationSlot);
5379
+ const approvalStatus = slotStatus(opts.approvalArtifact, approvalSlot);
5380
+ const verifiedAtCreation = opts.runtimeEvidence.decision === "allow";
5381
+ const runtime = toRuntimeSlot(opts.runtimeEvidence, verifiedAtCreation);
5382
+ const delta = {
5383
+ status: "pending",
5384
+ computed_at: null,
5385
+ policy_version_at_claim: null,
5386
+ policy_version_current: null,
5387
+ policy_drift_detected: null,
5388
+ schema_version_at_claim: schemaVersion,
5389
+ schema_version_current: schemaVersion,
5390
+ schema_drift_detected: false,
5391
+ drift_details: []
5392
+ };
5393
+ const lastVerifiedAt = verifiedAtCreation ? now : null;
5394
+ const checklist = buildChecklist(
5395
+ runtime,
5396
+ deployStatus,
5397
+ integrationStatus,
5398
+ approvalStatus,
5399
+ delta,
5400
+ lastVerifiedAt,
5401
+ now
5402
+ );
5403
+ const linkAlgorithm = opts.signingSecret ? "hmac-sha256" : "none";
5404
+ const body = {
5405
+ version: "claim_evidence_link.v1",
5406
+ link_id: linkId,
5407
+ claim_id: opts.claimId,
5408
+ org_id: orgId,
5409
+ linked_at: now,
5410
+ updated_at: now,
5411
+ revision: 1,
5412
+ link_algorithm: linkAlgorithm,
5413
+ runtime_evidence: runtime,
5414
+ deploy_evidence: deploySlot,
5415
+ integration_evidence: integrationSlot,
5416
+ approval_artifact: approvalSlot,
5417
+ delta,
5418
+ verification_checklist: checklist
5419
+ };
5420
+ const linkHash = computeLinkHash(body);
5421
+ const linkSignature = opts.signingSecret ? hmacSha256Base64url(linkHash, opts.signingSecret) : null;
5422
+ return { ...body, link_hash: linkHash, link_signature: linkSignature };
5423
+ }
5424
+ function verifyClaimEvidenceLink(link, opts = {}) {
5425
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5426
+ const { link_hash: _lh, link_signature: _ls, ...body } = link;
5427
+ const expectedHash = computeLinkHash(
5428
+ body
5429
+ );
5430
+ const hashValid = expectedHash === link.link_hash;
5431
+ let sigValid = true;
5432
+ if (link.link_algorithm === "hmac-sha256") {
5433
+ if (!opts.signingSecret) {
5434
+ sigValid = false;
5435
+ } else {
5436
+ const expected = hmacSha256Base64url(link.link_hash, opts.signingSecret);
5437
+ sigValid = expected === link.link_signature;
5438
+ }
5439
+ }
5440
+ const runtime = {
5441
+ ...link.runtime_evidence,
5442
+ // If the link hash or signature is invalid, mark creation-time verification as failed
5443
+ verified_at_link_creation: hashValid && sigValid ? link.runtime_evidence.verified_at_link_creation : false
5444
+ };
5445
+ const checklist = buildChecklist(
5446
+ runtime,
5447
+ link.verification_checklist.deploy_evidence_status,
5448
+ link.verification_checklist.integration_evidence_status,
5449
+ link.verification_checklist.approval_artifact_status,
5450
+ link.delta,
5451
+ runtime.verified_at_link_creation ? link.verification_checklist.last_verified_at ?? now : null,
5452
+ now
5453
+ );
5454
+ const updatedBody = {
5455
+ version: link.version,
5456
+ link_id: link.link_id,
5457
+ claim_id: link.claim_id,
5458
+ org_id: link.org_id,
5459
+ linked_at: link.linked_at,
5460
+ updated_at: now,
5461
+ revision: link.revision + 1,
5462
+ link_algorithm: link.link_algorithm,
5463
+ runtime_evidence: runtime,
5464
+ deploy_evidence: link.deploy_evidence,
5465
+ integration_evidence: link.integration_evidence,
5466
+ approval_artifact: link.approval_artifact,
5467
+ delta: link.delta,
5468
+ verification_checklist: checklist
5469
+ };
5470
+ const newHash = computeLinkHash(updatedBody);
5471
+ const newSignature = opts.signingSecret ? hmacSha256Base64url(newHash, opts.signingSecret) : link.link_algorithm === "none" ? null : link.link_signature;
5472
+ const updatedLink = {
5473
+ ...updatedBody,
5474
+ link_hash: newHash,
5475
+ link_signature: newSignature
5476
+ };
5477
+ const failedSlots = [];
5478
+ if (!hashValid) failedSlots.push("link_hash");
5479
+ if (!sigValid) failedSlots.push("link_signature");
5480
+ if (!checklist.verified_at_claim_time) failedSlots.push("verified_at_claim_time");
5481
+ if (!checklist.verified_at_link_creation) failedSlots.push("verified_at_link_creation");
5482
+ if (!checklist.delta_computed) failedSlots.push("delta_computed");
5483
+ if (checklist.policy_drift_clean === false) failedSlots.push("policy_drift_clean");
5484
+ if (!checklist.schema_drift_clean) failedSlots.push("schema_drift_clean");
5485
+ if (checklist.deploy_evidence_status === "missing") failedSlots.push("deploy_evidence_status");
5486
+ if (checklist.integration_evidence_status === "missing") failedSlots.push("integration_evidence_status");
5487
+ if (checklist.approval_artifact_status === "missing") failedSlots.push("approval_artifact_status");
5488
+ const valid = failedSlots.length === 0;
5489
+ if (!valid) {
5490
+ throw new AtlaSentError(
5491
+ `ClaimEvidenceLink verification failed: ${failedSlots.join(", ")}`,
5492
+ { code: "claim_evidence_incomplete" }
5493
+ );
5494
+ }
5495
+ return { link: updatedLink, valid, failedSlots };
5496
+ }
5497
+ function buildClaimEvidenceLinkFromActionBundle(bundle, opts) {
5498
+ const runtimeEvidence = {
5499
+ receipt_id: bundle.receipt.receipt_id,
5500
+ evaluation_id: bundle.receipt.evaluation_id,
5501
+ org_id: opts.orgId ?? "",
5502
+ decision: bundle.receipt.decision,
5503
+ action: bundle.action,
5504
+ actor: bundle.actor,
5505
+ resource_type: null,
5506
+ resource_id: null,
5507
+ reasons: [],
5508
+ why_trace: null,
5509
+ permit_id: bundle.receipt.permit_id,
5510
+ permit_hash: null,
5511
+ audit_hash: bundle.receipt.audit_hash ?? "",
5512
+ context_hash: "",
5513
+ issued_at: bundle.receipt.issued_at,
5514
+ expires_at: null,
5515
+ algorithm: bundle.receipt.algorithm,
5516
+ signature: bundle.receipt.signature,
5517
+ signing_key_id: null,
5518
+ payload: {
5519
+ receipt_id: bundle.receipt.receipt_id,
5520
+ evaluation_id: bundle.receipt.evaluation_id,
5521
+ org_id: opts.orgId ?? "",
5522
+ decision: bundle.receipt.decision,
5523
+ action: bundle.action,
5524
+ actor: bundle.actor,
5525
+ resource_type: null,
5526
+ resource_id: null,
5527
+ reasons: [],
5528
+ why_summary: "",
5529
+ permit_id: bundle.receipt.permit_id,
5530
+ permit_hash: null,
5531
+ audit_hash: bundle.receipt.audit_hash ?? "",
5532
+ context_hash: "",
5533
+ issued_at: bundle.receipt.issued_at,
5534
+ expires_at: null
5535
+ }
5536
+ };
5537
+ const deployEvidence = opts.deployNotApplicable ? NOT_APPLICABLE : {
5538
+ deploy_id: bundle.bundle_id,
5539
+ environment: bundle.environment,
5540
+ sha: bundle.sha,
5541
+ actor_id: bundle.actor,
5542
+ deployed_at: bundle.generated_at,
5543
+ gate_permit_token: bundle.receipt.permit_id ?? bundle.receipt.receipt_id
5544
+ };
5545
+ return buildClaimEvidenceLink({
5546
+ claimId: opts.claimId,
5547
+ ...opts.orgId !== void 0 ? { orgId: opts.orgId } : {},
5548
+ runtimeEvidence,
5549
+ deployEvidence,
5550
+ ...opts.signingSecret !== void 0 ? { signingSecret: opts.signingSecret } : {},
5551
+ ...opts.schemaVersion !== void 0 ? { schemaVersion: opts.schemaVersion } : {}
5552
+ });
5553
+ }
5554
+
5555
+ // src/bccae.ts
5556
+ var DEFAULT_BASE_URL2 = "https://api.atlasent.io";
5557
+ var DEFAULT_TIMEOUT_MS2 = 1e4;
5558
+ function enforceTls(raw) {
5559
+ let parsed;
5560
+ try {
5561
+ parsed = new URL(raw);
5562
+ } catch {
5563
+ throw new AtlaSentError(
5564
+ "BCCAEClient baseUrl is not a valid URL",
5565
+ { code: "network" }
5566
+ );
5567
+ }
5568
+ if (parsed.protocol === "http:") {
5569
+ const h = parsed.hostname;
5570
+ if (h !== "localhost" && h !== "127.0.0.1" && h !== "[::1]") {
5571
+ throw new AtlaSentError(
5572
+ "BCCAEClient baseUrl must use https:// for non-local endpoints",
5573
+ { code: "network" }
5574
+ );
5575
+ }
5576
+ }
5577
+ return parsed.origin;
5578
+ }
5579
+ function generateBccaeNonce() {
5580
+ const bytes = new Uint8Array(32);
5581
+ globalThis.crypto.getRandomValues(bytes);
5582
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
5583
+ }
5584
+ var BCCAEClient = class {
5585
+ apiKey;
5586
+ baseUrl;
5587
+ timeoutMs;
5588
+ fetchImpl;
5589
+ constructor(options) {
5590
+ if (!options.apiKey || typeof options.apiKey !== "string") {
5591
+ throw new AtlaSentError("BCCAEClient: apiKey is required", {
5592
+ code: "invalid_api_key"
5593
+ });
5594
+ }
5595
+ this.apiKey = options.apiKey;
5596
+ this.baseUrl = enforceTls(options.baseUrl ?? DEFAULT_BASE_URL2);
5597
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
5598
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
5599
+ }
5600
+ async evaluate(input) {
5601
+ const { body } = await this.post(
5602
+ "/v1/bccae/evaluations",
5603
+ input
5604
+ );
5605
+ return body;
5606
+ }
5607
+ async execute(input) {
5608
+ const { body } = await this.post(
5609
+ "/v1/bccae/execute",
5610
+ input
5611
+ );
5612
+ return body;
5613
+ }
5614
+ async revoke(input) {
5615
+ const { body } = await this.post(
5616
+ "/v1/bccae/revocations",
5617
+ input
5618
+ );
5619
+ return body;
5620
+ }
5621
+ async getEvidence(evidenceId) {
5622
+ if (!evidenceId || typeof evidenceId !== "string") {
5623
+ throw new AtlaSentError("BCCAEClient: evidenceId is required", {
5624
+ code: "bad_request"
5625
+ });
5626
+ }
5627
+ const { body } = await this.get(
5628
+ `/v1/bccae/evidence/${encodeURIComponent(evidenceId)}`
5629
+ );
5630
+ return body;
5631
+ }
5632
+ // ── HTTP primitives ─────────────────────────────────────────────────────────
5633
+ async post(path, body) {
5634
+ return this.request(path, "POST", body);
5635
+ }
5636
+ async get(path) {
5637
+ return this.request(path, "GET", void 0);
5638
+ }
5639
+ async request(path, method, body) {
5640
+ const url = `${this.baseUrl}${path}`;
5641
+ const headers = {
5642
+ Accept: "application/json",
5643
+ Authorization: `Bearer ${this.apiKey}`,
5644
+ "User-Agent": "atlasent-bccae-client/1.0"
5645
+ };
5646
+ if (method === "POST") headers["Content-Type"] = "application/json";
5647
+ let response;
5648
+ try {
5649
+ response = await this.fetchImpl(url, {
5650
+ method,
5651
+ headers,
5652
+ signal: AbortSignal.timeout(this.timeoutMs),
5653
+ ...method === "POST" ? { body: JSON.stringify(body) } : {}
5654
+ });
5655
+ } catch (err) {
5656
+ throw new AtlaSentError(
5657
+ `BCCAEClient: network error on ${method} ${path}: ${err instanceof Error ? err.message : String(err)}`,
5658
+ { code: "network" }
5659
+ );
5660
+ }
5661
+ let responseBody;
5662
+ try {
5663
+ responseBody = await response.json();
5664
+ } catch {
5665
+ throw new AtlaSentError(
5666
+ `BCCAEClient: non-JSON response (status ${response.status}) from ${method} ${path}`,
5667
+ { code: "network" }
5668
+ );
5669
+ }
5670
+ if (!response.ok) {
5671
+ const err = responseBody;
5672
+ const message = typeof err?.message === "string" ? err.message : `BCCAE request failed with status ${response.status}`;
5673
+ const code = response.status === 401 ? "invalid_api_key" : response.status === 403 ? "forbidden" : response.status === 429 ? "rate_limited" : response.status >= 500 ? "server_error" : "network";
5674
+ throw new AtlaSentError(message, { code });
5675
+ }
5676
+ return { body: responseBody };
5677
+ }
5678
+ };
5679
+
5680
+ // src/governanceAgents.ts
5681
+ var SEVERITY_RANK = {
5682
+ info: 1,
5683
+ low: 2,
5684
+ medium: 3,
5685
+ high: 4,
5686
+ blocker: 5
5687
+ };
5688
+ function highestAgentFindingSeverity(findings) {
5689
+ let best = null;
5690
+ let rank = 0;
5691
+ for (const f of findings) {
5692
+ const r = SEVERITY_RANK[f.severity];
5693
+ if (r > rank) {
5694
+ rank = r;
5695
+ best = f.severity;
5696
+ }
5697
+ }
5698
+ return best;
5699
+ }
5700
+
3562
5701
  // src/index.ts
3563
5702
  var atlasent = {
3564
5703
  protect,
@@ -3581,13 +5720,18 @@ var index_default = atlasent;
3581
5720
  AtlaSentDeniedError,
3582
5721
  AtlaSentError,
3583
5722
  AtlaSentEscalateError,
5723
+ BCCAEClient,
3584
5724
  DEFAULT_INCENTIVE_CONFIG,
5725
+ DEFAULT_REDACTION_RULES,
3585
5726
  DEFAULT_RETRY_POLICY,
3586
5727
  DEFAULT_RISK_TIER_THRESHOLDS,
3587
5728
  DEPLOYMENT_PRODUCTION_ACTION,
3588
5729
  DEPLOY_GATE_CODES,
5730
+ EscalationDeniedError,
5731
+ EscalationTimeoutError,
3589
5732
  FeatureNotEnabledError,
3590
5733
  GovernanceEnforcementError,
5734
+ NOT_APPLICABLE,
3591
5735
  PRODUCTION_DEPLOY_ACTION,
3592
5736
  PermitRevoked,
3593
5737
  StreamParseError,
@@ -3602,6 +5746,9 @@ var index_default = atlasent;
3602
5746
  assertWebhook,
3603
5747
  authorizeStream,
3604
5748
  budgetUtilizationSeverity,
5749
+ buildActionContext,
5750
+ buildClaimEvidenceLink,
5751
+ buildClaimEvidenceLinkFromActionBundle,
3605
5752
  buildLiabilityChain,
3606
5753
  buildLiabilityVisualization,
3607
5754
  buildRiskTimeline,
@@ -3610,9 +5757,11 @@ var index_default = atlasent;
3610
5757
  canonicalizeForEvidence,
3611
5758
  checkAutonomousBounds,
3612
5759
  checkBudgetConstraints,
5760
+ checkIntegrationHealth,
3613
5761
  clampTokenDuration,
3614
5762
  classifyCommand,
3615
5763
  classifyRiskTier,
5764
+ classifyToolRisk,
3616
5765
  computeApprovalRiskScore,
3617
5766
  computeBackoffMs,
3618
5767
  computeEscalatedApprovalCount,
@@ -3625,6 +5774,10 @@ var index_default = atlasent;
3625
5774
  computeRemediationUrgency,
3626
5775
  computeSignalEngagementRate,
3627
5776
  configure,
5777
+ configureApprovalRuntime,
5778
+ configureControlSurface,
5779
+ configureShadow,
5780
+ createEscalation,
3628
5781
  delegationPropagationHadEffect,
3629
5782
  deployGate,
3630
5783
  detectAutonomousAnomaly,
@@ -3638,10 +5791,15 @@ var index_default = atlasent;
3638
5791
  evaluateMany,
3639
5792
  evidenceRunPasses,
3640
5793
  findPrimaryLiabilityParties,
5794
+ flattenActionContext,
3641
5795
  formatPolicySyncDiff,
5796
+ generateBccaeNonce,
5797
+ getEnforcementStatus,
5798
+ getOrgSummary,
3642
5799
  graphql,
3643
5800
  hasAttemptsLeft,
3644
5801
  hhiToConcentrationScore,
5802
+ highestAgentFindingSeverity,
3645
5803
  highestSeverityAction,
3646
5804
  hitlRequiredApproverCount,
3647
5805
  isBudgetExceptionActive,
@@ -3661,6 +5819,17 @@ var index_default = atlasent;
3661
5819
  normalizeEvaluateResponse,
3662
5820
  normalizePermitOutcome,
3663
5821
  protect,
5822
+ protectCloseAction,
5823
+ protectDeploy,
5824
+ protectOrEscalate,
5825
+ protectPaymentRelease,
5826
+ protectShadow,
5827
+ protectToolCall,
5828
+ protectWithEvidence,
5829
+ redactContext,
5830
+ reportProtectedAction,
5831
+ reportShadowEvent,
5832
+ requestOverride,
3664
5833
  requirePermit,
3665
5834
  scoreToRiskTier,
3666
5835
  serializeSignableContent,
@@ -3668,12 +5837,15 @@ var index_default = atlasent;
3668
5837
  summarizeCrossOrgPermission,
3669
5838
  transitionDispute,
3670
5839
  transitionReversal,
5840
+ validateActionContext,
3671
5841
  validateLiabilityChain,
3672
5842
  verifyAuditBundle,
3673
5843
  verifyBundle,
5844
+ verifyClaimEvidenceLink,
3674
5845
  verifyEvidenceBundleStructure,
3675
5846
  verifyWebhook,
3676
5847
  verifyWebhookSignature,
5848
+ waitForEscalationApproval,
3677
5849
  withPermit,
3678
5850
  withinAutonomousCeiling
3679
5851
  });