@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/README.md +35 -0
- package/dist/hono.cjs +445 -11
- package/dist/hono.cjs.map +1 -1
- package/dist/hono.d.cts +2 -2
- package/dist/hono.d.ts +2 -2
- package/dist/hono.js +445 -11
- package/dist/hono.js.map +1 -1
- package/dist/index.cjs +2185 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1699 -180
- package/dist/index.d.ts +1699 -180
- package/dist/index.js +2159 -13
- package/dist/index.js.map +1 -1
- package/dist/{protect-DiRVfVLq.d.cts → protect-C0t0fP1y.d.cts} +449 -2
- package/dist/{protect-DiRVfVLq.d.ts → protect-C0t0fP1y.d.ts} +449 -2
- package/package.json +6 -1
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
|
-
|
|
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
|
-
|
|
2223
|
-
|
|
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
|
-
|
|
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
|
|
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
|
});
|