@nomosprotocol/sdk 1.0.0 → 1.2.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.
Files changed (41) hide show
  1. package/README.md +98 -0
  2. package/dist/index.cjs +608 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +489 -0
  5. package/dist/index.d.ts +489 -4
  6. package/dist/index.js +575 -2
  7. package/dist/index.js.map +1 -1
  8. package/package.json +25 -7
  9. package/dist/errors.d.ts +0 -31
  10. package/dist/errors.d.ts.map +0 -1
  11. package/dist/errors.js +0 -54
  12. package/dist/errors.js.map +0 -1
  13. package/dist/http-client.d.ts +0 -14
  14. package/dist/http-client.d.ts.map +0 -1
  15. package/dist/http-client.js +0 -70
  16. package/dist/http-client.js.map +0 -1
  17. package/dist/index.d.ts.map +0 -1
  18. package/dist/nomos.d.ts +0 -13
  19. package/dist/nomos.d.ts.map +0 -1
  20. package/dist/nomos.js +0 -26
  21. package/dist/nomos.js.map +0 -1
  22. package/dist/resources/artifacts.d.ts +0 -22
  23. package/dist/resources/artifacts.d.ts.map +0 -1
  24. package/dist/resources/artifacts.js +0 -46
  25. package/dist/resources/artifacts.js.map +0 -1
  26. package/dist/resources/decisions.d.ts +0 -24
  27. package/dist/resources/decisions.d.ts.map +0 -1
  28. package/dist/resources/decisions.js +0 -53
  29. package/dist/resources/decisions.js.map +0 -1
  30. package/dist/resources/governance.d.ts +0 -30
  31. package/dist/resources/governance.d.ts.map +0 -1
  32. package/dist/resources/governance.js +0 -33
  33. package/dist/resources/governance.js.map +0 -1
  34. package/dist/types.d.ts +0 -117
  35. package/dist/types.d.ts.map +0 -1
  36. package/dist/types.js +0 -3
  37. package/dist/types.js.map +0 -1
  38. package/dist/util.d.ts +0 -4
  39. package/dist/util.d.ts.map +0 -1
  40. package/dist/util.js +0 -11
  41. package/dist/util.js.map +0 -1
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @nomosprotocol/sdk
2
+
3
+ Official TypeScript SDK for [NOMOS](https://www.nomosprotocol.com) — ask a public authority a question from your code and get back a deterministic, cryptographically verifiable verdict.
4
+
5
+ ```bash
6
+ npm install @nomosprotocol/sdk
7
+ ```
8
+
9
+ ## `nomos.can()` — the keyless primitive
10
+
11
+ Every public authority on the [NOMOS Exchange](https://www.nomosprotocol.com/exchange) is queryable with one call. No API key, no setup — this is the front door.
12
+
13
+ ```ts
14
+ import { nomos } from "@nomosprotocol/sdk";
15
+
16
+ const verdict = await nomos.can({
17
+ authority: "eu-ai-act-full-regulation-2024-1689",
18
+ ai_categorises_biometric_data_for_sensitive_attributes: true,
19
+ });
20
+
21
+ if (!verdict.allowed) {
22
+ console.log(verdict.verdict); // "DENIED"
23
+ console.log(verdict.reason); // why
24
+ console.log(verdict.query_id); // permanent transcript id
25
+ }
26
+ ```
27
+
28
+ Facts can be spread flat alongside `authority` (as above), or passed explicitly:
29
+
30
+ ```ts
31
+ const verdict = await nomos.can({
32
+ authority: "eu-ai-act-full-regulation-2024-1689",
33
+ inputs: { ai_categorises_biometric_data_for_sensitive_attributes: true },
34
+ action: "screen a new feature before launch", // optional, recorded in the transcript
35
+ });
36
+ ```
37
+
38
+ ### The verdict
39
+
40
+ `nomos.can()` always returns the authority's own verdict, verbatim — nothing invented, nothing paraphrased:
41
+
42
+ | Field | Type | Meaning |
43
+ |---|---|---|
44
+ | `allowed` | `boolean` | `true` only when `verdict === "AUTHORIZED"` |
45
+ | `verdict` | `"AUTHORIZED" \| "DENIED" \| "ESCALATED"` | The authority's own answer |
46
+ | `reason` | `string` | Human-readable explanation |
47
+ | `rule` | `string \| null` | The rule id that decided it, if any |
48
+ | `query_id` | `string` | Permanent, addressable transcript id |
49
+ | `url` | `string` | Path to the transcript's permalink page |
50
+ | `authority` | `{ id, title, standing }` | Which authority answered, and its standing |
51
+ | `missing_inputs` | `string[]` | Fields the authority needed but didn't get — present on `ESCALATED` |
52
+
53
+ `ESCALATED` means the authority needs more facts to decide — not a flat no. Check `missing_inputs` for what to supply.
54
+
55
+ Every call mints a permanent, cryptographically sealed transcript — open `query_id` any time at `nomosprotocol.com/queries/<query_id>` to replay exactly what was asked and answered, bound to the exact authority version that responded.
56
+
57
+ ### Options
58
+
59
+ ```ts
60
+ await nomos.can(
61
+ { authority: "...", ...facts },
62
+ {
63
+ baseUrl: "https://www.nomosprotocol.com", // override for local/staging use
64
+ signal: abortController.signal, // cancel the request
65
+ },
66
+ );
67
+ ```
68
+
69
+ ## `Nomos` — authenticated client
70
+
71
+ For private/custom artifacts, governance generation, and webhook verification, use the authenticated client:
72
+
73
+ ```ts
74
+ import { Nomos } from "@nomosprotocol/sdk";
75
+
76
+ const client = new Nomos("nms_live_..."); // or "nms_test_..." for test mode
77
+
78
+ const result = await client.decisions.verify({
79
+ artifact_id: "your_artifact_id",
80
+ decision_context: { /* facts */ },
81
+ });
82
+
83
+ for await (const artifact of client.artifacts.listAutoPaging()) {
84
+ console.log(artifact.id);
85
+ }
86
+
87
+ const event = await client.webhooks.constructEvent(payload, signatureHeader, webhookSecret);
88
+ ```
89
+
90
+ See [nomosprotocol.com/docs](https://www.nomosprotocol.com/docs) for full API reference, error types, and webhook event shapes.
91
+
92
+ ## Requirements
93
+
94
+ Node.js 18+. Ships as dual ESM/CJS with full TypeScript types.
95
+
96
+ ## License
97
+
98
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,608 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Nomos: () => Nomos,
24
+ NomosAPIError: () => NomosAPIError,
25
+ NomosAuthenticationError: () => NomosAuthenticationError,
26
+ NomosAuthorizationError: () => NomosAuthorizationError,
27
+ NomosError: () => NomosError,
28
+ NomosNetworkError: () => NomosNetworkError,
29
+ NomosNotFoundError: () => NomosNotFoundError,
30
+ NomosRateLimitError: () => NomosRateLimitError,
31
+ NomosValidationError: () => NomosValidationError,
32
+ NomosWebhookSignatureError: () => NomosWebhookSignatureError,
33
+ can: () => can,
34
+ nomos: () => nomos
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/errors.ts
39
+ var errors_exports = {};
40
+ __export(errors_exports, {
41
+ NomosAPIError: () => NomosAPIError,
42
+ NomosAuthenticationError: () => NomosAuthenticationError,
43
+ NomosAuthorizationError: () => NomosAuthorizationError,
44
+ NomosError: () => NomosError,
45
+ NomosNetworkError: () => NomosNetworkError,
46
+ NomosNotFoundError: () => NomosNotFoundError,
47
+ NomosRateLimitError: () => NomosRateLimitError,
48
+ NomosValidationError: () => NomosValidationError,
49
+ NomosWebhookSignatureError: () => NomosWebhookSignatureError
50
+ });
51
+ var NomosError = class extends Error {
52
+ constructor(message, code, status, requestId) {
53
+ super(message);
54
+ this.name = "NomosError";
55
+ this.code = code;
56
+ this.status = status;
57
+ this.requestId = requestId;
58
+ }
59
+ };
60
+ var NomosAuthenticationError = class extends NomosError {
61
+ constructor(message = "Invalid API key", requestId) {
62
+ super(message, "authentication_error", 401, requestId);
63
+ this.name = "NomosAuthenticationError";
64
+ }
65
+ };
66
+ var NomosAuthorizationError = class extends NomosError {
67
+ constructor(message = "Access denied", requestId) {
68
+ super(message, "authorization_error", 403, requestId);
69
+ this.name = "NomosAuthorizationError";
70
+ }
71
+ };
72
+ var NomosRateLimitError = class extends NomosError {
73
+ constructor(message = "Rate limit exceeded", retryAfter, requestId) {
74
+ super(message, "rate_limit_error", 429, requestId);
75
+ this.name = "NomosRateLimitError";
76
+ this.retryAfter = retryAfter;
77
+ }
78
+ };
79
+ var NomosValidationError = class extends NomosError {
80
+ constructor(message, details, requestId) {
81
+ super(message, "validation_error", 400, requestId);
82
+ this.name = "NomosValidationError";
83
+ this.details = details;
84
+ }
85
+ };
86
+ var NomosNotFoundError = class extends NomosError {
87
+ constructor(message, requestId) {
88
+ super(message, "not_found", 404, requestId);
89
+ this.name = "NomosNotFoundError";
90
+ }
91
+ };
92
+ var NomosAPIError = class extends NomosError {
93
+ constructor(message, status, requestId) {
94
+ super(message, "api_error", status, requestId);
95
+ this.name = "NomosAPIError";
96
+ }
97
+ };
98
+ var NomosNetworkError = class extends NomosError {
99
+ constructor(message) {
100
+ super(message, "network_error");
101
+ this.name = "NomosNetworkError";
102
+ }
103
+ };
104
+ var NomosWebhookSignatureError = class extends NomosError {
105
+ constructor(message) {
106
+ super(message, "webhook_signature_error", 400);
107
+ this.name = "NomosWebhookSignatureError";
108
+ }
109
+ };
110
+
111
+ // src/util.ts
112
+ async function sleep(ms) {
113
+ return new Promise((resolve) => setTimeout(resolve, ms));
114
+ }
115
+ function jitteredBackoff(attempt, baseMs = 500) {
116
+ const exp = Math.min(baseMs * 2 ** attempt, 16e3);
117
+ return exp * (0.75 + Math.random() * 0.5);
118
+ }
119
+ function isRetryable(status) {
120
+ return status === 429 || status >= 500;
121
+ }
122
+
123
+ // src/http-client.ts
124
+ var SDK_VERSION = "1.1.0";
125
+ var HttpClient = class {
126
+ constructor(opts) {
127
+ this.opts = opts;
128
+ }
129
+ async get(path, reqOpts) {
130
+ return this.request("GET", path, void 0, reqOpts);
131
+ }
132
+ async post(path, body, reqOpts) {
133
+ return this.request("POST", path, body, reqOpts);
134
+ }
135
+ async request(method, path, body, reqOpts, attempt = 0) {
136
+ const apiKey = reqOpts?.apiKey ?? this.opts.apiKey;
137
+ const timeout = reqOpts?.timeout ?? this.opts.timeout;
138
+ const maxRetries = reqOpts?.maxRetries ?? this.opts.maxRetries;
139
+ const url = `${this.opts.baseUrl}${path}`;
140
+ const controller = new AbortController();
141
+ const timer = setTimeout(() => controller.abort(), timeout);
142
+ const headers = {
143
+ "Content-Type": "application/json",
144
+ "X-Nomos-Api-Key": apiKey,
145
+ "User-Agent": `nomos-node/${SDK_VERSION}`,
146
+ "NOMOS-SDK-Version": SDK_VERSION
147
+ };
148
+ if (reqOpts?.idempotencyKey) {
149
+ headers["Idempotency-Key"] = reqOpts.idempotencyKey;
150
+ }
151
+ let res;
152
+ try {
153
+ res = await fetch(url, {
154
+ method,
155
+ signal: controller.signal,
156
+ headers,
157
+ body: body != null ? JSON.stringify(body) : void 0
158
+ });
159
+ } catch (err) {
160
+ clearTimeout(timer);
161
+ if (err.name === "AbortError") {
162
+ throw new NomosNetworkError(`Request timed out after ${timeout}ms`);
163
+ }
164
+ throw new NomosNetworkError(err.message ?? "Network error");
165
+ } finally {
166
+ clearTimeout(timer);
167
+ }
168
+ const requestId = res.headers.get("x-request-id") ?? "";
169
+ const rawHeaders = {};
170
+ res.headers.forEach((value, key) => {
171
+ rawHeaders[key] = value;
172
+ });
173
+ const lastResponse = {
174
+ requestId,
175
+ statusCode: res.status,
176
+ headers: rawHeaders
177
+ };
178
+ if (res.ok) {
179
+ const data = await res.json();
180
+ return { data, lastResponse };
181
+ }
182
+ if (isRetryable(res.status) && attempt < maxRetries) {
183
+ const retryAfter = res.headers.get("retry-after");
184
+ const delay = retryAfter ? parseInt(retryAfter, 10) * 1e3 : jitteredBackoff(attempt);
185
+ await sleep(delay);
186
+ return this.request(method, path, body, reqOpts, attempt + 1);
187
+ }
188
+ let errBody = {};
189
+ try {
190
+ errBody = await res.json();
191
+ } catch {
192
+ }
193
+ const message = errBody?.error ?? errBody?.message ?? res.statusText;
194
+ switch (res.status) {
195
+ case 401:
196
+ throw new NomosAuthenticationError(message, requestId);
197
+ case 403:
198
+ throw new NomosAuthorizationError(message, requestId);
199
+ case 404:
200
+ throw new NomosNotFoundError(message, requestId);
201
+ case 422:
202
+ case 400:
203
+ throw new NomosValidationError(message, errBody?.details, requestId);
204
+ case 429:
205
+ throw new NomosRateLimitError(
206
+ message,
207
+ res.headers.get("retry-after") ? parseInt(res.headers.get("retry-after"), 10) : void 0,
208
+ requestId
209
+ );
210
+ default:
211
+ throw new NomosAPIError(message, res.status, requestId);
212
+ }
213
+ }
214
+ };
215
+
216
+ // src/resources/decisions.ts
217
+ var Decisions = class {
218
+ constructor(http) {
219
+ this.http = http;
220
+ }
221
+ /**
222
+ * Verify whether an AI decision is permitted under a governance artifact.
223
+ *
224
+ * Public demo artifacts (`pub_*`) work without an API key.
225
+ * Custom artifacts require a valid API key.
226
+ *
227
+ * @example
228
+ * const result = await nomos.decisions.verify({
229
+ * artifact_id: 'pub_lending_v1',
230
+ * decision_context: { credit_score: 720, loan_amount: 50000 },
231
+ * });
232
+ *
233
+ * if (!result.allowed) {
234
+ * throw new Error(`Blocked: ${result.verdict_description}`);
235
+ * }
236
+ *
237
+ * // Debugging / tracing
238
+ * console.log(result.lastResponse.requestId);
239
+ *
240
+ * @example
241
+ * // Per-request options
242
+ * const result = await nomos.decisions.verify(params, {
243
+ * idempotencyKey: `verify-${transactionId}`,
244
+ * timeout: 5000,
245
+ * });
246
+ */
247
+ async verify(params, opts) {
248
+ const { artifact_id, decision_context, proposed_decision, correlation_id } = params;
249
+ const isPublic = artifact_id.startsWith("pub_");
250
+ let httpResp;
251
+ if (isPublic) {
252
+ httpResp = await this.http.post("/api/v1/verify", {
253
+ artifact_id,
254
+ decision: proposed_decision ?? "evaluate",
255
+ inputs: decision_context
256
+ }, opts);
257
+ } else {
258
+ httpResp = await this.http.post("/api/v1/verify-decision", {
259
+ artifact_id,
260
+ decision: proposed_decision ?? "evaluate",
261
+ inputs: decision_context,
262
+ caller: { correlation_id: correlation_id ?? globalThis.crypto.randomUUID() }
263
+ }, opts);
264
+ }
265
+ const raw = httpResp.data;
266
+ return {
267
+ id: raw.id ?? `req_${raw.execution_id ?? globalThis.crypto.randomUUID()}`,
268
+ object: raw.object ?? "decision",
269
+ artifact_id: raw.artifact_id ?? artifact_id,
270
+ artifact_name: raw.artifact_name ?? "",
271
+ allowed: raw.allowed ?? false,
272
+ verdict: raw.verdict ?? (raw.outcome === "auto_approved" ? "approved" : raw.outcome === "auto_rejected" ? "rejected" : "escalated"),
273
+ rule: raw.rule,
274
+ evaluation: raw.evaluation ?? { rules_evaluated: raw.rules_evaluated ?? 0, rules_matched: 0, rules_expired: 0, conflicts_resolved: 0 },
275
+ confidence: raw.confidence ?? { tier: raw.artifact_confidence ?? "DECLARED", score: 1, description: "" },
276
+ audit: raw.audit ?? { hash: raw.audit_hash ?? raw.audit_record ?? "", timestamp: raw.ts ?? (/* @__PURE__ */ new Date()).toISOString() },
277
+ performance: raw.performance ?? { latency_ms: raw.latency_ms ?? 0 },
278
+ schema_version: raw.schema_version ?? "1.0.0",
279
+ verdict_description: raw.verdict_description ?? "",
280
+ incomplete_inputs: raw.incomplete_inputs,
281
+ paths_to_approval: raw.paths_to_approval,
282
+ lastResponse: httpResp.lastResponse
283
+ };
284
+ }
285
+ };
286
+
287
+ // src/resources/artifacts.ts
288
+ var Artifacts = class {
289
+ constructor(http) {
290
+ this.http = http;
291
+ }
292
+ /**
293
+ * Retrieve a single artifact by ID.
294
+ *
295
+ * @example
296
+ * const artifact = await nomos.artifacts.retrieve('loan_approval_v1');
297
+ * console.log(artifact.ari_score, artifact.autonomy_band);
298
+ * console.log(artifact.lastResponse.requestId); // for support tickets
299
+ */
300
+ async retrieve(artifactId, opts) {
301
+ const { data, lastResponse } = await this.http.get(
302
+ `/api/exchange/artifacts/${encodeURIComponent(artifactId)}`,
303
+ opts
304
+ );
305
+ return { ...data, lastResponse };
306
+ }
307
+ /**
308
+ * List published artifacts on the Exchange with optional filters.
309
+ *
310
+ * @example
311
+ * const { data, total } = await nomos.artifacts.list({ band: 'autonomous', limit: 10 });
312
+ *
313
+ * @example
314
+ * // With per-request options
315
+ * const page = await nomos.artifacts.list({ limit: 20 }, { timeout: 5000 });
316
+ */
317
+ async list(params = {}, opts) {
318
+ const qs = new URLSearchParams();
319
+ if (params.band) qs.set("band", params.band);
320
+ if (params.domain) qs.set("domain", params.domain);
321
+ if (params.jurisdiction) qs.set("jurisdiction", params.jurisdiction);
322
+ if (params.sort) qs.set("sort", params.sort);
323
+ if (params.limit != null) qs.set("limit", String(params.limit));
324
+ if (params.offset != null) qs.set("offset", String(params.offset));
325
+ const query = qs.toString();
326
+ const { data: raw, lastResponse } = await this.http.get(
327
+ `/api/exchange/artifacts${query ? `?${query}` : ""}`,
328
+ opts
329
+ );
330
+ const normalized = {
331
+ data: Array.isArray(raw) ? raw : raw.artifacts ?? raw.data ?? [],
332
+ total: raw.total ?? (Array.isArray(raw) ? raw.length : 0),
333
+ limit: params.limit ?? 20,
334
+ offset: params.offset ?? 0
335
+ };
336
+ return { ...normalized, lastResponse };
337
+ }
338
+ /**
339
+ * Auto-paginating iterator — yields every artifact across all pages.
340
+ * Stops automatically when the last page is reached.
341
+ *
342
+ * @example
343
+ * for await (const artifact of nomos.artifacts.listAutoPaging({ band: 'autonomous' })) {
344
+ * console.log(artifact.title, artifact.ari_score);
345
+ * }
346
+ */
347
+ async *listAutoPaging(params = {}, opts) {
348
+ let offset = params.offset ?? 0;
349
+ const limit = params.limit ?? 20;
350
+ while (true) {
351
+ const page = await this.list({ ...params, offset, limit }, opts);
352
+ for (const item of page.data) yield item;
353
+ if (page.data.length < limit || offset + page.data.length >= page.total) break;
354
+ offset += page.data.length;
355
+ }
356
+ }
357
+ /**
358
+ * Retrieve the declared input schema for a sealed artifact.
359
+ *
360
+ * Returns every field the artifact's rules reference — name, type, whether it's
361
+ * required, and a ready-to-use example_inputs object. Use this to build forms,
362
+ * validate payloads, or generate API docs without reading the rules manually.
363
+ *
364
+ * Requires an API key (artifact schemas are not public).
365
+ *
366
+ * @example
367
+ * const schema = await nomos.artifacts.schema('art_abc123');
368
+ * console.log(schema.required_fields); // ['credit_score', 'loan_amount']
369
+ * console.log(schema.example_inputs); // { credit_score: 0, loan_amount: 0 }
370
+ */
371
+ async schema(artifactId, opts) {
372
+ const { data, lastResponse } = await this.http.get(
373
+ `/api/v1/artifacts/${encodeURIComponent(artifactId)}/schema`,
374
+ opts
375
+ );
376
+ return { ...data, lastResponse };
377
+ }
378
+ };
379
+
380
+ // src/resources/governance.ts
381
+ var Governance = class {
382
+ constructor(http) {
383
+ this.http = http;
384
+ }
385
+ /**
386
+ * Generate a sealed .nomos governance artifact from policy documents
387
+ * and optional historical decision logs.
388
+ *
389
+ * @example
390
+ * const artifact = await nomos.governance.generate({
391
+ * policies: [policyText],
392
+ * decision_logs: historicalRows,
393
+ * domain: 'loan_approval',
394
+ * });
395
+ * console.log(artifact.ari_score, artifact.seal_hash);
396
+ */
397
+ async generate(params, opts) {
398
+ const { data, lastResponse } = await this.http.post(
399
+ "/api/v1/generate-governance",
400
+ params,
401
+ opts
402
+ );
403
+ return { ...data, lastResponse };
404
+ }
405
+ /**
406
+ * Detect contradictions and conflicts in a rule set before sealing.
407
+ *
408
+ * @example
409
+ * const report = await nomos.governance.detectContradictions({ rules });
410
+ * if (!report.clean) {
411
+ * console.log(report.contradictions);
412
+ * }
413
+ */
414
+ async detectContradictions(params, opts) {
415
+ const { data, lastResponse } = await this.http.post(
416
+ "/api/v1/detect-contradictions",
417
+ params,
418
+ opts
419
+ );
420
+ return { ...data, lastResponse };
421
+ }
422
+ };
423
+
424
+ // src/resources/webhooks.ts
425
+ function hexToBytes(hex) {
426
+ if (hex.length % 2 !== 0) throw new NomosWebhookSignatureError("Invalid hex encoding in signature");
427
+ const bytes = new Uint8Array(hex.length / 2);
428
+ for (let i = 0; i < hex.length; i += 2) {
429
+ const byte = parseInt(hex.slice(i, i + 2), 16);
430
+ if (isNaN(byte)) throw new NomosWebhookSignatureError("Invalid hex encoding in signature");
431
+ bytes[i / 2] = byte;
432
+ }
433
+ return bytes;
434
+ }
435
+ function safeEqual(a, b) {
436
+ if (a.length !== b.length) return false;
437
+ let diff = 0;
438
+ for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
439
+ return diff === 0;
440
+ }
441
+ var Webhooks = class {
442
+ /**
443
+ * Verify a NOMOS webhook signature and return the parsed event.
444
+ *
445
+ * Signature header format: `t=<unix_ts>,v1=<hmac_sha256_hex>`
446
+ * Signed payload: `<timestamp>.<raw_body>`
447
+ *
448
+ * @example
449
+ * // Express
450
+ * app.post('/webhooks/nomos', express.raw({ type: 'application/json' }), async (req, res) => {
451
+ * const event = await nomos.webhooks.constructEvent(
452
+ * req.body,
453
+ * req.headers['nomos-signature'] as string,
454
+ * process.env.NOMOS_WEBHOOK_SECRET!,
455
+ * );
456
+ * console.log(event.type, event.data);
457
+ * res.sendStatus(200);
458
+ * });
459
+ *
460
+ * @throws {NomosWebhookSignatureError} if signature is invalid, expired, or malformed
461
+ */
462
+ async constructEvent(payload, signature, secret, toleranceSeconds = 300) {
463
+ const body = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
464
+ const parts = {};
465
+ for (const part of signature.split(",")) {
466
+ const eq = part.indexOf("=");
467
+ if (eq > -1) parts[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
468
+ }
469
+ if (!parts["t"] || !parts["v1"]) {
470
+ throw new NomosWebhookSignatureError(
471
+ 'Invalid signature format \u2014 expected "t=<timestamp>,v1=<hmac>"'
472
+ );
473
+ }
474
+ const timestamp = parseInt(parts["t"], 10);
475
+ if (isNaN(timestamp)) {
476
+ throw new NomosWebhookSignatureError("Invalid timestamp in webhook signature");
477
+ }
478
+ const ageSecs = Math.abs(Date.now() / 1e3 - timestamp);
479
+ if (toleranceSeconds > 0 && ageSecs > toleranceSeconds) {
480
+ throw new NomosWebhookSignatureError(
481
+ `Webhook timestamp too old (${Math.round(ageSecs)}s > ${toleranceSeconds}s tolerance). Ensure your server clock is synced and the event is not being replayed.`
482
+ );
483
+ }
484
+ const enc = new TextEncoder();
485
+ const keyMaterial = await globalThis.crypto.subtle.importKey(
486
+ "raw",
487
+ enc.encode(secret),
488
+ { name: "HMAC", hash: "SHA-256" },
489
+ false,
490
+ ["sign"]
491
+ );
492
+ const signedPayload = `${parts["t"]}.${body}`;
493
+ const expectedBuf = await globalThis.crypto.subtle.sign(
494
+ "HMAC",
495
+ keyMaterial,
496
+ enc.encode(signedPayload)
497
+ );
498
+ const expected = new Uint8Array(expectedBuf);
499
+ const received = hexToBytes(parts["v1"]);
500
+ if (!safeEqual(expected, received)) {
501
+ throw new NomosWebhookSignatureError(
502
+ "Webhook signature mismatch \u2014 verify your NOMOS_WEBHOOK_SECRET matches the one in Studio."
503
+ );
504
+ }
505
+ try {
506
+ return JSON.parse(body);
507
+ } catch {
508
+ throw new NomosWebhookSignatureError("Webhook payload is not valid JSON");
509
+ }
510
+ }
511
+ /**
512
+ * Verify a webhook signature without parsing the event.
513
+ * Returns `true` if valid, `false` if not (never throws).
514
+ *
515
+ * @example
516
+ * const valid = await nomos.webhooks.verifySignature(rawBody, sig, secret);
517
+ * if (!valid) return res.sendStatus(400);
518
+ */
519
+ async verifySignature(payload, signature, secret, toleranceSeconds = 300) {
520
+ try {
521
+ await this.constructEvent(payload, signature, secret, toleranceSeconds);
522
+ return true;
523
+ } catch {
524
+ return false;
525
+ }
526
+ }
527
+ };
528
+
529
+ // src/nomos.ts
530
+ var DEFAULT_BASE_URL = "https://nomos.ai";
531
+ var DEFAULT_MAX_RETRIES = 3;
532
+ var DEFAULT_TIMEOUT = 3e4;
533
+ var Nomos = class {
534
+ static {
535
+ this.errors = errors_exports;
536
+ }
537
+ constructor(apiKey, config = {}) {
538
+ if (!apiKey || typeof apiKey !== "string") {
539
+ throw new NomosAuthenticationError(
540
+ 'A valid API key is required. Pass it as the first argument: new Nomos("nms_live_...")'
541
+ );
542
+ }
543
+ this.mode = apiKey.startsWith("nms_test_") ? "test" : "live";
544
+ const http = new HttpClient({
545
+ apiKey,
546
+ baseUrl: (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, ""),
547
+ maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
548
+ timeout: config.timeout ?? DEFAULT_TIMEOUT
549
+ });
550
+ this.decisions = new Decisions(http);
551
+ this.artifacts = new Artifacts(http);
552
+ this.governance = new Governance(http);
553
+ this.webhooks = new Webhooks();
554
+ }
555
+ };
556
+
557
+ // src/can.ts
558
+ var DEFAULT_EXCHANGE_BASE = "https://www.nomosprotocol.com";
559
+ async function can(params, opts) {
560
+ const { authority, action, inputs, ...flat } = params;
561
+ if (!authority || typeof authority !== "string") {
562
+ throw new Error("can(): `authority` (an Exchange artifact id or slug) is required.");
563
+ }
564
+ const facts = inputs && typeof inputs === "object" ? inputs : flat;
565
+ const base = (opts?.baseUrl ?? DEFAULT_EXCHANGE_BASE).replace(/\/$/, "");
566
+ const url = `${base}/api/exchange/artifacts/${encodeURIComponent(authority)}/query`;
567
+ const res = await fetch(url, {
568
+ method: "POST",
569
+ headers: { "Content-Type": "application/json" },
570
+ body: JSON.stringify({ inputs: facts, action }),
571
+ signal: opts?.signal
572
+ });
573
+ const data = await res.json().catch(() => null);
574
+ if (!res.ok) {
575
+ throw new Error(data?.error ?? `NOMOS query failed (HTTP ${res.status})`);
576
+ }
577
+ return {
578
+ allowed: data.verdict === "AUTHORIZED",
579
+ verdict: data.verdict,
580
+ reason: data.verdict_description,
581
+ rule: data.matched_rule_id ?? null,
582
+ query_id: data.query_id,
583
+ url: data.url,
584
+ authority: {
585
+ id: data.artifact?.id ?? authority,
586
+ title: data.artifact?.title ?? "",
587
+ standing: data.artifact?.standing ?? ""
588
+ },
589
+ missing_inputs: data.missing_inputs ?? []
590
+ };
591
+ }
592
+ var nomos = { can };
593
+ // Annotate the CommonJS export names for ESM import in node:
594
+ 0 && (module.exports = {
595
+ Nomos,
596
+ NomosAPIError,
597
+ NomosAuthenticationError,
598
+ NomosAuthorizationError,
599
+ NomosError,
600
+ NomosNetworkError,
601
+ NomosNotFoundError,
602
+ NomosRateLimitError,
603
+ NomosValidationError,
604
+ NomosWebhookSignatureError,
605
+ can,
606
+ nomos
607
+ });
608
+ //# sourceMappingURL=index.cjs.map