@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.js CHANGED
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/errors.ts
2
9
  var StreamTimeoutError = class extends Error {
3
10
  name = "StreamTimeoutError";
@@ -163,9 +170,8 @@ function normalizeEvaluateRequest(input) {
163
170
  action_type: legacy.action,
164
171
  actor_id: legacy.agent
165
172
  };
166
- if (legacy.context !== void 0) {
167
- normalized.context = legacy.context;
168
- }
173
+ if (legacy.context !== void 0) normalized.context = legacy.context;
174
+ if (legacy.explain !== void 0) normalized.explain = legacy.explain;
169
175
  return normalized;
170
176
  }
171
177
  return input;
@@ -353,6 +359,7 @@ var AtlaSentClient = class {
353
359
  actor_id: normalized.actor_id,
354
360
  context: normalized.context ?? {}
355
361
  };
362
+ if (normalized.explain !== void 0) body.explain = normalized.explain;
356
363
  const { body: wire, rateLimit } = await this.post(
357
364
  "/v1-evaluate",
358
365
  body
@@ -389,9 +396,211 @@ var AtlaSentClient = class {
389
396
  reason,
390
397
  auditHash: wire.audit_hash ?? "",
391
398
  timestamp: wire.timestamp ?? "",
399
+ rateLimit,
400
+ ...wire.risk_envelope && {
401
+ riskEnvelope: {
402
+ weightedScore: wire.risk_envelope.weighted_score,
403
+ engineDecision: wire.risk_envelope.engine_decision,
404
+ envelopeDecision: wire.risk_envelope.envelope_decision,
405
+ promoted: wire.risk_envelope.promoted,
406
+ hardBlocks: wire.risk_envelope.hard_blocks ?? [],
407
+ ...wire.risk_envelope.factors && { factors: wire.risk_envelope.factors }
408
+ }
409
+ }
410
+ };
411
+ }
412
+ /**
413
+ * Batch evaluate — send up to 100 decisions in a single round-trip.
414
+ *
415
+ * Wraps `POST /v1-evaluate-batch`. The server evaluates each item
416
+ * against the active policy bundle and returns results in the same
417
+ * order as the input. One rate-limit token is consumed for the
418
+ * whole batch, and one audit-chain entry lists every included
419
+ * decision id.
420
+ *
421
+ * A per-item policy `deny` is **not** thrown — it appears as
422
+ * `item.decision === "deny"` in the returned items. A whole-batch
423
+ * network error, 4xx, or 5xx throws {@link AtlaSentError}.
424
+ *
425
+ * Requires the `v2_batch` tenant feature flag to be enabled on the
426
+ * org (returns 404 when off). Requires scope `evaluate:write`.
427
+ *
428
+ * @param requests - 1–100 evaluate items.
429
+ * @param batchId - Optional caller-supplied UUID for idempotency.
430
+ * A retried call with the same `batchId` and identical items
431
+ * returns the cached response within 24 h (`replayed: true`).
432
+ */
433
+ async evaluateBatch(requests, batchId) {
434
+ if (!Array.isArray(requests) || requests.length === 0) {
435
+ throw new AtlaSentError(
436
+ "evaluateBatch: requests must be a non-empty array",
437
+ { code: "bad_request" }
438
+ );
439
+ }
440
+ if (requests.length > 100) {
441
+ throw new AtlaSentError(
442
+ `evaluateBatch: requests.length ${requests.length} exceeds the 100-item cap`,
443
+ { code: "bad_request" }
444
+ );
445
+ }
446
+ const wireItems = requests.map((r) => ({
447
+ action_type: r.action,
448
+ actor_id: r.agent,
449
+ context: r.context ?? {}
450
+ }));
451
+ const wireBody = { items: wireItems };
452
+ if (batchId) wireBody.batch_id = batchId;
453
+ const { body: wire, rateLimit } = await this.post(
454
+ "/v1-evaluate-batch",
455
+ wireBody
456
+ );
457
+ const items = (wire.items ?? []).map(
458
+ (item) => {
459
+ const rawDecision = typeof item.decision === "string" ? item.decision.toLowerCase() : void 0;
460
+ const decision = rawDecision === "allow" || rawDecision === "deny" || rawDecision === "hold" || rawDecision === "escalate" ? rawDecision : void 0;
461
+ return {
462
+ index: item.index,
463
+ ...decision !== void 0 ? { decision } : {},
464
+ ...item.decision_id ? { decisionId: item.decision_id } : {},
465
+ ...item.permit_token != null ? { permitToken: item.permit_token } : {},
466
+ ...item.reason != null ? { reason: item.reason } : {},
467
+ ...item.audit_entry_hash ? { auditHash: item.audit_entry_hash } : {},
468
+ ...item.timestamp ? { timestamp: item.timestamp } : {},
469
+ ...item.error ? { error: item.error } : {},
470
+ ...item.message ? { message: item.message } : {}
471
+ };
472
+ }
473
+ );
474
+ return {
475
+ batchId: wire.batch_id,
476
+ items,
477
+ partial: wire.partial ?? false,
478
+ ...wire.replayed ? { replayed: wire.replayed } : {},
392
479
  rateLimit
393
480
  };
394
481
  }
