@atlasent/sdk 1.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 +141 -27
- package/dist/hono.cjs +1943 -64
- package/dist/hono.cjs.map +1 -1
- package/dist/hono.d.cts +4 -4
- package/dist/hono.d.ts +4 -4
- package/dist/hono.js +1933 -64
- package/dist/hono.js.map +1 -1
- package/dist/index.cjs +5299 -210
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5465 -502
- package/dist/index.d.ts +5465 -502
- package/dist/index.js +5177 -209
- package/dist/index.js.map +1 -1
- package/dist/protect-C0t0fP1y.d.cts +1776 -0
- package/dist/protect-C0t0fP1y.d.ts +1776 -0
- package/dist/state.cjs +46 -0
- package/dist/state.cjs.map +1 -0
- package/dist/state.d.cts +152 -0
- package/dist/state.d.ts +152 -0
- package/dist/state.js +21 -0
- package/dist/state.js.map +1 -0
- package/package.json +29 -2
- package/dist/protect-BKxcoR_2.d.cts +0 -159
- package/dist/protect-BKxcoR_2.d.ts +0 -159
package/README.md
CHANGED
|
@@ -13,32 +13,69 @@ import { AtlaSentClient } from "@atlasent/sdk";
|
|
|
13
13
|
|
|
14
14
|
const client = new AtlaSentClient({ apiKey: process.env.ATLASENT_API_KEY! });
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
action: "modify_patient_record",
|
|
19
|
-
context: { user: "dr_smith", environment: "production" },
|
|
16
|
+
const gate = await client.deployGate({
|
|
17
|
+
context: { repo: "atlasent/api", commit: process.env.GIT_SHA },
|
|
20
18
|
});
|
|
21
19
|
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
console.warn("Blocked:", result.reason);
|
|
20
|
+
if (!gate.allowed) {
|
|
21
|
+
console.error("Deploy blocked:", gate.reason);
|
|
22
|
+
process.exit(1);
|
|
26
23
|
}
|
|
24
|
+
|
|
25
|
+
// runDeploy();
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
That's it. `
|
|
28
|
+
That's it. `deployGate()` performs the V1 Deploy Gate sequence against `production.deploy`: `evaluate()` calls `POST /v1-evaluate`, receives a permit when allowed, then `verifyPermit()` calls `POST /v1-verify-permit` before your deployment can run. A clean `deny` is returned as a block result — network / server / auth failures are thrown.
|
|
30
29
|
|
|
31
|
-
##
|
|
30
|
+
## Simple V1 surface
|
|
32
31
|
|
|
33
32
|
```ts
|
|
34
33
|
client.evaluate({ agent, action, context? })
|
|
35
|
-
// → { decision: "
|
|
34
|
+
// → { decision: "allow" | "deny" | "hold" | "escalate", permitId, reason, auditHash, timestamp }
|
|
36
35
|
|
|
37
36
|
client.verifyPermit({ permitId, agent?, action?, context? })
|
|
38
37
|
// → { verified, outcome, permitHash, timestamp }
|
|
38
|
+
|
|
39
|
+
client.deployGate({ agent?, action?, context? })
|
|
40
|
+
// defaults action to "production.deploy" and returns { allowed, reason, evidence }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`verifyPermit()` confirms a previously-issued permit server-side. Signed/offline permit artifacts never imply deployment authorization by themselves.
|
|
44
|
+
|
|
45
|
+
## Decision replay
|
|
46
|
+
|
|
47
|
+
Re-evaluate a recorded decision against its originally-pinned policy bundle and engine version. **Side-effect-free**: no audit row is written, no permit is minted (ADR-016 `mode: "replay"` sentinel). Useful for compliance review, regression-testing bundle changes, and post-incident investigation.
|
|
48
|
+
|
|
49
|
+
Two surfaces exist; pick the one that matches your call site:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// SDK-canonical (preferred for new code) — wire DECISION_CHANGED is normalized
|
|
53
|
+
// to POLICY_DRIFT; 409 replay_not_eligible returns ENGINE_DRIFT or BUNDLE_MISSING
|
|
54
|
+
// instead of throwing. You can always `switch` on the variance kind.
|
|
55
|
+
const r = await client.replay({ evaluationId: "dec_abc123" });
|
|
56
|
+
switch (r.varianceKind) {
|
|
57
|
+
case "NONE": /* replay agrees */ break;
|
|
58
|
+
case "POLICY_DRIFT": /* same envelope/bundle, different decision */ break;
|
|
59
|
+
case "ENVELOPE_DRIFT": /* recorded envelope no longer hashes */ break;
|
|
60
|
+
case "ENGINE_DRIFT": /* original engine retired beyond archival */ break;
|
|
61
|
+
case "BUNDLE_MISSING": /* original eval had no bundle pinned */ break;
|
|
62
|
+
case "CHAIN_TAMPER": /* audit-chain v5 detector tripped */ break;
|
|
63
|
+
}
|
|
39
64
|
```
|
|
40
65
|
|
|
41
|
-
|
|
66
|
+
```ts
|
|
67
|
+
// Raw-wire surface — variance values pass through verbatim
|
|
68
|
+
// (NONE / DECISION_CHANGED / ENVELOPE_DRIFT); 409 throws AtlaSentError
|
|
69
|
+
const result = await client.replayDecision("dec_abc123");
|
|
70
|
+
if (result.variance === "DECISION_CHANGED") {
|
|
71
|
+
console.warn(
|
|
72
|
+
`Decision ${result.decision_id} drifted: ` +
|
|
73
|
+
`${result.original_decision} → ${result.replay_decision}`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`/v1/decisions/:id/replay` is alpha per `atlasent-api/docs/STABLE_V2_PROMOTION.md` — wire shapes can shift without a deprecation cycle until it graduates to stable v1.
|
|
42
79
|
|
|
43
80
|
## CI deploy-gate pattern
|
|
44
81
|
|
|
@@ -49,11 +86,11 @@ const client = new AtlaSentClient({ apiKey: process.env.ATLASENT_API_KEY! });
|
|
|
49
86
|
|
|
50
87
|
const evaluation = await client.evaluate({
|
|
51
88
|
agent: "ci-deploy-bot",
|
|
52
|
-
action: "
|
|
89
|
+
action: "production.deploy",
|
|
53
90
|
context: { service: "billing-api", commit: process.env.GIT_SHA },
|
|
54
91
|
});
|
|
55
92
|
|
|
56
|
-
if (evaluation.decision !== "
|
|
93
|
+
if (evaluation.decision !== "allow") {
|
|
57
94
|
console.error("Deploy blocked:", evaluation.reason);
|
|
58
95
|
process.exit(1);
|
|
59
96
|
}
|
|
@@ -76,10 +113,10 @@ See [`examples/deploy-gate.ts`](./examples/deploy-gate.ts) for a complete CI-sha
|
|
|
76
113
|
|
|
77
114
|
```ts
|
|
78
115
|
new AtlaSentClient({
|
|
79
|
-
apiKey: "ask_live_...",
|
|
116
|
+
apiKey: "ask_live_...", // required
|
|
80
117
|
baseUrl: "https://api.atlasent.io", // default
|
|
81
|
-
timeoutMs: 10_000,
|
|
82
|
-
fetch: customFetch,
|
|
118
|
+
timeoutMs: 10_000, // default — per-request
|
|
119
|
+
fetch: customFetch, // default: globalThis.fetch
|
|
83
120
|
});
|
|
84
121
|
```
|
|
85
122
|
|
|
@@ -99,16 +136,16 @@ try {
|
|
|
99
136
|
}
|
|
100
137
|
```
|
|
101
138
|
|
|
102
|
-
| `err.code`
|
|
103
|
-
|
|
104
|
-
| `invalid_api_key`
|
|
105
|
-
| `forbidden`
|
|
106
|
-
| `rate_limited`
|
|
107
|
-
| `bad_request`
|
|
108
|
-
| `server_error`
|
|
109
|
-
| `timeout`
|
|
110
|
-
| `network`
|
|
111
|
-
| `bad_response`
|
|
139
|
+
| `err.code` | When it's thrown |
|
|
140
|
+
| ----------------- | ---------------------------------------- |
|
|
141
|
+
| `invalid_api_key` | HTTP 401 |
|
|
142
|
+
| `forbidden` | HTTP 403 |
|
|
143
|
+
| `rate_limited` | HTTP 429 (check `err.retryAfterMs`) |
|
|
144
|
+
| `bad_request` | HTTP 4xx (other than 401/403/429) |
|
|
145
|
+
| `server_error` | HTTP 5xx |
|
|
146
|
+
| `timeout` | `timeoutMs` exceeded |
|
|
147
|
+
| `network` | DNS / connection failure, fetch threw |
|
|
148
|
+
| `bad_response` | non-JSON body or missing required fields |
|
|
112
149
|
|
|
113
150
|
Every `AtlaSentError` carries `err.requestId` — the UUID the SDK sent as `X-Request-ID`, correlatable in your server logs.
|
|
114
151
|
|
|
@@ -122,8 +159,85 @@ Every `AtlaSentError` carries `err.requestId` — the UUID the SDK sent as `X-Re
|
|
|
122
159
|
## Requirements
|
|
123
160
|
|
|
124
161
|
- Node.js **20** or newer (native `fetch`, `AbortSignal.timeout`, `crypto.randomUUID`).
|
|
162
|
+
- **Browser:** Chrome 103+, Firefox 100+, Safari 16+, Edge 103+. The SDK uses
|
|
163
|
+
`AbortSignal.timeout` for per-request deadlines — the constructor throws a
|
|
164
|
+
clear `AtlaSentError(code: "network")` on runtimes that lack it so the failure
|
|
165
|
+
is loud rather than silent.
|
|
125
166
|
- TypeScript **5.0+** for best type-inference ergonomics (older is fine — types are plain interfaces).
|
|
126
167
|
|
|
168
|
+
## Hono middleware
|
|
169
|
+
|
|
170
|
+
Drop-in protection for [Hono](https://hono.dev) routes via the
|
|
171
|
+
`@atlasent/sdk/hono` subpath export (requires `hono` as a peer dep):
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { Hono } from "hono";
|
|
175
|
+
import { atlaSentGuard, atlaSentErrorHandler } from "@atlasent/sdk/hono";
|
|
176
|
+
|
|
177
|
+
const app = new Hono();
|
|
178
|
+
app.onError(atlaSentErrorHandler());
|
|
179
|
+
|
|
180
|
+
app.post(
|
|
181
|
+
"/deploy/:service",
|
|
182
|
+
atlaSentGuard({
|
|
183
|
+
action: (c) => `deploy_${c.req.param("service")}`,
|
|
184
|
+
agent: (c) => c.req.header("x-agent-id") ?? "anonymous",
|
|
185
|
+
context: async (c) => ({ commit: (await c.req.json()).commit }),
|
|
186
|
+
}),
|
|
187
|
+
(c) => c.json({ ok: true, permit: c.get("atlasent") }),
|
|
188
|
+
);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`atlaSentGuard` calls `protect()` under the hood — fail-closed
|
|
192
|
+
semantics. On allow it stashes a `Permit` on the context (key:
|
|
193
|
+
`"atlasent"`, override via `options.key`). On deny or transport error
|
|
194
|
+
it throws; `atlaSentErrorHandler` maps those to 403 / 503 responses
|
|
195
|
+
so every guarded route shares one error-handling path.
|
|
196
|
+
|
|
197
|
+
> **Upcoming migration:** after `@atlasent/enforce` reaches GA the
|
|
198
|
+
> guard API will change to accept a pre-constructed `Enforce` instance
|
|
199
|
+
> instead of per-route `action/agent/context` options. The current API
|
|
200
|
+
> is **not deprecated** until that ships. See the
|
|
201
|
+
> [CHANGELOG](./CHANGELOG.md) for the full before/after and
|
|
202
|
+
> [`contract/ENFORCE_PACK.md`](../contract/ENFORCE_PACK.md) for
|
|
203
|
+
> migration details.
|
|
204
|
+
|
|
205
|
+
## Browser support
|
|
206
|
+
|
|
207
|
+
The SDK is universal and works in modern browsers with no build-time changes:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { AtlaSentClient } from "@atlasent/sdk";
|
|
211
|
+
|
|
212
|
+
const client = new AtlaSentClient({
|
|
213
|
+
apiKey: import.meta.env.VITE_ATLASENT_API_KEY,
|
|
214
|
+
baseUrl: import.meta.env.VITE_ATLASENT_API_URL,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const result = await client.evaluate({
|
|
218
|
+
agent: currentUser.id,
|
|
219
|
+
action: "view_sensitive_report",
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Auth model in browser contexts.** Shipping a long-lived API key in a browser
|
|
224
|
+
bundle exposes it in DevTools and makes it replayable if exfiltrated. The
|
|
225
|
+
recommended options in increasing security order are:
|
|
226
|
+
|
|
227
|
+
- **Option B — browser-scoped keys (short term):** Create a read-only,
|
|
228
|
+
scope-restricted, IP-allowlisted key class from the AtlaSent console.
|
|
229
|
+
Safe for internal dashboards where you control the network. Not suitable
|
|
230
|
+
for public-facing apps.
|
|
231
|
+
- **Option A — session-token mode (recommended for atlasent-hosted surfaces):**
|
|
232
|
+
After SSO sign-in, the frontend obtains a short-lived (15-min) Bearer token
|
|
233
|
+
from `GET /v1-session/token` bound to the user's scopes and tenant. The SDK
|
|
234
|
+
handles token refresh transparently. See
|
|
235
|
+
[atlasent-api#144](https://github.com/AtlaSent-Systems-Inc/atlasent-api/issues/144).
|
|
236
|
+
|
|
237
|
+
The `User-Agent` header is set to `@atlasent/sdk/<version> browser` in browser
|
|
238
|
+
runtimes (browsers strip this header anyway — it's harmless) and
|
|
239
|
+
`@atlasent/sdk/<version> node/<node-version>` in Node.
|
|
240
|
+
|
|
127
241
|
## Related
|
|
128
242
|
|
|
129
243
|
- **Python SDK:** same repo, [`../python/`](../python/README.md). Wire-compatible.
|