@atlasent/sdk 1.5.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,32 +13,34 @@ import { AtlaSentClient } from "@atlasent/sdk";
13
13
 
14
14
  const client = new AtlaSentClient({ apiKey: process.env.ATLASENT_API_KEY! });
15
15
 
16
- const result = await client.evaluate({
17
- agent: "clinical-data-agent",
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 (result.decision === "ALLOW") {
23
- // execute the action
24
- } else {
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. `evaluate()` calls the AtlaSent policy engine, generates a hash-chained audit entry (21 CFR Part 11 / GxP-ready), and returns a result you branch on. A clean `DENY` is **not** thrown — network / server / auth failures are.
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
- ## Two methods, that's the whole surface
30
+ ## Simple V1 surface
32
31
 
33
32
  ```ts
34
33
  client.evaluate({ agent, action, context? })
35
- // → { decision: "ALLOW" | "DENY", permitId, reason, auditHash, timestamp }
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 }
39
41
  ```
40
42
 
41
- `verifyPermit()` confirms a previously-issued permit end-to-end use it as a second-factor gate (e.g., in a CI deploy pipeline before side-effects run).
43
+ `verifyPermit()` confirms a previously-issued permit server-side. Signed/offline permit artifacts never imply deployment authorization by themselves.
42
44
 
43
45
  ## CI deploy-gate pattern
44
46
 
@@ -49,11 +51,11 @@ const client = new AtlaSentClient({ apiKey: process.env.ATLASENT_API_KEY! });
49
51
 
50
52
  const evaluation = await client.evaluate({
51
53
  agent: "ci-deploy-bot",
52
- action: "deploy_to_production",
54
+ action: "production.deploy",
53
55
  context: { service: "billing-api", commit: process.env.GIT_SHA },
54
56
  });
55
57
 
56
- if (evaluation.decision !== "ALLOW") {
58
+ if (evaluation.decision !== "allow") {
57
59
  console.error("Deploy blocked:", evaluation.reason);
58
60
  process.exit(1);
59
61
  }
@@ -76,10 +78,10 @@ See [`examples/deploy-gate.ts`](./examples/deploy-gate.ts) for a complete CI-sha
76
78
 
77
79
  ```ts
78
80
  new AtlaSentClient({
79
- apiKey: "ask_live_...", // required
81
+ apiKey: "ask_live_...", // required
80
82
  baseUrl: "https://api.atlasent.io", // default
81
- timeoutMs: 10_000, // default — per-request
82
- fetch: customFetch, // default: globalThis.fetch
83
+ timeoutMs: 10_000, // default — per-request
84
+ fetch: customFetch, // default: globalThis.fetch
83
85
  });
84
86
  ```
85
87
 
@@ -99,16 +101,16 @@ try {
99
101
  }
100
102
  ```
101
103
 
102
- | `err.code` | When it's thrown |
103
- |--------------------|---------------------------------------------------------|
104
- | `invalid_api_key` | HTTP 401 |
105
- | `forbidden` | HTTP 403 |
106
- | `rate_limited` | HTTP 429 (check `err.retryAfterMs`) |
107
- | `bad_request` | HTTP 4xx (other than 401/403/429) |
108
- | `server_error` | HTTP 5xx |
109
- | `timeout` | `timeoutMs` exceeded |
110
- | `network` | DNS / connection failure, fetch threw |
111
- | `bad_response` | non-JSON body or missing required fields |
104
+ | `err.code` | When it's thrown |
105
+ | ----------------- | ---------------------------------------- |
106
+ | `invalid_api_key` | HTTP 401 |
107
+ | `forbidden` | HTTP 403 |
108
+ | `rate_limited` | HTTP 429 (check `err.retryAfterMs`) |
109
+ | `bad_request` | HTTP 4xx (other than 401/403/429) |
110
+ | `server_error` | HTTP 5xx |
111
+ | `timeout` | `timeoutMs` exceeded |
112
+ | `network` | DNS / connection failure, fetch threw |
113
+ | `bad_response` | non-JSON body or missing required fields |
112
114
 
113
115
  Every `AtlaSentError` carries `err.requestId` — the UUID the SDK sent as `X-Request-ID`, correlatable in your server logs.
114
116
 
@@ -122,8 +124,85 @@ Every `AtlaSentError` carries `err.requestId` — the UUID the SDK sent as `X-Re
122
124
  ## Requirements
123
125
 
124
126
  - Node.js **20** or newer (native `fetch`, `AbortSignal.timeout`, `crypto.randomUUID`).
127
+ - **Browser:** Chrome 103+, Firefox 100+, Safari 16+, Edge 103+. The SDK uses
128
+ `AbortSignal.timeout` for per-request deadlines — the constructor throws a
129
+ clear `AtlaSentError(code: "network")` on runtimes that lack it so the failure
130
+ is loud rather than silent.
125
131
  - TypeScript **5.0+** for best type-inference ergonomics (older is fine — types are plain interfaces).
126
132
 
133
+ ## Hono middleware
134
+
135
+ Drop-in protection for [Hono](https://hono.dev) routes via the
136
+ `@atlasent/sdk/hono` subpath export (requires `hono` as a peer dep):
137
+
138
+ ```ts
139
+ import { Hono } from "hono";
140
+ import { atlaSentGuard, atlaSentErrorHandler } from "@atlasent/sdk/hono";
141
+
142
+ const app = new Hono();
143
+ app.onError(atlaSentErrorHandler());
144
+
145
+ app.post(
146
+ "/deploy/:service",
147
+ atlaSentGuard({
148
+ action: (c) => `deploy_${c.req.param("service")}`,
149
+ agent: (c) => c.req.header("x-agent-id") ?? "anonymous",
150
+ context: async (c) => ({ commit: (await c.req.json()).commit }),
151
+ }),
152
+ (c) => c.json({ ok: true, permit: c.get("atlasent") }),
153
+ );
154
+ ```
155
+
156
+ `atlaSentGuard` calls `protect()` under the hood — fail-closed
157
+ semantics. On allow it stashes a `Permit` on the context (key:
158
+ `"atlasent"`, override via `options.key`). On deny or transport error
159
+ it throws; `atlaSentErrorHandler` maps those to 403 / 503 responses
160
+ so every guarded route shares one error-handling path.
161
+
162
+ > **Upcoming migration:** after `@atlasent/enforce` reaches GA the
163
+ > guard API will change to accept a pre-constructed `Enforce` instance
164
+ > instead of per-route `action/agent/context` options. The current API
165
+ > is **not deprecated** until that ships. See the
166
+ > [CHANGELOG](./CHANGELOG.md) for the full before/after and
167
+ > [`contract/ENFORCE_PACK.md`](../contract/ENFORCE_PACK.md) for
168
+ > migration details.
169
+
170
+ ## Browser support
171
+
172
+ The SDK is universal and works in modern browsers with no build-time changes:
173
+
174
+ ```ts
175
+ import { AtlaSentClient } from "@atlasent/sdk";
176
+
177
+ const client = new AtlaSentClient({
178
+ apiKey: import.meta.env.VITE_ATLASENT_API_KEY,
179
+ baseUrl: import.meta.env.VITE_ATLASENT_API_URL,
180
+ });
181
+
182
+ const result = await client.evaluate({
183
+ agent: currentUser.id,
184
+ action: "view_sensitive_report",
185
+ });
186
+ ```
187
+
188
+ **Auth model in browser contexts.** Shipping a long-lived API key in a browser
189
+ bundle exposes it in DevTools and makes it replayable if exfiltrated. The
190
+ recommended options in increasing security order are:
191
+
192
+ - **Option B — browser-scoped keys (short term):** Create a read-only,
193
+ scope-restricted, IP-allowlisted key class from the AtlaSent console.
194
+ Safe for internal dashboards where you control the network. Not suitable
195
+ for public-facing apps.
196
+ - **Option A — session-token mode (recommended for atlasent-hosted surfaces):**
197
+ After SSO sign-in, the frontend obtains a short-lived (15-min) Bearer token
198
+ from `GET /v1-session/token` bound to the user's scopes and tenant. The SDK
199
+ handles token refresh transparently. See
200
+ [atlasent-api#144](https://github.com/AtlaSent-Systems-Inc/atlasent-api/issues/144).
201
+
202
+ The `User-Agent` header is set to `@atlasent/sdk/<version> browser` in browser
203
+ runtimes (browsers strip this header anyway — it's harmless) and
204
+ `@atlasent/sdk/<version> node/<node-version>` in Node.
205
+
127
206
  ## Related
128
207
 
129
208
  - **Python SDK:** same repo, [`../python/`](../python/README.md). Wire-compatible.