482
+ /**
483
+ * Subscribe to a live stream of decisions for this org.
484
+ *
485
+ * Wraps `GET /v1-decisions-stream`. The server emits one SSE frame
486
+ * per audit event and sends a heartbeat every 15 s. The session
487
+ * auto-closes after `maxSeconds` (default 30 min); reconnect with
488
+ * the last received `event.id` to resume without replaying history.
489
+ *
490
+ * ```ts
491
+ * const controller = new AbortController();
492
+ * for await (const event of client.subscribeDecisions({ signal: controller.signal })) {
493
+ * if (event.type === "heartbeat") continue;
494
+ * console.log(event.type, event.decision, event.actorId);
495
+ * if (event.type === "session_end") break; // reconnect
496
+ * }
497
+ * ```
498
+ *
499
+ * Requires scope `audit:read`. Requires the `v2_decisions_stream`
500
+ * tenant feature flag (returns 404 when off).
501
+ */
502
+ async *subscribeDecisions(opts = {}) {
503
+ const url = new URL(`${this.baseUrl}/v1-decisions-stream`);
504
+ if (opts.types?.length) url.searchParams.set("types", opts.types.join(","));
505
+ if (opts.actorId) url.searchParams.set("actor_id", opts.actorId);
506
+ if (opts.maxSeconds !== void 0) url.searchParams.set("max_seconds", String(opts.maxSeconds));
507
+ const headers = {
508
+ Accept: "text/event-stream",
509
+ Authorization: `Bearer ${this.apiKey}`,
510
+ "User-Agent": this.userAgent,
511
+ // ADR-025: declare the wire-protocol version we were built
512
+ // against. Runtime serves this version's response shape; older
513
+ // versions outside the compatibility window get 426.
514
+ "X-AtlaSent-Protocol-Version": "1"
515
+ };
516
+ if (opts.lastEventId) headers["Last-Event-ID"] = opts.lastEventId;
517
+ let response;
518
+ try {
519
+ response = await this.fetchImpl(url.toString(), {
520
+ method: "GET",
521
+ headers,
522
+ ...opts.signal ? { signal: opts.signal } : {}
523
+ });
524
+ } catch (err) {
525
+ if (err instanceof Error && err.name === "AbortError") return;
526
+ throw new AtlaSentError(
527
+ `Failed to connect to decisions stream: ${err instanceof Error ? err.message : String(err)}`,
528
+ { code: "network" }
529
+ );
530
+ }
531
+ if (!response.ok) {
532
+ const code = response.status === 401 ? "invalid_api_key" : "server_error";
533
+ throw new AtlaSentError(
534
+ `Decisions stream returned ${response.status}`,
535
+ { code, status: response.status }
536
+ );
537
+ }
538
+ if (!response.body) {
539
+ throw new AtlaSentError("Decisions stream response has no body", { code: "bad_response" });
540
+ }
541
+ const reader = response.body.getReader();
542
+ const decoder = new TextDecoder("utf-8");
543
+ let buf = "";
544
+ try {
545
+ while (true) {
546
+ let chunk;
547
+ try {
548
+ chunk = await reader.read();
549
+ } catch (err) {
550
+ if (err instanceof Error && err.name === "AbortError") return;
551
+ throw new AtlaSentError(
552
+ `Decisions stream read error: ${err instanceof Error ? err.message : String(err)}`,
553
+ { code: "network" }
554
+ );
555
+ }
556
+ if (chunk.done) break;
557
+ buf += decoder.decode(chunk.value, { stream: true });
558
+ const rawBlocks = buf.split("\n\n");
559
+ buf = rawBlocks.pop() ?? "";
560
+ for (const block of rawBlocks) {
561
+ if (!block.trim()) continue;
562
+ if (block.trimStart().startsWith(":")) {
563
+ yield { type: "heartbeat" };
564
+ continue;
565
+ }
566
+ let id;
567
+ let eventType = "audit_event";
568
+ let dataLine = "";
569
+ for (const line of block.split("\n")) {
570
+ if (line.startsWith("id:")) id = line.slice(3).trim();
571
+ else if (line.startsWith("event:")) eventType = line.slice(6).trim();
572
+ else if (line.startsWith("data:")) dataLine = line.slice(5).trim();
573
+ }
574
+ if (!dataLine) continue;
575
+ let parsed;
576
+ try {
577
+ parsed = JSON.parse(dataLine);
578
+ } catch {
579
+ continue;
580
+ }
581
+ if (eventType === "session_end") {
582
+ yield { ...id !== void 0 ? { id } : {}, type: "session_end", payload: parsed };
583
+ return;
584
+ }
585
+ const decision = typeof parsed.decision === "string" ? parsed.decision.toLowerCase() : void 0;
586
+ yield {
587
+ ...id !== void 0 ? { id } : {},
588
+ type: eventType,
589
+ ...decision ? { decision } : {},
590
+ ...typeof parsed.actor_id === "string" ? { actorId: parsed.actor_id } : {},
591
+ ...typeof parsed.resource_type === "string" ? { resourceType: parsed.resource_type } : {},
592
+ ...typeof parsed.resource_id === "string" ? { resourceId: parsed.resource_id } : {},
593
+ ...parsed.payload && typeof parsed.payload === "object" ? { payload: parsed.payload } : {},
594
+ ...typeof parsed.hash === "string" ? { hash: parsed.hash } : {},
595
+ ...typeof parsed.previous_hash === "string" ? { previousHash: parsed.previous_hash } : {},
596
+ ...typeof parsed.occurred_at === "string" ? { occurredAt: parsed.occurred_at } : {}
597
+ };
598
+ }
599
+ }
600
+ } finally {
601
+ reader.releaseLock();
602
+ }
603
+ }
395
604
  /**
396
605
  * Pre-flight evaluation that always returns the constraint trace.
397
606
  *
@@ -458,7 +667,17 @@ var AtlaSentClient = class {
458
667
  reason,
459
668
  auditHash: wire.audit_hash ?? "",
460
669
  timestamp: wire.timestamp ?? "",
461
- rateLimit
670
+ rateLimit,
671
+ ...wire.risk_envelope && {
672
+ riskEnvelope: {
673
+ weightedScore: wire.risk_envelope.weighted_score,
674
+ engineDecision: wire.risk_envelope.engine_decision,
675
+ envelopeDecision: wire.risk_envelope.envelope_decision,
676
+ promoted: wire.risk_envelope.promoted,
677
+ hardBlocks: wire.risk_envelope.hard_blocks ?? [],
678
+ ...wire.risk_envelope.factors && { factors: wire.risk_envelope.factors }
679
+ }
680
+ }
462
681
  };
463
682
  let constraintTrace = null;
464
683
  if (wire.constraint_trace !== void 0 && wire.constraint_trace !== null && typeof wire.constraint_trace === "object") {
@@ -507,6 +726,7 @@ var AtlaSentClient = class {
507
726
  outcome: wire.outcome ?? "",
508
727
  permitHash: wire.permit_hash ?? "",
509
728
  timestamp: wire.timestamp ?? "",
729
+ expiresAt: wire.expires_at ?? null,
510
730
  rateLimit
511
731
  };
512
732
  }
@@ -828,6 +1048,151 @@ var AtlaSentClient = class {
828
1048
  }
829
1049
  return { ...wire, rateLimit };
830
1050
  }
1051
+ /**
1052
+ * Re-evaluate a recorded decision against its originally-pinned policy
1053
+ * bundle and engine version, and report whether the result agrees with
1054
+ * what was recorded.
1055
+ *
1056
+ * Wraps `POST /v1-decisions-replay/:id/replay`. **Side-effect-free** — no
1057
+ * audit chain row is written and no permit is issued (per ADR-016).
1058
+ * Useful for compliance review, regression testing of bundle changes,
1059
+ * and post-incident investigation.
1060
+ *
1061
+ * Outcomes encoded in the response:
1062
+ * - `variance: "NONE"` — replay agrees with the original decision.
1063
+ * - `variance: "DECISION_CHANGED"` — same envelope, same bundle, different
1064
+ * decision. Almost always indicates non-determinism in a rule
1065
+ * (e.g. wall-clock comparison) and warrants investigation.
1066
+ * - `variance: "ENVELOPE_DRIFT"` — the recorded request envelope no longer
1067
+ * hashes to the recorded value. The replay short-circuits without
1068
+ * running the engine; `replay_decision` is absent. Treat as evidence
1069
+ * of substrate tamper or a recorder bug.
1070
+ *
1071
+ * Server-side 409 responses (replay refused because the engine version
1072
+ * does not accept replay, or because no bundle was pinned) surface as
1073
+ * `AtlaSentError` with `code: "replay_not_eligible"` — callers should
1074
+ * treat them as expected for old / un-pinned decisions, not as bugs.
1075
+ *
1076
+ * Requires the `evaluate:write` API key scope.
1077
+ *
1078
+ * @param decisionId The UUID of the recorded decision to replay.
1079
+ * Matches `execution_evaluations.request_id`.
1080
+ *
1081
+ * @example
1082
+ * ```ts
1083
+ * const result = await client.replayDecision("dec_abc123");
1084
+ * if (result.variance === "DECISION_CHANGED") {
1085
+ * console.warn(
1086
+ * `Decision ${result.decision_id} changed on replay: ` +
1087
+ * `${result.original_decision} → ${result.replay_decision}`,
1088
+ * );
1089
+ * }
1090
+ * ```
1091
+ */
1092
+ async replayDecision(decisionId) {
1093
+ if (typeof decisionId !== "string" || decisionId.length === 0) {
1094
+ throw new AtlaSentError("decisionId is required", {
1095
+ code: "bad_request"
1096
+ });
1097
+ }
1098
+ const path = `/v1-decisions-replay/${encodeURIComponent(decisionId)}/replay`;
1099
+ const { body: wire, rateLimit } = await this.post(
1100
+ path,
1101
+ {}
1102
+ );
1103
+ 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") {
1104
+ throw new AtlaSentError(
1105
+ "Malformed response from /v1-decisions-replay/:id/replay: missing required fields",
1106
+ { code: "bad_response" }
1107
+ );
1108
+ }
1109
+ return { ...wire, rateLimit };
1110
+ }
1111
+ /**
1112
+ * ADR-015 Phase C — SDK-canonical replay runtime.
1113
+ *
1114
+ * Re-evaluates a recorded decision against its originally-pinned policy
1115
+ * bundle and engine version via `POST /v1/decisions/:id/replay`.
1116
+ * Side-effect-free server-side: no audit chain row is written and no
1117
+ * permit is issued (ADR-016 `mode: "replay"` sentinel).
1118
+ *
1119
+ * Differences from {@link replayDecision} (the 2.7.0 raw-wire surface):
1120
+ *
1121
+ * | | `replayDecision()` | `replay()` |
1122
+ * | --- | --- | --- |
1123
+ * | Path | `/v1-decisions-replay/:id/replay` | `/v1/decisions/:id/replay` |
1124
+ * | Variance | raw wire (`DECISION_CHANGED`) | SDK-canonical (`POLICY_DRIFT`) |
1125
+ * | 409 handling | throws `AtlaSentError` | returns `ENGINE_DRIFT` / `BUNDLE_MISSING` |
1126
+ * | Input shape | `decisionId: string` | `{ evaluationId }` |
1127
+ *
1128
+ * **Never throws on `409 replay_not_eligible`** — instead returns a
1129
+ * `ReplayResponse` with `varianceKind: "ENGINE_DRIFT"` (engine retired
1130
+ * beyond archival window) or `"BUNDLE_MISSING"` (no bundle pinned on
1131
+ * the original evaluation). Callers can always `switch` on
1132
+ * `result.varianceKind` without a try/catch.
1133
+ *
1134
+ * Fix-forward note: this method was originally landed in PR #275 but
1135
+ * dropped from the squash merge. The TS types (`ReplayResponse`,
1136
+ * `ReplayRequest`) and CHANGELOG made it through; the method itself
1137
+ * did not. Restored here to match the Python {@link
1138
+ * AtlaSentClient}.replay() that landed in atlasent-sdk@2.6.0 (Python).
1139
+ */
1140
+ async replay(input) {
1141
+ if (!input || typeof input.evaluationId !== "string" || input.evaluationId.length === 0) {
1142
+ throw new AtlaSentError("evaluationId is required", {
1143
+ code: "bad_request"
1144
+ });
1145
+ }
1146
+ const path = `/v1/decisions/${encodeURIComponent(input.evaluationId)}/replay`;
1147
+ let wire;
1148
+ let rateLimit;
1149
+ try {
1150
+ const result = await this.post(path, {});
1151
+ wire = result.body;
1152
+ rateLimit = result.rateLimit;
1153
+ } catch (err) {
1154
+ if (err instanceof AtlaSentError && err.status === 409) {
1155
+ const msg = (err.message ?? "").toLowerCase();
1156
+ const varianceKind2 = msg.includes("bundle") ? "BUNDLE_MISSING" : "ENGINE_DRIFT";
1157
+ return {
1158
+ decisionId: input.evaluationId,
1159
+ varianceKind: varianceKind2,
1160
+ originalDecision: "deny",
1161
+ acceptsReplay: false,
1162
+ replayedAt: (/* @__PURE__ */ new Date()).toISOString(),
1163
+ rateLimit: null
1164
+ };
1165
+ }
1166
+ throw err;
1167
+ }
1168
+ const VARIANCE_MAP = {
1169
+ NONE: "NONE",
1170
+ DECISION_CHANGED: "POLICY_DRIFT",
1171
+ ENVELOPE_DRIFT: "ENVELOPE_DRIFT",
1172
+ CHAIN_TAMPER: "CHAIN_TAMPER",
1173
+ BUNDLE_MISSING: "BUNDLE_MISSING",
1174
+ ENGINE_DRIFT: "ENGINE_DRIFT"
1175
+ };
1176
+ const rawVariance = typeof wire.variance === "string" ? wire.variance : "";
1177
+ const varianceKind = VARIANCE_MAP[rawVariance] ?? "NONE";
1178
+ const replayDec = typeof wire.replay_decision === "string" ? wire.replay_decision.toLowerCase() : void 0;
1179
+ const originalDec = typeof wire.original_decision === "string" ? wire.original_decision.toLowerCase() : "deny";
1180
+ const response = {
1181
+ decisionId: typeof wire.decision_id === "string" ? wire.decision_id : input.evaluationId,
1182
+ varianceKind,
1183
+ originalDecision: originalDec,
1184
+ acceptsReplay: typeof wire.accepts_replay === "boolean" ? wire.accepts_replay : true,
1185
+ replayedAt: typeof wire.replayed_at === "string" ? wire.replayed_at : (/* @__PURE__ */ new Date()).toISOString(),
1186
+ rateLimit
1187
+ };
1188
+ if (typeof wire.original_deny_code === "string") response.originalDenyCode = wire.original_deny_code;
1189
+ if (replayDec !== void 0) response.replayedDecision = replayDec;
1190
+ if (typeof wire.replay_deny_code === "string") response.replayedDenyCode = wire.replay_deny_code;
1191
+ if (typeof wire.engine_version === "string") response.engineVersion = wire.engine_version;
1192
+ if (typeof wire.engine_version_kind === "string") response.engineVersionKind = wire.engine_version_kind;
1193
+ if (typeof wire.envelope_verification === "string") response.envelopeVerification = wire.envelope_verification;
1194
+ return response;
1195
+ }
831
1196
  /**
832
1197
  * Open a streaming evaluation session against `POST /v1-evaluate-stream`.
833
1198
  *
@@ -876,6 +1241,8 @@ var AtlaSentClient = class {
876
1241
  "Content-Type": "application/json",
877
1242
  Authorization: `Bearer ${this.apiKey}`,
878
1243
  "User-Agent": this.userAgent,
1244
+ // ADR-025: wire-protocol version declared on every request.
1245
+ "X-AtlaSent-Protocol-Version": "1",
879
1246
  "X-Request-ID": requestId
880
1247
  };
881
1248
  if (lastEventId !== void 0) {
@@ -963,7 +1330,9 @@ var AtlaSentClient = class {
963
1330
  Accept: "application/json",
964
1331
  Authorization: `Bearer ${this.apiKey}`,
965
1332
  "User-Agent": this.userAgent,
966
- "X-Request-ID": requestId
1333
+ "X-Request-ID": requestId,
1334
+ // ADR-025: wire-protocol version declared on every request.
1335
+ "X-AtlaSent-Protocol-Version": "1"
967
1336
  };
968
1337
  if (method === "POST") headers["Content-Type"] = "application/json";
969
1338
  const bodyStr = method === "POST" ? JSON.stringify(body) : void 0;
@@ -1579,6 +1948,69 @@ var AtlaSentClient = class {
1579
1948
  );
1580
1949
  return body;
1581
1950
  }
1951
+ // ── Constrained governance agents (read surface) ──────────────────────────
1952
+ //
1953
+ // Three GETs onto the v1-governance-agents edge function. Doctrine:
1954
+ // findings produced by these endpoints are advisory signal, never
1955
+ // authority. There is no `runGovernanceAgent` method on this client —
1956
+ // invocation belongs in CI (atlasent-action `governance-agents` mode),
1957
+ // not in application code.
1958
+ /**
1959
+ * List the advisory governance-agent registry for the calling org.
1960
+ *
1961
+ * Calls `GET /v1/governance/agents`. The registry is reference data
1962
+ * seeded at runtime-DB migration time; every row has
1963
+ * `authority_class = "advisory"` and `can_authorize = false` —
1964
+ * structural invariants enforced by the schema, not policy.
1965
+ */
1966
+ async listGovernanceAgents() {
1967
+ const { body } = await this.get(
1968
+ "/v1/governance/agents"
1969
+ );
1970
+ return [...body.agents ?? []];
1971
+ }
1972
+ /**
1973
+ * List advisory findings emitted against one governed change.
1974
+ *
1975
+ * Calls `GET /v1/governance/findings?change_id=…[&agent_slug=…]`.
1976
+ * Returns the typed-finding rows in `created_at DESC` order, including
1977
+ * `routed_gate_id` when the finding→gate trigger linked them. Findings
1978
+ * with `can_authorize === false` (always) are advisory; rendering them
1979
+ * never satisfies a gate.
1980
+ */
1981
+ async listGovernanceFindings(query) {
1982
+ if (!query?.change_id) {
1983
+ throw new AtlaSentError("change_id is required", { code: "bad_request" });
1984
+ }
1985
+ const params = new URLSearchParams({ change_id: query.change_id });
1986
+ if (query.agent_slug) params.set("agent_slug", query.agent_slug);
1987
+ const { body } = await this.get(
1988
+ "/v1/governance/findings",
1989
+ params
1990
+ );
1991
+ return [...body.findings ?? []];
1992
+ }
1993
+ /**
1994
+ * List agent run records against one governed change.
1995
+ *
1996
+ * Calls `GET /v1/governance/evaluations?change_id=…[&agent_slug=…]`.
1997
+ * Returns every persisted evaluation, including `failed` / `timeout`
1998
+ * runs and `completed` runs with zero findings — the latter is the
1999
+ * positive signal "the agent ran and found nothing", which the UI
2000
+ * surfaces as `clear`.
2001
+ */
2002
+ async listGovernanceEvaluations(query) {
2003
+ if (!query?.change_id) {
2004
+ throw new AtlaSentError("change_id is required", { code: "bad_request" });
2005
+ }
2006
+ const params = new URLSearchParams({ change_id: query.change_id });
2007
+ if (query.agent_slug) params.set("agent_slug", query.agent_slug);
2008
+ const { body } = await this.get(
2009
+ "/v1/governance/evaluations",
2010
+ params
2011
+ );
2012
+ return [...body.evaluations ?? []];
2013
+ }
1582
2014
  };
1583
2015
  function parseRateLimitHeaders(headers) {
1584
2016
  const rawLimit = headers.get("x-ratelimit-limit");
@@ -2005,6 +2437,174 @@ async function verifyBundle(pathOrBundle, options) {
2005
2437
  return verifyAuditBundle(bundle, keys);
2006
2438
  }
2007
2439
 
2440
+ // src/evidenceEngine.ts
2441
+ function buildWhyTrace(decision, reasons, trace) {
2442
+ if (!trace) {
2443
+ return {
2444
+ decision,
2445
+ summary: formatSummary(decision, reasons, void 0, void 0),
2446
+ policy_evaluations: [],
2447
+ total_stages_evaluated: 0
2448
+ };
2449
+ }
2450
+ const matchedPolicyId = typeof trace.matching_policy_id === "string" ? trace.matching_policy_id : void 0;
2451
+ let terminalStage;
2452
+ let totalStages = 0;
2453
+ const policyEvaluations = (trace.rules_evaluated ?? []).map((policy) => {
2454
+ const wasDecisive = matchedPolicyId === policy.policy_id;
2455
+ let foundTerminal = false;
2456
+ const stages = (policy.stages ?? []).map(
2457
+ (s, idx) => {
2458
+ totalStages++;
2459
+ const isLast = idx === (policy.stages?.length ?? 1) - 1;
2460
+ const candidateForTerminal = wasDecisive && !foundTerminal && (s.matched || isLast);
2461
+ let impact = "passing";
2462
+ if (candidateForTerminal) {
2463
+ impact = "terminal";
2464
+ foundTerminal = true;
2465
+ terminalStage = {
2466
+ stage: s.stage,
2467
+ ...s.rule !== void 0 ? { rule: s.rule } : {},
2468
+ matched: s.matched,
2469
+ ...s.detail !== void 0 ? { detail: s.detail } : {},
2470
+ impact: "terminal"
2471
+ };
2472
+ } else if (s.matched) {
2473
+ impact = "contributing";
2474
+ }
2475
+ return {
2476
+ stage: s.stage,
2477
+ ...s.rule !== void 0 ? { rule: s.rule } : {},
2478
+ matched: s.matched,
2479
+ ...s.detail !== void 0 ? { detail: s.detail } : {},
2480
+ impact
2481
+ };
2482
+ }
2483
+ );
2484
+ return {
2485
+ policy_id: policy.policy_id,
2486
+ decision: policy.decision,
2487
+ fingerprint: policy.fingerprint,
2488
+ ...policy.risk_score !== void 0 ? { risk_score: policy.risk_score } : {},
2489
+ stages,
2490
+ was_decisive: wasDecisive
2491
+ };
2492
+ });
2493
+ return {
2494
+ decision,
2495
+ summary: formatSummary(decision, reasons, matchedPolicyId, terminalStage),
2496
+ ...matchedPolicyId !== void 0 ? { matched_policy_id: matchedPolicyId } : {},
2497
+ policy_evaluations: policyEvaluations,
2498
+ ...terminalStage !== void 0 ? { terminal_stage: terminalStage } : {},
2499
+ total_stages_evaluated: totalStages
2500
+ };
2501
+ }
2502
+ function formatSummary(decision, reasons, matchedPolicyId, terminalStage) {
2503
+ const reason0 = reasons.length > 0 ? reasons[0] : void 0;
2504
+ switch (decision) {
2505
+ case "allow":
2506
+ return reason0 ? `Allowed: ${reason0}` : "Allowed: all policy checks passed.";
2507
+ case "deny":
2508
+ if (reason0) return `Denied: ${reason0}`;
2509
+ if (terminalStage?.detail)
2510
+ return `Denied at stage "${terminalStage.stage}": ${terminalStage.detail}`;
2511
+ if (terminalStage)
2512
+ return `Denied at stage "${terminalStage.stage}".`;
2513
+ if (matchedPolicyId)
2514
+ return `Denied by policy ${matchedPolicyId}.`;
2515
+ return "Denied: policy check failed.";
2516
+ case "hold":
2517
+ return reason0 ? `Held for review: ${reason0}` : "Held pending human review.";
2518
+ case "escalate":
2519
+ return reason0 ? `Escalated: ${reason0}` : "Escalated to a human reviewer.";
2520
+ }
2521
+ }
2522
+ function sortedJSON(val) {
2523
+ if (val === null || val === void 0) return "null";
2524
+ if (typeof val === "number")
2525
+ return Number.isFinite(val) ? String(val) : "null";
2526
+ if (typeof val === "boolean") return val ? "true" : "false";
2527
+ if (typeof val === "string") return JSON.stringify(val);
2528
+ if (Array.isArray(val)) return "[" + val.map(sortedJSON).join(",") + "]";
2529
+ if (typeof val === "object") {
2530
+ const obj = val;
2531
+ return "{" + Object.keys(obj).sort().map((k) => JSON.stringify(k) + ":" + sortedJSON(obj[k])).join(",") + "}";
2532
+ }
2533
+ return "null";
2534
+ }
2535
+ function hexEncode(bytes) {
2536
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
2537
+ }
2538
+ async function sha256Hex2(input) {
2539
+ const bytes = new TextEncoder().encode(input);
2540
+ if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle?.digest) {
2541
+ const buf = await globalThis.crypto.subtle.digest("SHA-256", bytes);
2542
+ return hexEncode(new Uint8Array(buf));
2543
+ }
2544
+ try {
2545
+ const { createHash } = await import(
2546
+ /* @vite-ignore */
2547
+ /* webpackIgnore: true */
2548
+ "crypto"
2549
+ );
2550
+ return createHash("sha256").update(input, "utf8").digest("hex");
2551
+ } catch {
2552
+ return "";
2553
+ }
2554
+ }
2555
+ async function computeContextHash(context) {
2556
+ return sha256Hex2(sortedJSON(context));
2557
+ }
2558
+ function buildDecisionReceiptPayload(args) {
2559
+ return {
2560
+ receipt_id: args.receipt_id,
2561
+ evaluation_id: args.evaluation_id,
2562
+ org_id: args.org_id,
2563
+ decision: args.decision,
2564
+ action: args.action,
2565
+ actor: args.actor,
2566
+ resource_type: args.resource_type ?? null,
2567
+ resource_id: args.resource_id ?? null,
2568
+ reasons: Array.from(args.reasons),
2569
+ why_summary: args.why_summary,
2570
+ permit_id: args.permit_id ?? null,
2571
+ permit_hash: args.permit_hash ?? null,
2572
+ audit_hash: args.audit_hash,
2573
+ context_hash: args.context_hash,
2574
+ issued_at: args.issued_at,
2575
+ expires_at: args.expires_at ?? null
2576
+ };
2577
+ }
2578
+ function receiptSigningInput(payload) {
2579
+ return payload.receipt_id + "\n" + payload.issued_at + "\n" + JSON.stringify(payload);
2580
+ }
2581
+ async function signDecisionReceiptHmac(payload, secret) {
2582
+ const input = receiptSigningInput(payload);
2583
+ const keyBytes = new TextEncoder().encode(secret);
2584
+ const msgBytes = new TextEncoder().encode(input);
2585
+ if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle) {
2586
+ const key = await globalThis.crypto.subtle.importKey(
2587
+ "raw",
2588
+ keyBytes,
2589
+ { name: "HMAC", hash: "SHA-256" },
2590
+ false,
2591
+ ["sign"]
2592
+ );
2593
+ const sig = await globalThis.crypto.subtle.sign("HMAC", key, msgBytes);
2594
+ return hexEncode(new Uint8Array(sig));
2595
+ }
2596
+ try {
2597
+ const { createHmac: createHmac2 } = await import(
2598
+ /* @vite-ignore */
2599
+ /* webpackIgnore: true */
2600
+ "crypto"
2601
+ );
2602
+ return createHmac2("sha256", secret).update(input).digest("hex");
2603
+ } catch {
2604
+ return "";
2605
+ }
2606
+ }
2607
+
2008
2608
  // src/protect.ts
2009
2609
  var sharedClient = null;
2010
2610
  var overrides = {};
@@ -2035,6 +2635,7 @@ function getClient() {
2035
2635
  sharedClient = new AtlaSentClient(options);
2036
2636
  return sharedClient;
2037
2637
  }
2638
+ var ACTION_TYPE_RE = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
2038
2639
  function wireDecisionToDenied(serverDecision) {
2039
2640
  const lower = serverDecision.toLowerCase();
2040
2641
  if (lower === "hold" || lower === "escalate") return lower;
@@ -2072,7 +2673,19 @@ async function computeExecutionHash(payload) {
2072
2673
  return "";
2073
2674
  }
2074
2675
  }
2676
+ function generateReceiptId() {
2677
+ if (typeof globalThis !== "undefined" && typeof globalThis.crypto?.randomUUID === "function") {
2678
+ return globalThis.crypto.randomUUID();
2679
+ }
2680
+ return `rcpt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
2681
+ }
2075
2682
  async function protect(request) {
2683
+ if (!ACTION_TYPE_RE.test(request.action)) {
2684
+ throw new AtlaSentError(
2685
+ `action must be in dot-notation format (e.g. "production.deploy"). Got: ${JSON.stringify(request.action)}`,
2686
+ { code: "bad_request" }
2687
+ );
2688
+ }
2076
2689
  const client = getClient();
2077
2690
  const evaluation = await client.evaluate(request);
2078
2691
  if (evaluation.decision !== "allow") {
@@ -2083,12 +2696,71 @@ async function protect(request) {
2083
2696
  auditHash: evaluation.auditHash
2084
2697
  });
2085
2698
  }
2086
- const environment = request.context?.environment ?? (() => {
2087
- console.warn(
2088
- "[atlasent] environment not set on evaluate request \u2014 defaulting to 'production'. Set context.environment explicitly to suppress."
2699
+ const environment = request.context?.environment;
2700
+ if (!environment) {
2701
+ throw new AtlaSentError(
2702
+ 'context.environment is required. Pass the environment where this action executes (e.g. "production", "staging").',
2703
+ { code: "bad_request" }
2704
+ );
2705
+ }
2706
+ const evaluatePayload = {
2707
+ action_type: request.action,
2708
+ actor_id: request.agent,
2709
+ context: request.context ?? {}
2710
+ };
2711
+ const execution_hash = await computeExecutionHash(evaluatePayload);
2712
+ const verifyRequest = {
2713
+ permitId: evaluation.permitId,
2714
+ agent: request.agent,
2715
+ action: request.action,
2716
+ environment,
2717
+ ...execution_hash ? { execution_hash } : {}
2718
+ };
2719
+ if (request.context !== void 0) verifyRequest.context = request.context;
2720
+ const verification = await client.verifyPermit(verifyRequest);
2721
+ if (!verification.verified) {
2722
+ const outcome = normalizePermitOutcome(verification.outcome);
2723
+ throw new AtlaSentDeniedError({
2724
+ decision: "deny",
2725
+ evaluationId: evaluation.permitId,
2726
+ reason: `Permit failed verification (${verification.outcome})`,
2727
+ auditHash: evaluation.auditHash,
2728
+ ...outcome !== void 0 && { outcome }
2729
+ });
2730
+ }
2731
+ return {
2732
+ permitId: evaluation.permitId,
2733
+ permitHash: verification.permitHash,
2734
+ auditHash: evaluation.auditHash,
2735
+ reason: evaluation.reason,
2736
+ timestamp: verification.timestamp,
2737
+ permitExpiresAt: verification.expiresAt ?? null
2738
+ };
2739
+ }
2740
+ async function protectWithEvidence(request, opts = {}) {
2741
+ if (!ACTION_TYPE_RE.test(request.action)) {
2742
+ throw new AtlaSentError(
2743
+ `action must be in dot-notation format (e.g. "production.deploy"). Got: ${JSON.stringify(request.action)}`,
2744
+ { code: "bad_request" }
2745
+ );
2746
+ }
2747
+ const client = getClient();
2748
+ const evaluation = await client.evaluate(request);
2749
+ if (evaluation.decision !== "allow") {
2750
+ throw new AtlaSentDeniedError({
2751
+ decision: wireDecisionToDenied(evaluation.decision),
2752
+ evaluationId: evaluation.permitId,
2753
+ reason: evaluation.reason,
2754
+ auditHash: evaluation.auditHash
2755
+ });
2756
+ }
2757
+ const environment = request.context?.environment;
2758
+ if (!environment) {
2759
+ throw new AtlaSentError(
2760
+ 'context.environment is required. Pass the environment where this action executes (e.g. "production", "staging").',
2761
+ { code: "bad_request" }
2089
2762
  );
2090
- return "production";
2091
- })();
2763
+ }
2092
2764
  const evaluatePayload = {
2093
2765
  action_type: request.action,
2094
2766
  actor_id: request.agent,
@@ -2114,12 +2786,68 @@ async function protect(request) {
2114
2786
  ...outcome !== void 0 && { outcome }
2115
2787
  });
2116
2788
  }
2789
+ const contextHash = await computeContextHash(request.context ?? {});
2790
+ const whyTrace = buildWhyTrace(
2791
+ "allow",
2792
+ evaluation.reasons,
2793
+ opts.constraintTrace ?? null
2794
+ );
2795
+ const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
2796
+ const receiptId = generateReceiptId();
2797
+ const orgId = evaluation.permit?.orgId ?? "";
2798
+ const payload = buildDecisionReceiptPayload({
2799
+ receipt_id: receiptId,
2800
+ evaluation_id: evaluation.evaluationId,
2801
+ org_id: orgId,
2802
+ decision: "allow",
2803
+ action: request.action,
2804
+ actor: request.agent,
2805
+ resource_type: request.context?.resource_type ?? null,
2806
+ resource_id: request.context?.resource_id ?? null,
2807
+ reasons: evaluation.reasons,
2808
+ why_summary: whyTrace.summary,
2809
+ permit_id: evaluation.permitId,
2810
+ permit_hash: verification.permitHash,
2811
+ audit_hash: evaluation.auditHash,
2812
+ context_hash: contextHash,
2813
+ issued_at: issuedAt
2814
+ });
2815
+ let signature = null;
2816
+ let algorithm = "none";
2817
+ if (opts.signingSecret) {
2818
+ signature = await signDecisionReceiptHmac(payload, opts.signingSecret);
2819
+ algorithm = "hmac-sha256";
2820
+ }
2821
+ const receipt = {
2822
+ receipt_id: receiptId,
2823
+ evaluation_id: evaluation.evaluationId,
2824
+ org_id: orgId,
2825
+ decision: "allow",
2826
+ action: request.action,
2827
+ actor: request.agent,
2828
+ resource_type: request.context?.resource_type ?? null,
2829
+ resource_id: request.context?.resource_id ?? null,
2830
+ reasons: evaluation.reasons,
2831
+ why_trace: opts.constraintTrace !== void 0 ? whyTrace : null,
2832
+ permit_id: evaluation.permitId,
2833
+ permit_hash: verification.permitHash,
2834
+ audit_hash: evaluation.auditHash,
2835
+ context_hash: contextHash,
2836
+ issued_at: issuedAt,
2837
+ expires_at: null,
2838
+ algorithm,
2839
+ signature,
2840
+ signing_key_id: opts.signingKeyId ?? null,
2841
+ payload
2842
+ };
2117
2843
  return {
2118
2844
  permitId: evaluation.permitId,
2119
2845
  permitHash: verification.permitHash,
2120
2846
  auditHash: evaluation.auditHash,
2121
2847
  reason: evaluation.reason,
2122
- timestamp: verification.timestamp
2848
+ timestamp: verification.timestamp,
2849
+ permitExpiresAt: verification.expiresAt ?? null,
2850
+ receipt
2123
2851
  };
2124
2852
  }
2125
2853
 
@@ -3025,8 +3753,8 @@ async function _hmacSha256Hex(payload, secret) {
3025
3753
  const sig = await crypto.subtle.sign("HMAC", key, enc.encode(payload));
3026
3754
  return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
3027
3755
  }
3028
- const { createHmac } = await import("crypto");
3029
- return createHmac("sha256", secret).update(payload).digest("hex");
3756
+ const { createHmac: createHmac2 } = await import("crypto");
3757
+ return createHmac2("sha256", secret).update(payload).digest("hex");
3030
3758
  }
3031
3759
  async function verifyWebhook(payload, signature, secret) {
3032
3760
  try {
@@ -3424,6 +4152,1391 @@ async function safeJson(response, path, requestId) {
3424
4152
  }
3425
4153
  }
3426
4154
 
4155
+ // src/approvalRuntime.ts
4156
+ var _runtimeConfig = {};
4157
+ function configureApprovalRuntime(config) {
4158
+ _runtimeConfig = { ..._runtimeConfig, ...config };
4159
+ }
4160
+ function resolveConfig(overrides2) {
4161
+ const apiKey = overrides2?.apiKey ?? _runtimeConfig.apiKey ?? (typeof process !== "undefined" && process.env ? process.env["ATLASENT_API_KEY"] : void 0);
4162
+ if (!apiKey) {
4163
+ throw new AtlaSentError(
4164
+ "ApprovalRuntime: no API key configured. Set ATLASENT_API_KEY or call configureApprovalRuntime({ apiKey }).",
4165
+ { code: "invalid_api_key" }
4166
+ );
4167
+ }
4168
+ return {
4169
+ apiKey,
4170
+ baseUrl: overrides2?.baseUrl ?? _runtimeConfig.baseUrl ?? "https://api.atlasent.io",
4171
+ requestTimeoutMs: _runtimeConfig.timeoutMs ?? 3e4
4172
+ };
4173
+ }
4174
+ async function apiPost(path, body, cfg) {
4175
+ const url = `${cfg.baseUrl}${path}`;
4176
+ let resp;
4177
+ try {
4178
+ resp = await fetch(url, {
4179
+ method: "POST",
4180
+ headers: {
4181
+ Accept: "application/json",
4182
+ Authorization: `Bearer ${cfg.apiKey}`,
4183
+ "Content-Type": "application/json"
4184
+ },
4185
+ body: JSON.stringify(body),
4186
+ signal: AbortSignal.timeout(cfg.requestTimeoutMs)
4187
+ });
4188
+ } catch (err) {
4189
+ throw new AtlaSentError(
4190
+ `ApprovalRuntime: network error calling ${path}`,
4191
+ { code: "network", cause: err }
4192
+ );
4193
+ }
4194
+ if (!resp.ok) {
4195
+ const text = await resp.text().catch(() => "");
4196
+ const code = resp.status === 401 ? "invalid_api_key" : resp.status === 403 ? "forbidden" : resp.status === 429 ? "rate_limited" : "server_error";
4197
+ throw new AtlaSentError(
4198
+ `ApprovalRuntime: API error ${resp.status} at ${path}: ${text.slice(0, 200)}`,
4199
+ { code, status: resp.status }
4200
+ );
4201
+ }
4202
+ return resp.json();
4203
+ }
4204
+ async function apiGet(path, cfg) {
4205
+ const url = `${cfg.baseUrl}${path}`;
4206
+ let resp;
4207
+ try {
4208
+ resp = await fetch(url, {
4209
+ method: "GET",
4210
+ headers: {
4211
+ Accept: "application/json",
4212
+ Authorization: `Bearer ${cfg.apiKey}`
4213
+ },
4214
+ signal: AbortSignal.timeout(cfg.requestTimeoutMs)
4215
+ });
4216
+ } catch (err) {
4217
+ throw new AtlaSentError(
4218
+ `ApprovalRuntime: network error calling ${path}`,
4219
+ { code: "network", cause: err }
4220
+ );
4221
+ }
4222
+ if (!resp.ok) {
4223
+ const text = await resp.text().catch(() => "");
4224
+ const code = resp.status === 401 ? "invalid_api_key" : resp.status === 403 ? "forbidden" : resp.status === 429 ? "rate_limited" : "server_error";
4225
+ throw new AtlaSentError(
4226
+ `ApprovalRuntime: API error ${resp.status} at ${path}: ${text.slice(0, 200)}`,
4227
+ { code, status: resp.status }
4228
+ );
4229
+ }
4230
+ return resp.json();
4231
+ }
4232
+ function sleep2(ms) {
4233
+ return new Promise((resolve) => setTimeout(resolve, ms));
4234
+ }
4235
+ var EscalationDeniedError = class extends Error {
4236
+ name = "EscalationDeniedError";
4237
+ escalationId;
4238
+ outcome;
4239
+ constructor(outcome) {
4240
+ super(
4241
+ `Escalation ${outcome.escalation.id} was rejected` + (outcome.resolutionNote ? `: ${outcome.resolutionNote}` : "")
4242
+ );
4243
+ this.escalationId = outcome.escalation.id;
4244
+ this.outcome = outcome;
4245
+ }
4246
+ };
4247
+ var EscalationTimeoutError = class extends Error {
4248
+ name = "EscalationTimeoutError";
4249
+ escalationId;
4250
+ outcome;
4251
+ constructor(outcome) {
4252
+ super(
4253
+ `Escalation ${outcome.escalation.id} timed out waiting for approval`
4254
+ );
4255
+ this.escalationId = outcome.escalation.id;
4256
+ this.outcome = outcome;
4257
+ }
4258
+ };
4259
+ async function createEscalation(opts) {
4260
+ const { apiKey, baseUrl, ...hitlBody } = opts;
4261
+ const cfg = resolveConfig({
4262
+ ...apiKey !== void 0 ? { apiKey } : {},
4263
+ ...baseUrl !== void 0 ? { baseUrl } : {}
4264
+ });
4265
+ const body = {
4266
+ agent_id: hitlBody.agent_id ?? "unknown",
4267
+ escalation_reason: hitlBody.escalation_reason ?? "Policy hold \u2014 awaiting human approval",
4268
+ ...hitlBody
4269
+ };
4270
+ const escalation = await apiPost("/v1/hitl", body, cfg);
4271
+ return {
4272
+ escalationId: escalation.id,
4273
+ createdAt: escalation.created_at,
4274
+ timeoutAt: escalation.timeout_at ?? null,
4275
+ assignedToRole: escalation.assigned_to_role ?? null
4276
+ };
4277
+ }
4278
+ async function waitForEscalationApproval(opts) {
4279
+ const cfg = resolveConfig({
4280
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4281
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4282
+ });
4283
+ const waitMs = opts.waitMs ?? 6e5;
4284
+ const pollIntervalMs = Math.max(opts.pollIntervalMs ?? 5e3, 1e3);
4285
+ const deadline = Date.now() + waitMs;
4286
+ const toOutcome = (escalation2) => {
4287
+ const terminal = escalation2.status === "approved" || escalation2.status === "rejected" || escalation2.status === "auto_approved" || escalation2.status === "timed_out";
4288
+ if (!terminal) return null;
4289
+ const status = escalation2.status === "approved" || escalation2.status === "auto_approved" ? "approved" : escalation2.status === "timed_out" ? "timed_out" : "rejected";
4290
+ return {
4291
+ status,
4292
+ escalation: escalation2,
4293
+ resolvedBy: escalation2.resolved_by ?? null,
4294
+ resolutionNote: escalation2.resolution_note ?? null,
4295
+ resolvedAt: escalation2.resolved_at ?? null
4296
+ };
4297
+ };
4298
+ while (Date.now() < deadline) {
4299
+ const escalation2 = await apiGet(
4300
+ `/v1/escalations/${opts.escalationId}`,
4301
+ cfg
4302
+ );
4303
+ const outcome2 = toOutcome(escalation2);
4304
+ if (outcome2) return outcome2;
4305
+ const remaining = deadline - Date.now();
4306
+ if (remaining <= 0) break;
4307
+ await sleep2(Math.min(pollIntervalMs, remaining));
4308
+ }
4309
+ const escalation = await apiGet(
4310
+ `/v1/escalations/${opts.escalationId}`,
4311
+ cfg
4312
+ );
4313
+ const outcome = toOutcome(escalation);
4314
+ if (outcome) return outcome;
4315
+ return {
4316
+ status: "timed_out",
4317
+ escalation,
4318
+ resolvedBy: null,
4319
+ resolutionNote: "Client-side wait timeout elapsed",
4320
+ resolvedAt: null
4321
+ };
4322
+ }
4323
+ async function protectOrEscalate(request, opts = {}) {
4324
+ try {
4325
+ const permit = await protect(request);
4326
+ return {
4327
+ ...permit,
4328
+ escalationId: "",
4329
+ resolvedBy: null,
4330
+ resolutionNote: null,
4331
+ resolvedAt: permit.timestamp,
4332
+ approvalBasis: "direct_policy"
4333
+ };
4334
+ } catch (err) {
4335
+ if (!(err instanceof AtlaSentDeniedError) || err.decision !== "hold" && err.decision !== "escalate") {
4336
+ throw err;
4337
+ }
4338
+ }
4339
+ const proposedAction = opts.proposedAction ?? request.context;
4340
+ const handle = await createEscalation({
4341
+ agent_id: opts.agentId ?? request.agent,
4342
+ escalation_reason: opts.escalationReason ?? `Policy hold for "${request.action}" by "${request.agent}"`,
4343
+ ...proposedAction !== void 0 ? { proposed_action: proposedAction } : {},
4344
+ ...opts.riskScore !== void 0 ? { risk_score: opts.riskScore } : {},
4345
+ ...opts.assignedToRole !== void 0 ? { assigned_to_role: opts.assignedToRole } : {},
4346
+ ...opts.quorumRequired !== void 0 ? { quorum_required: opts.quorumRequired } : {},
4347
+ ...opts.fallbackDecision !== void 0 ? { fallback_decision: opts.fallbackDecision } : {},
4348
+ ...opts.timeoutAt !== void 0 ? { timeout_at: opts.timeoutAt } : {},
4349
+ ...opts.metadata !== void 0 ? { metadata: opts.metadata } : {},
4350
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4351
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4352
+ });
4353
+ opts.onEscalationCreated?.(handle);
4354
+ const outcome = await waitForEscalationApproval({
4355
+ escalationId: handle.escalationId,
4356
+ ...opts.waitMs !== void 0 ? { waitMs: opts.waitMs } : {},
4357
+ ...opts.pollIntervalMs !== void 0 ? { pollIntervalMs: opts.pollIntervalMs } : {},
4358
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4359
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4360
+ });
4361
+ if (outcome.status === "rejected") throw new EscalationDeniedError(outcome);
4362
+ if (outcome.status === "timed_out") throw new EscalationTimeoutError(outcome);
4363
+ return {
4364
+ permitId: `escl_${handle.escalationId}`,
4365
+ permitHash: "",
4366
+ auditHash: outcome.escalation.id,
4367
+ reason: outcome.resolutionNote ?? "Approved by human reviewer",
4368
+ timestamp: outcome.resolvedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4369
+ permitExpiresAt: null,
4370
+ escalationId: handle.escalationId,
4371
+ resolvedBy: outcome.resolvedBy,
4372
+ resolutionNote: outcome.resolutionNote,
4373
+ resolvedAt: outcome.resolvedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4374
+ approvalBasis: "human_approval"
4375
+ };
4376
+ }
4377
+ async function requestOverride(opts) {
4378
+ const cfg = resolveConfig({
4379
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4380
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4381
+ });
4382
+ const body = {
4383
+ reason: opts.reason,
4384
+ evaluationId: opts.evaluationId,
4385
+ ...opts.ttlSeconds !== void 0 && { ttlSeconds: opts.ttlSeconds },
4386
+ ...opts.metadata !== void 0 && { metadata: opts.metadata }
4387
+ };
4388
+ return apiPost("/v1/overrides", body, cfg);
4389
+ }
4390
+
4391
+ // src/actionContext.ts
4392
+ function buildActionContext(input) {
4393
+ const env = typeof input.environment === "string" ? { name: input.environment } : input.environment;
4394
+ const ctx = {
4395
+ actor: input.actor,
4396
+ ...input.resource !== void 0 && { resource: input.resource },
4397
+ ...env !== void 0 && { environment: env },
4398
+ ...input.action_meta !== void 0 && { action_meta: input.action_meta },
4399
+ ...input.history !== void 0 && { history: input.history },
4400
+ ...input.extra ?? {}
4401
+ };
4402
+ if (env?.name !== void 0) ctx.environment_name = env.name;
4403
+ if (input.resource?.type !== void 0)
4404
+ ctx.resource_type = input.resource.type;
4405
+ if (input.resource?.id !== void 0) ctx.resource_id = input.resource.id;
4406
+ return ctx;
4407
+ }
4408
+ function getNestedValue(obj, path) {
4409
+ return path.split(".").reduce((cur, key) => {
4410
+ if (cur !== null && typeof cur === "object") {
4411
+ return cur[key];
4412
+ }
4413
+ return void 0;
4414
+ }, obj);
4415
+ }
4416
+ function validateActionContext(ctx, opts = {}) {
4417
+ const errors = [];
4418
+ const warnings = [];
4419
+ const ctxObj = ctx;
4420
+ if (ctx.actor !== void 0) {
4421
+ if (!ctx.actor.id || typeof ctx.actor.id !== "string") {
4422
+ errors.push({
4423
+ field: "actor.id",
4424
+ code: "required",
4425
+ message: "actor.id is required when actor is provided"
4426
+ });
4427
+ }
4428
+ }
4429
+ const hasEnvName = ctx.environment?.name !== void 0 || ctx.environment_name !== void 0;
4430
+ if (!hasEnvName) {
4431
+ warnings.push({
4432
+ field: "environment.name",
4433
+ code: "recommended",
4434
+ message: "environment.name is not set; protect() will default to 'production' with a console warning"
4435
+ });
4436
+ }
4437
+ if (!opts.skipCrossFieldChecks) {
4438
+ const amount = ctx.action_meta?.estimated_amount;
4439
+ if (typeof amount === "number" && amount > 0) {
4440
+ if (!ctx.action_meta?.currency) {
4441
+ errors.push({
4442
+ field: "action_meta.currency",
4443
+ code: "cross_field",
4444
+ message: "action_meta.currency is required when action_meta.estimated_amount > 0"
4445
+ });
4446
+ } else if (!/^[A-Z]{3}$/.test(ctx.action_meta.currency)) {
4447
+ errors.push({
4448
+ field: "action_meta.currency",
4449
+ code: "invalid_value",
4450
+ message: `action_meta.currency '${ctx.action_meta.currency}' is not a valid ISO 4217 code (expected 3 uppercase letters)`
4451
+ });
4452
+ }
4453
+ }
4454
+ }
4455
+ if (ctx.history?.last_action_at !== void 0) {
4456
+ const ts = new Date(ctx.history.last_action_at);
4457
+ if (isNaN(ts.getTime())) {
4458
+ errors.push({
4459
+ field: "history.last_action_at",
4460
+ code: "invalid_type",
4461
+ message: `history.last_action_at '${ctx.history.last_action_at}' is not a valid ISO-8601 timestamp`
4462
+ });
4463
+ }
4464
+ }
4465
+ const knownSensitivities = /* @__PURE__ */ new Set([
4466
+ "public",
4467
+ "internal",
4468
+ "confidential",
4469
+ "restricted"
4470
+ ]);
4471
+ if (ctx.resource?.sensitivity !== void 0 && !knownSensitivities.has(ctx.resource.sensitivity)) {
4472
+ errors.push({
4473
+ field: "resource.sensitivity",
4474
+ code: "invalid_value",
4475
+ message: `resource.sensitivity '${ctx.resource.sensitivity}' is not one of: public, internal, confidential, restricted`
4476
+ });
4477
+ }
4478
+ for (const fieldPath of opts.requiredFields ?? []) {
4479
+ const value = getNestedValue(ctxObj, fieldPath);
4480
+ if (value === void 0 || value === null || value === "") {
4481
+ errors.push({
4482
+ field: fieldPath,
4483
+ code: "required",
4484
+ message: `${fieldPath} is required by the caller's validation rules`
4485
+ });
4486
+ }
4487
+ }
4488
+ return { valid: errors.length === 0, errors, warnings };
4489
+ }
4490
+ var DEFAULT_REDACTION_RULES = [
4491
+ {
4492
+ field: /password|passwd|passphrase/i,
4493
+ mode: "remove"
4494
+ },
4495
+ {
4496
+ field: /secret|private_key|client_secret|signing_secret/i,
4497
+ mode: "remove"
4498
+ },
4499
+ {
4500
+ field: /api_key|apikey|access_key|access_token/i,
4501
+ mode: "remove"
4502
+ },
4503
+ {
4504
+ field: /\btoken\b|auth_token|bearer/i,
4505
+ mode: "mask"
4506
+ },
4507
+ {
4508
+ field: /\bssn\b|social_security|tax_id|\bsin\b/i,
4509
+ mode: "remove"
4510
+ },
4511
+ {
4512
+ field: /credit_card|card_number|pan\b|cvv|cvc|expiry/i,
4513
+ mode: "remove"
4514
+ },
4515
+ {
4516
+ field: /\bemail\b/i,
4517
+ mode: "mask"
4518
+ },
4519
+ {
4520
+ field: /phone|mobile|cell\b/i,
4521
+ mode: "mask"
4522
+ },
4523
+ {
4524
+ field: /\bip\b|ip_address|remote_addr/i,
4525
+ mode: "mask"
4526
+ },
4527
+ {
4528
+ field: /dob|date_of_birth|birth_date|birthdate/i,
4529
+ mode: "remove"
4530
+ }
4531
+ ];
4532
+ var MASK_PLACEHOLDER = "[REDACTED]";
4533
+ function matchesRule(key, rule) {
4534
+ if (typeof rule.field === "string") {
4535
+ return key.toLowerCase() === rule.field.toLowerCase();
4536
+ }
4537
+ return rule.field.test(key);
4538
+ }
4539
+ function redactValue(value, mode) {
4540
+ if (mode === "remove") return void 0;
4541
+ if (mode === "mask") return MASK_PLACEHOLDER;
4542
+ return "[HASHED]";
4543
+ }
4544
+ function redactObject(obj, rules, currentPath) {
4545
+ const result = {};
4546
+ for (const [key, value] of Object.entries(obj)) {
4547
+ const fieldPath = currentPath ? `${currentPath}.${key}` : key;
4548
+ const matchingRule = rules.find(
4549
+ (r) => matchesRule(key, r) && (r.path === void 0 || r.path === fieldPath)
4550
+ );
4551
+ if (matchingRule) {
4552
+ const redacted = redactValue(value, matchingRule.mode);
4553
+ if (redacted !== void 0) result[key] = redacted;
4554
+ } else if (Array.isArray(value)) {
4555
+ result[key] = value.map(
4556
+ (item) => item !== null && typeof item === "object" && !Array.isArray(item) ? redactObject(item, rules, fieldPath) : item
4557
+ );
4558
+ } else if (value !== null && typeof value === "object") {
4559
+ result[key] = redactObject(
4560
+ value,
4561
+ rules,
4562
+ fieldPath
4563
+ );
4564
+ } else {
4565
+ result[key] = value;
4566
+ }
4567
+ }
4568
+ return result;
4569
+ }
4570
+ function redactContext(ctx, rules = DEFAULT_REDACTION_RULES) {
4571
+ return redactObject(
4572
+ ctx,
4573
+ rules,
4574
+ ""
4575
+ );
4576
+ }
4577
+ function flattenActionContext(ctx) {
4578
+ const flat = {};
4579
+ for (const [key, value] of Object.entries(ctx)) {
4580
+ flat[key] = value;
4581
+ }
4582
+ const envName = ctx.environment?.name ?? ctx.environment_name;
4583
+ if (envName !== void 0) {
4584
+ flat["environment_name"] = envName;
4585
+ }
4586
+ return flat;
4587
+ }
4588
+
4589
+ // src/shadow.ts
4590
+ var _defaultConfig = {};
4591
+ function configureShadow(config) {
4592
+ _defaultConfig = { ..._defaultConfig, ...config };
4593
+ }
4594
+ async function protectShadow(request, opts) {
4595
+ const merged = { ..._defaultConfig, ...opts };
4596
+ const mode = merged.mode ?? "observe";
4597
+ if (mode === "enforce") {
4598
+ const start2 = Date.now();
4599
+ const permit = await protect(request);
4600
+ const outcome = {
4601
+ decision: "permit",
4602
+ permit,
4603
+ error: null,
4604
+ would_have_blocked: false,
4605
+ latencyMs: Date.now() - start2,
4606
+ evaluationId: permit.permitId,
4607
+ request,
4608
+ mode
4609
+ };
4610
+ await _notify(outcome, merged);
4611
+ return outcome;
4612
+ }
4613
+ const start = Date.now();
4614
+ try {
4615
+ const permit = await protect(request);
4616
+ const outcome = {
4617
+ decision: "permit",
4618
+ permit,
4619
+ error: null,
4620
+ would_have_blocked: false,
4621
+ latencyMs: Date.now() - start,
4622
+ evaluationId: permit.permitId,
4623
+ request,
4624
+ mode
4625
+ };
4626
+ await _notify(outcome, merged);
4627
+ if (merged.reportToApi) {
4628
+ void reportShadowEvent(outcome, merged).catch(() => void 0);
4629
+ }
4630
+ return outcome;
4631
+ } catch (err) {
4632
+ if (err instanceof AtlaSentDeniedError) {
4633
+ const outcome = {
4634
+ decision: err.decision,
4635
+ permit: null,
4636
+ error: err,
4637
+ would_have_blocked: true,
4638
+ latencyMs: Date.now() - start,
4639
+ evaluationId: err.evaluationId ?? null,
4640
+ request,
4641
+ mode
4642
+ };
4643
+ if (mode === "warn") {
4644
+ console.warn(
4645
+ `[AtlaSent shadow:warn] Action '${request.action}' would have been blocked (decision=${err.decision}, evaluationId=${err.evaluationId ?? "unknown"})`
4646
+ );
4647
+ }
4648
+ await _notify(outcome, merged);
4649
+ if (merged.reportToApi) {
4650
+ void reportShadowEvent(outcome, merged).catch(() => void 0);
4651
+ }
4652
+ return outcome;
4653
+ }
4654
+ throw err;
4655
+ }
4656
+ }
4657
+ async function _notify(outcome, config) {
4658
+ if (config.onOutcome) {
4659
+ try {
4660
+ await config.onOutcome(outcome);
4661
+ } catch {
4662
+ }
4663
+ }
4664
+ }
4665
+ async function reportShadowEvent(outcome, opts) {
4666
+ const apiKey = opts?.apiKey ?? _defaultConfig.apiKey ?? process.env["ATLASENT_API_KEY"] ?? "";
4667
+ const baseUrl = opts?.baseUrl ?? _defaultConfig.baseUrl ?? process.env["ATLASENT_BASE_URL"] ?? "https://api.atlasent.ai";
4668
+ const payload = {
4669
+ action: outcome.request.action,
4670
+ agentId: outcome.request.agent ?? null,
4671
+ decision: outcome.decision,
4672
+ would_have_blocked: outcome.would_have_blocked,
4673
+ latencyMs: outcome.latencyMs,
4674
+ evaluationId: outcome.evaluationId,
4675
+ mode: outcome.mode,
4676
+ ...outcome.error ? { deniedReason: outcome.error.message } : {},
4677
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4678
+ };
4679
+ const response = await fetch(`${baseUrl}/v1/shadow-events`, {
4680
+ method: "POST",
4681
+ headers: {
4682
+ "Content-Type": "application/json",
4683
+ Authorization: `Bearer ${apiKey}`
4684
+ },
4685
+ body: JSON.stringify(payload)
4686
+ });
4687
+ if (!response.ok && response.status >= 500) {
4688
+ throw new Error(`Shadow event reporting failed: ${response.status}`);
4689
+ }
4690
+ }
4691
+
4692
+ // src/controlSurface.ts
4693
+ var _config = {};
4694
+ function configureControlSurface(config) {
4695
+ _config = { ..._config, ...config };
4696
+ }
4697
+ function resolveConfig2(opts) {
4698
+ return {
4699
+ apiKey: opts?.apiKey ?? _config.apiKey ?? process.env["ATLASENT_API_KEY"] ?? "",
4700
+ baseUrl: opts?.baseUrl ?? _config.baseUrl ?? process.env["ATLASENT_BASE_URL"] ?? "https://api.atlasent.ai",
4701
+ timeoutMs: opts?.timeoutMs ?? _config.timeoutMs ?? 1e4
4702
+ };
4703
+ }
4704
+ async function apiGet2(path, config) {
4705
+ const controller = new AbortController();
4706
+ const timer = setTimeout(() => controller.abort(), config.timeoutMs);
4707
+ try {
4708
+ const res = await fetch(`${config.baseUrl}${path}`, {
4709
+ method: "GET",
4710
+ headers: {
4711
+ Authorization: `Bearer ${config.apiKey}`,
4712
+ Accept: "application/json"
4713
+ },
4714
+ signal: controller.signal
4715
+ });
4716
+ if (!res.ok) {
4717
+ throw new Error(`HTTP ${res.status}`);
4718
+ }
4719
+ return res.json();
4720
+ } finally {
4721
+ clearTimeout(timer);
4722
+ }
4723
+ }
4724
+ async function apiPost2(path, body, config) {
4725
+ const controller = new AbortController();
4726
+ const timer = setTimeout(() => controller.abort(), config.timeoutMs);
4727
+ try {
4728
+ const res = await fetch(`${config.baseUrl}${path}`, {
4729
+ method: "POST",
4730
+ headers: {
4731
+ "Content-Type": "application/json",
4732
+ Authorization: `Bearer ${config.apiKey}`
4733
+ },
4734
+ body: JSON.stringify(body),
4735
+ signal: controller.signal
4736
+ });
4737
+ if (!res.ok) {
4738
+ throw new Error(`HTTP ${res.status}`);
4739
+ }
4740
+ return res.json();
4741
+ } finally {
4742
+ clearTimeout(timer);
4743
+ }
4744
+ }
4745
+ async function checkIntegrationHealth(opts) {
4746
+ const config = resolveConfig2(opts);
4747
+ const errors = [];
4748
+ let apiReachable = false;
4749
+ let authenticated = false;
4750
+ let latencyMs = null;
4751
+ let apiVersion = null;
4752
+ if (!config.apiKey) {
4753
+ errors.push("ATLASENT_API_KEY is not configured");
4754
+ }
4755
+ const start = Date.now();
4756
+ try {
4757
+ const data = await apiGet2("/v1/health", config);
4758
+ latencyMs = Date.now() - start;
4759
+ apiReachable = true;
4760
+ apiVersion = data.version ?? null;
4761
+ if (data.status === "ok" || data.status === "healthy") {
4762
+ authenticated = true;
4763
+ } else {
4764
+ errors.push(`API health status: ${data.status ?? "unknown"}`);
4765
+ }
4766
+ } catch (err) {
4767
+ latencyMs = Date.now() - start;
4768
+ const message = err instanceof Error ? err.message : String(err);
4769
+ if (message.includes("401") || message.includes("403")) {
4770
+ apiReachable = true;
4771
+ errors.push("API key is invalid or lacks required permissions");
4772
+ } else {
4773
+ errors.push(`API unreachable: ${message}`);
4774
+ }
4775
+ }
4776
+ return {
4777
+ healthy: apiReachable && authenticated && errors.length === 0,
4778
+ apiReachable,
4779
+ authenticated,
4780
+ latencyMs,
4781
+ apiVersion,
4782
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
4783
+ errors
4784
+ };
4785
+ }
4786
+ async function reportProtectedAction(opts) {
4787
+ const config = resolveConfig2(opts);
4788
+ return apiPost2(
4789
+ "/v1/control-surface/actions",
4790
+ {
4791
+ action_class: opts.actionClass,
4792
+ enforcement_mode: opts.enforcementMode ?? "observe",
4793
+ schema_id: opts.schemaId ?? null,
4794
+ tags: opts.tags ?? []
4795
+ },
4796
+ config
4797
+ );
4798
+ }
4799
+ async function getEnforcementStatus(opts) {
4800
+ const config = resolveConfig2(opts);
4801
+ return apiGet2(
4802
+ `/v1/control-surface/actions/${encodeURIComponent(opts.actionClass)}/status`,
4803
+ config
4804
+ );
4805
+ }
4806
+ async function getOrgSummary(opts) {
4807
+ const config = resolveConfig2(opts);
4808
+ return apiGet2("/v1/control-surface/summary", config);
4809
+ }
4810
+
4811
+ // src/verticals/deployGate.ts
4812
+ function resolveEnvActor() {
4813
+ return process.env["GITHUB_ACTOR"] ?? process.env["GITLAB_USER_LOGIN"] ?? process.env["CIRCLE_USERNAME"] ?? process.env["BITBUCKET_STEP_TRIGGERER_UUID"] ?? void 0;
4814
+ }
4815
+ function resolveEnvSha() {
4816
+ return process.env["GITHUB_SHA"] ?? process.env["CI_COMMIT_SHA"] ?? process.env["CIRCLE_SHA1"] ?? process.env["BITBUCKET_COMMIT"] ?? void 0;
4817
+ }
4818
+ function resolveEnvWorkflow() {
4819
+ return process.env["GITHUB_WORKFLOW"] ?? process.env["CI_PIPELINE_NAME"] ?? process.env["CIRCLE_WORKFLOW_NAME"] ?? void 0;
4820
+ }
4821
+ async function protectDeploy(opts) {
4822
+ const actorId = opts.actorId ?? resolveEnvActor() ?? "ci-system";
4823
+ const sha = opts.sha ?? resolveEnvSha();
4824
+ const workflow = opts.workflow ?? resolveEnvWorkflow();
4825
+ const environment = opts.environment ?? "production";
4826
+ const isProduction = environment === "production";
4827
+ const ctx = buildActionContext({
4828
+ actor: {
4829
+ id: actorId,
4830
+ type: "service_account",
4831
+ ...opts.actorLabel !== void 0 ? { label: opts.actorLabel } : {}
4832
+ },
4833
+ resource: {
4834
+ id: opts.service,
4835
+ type: opts.resourceType ?? "service"
4836
+ },
4837
+ environment,
4838
+ action_meta: {
4839
+ risk_level: isProduction ? "critical" : "medium",
4840
+ reversibility: "partial",
4841
+ ...opts.description !== void 0 ? { description: opts.description } : sha !== void 0 ? { description: `Deploy ${sha.slice(0, 8)} to ${environment}` } : {}
4842
+ },
4843
+ extra: {
4844
+ sha,
4845
+ workflow
4846
+ }
4847
+ });
4848
+ const request = {
4849
+ action: "production.deploy",
4850
+ agent: actorId,
4851
+ context: flattenActionContext(ctx)
4852
+ };
4853
+ if (opts.requireApproval || isProduction) {
4854
+ return protectOrEscalate(request, {
4855
+ escalationReason: `Production deployment of ${opts.service} requires human approval`,
4856
+ assignedToRole: opts.assignedToRole ?? "release-manager",
4857
+ waitMs: opts.waitMs ?? 30 * 60 * 1e3,
4858
+ ...opts.onEscalationCreated !== void 0 ? { onEscalationCreated: opts.onEscalationCreated } : {},
4859
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4860
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4861
+ });
4862
+ }
4863
+ return protect(request);
4864
+ }
4865
+
4866
+ // src/verticals/closeGovernance.ts
4867
+ var ACTION_RISK = {
4868
+ "period.close": "critical",
4869
+ "period.reopen": "critical",
4870
+ "reconciliation.lock": "high",
4871
+ "data.export": "high"
4872
+ };
4873
+ var ACTION_REVERSIBILITY = {
4874
+ "period.close": "partial",
4875
+ "period.reopen": "partial",
4876
+ "reconciliation.lock": "reversible",
4877
+ "data.export": "irreversible"
4878
+ };
4879
+ async function protectCloseAction(opts) {
4880
+ const ctx = buildActionContext({
4881
+ actor: {
4882
+ id: opts.closedBy,
4883
+ type: "human",
4884
+ trust_level: "medium"
4885
+ },
4886
+ resource: {
4887
+ id: opts.entityId,
4888
+ type: "accounting_entity",
4889
+ sensitivity: opts.dataClassification ?? "confidential",
4890
+ ...opts.entityName !== void 0 ? { name: opts.entityName } : {}
4891
+ },
4892
+ environment: "production",
4893
+ action_meta: {
4894
+ risk_level: ACTION_RISK[opts.action],
4895
+ reversibility: ACTION_REVERSIBILITY[opts.action],
4896
+ description: opts.description ?? `${opts.action} for period ${opts.periodLabel} on entity ${opts.entityId}`
4897
+ },
4898
+ extra: {
4899
+ period_label: opts.periodLabel,
4900
+ close_action: opts.action
4901
+ }
4902
+ });
4903
+ return protectOrEscalate(
4904
+ {
4905
+ action: opts.action,
4906
+ agent: opts.closedBy,
4907
+ context: flattenActionContext(ctx)
4908
+ },
4909
+ {
4910
+ escalationReason: `Accounting ${opts.action} for period '${opts.periodLabel}' requires approval`,
4911
+ assignedToRole: opts.assignedToRole ?? "controller",
4912
+ quorumRequired: opts.requireDualApproval ?? opts.action === "period.close" ? "simple_majority" : "single_approver",
4913
+ waitMs: opts.waitMs ?? 24 * 60 * 60 * 1e3,
4914
+ ...opts.onEscalationCreated !== void 0 ? { onEscalationCreated: opts.onEscalationCreated } : {},
4915
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4916
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4917
+ }
4918
+ );
4919
+ }
4920
+
4921
+ // src/verticals/paymentRelease.ts
4922
+ var ISO_4217 = /^[A-Z]{3}$/;
4923
+ async function protectPaymentRelease(opts) {
4924
+ if (!ISO_4217.test(opts.currency)) {
4925
+ throw new TypeError(
4926
+ `Invalid currency code '${opts.currency}': must be a 3-letter ISO 4217 code (e.g. USD, EUR, GBP)`
4927
+ );
4928
+ }
4929
+ if (opts.amount <= 0) {
4930
+ throw new RangeError(`Payment amount must be greater than 0, got ${opts.amount}`);
4931
+ }
4932
+ const escalateThreshold = opts.autoEscalateAbove ?? 1e4;
4933
+ const dualThreshold = opts.requireDualApprovalAbove ?? 1e5;
4934
+ const needsEscalation = opts.amount > escalateThreshold;
4935
+ const needsDual = opts.amount > dualThreshold;
4936
+ const ctx = buildActionContext({
4937
+ actor: {
4938
+ id: opts.authorizedBy,
4939
+ type: "human"
4940
+ },
4941
+ resource: {
4942
+ id: opts.vendorId,
4943
+ type: "vendor",
4944
+ ...opts.vendorName !== void 0 ? { name: opts.vendorName } : {}
4945
+ },
4946
+ environment: "production",
4947
+ action_meta: {
4948
+ risk_level: opts.amount > dualThreshold ? "critical" : opts.amount > escalateThreshold ? "high" : "medium",
4949
+ reversibility: "irreversible",
4950
+ estimated_amount: opts.amount,
4951
+ currency: opts.currency,
4952
+ description: opts.description ?? `Release ${opts.currency} ${opts.amount.toLocaleString()} to ${opts.vendorName ?? opts.vendorId}`
4953
+ },
4954
+ extra: {
4955
+ reference: opts.reference
4956
+ }
4957
+ });
4958
+ const request = {
4959
+ action: "payment.release",
4960
+ agent: opts.authorizedBy,
4961
+ context: flattenActionContext(ctx)
4962
+ };
4963
+ if (needsEscalation) {
4964
+ return protectOrEscalate(request, {
4965
+ escalationReason: `Payment of ${opts.currency} ${opts.amount.toLocaleString()} to ${opts.vendorName ?? opts.vendorId} exceeds auto-approval threshold of ${opts.currency} ${escalateThreshold.toLocaleString()}`,
4966
+ assignedToRole: opts.assignedToRole ?? "finance-approver",
4967
+ quorumRequired: needsDual ? "simple_majority" : "single_approver",
4968
+ waitMs: opts.waitMs ?? 4 * 60 * 60 * 1e3,
4969
+ ...opts.onEscalationCreated !== void 0 ? { onEscalationCreated: opts.onEscalationCreated } : {},
4970
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
4971
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
4972
+ });
4973
+ }
4974
+ return protect(request);
4975
+ }
4976
+
4977
+ // src/verticals/agentTools.ts
4978
+ var HIGH_RISK_TOOLS = /* @__PURE__ */ new Set([
4979
+ "bash",
4980
+ "shell",
4981
+ "exec",
4982
+ "execute_code",
4983
+ "run_command",
4984
+ "write_file",
4985
+ "delete_file",
4986
+ "overwrite_file",
4987
+ "sql_execute",
4988
+ "db_write",
4989
+ "db_delete",
4990
+ "send_email",
4991
+ "send_message",
4992
+ "post_to_slack",
4993
+ "create_pr",
4994
+ "merge_pr",
4995
+ "push_code",
4996
+ "deploy",
4997
+ "release",
4998
+ "make_payment",
4999
+ "transfer_funds",
5000
+ "create_user",
5001
+ "delete_user",
5002
+ "modify_permissions",
5003
+ "aws_cli",
5004
+ "gcloud",
5005
+ "kubectl"
5006
+ ]);
5007
+ var CRITICAL_TOOLS = /* @__PURE__ */ new Set([
5008
+ "bash",
5009
+ "shell",
5010
+ "exec",
5011
+ "execute_code",
5012
+ "run_command",
5013
+ "delete_file",
5014
+ "db_delete",
5015
+ "make_payment",
5016
+ "transfer_funds",
5017
+ "delete_user",
5018
+ "modify_permissions",
5019
+ "deploy",
5020
+ "release"
5021
+ ]);
5022
+ function classifyToolRisk(toolName) {
5023
+ const normalized = toolName.toLowerCase().replace(/[^a-z0-9_]/g, "_");
5024
+ if (CRITICAL_TOOLS.has(normalized)) return "critical";
5025
+ if (HIGH_RISK_TOOLS.has(normalized)) return "high";
5026
+ if (normalized.includes("write") || normalized.includes("create") || normalized.includes("update")) return "medium";
5027
+ return "low";
5028
+ }
5029
+ async function protectToolCall(opts) {
5030
+ const inferredRisk = opts.riskLevel ?? classifyToolRisk(opts.toolName);
5031
+ const mode = opts.mode ?? (inferredRisk === "low" ? "enforce" : "escalate");
5032
+ const ctx = buildActionContext({
5033
+ actor: {
5034
+ id: opts.agentId,
5035
+ type: "agent",
5036
+ ...opts.sessionId !== void 0 ? { session_id: opts.sessionId } : {}
5037
+ },
5038
+ resource: {
5039
+ type: "agent_tool",
5040
+ id: opts.toolName
5041
+ },
5042
+ environment: "production",
5043
+ action_meta: {
5044
+ risk_level: inferredRisk,
5045
+ reversibility: inferredRisk === "critical" || inferredRisk === "high" ? "irreversible" : "reversible",
5046
+ description: opts.description ?? `Agent ${opts.agentId} calling tool '${opts.toolName}'`
5047
+ },
5048
+ extra: {
5049
+ tool_args_keys: Object.keys(opts.toolArgs),
5050
+ session_id: opts.sessionId
5051
+ }
5052
+ });
5053
+ const request = {
5054
+ action: `agent_tool.${opts.toolName}`,
5055
+ agent: opts.agentId,
5056
+ context: flattenActionContext(ctx)
5057
+ };
5058
+ if (mode === "observe") {
5059
+ return protectShadow(request, { mode: "observe" });
5060
+ }
5061
+ if (mode === "escalate" || inferredRisk === "critical") {
5062
+ return protectOrEscalate(request, {
5063
+ escalationReason: `Agent '${opts.agentId}' is calling ${inferredRisk}-risk tool '${opts.toolName}'`,
5064
+ assignedToRole: opts.assignedToRole ?? "agent-supervisor",
5065
+ riskScore: inferredRisk === "critical" ? 1 : inferredRisk === "high" ? 0.75 : 0.5,
5066
+ waitMs: opts.waitMs ?? 15 * 60 * 1e3,
5067
+ ...opts.onEscalationCreated !== void 0 ? { onEscalationCreated: opts.onEscalationCreated } : {},
5068
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
5069
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
5070
+ });
5071
+ }
5072
+ return protect(request);
5073
+ }
5074
+
5075
+ // src/claimLineage.ts
5076
+ import { createHmac, randomUUID } from "crypto";
5077
+ var NOT_APPLICABLE = { notApplicable: true };
5078
+ function isNotApplicable(v) {
5079
+ return typeof v === "object" && v !== null && v.notApplicable === true;
5080
+ }
5081
+ var SDK_VERSION2 = "@atlasent/sdk@1.4.2";
5082
+ function canonicalize(value) {
5083
+ if (value === null || value === void 0) return "null";
5084
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
5085
+ if (typeof value === "boolean") return value ? "true" : "false";
5086
+ if (typeof value === "string") return JSON.stringify(value);
5087
+ if (Array.isArray(value)) return "[" + value.map(canonicalize).join(",") + "]";
5088
+ if (typeof value === "object") {
5089
+ const obj = value;
5090
+ const keys = Object.keys(obj).sort();
5091
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalize(obj[k])).join(",") + "}";
5092
+ }
5093
+ return "null";
5094
+ }
5095
+ function sha256Hex3(input) {
5096
+ const { createHash } = __require("crypto");
5097
+ return createHash("sha256").update(input).digest("hex");
5098
+ }
5099
+ function hmacSha256Base64url(payload, secret) {
5100
+ return createHmac("sha256", secret).update(payload).digest("base64url");
5101
+ }
5102
+ function computeLinkHash(link) {
5103
+ return sha256Hex3(canonicalize(link));
5104
+ }
5105
+ function slotStatus(input, slot) {
5106
+ if (isNotApplicable(input)) return "not_applicable";
5107
+ if (slot !== null) return "present";
5108
+ return "missing";
5109
+ }
5110
+ function toDeploySlot(input) {
5111
+ if (input === void 0 || isNotApplicable(input)) return null;
5112
+ return {
5113
+ deploy_id: input.deploy_id,
5114
+ environment: input.environment,
5115
+ sha: input.sha,
5116
+ actor_id: input.actor_id,
5117
+ deployed_at: input.deployed_at,
5118
+ gate_permit_token: input.gate_permit_token
5119
+ };
5120
+ }
5121
+ function toIntegrationSlot(input) {
5122
+ if (input === void 0 || isNotApplicable(input)) return null;
5123
+ const run = input;
5124
+ return {
5125
+ run_id: run.id,
5126
+ framework: run.framework,
5127
+ period_start: run.period_start,
5128
+ period_end: run.period_end,
5129
+ status: run.status,
5130
+ passing_control_count: (run.controls ?? []).filter((c) => c.status === "pass").length,
5131
+ failing_control_count: (run.controls ?? []).filter((c) => c.status !== "pass").length,
5132
+ run_completed_at: run.created_at
5133
+ };
5134
+ }
5135
+ function toApprovalSlot(input) {
5136
+ if (input === void 0 || isNotApplicable(input)) return null;
5137
+ if ("escalation" in input) {
5138
+ const chain = input;
5139
+ const approvals = chain.approvals;
5140
+ const lastApproved = approvals.filter((a) => a.decision === "approve").map((a) => a.created_at).sort().at(-1) ?? chain.escalation.created_at;
5141
+ return {
5142
+ approval_id: chain.escalation.id,
5143
+ approval_kind: "hitl_chain",
5144
+ quorum_type: hitlQuorumToSlotQuorum(chain.escalation.quorum_required),
5145
+ approver_count: approvals.filter((a) => a.decision === "approve").length,
5146
+ approver_ids: approvals.filter((a) => a.decision === "approve").map((a) => a.user_id ?? a.actor_label ?? "unknown"),
5147
+ approved_at: lastApproved,
5148
+ artifact_hash: chain.artifact_hash
5149
+ };
5150
+ }
5151
+ const artifact = input;
5152
+ return {
5153
+ approval_id: artifact.approval_id,
5154
+ approval_kind: "approval_artifact",
5155
+ quorum_type: artifact.quorum_type,
5156
+ approver_count: artifact.approver_ids.length,
5157
+ approver_ids: artifact.approver_ids,
5158
+ approved_at: artifact.approved_at,
5159
+ artifact_hash: artifact.artifact_hash
5160
+ };
5161
+ }
5162
+ function hitlQuorumToSlotQuorum(tier) {
5163
+ switch (tier) {
5164
+ case "single_approver":
5165
+ return "single_approver";
5166
+ case "two_thirds":
5167
+ return "two_thirds";
5168
+ case "unanimous":
5169
+ return "unanimous";
5170
+ default:
5171
+ return "simple_majority";
5172
+ }
5173
+ }
5174
+ function toRuntimeSlot(receipt, verifiedAtCreation) {
5175
+ return {
5176
+ permit_token: receipt.permit_id ?? receipt.receipt_id,
5177
+ audit_hash: receipt.audit_hash,
5178
+ decision: receipt.decision === "allow" ? "allow" : receipt.decision === "escalate" ? "escalate" : "deny",
5179
+ decision_id: receipt.evaluation_id,
5180
+ evaluated_at: receipt.issued_at,
5181
+ algorithm: receipt.algorithm,
5182
+ signature: receipt.signature,
5183
+ permit_revoked_at: null,
5184
+ verified_at_claim_time: receipt.decision === "allow",
5185
+ verified_at_link_creation: verifiedAtCreation
5186
+ };
5187
+ }
5188
+ function buildChecklist(runtime, deployStatus, integrationStatus, approvalStatus, delta, lastVerifiedAt, now) {
5189
+ const deltaComputed = delta.status === "computed";
5190
+ const policyDriftClean = deltaComputed ? !delta.policy_drift_detected : null;
5191
+ const schemaDriftClean = !delta.schema_drift_detected;
5192
+ const allPass = runtime.verified_at_claim_time && runtime.verified_at_link_creation && deltaComputed && policyDriftClean === true && schemaDriftClean && deployStatus !== "missing" && integrationStatus !== "missing" && approvalStatus !== "missing";
5193
+ return {
5194
+ runtime_evidence_present: true,
5195
+ verified_at_claim_time: runtime.verified_at_claim_time,
5196
+ verified_at_link_creation: runtime.verified_at_link_creation,
5197
+ deploy_evidence_status: deployStatus,
5198
+ integration_evidence_status: integrationStatus,
5199
+ approval_artifact_status: approvalStatus,
5200
+ delta_computed: deltaComputed,
5201
+ policy_drift_clean: policyDriftClean,
5202
+ schema_drift_clean: schemaDriftClean,
5203
+ all_pass: allPass,
5204
+ last_verified_at: lastVerifiedAt,
5205
+ computed_at: now
5206
+ };
5207
+ }
5208
+ function buildClaimEvidenceLink(opts) {
5209
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5210
+ const linkId = `cel_${randomUUID().replace(/-/g, "")}`;
5211
+ const orgId = opts.orgId ?? opts.runtimeEvidence.org_id;
5212
+ const schemaVersion = opts.schemaVersion ?? SDK_VERSION2;
5213
+ const deploySlot = toDeploySlot(opts.deployEvidence);
5214
+ const integrationSlot = toIntegrationSlot(opts.integrationEvidence);
5215
+ const approvalSlot = toApprovalSlot(opts.approvalArtifact);
5216
+ const deployStatus = slotStatus(opts.deployEvidence, deploySlot);
5217
+ const integrationStatus = slotStatus(opts.integrationEvidence, integrationSlot);
5218
+ const approvalStatus = slotStatus(opts.approvalArtifact, approvalSlot);
5219
+ const verifiedAtCreation = opts.runtimeEvidence.decision === "allow";
5220
+ const runtime = toRuntimeSlot(opts.runtimeEvidence, verifiedAtCreation);
5221
+ const delta = {
5222
+ status: "pending",
5223
+ computed_at: null,
5224
+ policy_version_at_claim: null,
5225
+ policy_version_current: null,
5226
+ policy_drift_detected: null,
5227
+ schema_version_at_claim: schemaVersion,
5228
+ schema_version_current: schemaVersion,
5229
+ schema_drift_detected: false,
5230
+ drift_details: []
5231
+ };
5232
+ const lastVerifiedAt = verifiedAtCreation ? now : null;
5233
+ const checklist = buildChecklist(
5234
+ runtime,
5235
+ deployStatus,
5236
+ integrationStatus,
5237
+ approvalStatus,
5238
+ delta,
5239
+ lastVerifiedAt,
5240
+ now
5241
+ );
5242
+ const linkAlgorithm = opts.signingSecret ? "hmac-sha256" : "none";
5243
+ const body = {
5244
+ version: "claim_evidence_link.v1",
5245
+ link_id: linkId,
5246
+ claim_id: opts.claimId,
5247
+ org_id: orgId,
5248
+ linked_at: now,
5249
+ updated_at: now,
5250
+ revision: 1,
5251
+ link_algorithm: linkAlgorithm,
5252
+ runtime_evidence: runtime,
5253
+ deploy_evidence: deploySlot,
5254
+ integration_evidence: integrationSlot,
5255
+ approval_artifact: approvalSlot,
5256
+ delta,
5257
+ verification_checklist: checklist
5258
+ };
5259
+ const linkHash = computeLinkHash(body);
5260
+ const linkSignature = opts.signingSecret ? hmacSha256Base64url(linkHash, opts.signingSecret) : null;
5261
+ return { ...body, link_hash: linkHash, link_signature: linkSignature };
5262
+ }
5263
+ function verifyClaimEvidenceLink(link, opts = {}) {
5264
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5265
+ const { link_hash: _lh, link_signature: _ls, ...body } = link;
5266
+ const expectedHash = computeLinkHash(
5267
+ body
5268
+ );
5269
+ const hashValid = expectedHash === link.link_hash;
5270
+ let sigValid = true;
5271
+ if (link.link_algorithm === "hmac-sha256") {
5272
+ if (!opts.signingSecret) {
5273
+ sigValid = false;
5274
+ } else {
5275
+ const expected = hmacSha256Base64url(link.link_hash, opts.signingSecret);
5276
+ sigValid = expected === link.link_signature;
5277
+ }
5278
+ }
5279
+ const runtime = {
5280
+ ...link.runtime_evidence,
5281
+ // If the link hash or signature is invalid, mark creation-time verification as failed
5282
+ verified_at_link_creation: hashValid && sigValid ? link.runtime_evidence.verified_at_link_creation : false
5283
+ };
5284
+ const checklist = buildChecklist(
5285
+ runtime,
5286
+ link.verification_checklist.deploy_evidence_status,
5287
+ link.verification_checklist.integration_evidence_status,
5288
+ link.verification_checklist.approval_artifact_status,
5289
+ link.delta,
5290
+ runtime.verified_at_link_creation ? link.verification_checklist.last_verified_at ?? now : null,
5291
+ now
5292
+ );
5293
+ const updatedBody = {
5294
+ version: link.version,
5295
+ link_id: link.link_id,
5296
+ claim_id: link.claim_id,
5297
+ org_id: link.org_id,
5298
+ linked_at: link.linked_at,
5299
+ updated_at: now,
5300
+ revision: link.revision + 1,
5301
+ link_algorithm: link.link_algorithm,
5302
+ runtime_evidence: runtime,
5303
+ deploy_evidence: link.deploy_evidence,
5304
+ integration_evidence: link.integration_evidence,
5305
+ approval_artifact: link.approval_artifact,
5306
+ delta: link.delta,
5307
+ verification_checklist: checklist
5308
+ };
5309
+ const newHash = computeLinkHash(updatedBody);
5310
+ const newSignature = opts.signingSecret ? hmacSha256Base64url(newHash, opts.signingSecret) : link.link_algorithm === "none" ? null : link.link_signature;
5311
+ const updatedLink = {
5312
+ ...updatedBody,
5313
+ link_hash: newHash,
5314
+ link_signature: newSignature
5315
+ };
5316
+ const failedSlots = [];
5317
+ if (!hashValid) failedSlots.push("link_hash");
5318
+ if (!sigValid) failedSlots.push("link_signature");
5319
+ if (!checklist.verified_at_claim_time) failedSlots.push("verified_at_claim_time");
5320
+ if (!checklist.verified_at_link_creation) failedSlots.push("verified_at_link_creation");
5321
+ if (!checklist.delta_computed) failedSlots.push("delta_computed");
5322
+ if (checklist.policy_drift_clean === false) failedSlots.push("policy_drift_clean");
5323
+ if (!checklist.schema_drift_clean) failedSlots.push("schema_drift_clean");
5324
+ if (checklist.deploy_evidence_status === "missing") failedSlots.push("deploy_evidence_status");
5325
+ if (checklist.integration_evidence_status === "missing") failedSlots.push("integration_evidence_status");
5326
+ if (checklist.approval_artifact_status === "missing") failedSlots.push("approval_artifact_status");
5327
+ const valid = failedSlots.length === 0;
5328
+ if (!valid) {
5329
+ throw new AtlaSentError(
5330
+ `ClaimEvidenceLink verification failed: ${failedSlots.join(", ")}`,
5331
+ { code: "claim_evidence_incomplete" }
5332
+ );
5333
+ }
5334
+ return { link: updatedLink, valid, failedSlots };
5335
+ }
5336
+ function buildClaimEvidenceLinkFromActionBundle(bundle, opts) {
5337
+ const runtimeEvidence = {
5338
+ receipt_id: bundle.receipt.receipt_id,
5339
+ evaluation_id: bundle.receipt.evaluation_id,
5340
+ org_id: opts.orgId ?? "",
5341
+ decision: bundle.receipt.decision,
5342
+ action: bundle.action,
5343
+ actor: bundle.actor,
5344
+ resource_type: null,
5345
+ resource_id: null,
5346
+ reasons: [],
5347
+ why_trace: null,
5348
+ permit_id: bundle.receipt.permit_id,
5349
+ permit_hash: null,
5350
+ audit_hash: bundle.receipt.audit_hash ?? "",
5351
+ context_hash: "",
5352
+ issued_at: bundle.receipt.issued_at,
5353
+ expires_at: null,
5354
+ algorithm: bundle.receipt.algorithm,
5355
+ signature: bundle.receipt.signature,
5356
+ signing_key_id: null,
5357
+ payload: {
5358
+ receipt_id: bundle.receipt.receipt_id,
5359
+ evaluation_id: bundle.receipt.evaluation_id,
5360
+ org_id: opts.orgId ?? "",
5361
+ decision: bundle.receipt.decision,
5362
+ action: bundle.action,
5363
+ actor: bundle.actor,
5364
+ resource_type: null,
5365
+ resource_id: null,
5366
+ reasons: [],
5367
+ why_summary: "",
5368
+ permit_id: bundle.receipt.permit_id,
5369
+ permit_hash: null,
5370
+ audit_hash: bundle.receipt.audit_hash ?? "",
5371
+ context_hash: "",
5372
+ issued_at: bundle.receipt.issued_at,
5373
+ expires_at: null
5374
+ }
5375
+ };
5376
+ const deployEvidence = opts.deployNotApplicable ? NOT_APPLICABLE : {
5377
+ deploy_id: bundle.bundle_id,
5378
+ environment: bundle.environment,
5379
+ sha: bundle.sha,
5380
+ actor_id: bundle.actor,
5381
+ deployed_at: bundle.generated_at,
5382
+ gate_permit_token: bundle.receipt.permit_id ?? bundle.receipt.receipt_id
5383
+ };
5384
+ return buildClaimEvidenceLink({
5385
+ claimId: opts.claimId,
5386
+ ...opts.orgId !== void 0 ? { orgId: opts.orgId } : {},
5387
+ runtimeEvidence,
5388
+ deployEvidence,
5389
+ ...opts.signingSecret !== void 0 ? { signingSecret: opts.signingSecret } : {},
5390
+ ...opts.schemaVersion !== void 0 ? { schemaVersion: opts.schemaVersion } : {}
5391
+ });
5392
+ }
5393
+
5394
+ // src/bccae.ts
5395
+ var DEFAULT_BASE_URL2 = "https://api.atlasent.io";
5396
+ var DEFAULT_TIMEOUT_MS2 = 1e4;
5397
+ function enforceTls(raw) {
5398
+ let parsed;
5399
+ try {
5400
+ parsed = new URL(raw);
5401
+ } catch {
5402
+ throw new AtlaSentError(
5403
+ "BCCAEClient baseUrl is not a valid URL",
5404
+ { code: "network" }
5405
+ );
5406
+ }
5407
+ if (parsed.protocol === "http:") {
5408
+ const h = parsed.hostname;
5409
+ if (h !== "localhost" && h !== "127.0.0.1" && h !== "[::1]") {
5410
+ throw new AtlaSentError(
5411
+ "BCCAEClient baseUrl must use https:// for non-local endpoints",
5412
+ { code: "network" }
5413
+ );
5414
+ }
5415
+ }
5416
+ return parsed.origin;
5417
+ }
5418
+ function generateBccaeNonce() {
5419
+ const bytes = new Uint8Array(32);
5420
+ globalThis.crypto.getRandomValues(bytes);
5421
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
5422
+ }
5423
+ var BCCAEClient = class {
5424
+ apiKey;
5425
+ baseUrl;
5426
+ timeoutMs;
5427
+ fetchImpl;
5428
+ constructor(options) {
5429
+ if (!options.apiKey || typeof options.apiKey !== "string") {
5430
+ throw new AtlaSentError("BCCAEClient: apiKey is required", {
5431
+ code: "invalid_api_key"
5432
+ });
5433
+ }
5434
+ this.apiKey = options.apiKey;
5435
+ this.baseUrl = enforceTls(options.baseUrl ?? DEFAULT_BASE_URL2);
5436
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
5437
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
5438
+ }
5439
+ async evaluate(input) {
5440
+ const { body } = await this.post(
5441
+ "/v1/bccae/evaluations",
5442
+ input
5443
+ );
5444
+ return body;
5445
+ }
5446
+ async execute(input) {
5447
+ const { body } = await this.post(
5448
+ "/v1/bccae/execute",
5449
+ input
5450
+ );
5451
+ return body;
5452
+ }
5453
+ async revoke(input) {
5454
+ const { body } = await this.post(
5455
+ "/v1/bccae/revocations",
5456
+ input
5457
+ );
5458
+ return body;
5459
+ }
5460
+ async getEvidence(evidenceId) {
5461
+ if (!evidenceId || typeof evidenceId !== "string") {
5462
+ throw new AtlaSentError("BCCAEClient: evidenceId is required", {
5463
+ code: "bad_request"
5464
+ });
5465
+ }
5466
+ const { body } = await this.get(
5467
+ `/v1/bccae/evidence/${encodeURIComponent(evidenceId)}`
5468
+ );
5469
+ return body;
5470
+ }
5471
+ // ── HTTP primitives ─────────────────────────────────────────────────────────
5472
+ async post(path, body) {
5473
+ return this.request(path, "POST", body);
5474
+ }
5475
+ async get(path) {
5476
+ return this.request(path, "GET", void 0);
5477
+ }
5478
+ async request(path, method, body) {
5479
+ const url = `${this.baseUrl}${path}`;
5480
+ const headers = {
5481
+ Accept: "application/json",
5482
+ Authorization: `Bearer ${this.apiKey}`,
5483
+ "User-Agent": "atlasent-bccae-client/1.0"
5484
+ };
5485
+ if (method === "POST") headers["Content-Type"] = "application/json";
5486
+ let response;
5487
+ try {
5488
+ response = await this.fetchImpl(url, {
5489
+ method,
5490
+ headers,
5491
+ signal: AbortSignal.timeout(this.timeoutMs),
5492
+ ...method === "POST" ? { body: JSON.stringify(body) } : {}
5493
+ });
5494
+ } catch (err) {
5495
+ throw new AtlaSentError(
5496
+ `BCCAEClient: network error on ${method} ${path}: ${err instanceof Error ? err.message : String(err)}`,
5497
+ { code: "network" }
5498
+ );
5499
+ }
5500
+ let responseBody;
5501
+ try {
5502
+ responseBody = await response.json();
5503
+ } catch {
5504
+ throw new AtlaSentError(
5505
+ `BCCAEClient: non-JSON response (status ${response.status}) from ${method} ${path}`,
5506
+ { code: "network" }
5507
+ );
5508
+ }
5509
+ if (!response.ok) {
5510
+ const err = responseBody;
5511
+ const message = typeof err?.message === "string" ? err.message : `BCCAE request failed with status ${response.status}`;
5512
+ const code = response.status === 401 ? "invalid_api_key" : response.status === 403 ? "forbidden" : response.status === 429 ? "rate_limited" : response.status >= 500 ? "server_error" : "network";
5513
+ throw new AtlaSentError(message, { code });
5514
+ }
5515
+ return { body: responseBody };
5516
+ }
5517
+ };
5518
+
5519
+ // src/governanceAgents.ts
5520
+ var SEVERITY_RANK = {
5521
+ info: 1,
5522
+ low: 2,
5523
+ medium: 3,
5524
+ high: 4,
5525
+ blocker: 5
5526
+ };
5527
+ function highestAgentFindingSeverity(findings) {
5528
+ let best = null;
5529
+ let rank = 0;
5530
+ for (const f of findings) {
5531
+ const r = SEVERITY_RANK[f.severity];
5532
+ if (r > rank) {
5533
+ rank = r;
5534
+ best = f.severity;
5535
+ }
5536
+ }
5537
+ return best;
5538
+ }
5539
+
3427
5540
  // src/index.ts
3428
5541
  var atlasent = {
3429
5542
  protect,
@@ -3445,13 +5558,18 @@ export {
3445
5558
  AtlaSentDeniedError,
3446
5559
  AtlaSentError,
3447
5560
  AtlaSentEscalateError,
5561
+ BCCAEClient,
3448
5562
  DEFAULT_INCENTIVE_CONFIG,
5563
+ DEFAULT_REDACTION_RULES,
3449
5564
  DEFAULT_RETRY_POLICY,
3450
5565
  DEFAULT_RISK_TIER_THRESHOLDS,
3451
5566
  DEPLOYMENT_PRODUCTION_ACTION,
3452
5567
  DEPLOY_GATE_CODES,
5568
+ EscalationDeniedError,
5569
+ EscalationTimeoutError,
3453
5570
  FeatureNotEnabledError,
3454
5571
  GovernanceEnforcementError,
5572
+ NOT_APPLICABLE,
3455
5573
  PRODUCTION_DEPLOY_ACTION,
3456
5574
  PermitRevoked,
3457
5575
  StreamParseError,
@@ -3466,6 +5584,9 @@ export {
3466
5584
  assertWebhook,
3467
5585
  authorizeStream,
3468
5586
  budgetUtilizationSeverity,
5587
+ buildActionContext,
5588
+ buildClaimEvidenceLink,
5589
+ buildClaimEvidenceLinkFromActionBundle,
3469
5590
  buildLiabilityChain,
3470
5591
  buildLiabilityVisualization,
3471
5592
  buildRiskTimeline,
@@ -3474,9 +5595,11 @@ export {
3474
5595
  canonicalizeForEvidence,
3475
5596
  checkAutonomousBounds,
3476
5597
  checkBudgetConstraints,
5598
+ checkIntegrationHealth,
3477
5599
  clampTokenDuration,
3478
5600
  classifyCommand,
3479
5601
  classifyRiskTier,
5602
+ classifyToolRisk,
3480
5603
  computeApprovalRiskScore,
3481
5604
  computeBackoffMs,
3482
5605
  computeEscalatedApprovalCount,
@@ -3489,6 +5612,10 @@ export {
3489
5612
  computeRemediationUrgency,
3490
5613
  computeSignalEngagementRate,
3491
5614
  configure,
5615
+ configureApprovalRuntime,
5616
+ configureControlSurface,
5617
+ configureShadow,
5618
+ createEscalation,
3492
5619
  index_default as default,
3493
5620
  delegationPropagationHadEffect,
3494
5621
  deployGate,
@@ -3503,10 +5630,15 @@ export {
3503
5630
  evaluateMany,
3504
5631
  evidenceRunPasses,
3505
5632
  findPrimaryLiabilityParties,
5633
+ flattenActionContext,
3506
5634
  formatPolicySyncDiff,
5635
+ generateBccaeNonce,
5636
+ getEnforcementStatus,
5637
+ getOrgSummary,
3507
5638
  graphql,
3508
5639
  hasAttemptsLeft,
3509
5640
  hhiToConcentrationScore,
5641
+ highestAgentFindingSeverity,
3510
5642
  highestSeverityAction,
3511
5643
  hitlRequiredApproverCount,
3512
5644
  isBudgetExceptionActive,
@@ -3526,6 +5658,17 @@ export {
3526
5658
  normalizeEvaluateResponse,
3527
5659
  normalizePermitOutcome,
3528
5660
  protect,
5661
+ protectCloseAction,
5662
+ protectDeploy,
5663
+ protectOrEscalate,
5664
+ protectPaymentRelease,
5665
+ protectShadow,
5666
+ protectToolCall,
5667
+ protectWithEvidence,
5668
+ redactContext,
5669
+ reportProtectedAction,
5670
+ reportShadowEvent,
5671
+ requestOverride,
3529
5672
  requirePermit,
3530
5673
  scoreToRiskTier,
3531
5674
  serializeSignableContent,
@@ -3533,12 +5676,15 @@ export {
3533
5676
  summarizeCrossOrgPermission,
3534
5677
  transitionDispute,
3535
5678
  transitionReversal,
5679
+ validateActionContext,
3536
5680
  validateLiabilityChain,
3537
5681
  verifyAuditBundle,
3538
5682
  verifyBundle,
5683
+ verifyClaimEvidenceLink,
3539
5684
  verifyEvidenceBundleStructure,
3540
5685
  verifyWebhook,
3541
5686
  verifyWebhookSignature,
5687
+ waitForEscalationApproval,
3542
5688
  withPermit,
3543
5689
  withinAutonomousCeiling
3544
5690
  };