@nomosprotocol/sdk 1.2.1 → 1.3.1

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
@@ -1,14 +1,24 @@
1
1
  # @nomosprotocol/sdk
2
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.
3
+ Official TypeScript SDK for [NOMOS](https://www.nomosprotocol.com) — ask "can I do this?" from your code and get back a deterministic, cryptographically verifiable verdict.
4
4
 
5
5
  ```bash
6
6
  npm install @nomosprotocol/sdk
7
7
  ```
8
8
 
9
- ## `nomos.can()` — the keyless primitive
9
+ ## `can()` — one function, three ways to ask
10
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.
11
+ Same pattern as Stripe's PaymentIntents API: one function, routed entirely by what you pass it, one verdict vocabulary back regardless of which one ran.
12
+
13
+ | You ask about... | Call it as | Needs an API key? |
14
+ |---|---|---|
15
+ | A public authority on the [NOMOS Exchange](https://www.nomosprotocol.com/exchange) | `nomos.can({ authority, ...facts })` | No |
16
+ | Your own sealed artifact | `client.can({ artifact_id, ...facts })` | Yes |
17
+ | An artifact someone handed you directly — NOMOS has never seen it | `nomos.can({ artifact, key_certs, root_public_key_pem, ...facts })` | No |
18
+
19
+ ### Public Exchange authority (keyless)
20
+
21
+ No API key, no setup — this is the front door.
12
22
 
13
23
  ```ts
14
24
  import { nomos } from "@nomosprotocol/sdk";
@@ -35,9 +45,44 @@ const verdict = await nomos.can({
35
45
  });
36
46
  ```
37
47
 
48
+ A paid authority you haven't purchased throws `NomosPaymentRequiredError` (with price + purchase URL attached) instead of a generic failure — NOMOS never auto-charges on your behalf. See [Error handling](#error-handling).
49
+
50
+ ### Your own artifact (authenticated)
51
+
52
+ Same function, same verdict shape — swap `authority` for `artifact_id` and use the authenticated client:
53
+
54
+ ```ts
55
+ import { Nomos } from "@nomosprotocol/sdk";
56
+
57
+ const client = new Nomos("nms_live_..."); // or "nms_test_..." for test mode
58
+
59
+ const verdict = await client.can({
60
+ artifact_id: "loan_approval_v1",
61
+ credit_score: 720,
62
+ loan_amount: 50_000,
63
+ });
64
+ ```
65
+
66
+ ### Chain-of-trust (keyless, prototype)
67
+
68
+ For an artifact you received directly — from an agent, another system, anywhere — that NOMOS has never seen or registered. You supply the artifact, the key certificates presented alongside it, and the root public key **you've** independently decided to trust (there's deliberately no default). NOMOS verifies the chain resolves to that root, then evaluates the artifact's own rules against your facts — one call, no prior lookup:
69
+
70
+ ```ts
71
+ const verdict = await nomos.can({
72
+ artifact: theArtifactYouWereHanded,
73
+ key_certs: theCertificatesTheyIncluded,
74
+ root_public_key_pem: yourPinnedRootKey,
75
+ credit_score: 720,
76
+ });
77
+ ```
78
+
79
+ If the chain doesn't resolve — unrecognized issuer, revoked key, tampered artifact — this throws `NomosIssuerNotTrustedError` rather than a fake verdict; "the rules say no" and "we never established who's asking" are different claims that call for different next steps. See [Error handling](#error-handling).
80
+
81
+ This rail is a working **prototype**, not yet a numbered NOMOS spec — the primitive is in production use but hasn't been through a second independent implementation.
82
+
38
83
  ### The verdict
39
84
 
40
- `nomos.can()` always returns the authority's own verdict, verbatim — nothing invented, nothing paraphrased:
85
+ `can()` always returns the authority's own verdict, verbatim — nothing invented, nothing paraphrased — normalized into the same shape regardless of which rail answered:
41
86
 
42
87
  | Field | Type | Meaning |
43
88
  |---|---|---|
@@ -45,27 +90,44 @@ const verdict = await nomos.can({
45
90
  | `verdict` | `"AUTHORIZED" \| "DENIED" \| "ESCALATED"` | The authority's own answer |
46
91
  | `reason` | `string` | Human-readable explanation |
47
92
  | `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 |
93
+ | `query_id` | `string` | Permanent transcript id — Exchange rail only; absent (not faked) elsewhere |
94
+ | `url` | `string` | Path to the transcript's permalink page — same availability as `query_id` |
95
+ | `authority` | `{ id, title, standing, attestation? }` | Which authority answered, its standing, and — if a regulator/auditor/issuer has co-signed this exact version — who, in what role, and their statement |
96
+ | `version` | `string` | The artifact's own declared `meta.version`, when it has one |
51
97
  | `missing_inputs` | `string[]` | Fields the authority needed but didn't get — present on `ESCALATED` |
98
+ | `obligations` | `Array<{ kind, rule_id, rule_name }>` | Notify/log/flag-style duties that fired alongside the verdict — can accompany an `AUTHORIZED` result, not just a denial |
99
+ | `revoked` | `boolean` | `true` only if the issuer has revoked this exact sealed version — forces `verdict` to `DENIED` regardless of what the rules say. Absent (not `false`) when revocation doesn't apply here |
100
+ | `revocation_reason` | `string` | Present only alongside `revoked: true` |
101
+ | `max_age` | `number` | Recommended seconds to hold this verdict before re-checking — advisory, absent when not structurally revocable |
102
+ | `audit` | `{ hash, timestamp }` | Present only on the private-artifact rail — a hash you can store for compliance |
52
103
 
53
104
  `ESCALATED` means the authority needs more facts to decide — not a flat no. Check `missing_inputs` for what to supply.
54
105
 
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.
106
+ The Exchange rail mints a permanent, cryptographically sealed transcript on every call — 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. The other two rails are stateless by design (asking about your own artifact, or one NOMOS has never seen, has no transcript to point to) — `audit.hash` is the private-artifact rail's own provenance instead.
56
107
 
57
108
  ### Error handling
58
109
 
59
- A failed call (unknown authority, network issue, upstream error) rejects with a typed `NomosError` subclass — never a bare `Error` — so you can branch on `instanceof` or the stable `.code` string:
110
+ A failed call rejects with a typed `NomosError` subclass — never a bare `Error` — so you can branch on `instanceof` or the stable `.code` string:
60
111
 
61
112
  ```ts
62
- import { nomos, NomosNotFoundError, NomosNetworkError } from "@nomosprotocol/sdk";
113
+ import {
114
+ nomos,
115
+ NomosNotFoundError,
116
+ NomosNetworkError,
117
+ NomosPaymentRequiredError,
118
+ NomosIssuerNotTrustedError,
119
+ } from "@nomosprotocol/sdk";
63
120
 
64
121
  try {
65
122
  const verdict = await nomos.can({ authority: "eu-ai-act.nomos", ...facts });
66
123
  } catch (err) {
67
124
  if (err instanceof NomosNotFoundError) {
68
125
  // authority slug/id doesn't exist — err.code === 'not_found'
126
+ } else if (err instanceof NomosPaymentRequiredError) {
127
+ // paid, unpurchased — err.priceUsd / err.purchaseUrl
128
+ } else if (err instanceof NomosIssuerNotTrustedError) {
129
+ // chain-of-trust rail only — err.reason is one of
130
+ // ISSUER_NOT_RECOGNIZED | KEY_REVOKED | SEAL_INVALID
69
131
  } else if (err instanceof NomosNetworkError) {
70
132
  // request timed out or the network failed
71
133
  } else {
@@ -88,13 +150,15 @@ await nomos.can(
88
150
 
89
151
  ## `Nomos` — authenticated client
90
152
 
91
- For private/custom artifacts, governance generation, and webhook verification, use the authenticated client:
153
+ Beyond `client.can()`, the authenticated client covers the rest of the platform — the raw (richer) decision-verification shape, governance generation, and webhook verification:
92
154
 
93
155
  ```ts
94
156
  import { Nomos } from "@nomosprotocol/sdk";
95
157
 
96
158
  const client = new Nomos("nms_live_..."); // or "nms_test_..." for test mode
97
159
 
160
+ // Lower-level than client.can() — same evaluation, but returns the full raw shape
161
+ // (evaluation counts, confidence tier, paths_to_approval) instead of the normalized verdict.
98
162
  const result = await client.decisions.verify({
99
163
  artifact_id: "your_artifact_id",
100
164
  decision_context: { /* facts */ },
package/dist/index.cjs CHANGED
@@ -25,13 +25,16 @@ __export(index_exports, {
25
25
  NomosAuthenticationError: () => NomosAuthenticationError,
26
26
  NomosAuthorizationError: () => NomosAuthorizationError,
27
27
  NomosError: () => NomosError,
28
+ NomosIssuerNotTrustedError: () => NomosIssuerNotTrustedError,
28
29
  NomosNetworkError: () => NomosNetworkError,
29
30
  NomosNotFoundError: () => NomosNotFoundError,
31
+ NomosPaymentRequiredError: () => NomosPaymentRequiredError,
30
32
  NomosRateLimitError: () => NomosRateLimitError,
31
33
  NomosValidationError: () => NomosValidationError,
32
34
  NomosWebhookSignatureError: () => NomosWebhookSignatureError,
33
35
  can: () => can,
34
- nomos: () => nomos
36
+ nomos: () => nomos,
37
+ verifyChain: () => verifyChain
35
38
  });
36
39
  module.exports = __toCommonJS(index_exports);
37
40
 
@@ -42,8 +45,10 @@ __export(errors_exports, {
42
45
  NomosAuthenticationError: () => NomosAuthenticationError,
43
46
  NomosAuthorizationError: () => NomosAuthorizationError,
44
47
  NomosError: () => NomosError,
48
+ NomosIssuerNotTrustedError: () => NomosIssuerNotTrustedError,
45
49
  NomosNetworkError: () => NomosNetworkError,
46
50
  NomosNotFoundError: () => NomosNotFoundError,
51
+ NomosPaymentRequiredError: () => NomosPaymentRequiredError,
47
52
  NomosRateLimitError: () => NomosRateLimitError,
48
53
  NomosValidationError: () => NomosValidationError,
49
54
  NomosWebhookSignatureError: () => NomosWebhookSignatureError
@@ -107,6 +112,21 @@ var NomosWebhookSignatureError = class extends NomosError {
107
112
  this.name = "NomosWebhookSignatureError";
108
113
  }
109
114
  };
115
+ var NomosPaymentRequiredError = class extends NomosError {
116
+ constructor(message, priceUsd, purchaseUrl, requestId) {
117
+ super(message, "payment_required", 402, requestId);
118
+ this.name = "NomosPaymentRequiredError";
119
+ this.priceUsd = priceUsd;
120
+ this.purchaseUrl = purchaseUrl;
121
+ }
122
+ };
123
+ var NomosIssuerNotTrustedError = class extends NomosError {
124
+ constructor(message, reason, requestId) {
125
+ super(message, "issuer_not_trusted", 403, requestId);
126
+ this.name = "NomosIssuerNotTrustedError";
127
+ this.reason = reason;
128
+ }
129
+ };
110
130
 
111
131
  // src/util.ts
112
132
  async function sleep(ms) {
@@ -526,6 +546,192 @@ var Webhooks = class {
526
546
  }
527
547
  };
528
548
 
549
+ // src/can.ts
550
+ var CHAIN_PROTOCOL_VERSION = "1";
551
+ var DEFAULT_EXCHANGE_BASE = "https://www.nomosprotocol.com";
552
+ function splitFacts(params) {
553
+ const { authority, artifact_id, action, inputs, ...flat } = params;
554
+ void authority;
555
+ void artifact_id;
556
+ const facts = inputs && typeof inputs === "object" ? inputs : flat;
557
+ return { facts, action };
558
+ }
559
+ function mapHttpError(status, data, requestId) {
560
+ const message = data?.error ?? data?.message ?? `NOMOS query failed (HTTP ${status})`;
561
+ if (data?.code === "PURCHASE_REQUIRED") {
562
+ throw new NomosPaymentRequiredError(message, data.price_usd ?? 0, data.purchase_url ?? "", requestId);
563
+ }
564
+ switch (status) {
565
+ case 401:
566
+ throw new NomosAuthenticationError(message, requestId);
567
+ case 403:
568
+ throw new NomosAuthorizationError(message, requestId);
569
+ case 404:
570
+ throw new NomosNotFoundError(message, requestId);
571
+ case 422:
572
+ case 400:
573
+ throw new NomosValidationError(message, data?.details, requestId);
574
+ case 429:
575
+ throw new NomosRateLimitError(message, void 0, requestId);
576
+ default:
577
+ throw new NomosAPIError(message, status, requestId);
578
+ }
579
+ }
580
+ function normalizeAuthorityQueryResult(data, authority) {
581
+ return {
582
+ allowed: data.verdict === "AUTHORIZED",
583
+ verdict: data.verdict,
584
+ reason: data.verdict_description,
585
+ rule: data.matched_rule_id ?? null,
586
+ query_id: data.query_id,
587
+ url: data.url,
588
+ authority: {
589
+ id: data.artifact?.id ?? authority,
590
+ title: data.artifact?.title ?? "",
591
+ standing: data.artifact?.standing ?? "",
592
+ ...data.artifact?.attestation ? { attestation: data.artifact.attestation } : {}
593
+ },
594
+ ...data.artifact?.version ? { version: data.artifact.version } : {},
595
+ missing_inputs: data.missing_inputs ?? [],
596
+ ...typeof data.max_age === "number" ? { max_age: data.max_age } : {},
597
+ // authority-query.ts already returns { kind, rule_id, rule_name } — the public-safe shape
598
+ // CanResult uses directly.
599
+ ...Array.isArray(data.obligations) && data.obligations.length > 0 ? { obligations: data.obligations } : {},
600
+ // NOMOS-SPEC-006 — present only when the issuer has actually revoked this exact sealed
601
+ // version; omitted (not false) when revocation doesn't structurally apply here, so "checked
602
+ // and clean" and "couldn't check" never look the same to a caller.
603
+ ...data.revoked === true ? { revoked: true, revocation_reason: data.revocation_reason } : {}
604
+ };
605
+ }
606
+ async function canViaExchange(authority, facts, action, opts) {
607
+ const base = (opts?.baseUrl ?? DEFAULT_EXCHANGE_BASE).replace(/\/$/, "");
608
+ const url = `${base}/api/v1/can`;
609
+ let res;
610
+ try {
611
+ res = await fetch(url, {
612
+ method: "POST",
613
+ headers: { "Content-Type": "application/json" },
614
+ body: JSON.stringify({ artifact: authority, action, ...facts }),
615
+ signal: opts?.signal
616
+ });
617
+ } catch (err) {
618
+ if (err?.name === "AbortError") throw new NomosNetworkError("Request aborted");
619
+ throw new NomosNetworkError(err?.message ?? "Network error");
620
+ }
621
+ const requestId = res.headers.get("x-request-id") ?? "";
622
+ const data = await res.json().catch(() => null);
623
+ if (!res.ok) mapHttpError(res.status, data, requestId);
624
+ return normalizeAuthorityQueryResult(data, authority);
625
+ }
626
+ async function canViaExchangeAuthenticated(http, authority, facts, action) {
627
+ const { data } = await http.post("/api/v1/can", { artifact: authority, action, ...facts });
628
+ return normalizeAuthorityQueryResult(data, authority);
629
+ }
630
+ async function canViaArtifact(http, artifactId, facts, action) {
631
+ const isPublic = artifactId.startsWith("pub_");
632
+ const path = isPublic ? "/api/v1/verify" : "/api/v1/verify-decision";
633
+ const body = isPublic ? { artifact_id: artifactId, decision: action ?? "evaluate", inputs: facts } : { artifact_id: artifactId, decision: action ?? "evaluate", inputs: facts, caller: { correlation_id: globalThis.crypto.randomUUID() } };
634
+ const { data: raw } = await http.post(path, body);
635
+ const verdict = raw.verdict === "approved" ? "AUTHORIZED" : raw.verdict === "rejected" ? "DENIED" : raw.outcome === "auto_approved" ? "AUTHORIZED" : raw.outcome === "auto_rejected" ? "DENIED" : "ESCALATED";
636
+ return {
637
+ allowed: raw.allowed ?? verdict === "AUTHORIZED",
638
+ verdict,
639
+ reason: raw.verdict_description ?? "",
640
+ rule: raw.rule?.id ?? null,
641
+ authority: {
642
+ id: raw.artifact_id ?? artifactId,
643
+ title: raw.artifact_name ?? "",
644
+ standing: raw.confidence?.tier ?? ""
645
+ },
646
+ ...raw.artifact_version ? { version: raw.artifact_version } : {},
647
+ missing_inputs: raw.incomplete_inputs?.fields ?? [],
648
+ ...raw.audit ? { audit: { hash: raw.audit.hash ?? raw.audit_hash ?? "", timestamp: raw.audit.timestamp ?? raw.ts ?? (/* @__PURE__ */ new Date()).toISOString() } } : {},
649
+ // This rail's obligations are RuleEffect objects (authority-types.ts) — a richer, INTERNAL
650
+ // shape ({ effect_type, value, provenance: { rule_id, rule_name } }), NOT the same shape the
651
+ // Exchange rail already returns ({ kind, rule_id, rule_name }). Mapped explicitly here rather
652
+ // than passed through raw — the two rails' server-side representations genuinely differ, and
653
+ // assuming they matched would have been the actual bug.
654
+ ...Array.isArray(raw.obligations) && raw.obligations.length > 0 ? { obligations: raw.obligations.map((o) => ({
655
+ kind: o.kind ?? o.value ?? "obligation",
656
+ rule_id: o.rule_id ?? o.provenance?.rule_id ?? null,
657
+ rule_name: o.rule_name ?? o.provenance?.rule_name ?? null,
658
+ ...o.reason ? { reason: o.reason } : {}
659
+ })) } : {}
660
+ };
661
+ }
662
+ async function canViaChain(artifact, keyCerts, rootPublicKeyPem, facts, action, opts) {
663
+ const base = (opts?.baseUrl ?? DEFAULT_EXCHANGE_BASE).replace(/\/$/, "");
664
+ const url = `${base}/api/v1/chain-of-trust/verify`;
665
+ let res;
666
+ try {
667
+ res = await fetch(url, {
668
+ method: "POST",
669
+ headers: { "Content-Type": "application/json" },
670
+ body: JSON.stringify({ version: CHAIN_PROTOCOL_VERSION, artifact, key_certs: keyCerts, root_public_key_pem: rootPublicKeyPem, facts, action }),
671
+ signal: opts?.signal
672
+ });
673
+ } catch (err) {
674
+ if (err?.name === "AbortError") throw new NomosNetworkError("Request aborted");
675
+ throw new NomosNetworkError(err?.message ?? "Network error");
676
+ }
677
+ const requestId = res.headers.get("x-request-id") ?? "";
678
+ const data = await res.json().catch(() => null);
679
+ if (data?.decision === "AUTHORIZED" || data?.decision === "DENIED" || data?.decision === "ESCALATED") {
680
+ return {
681
+ allowed: data.decision === "AUTHORIZED",
682
+ verdict: data.decision,
683
+ reason: data.reason ?? "",
684
+ rule: data.rule ?? null,
685
+ authority: { id: "presented-artifact", title: "", standing: "" },
686
+ // Read directly off the artifact the caller already has — no server round-trip needed for
687
+ // data the caller is already holding.
688
+ ...artifact?.meta?.version ? { version: artifact.meta.version } : {},
689
+ missing_inputs: data.missing_inputs ?? [],
690
+ ...Array.isArray(data.obligations) && data.obligations.length > 0 ? { obligations: data.obligations } : {}
691
+ // Deliberately no query_id/url/audit — this evaluation is stateless by design (the whole
692
+ // point is answering without NOMOS needing to have seen this artifact before); there is no
693
+ // permanent transcript to point to, and faking one would misrepresent what just happened.
694
+ };
695
+ }
696
+ const message = data?.detail ?? `Chain verification failed (HTTP ${res.status})`;
697
+ if (data?.decision === "ISSUER_NOT_RECOGNIZED" || data?.decision === "KEY_REVOKED" || data?.decision === "SEAL_INVALID" || data?.decision === "ALLOWED") {
698
+ throw new NomosIssuerNotTrustedError(message, data.decision, requestId);
699
+ }
700
+ if (data?.decision === "MALFORMED") throw new NomosValidationError(message, data, requestId);
701
+ mapHttpError(res.status, data, requestId);
702
+ }
703
+ async function can(params, opts) {
704
+ if (params.artifact !== void 0) {
705
+ if (!params.key_certs || !params.root_public_key_pem) {
706
+ throw new Error("can(): `artifact` requires `key_certs` and `root_public_key_pem` alongside it.");
707
+ }
708
+ const { facts: facts2, action: action2 } = splitFacts(params);
709
+ return canViaChain(params.artifact, params.key_certs, params.root_public_key_pem, facts2, action2, opts);
710
+ }
711
+ if (params.artifact_id) {
712
+ throw new Error("can(): `artifact_id` requires an API key \u2014 use `client.can(...)` on an authenticated Nomos instance, not the free `can()` export.");
713
+ }
714
+ if (!params.authority || typeof params.authority !== "string") {
715
+ throw new Error("can(): `authority` (an Exchange artifact id or slug), `artifact_id` (via client.can()), or `artifact` (chain-of-trust) is required.");
716
+ }
717
+ const { facts, action } = splitFacts(params);
718
+ return canViaExchange(params.authority, facts, action, opts);
719
+ }
720
+ async function canAuthenticated(http, params, opts) {
721
+ void opts;
722
+ const { facts, action } = splitFacts(params);
723
+ if (params.artifact !== void 0) {
724
+ if (!params.key_certs || !params.root_public_key_pem) {
725
+ throw new Error("can(): `artifact` requires `key_certs` and `root_public_key_pem` alongside it.");
726
+ }
727
+ return canViaChain(params.artifact, params.key_certs, params.root_public_key_pem, facts, action, opts);
728
+ }
729
+ if (params.artifact_id) return canViaArtifact(http, params.artifact_id, facts, action);
730
+ if (params.authority) return canViaExchangeAuthenticated(http, params.authority, facts, action);
731
+ throw new Error("can(): `authority` (a public Exchange artifact), `artifact_id` (your own artifact), or `artifact` (chain-of-trust) is required.");
732
+ }
733
+ var nomos = { can };
734
+
529
735
  // src/nomos.ts
530
736
  var DEFAULT_BASE_URL = "https://nomos.ai";
531
737
  var DEFAULT_MAX_RETRIES = 3;
@@ -541,35 +747,51 @@ var Nomos = class {
541
747
  );
542
748
  }
543
749
  this.mode = apiKey.startsWith("nms_test_") ? "test" : "live";
544
- const http = new HttpClient({
750
+ this.http = new HttpClient({
545
751
  apiKey,
546
752
  baseUrl: (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, ""),
547
753
  maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
548
754
  timeout: config.timeout ?? DEFAULT_TIMEOUT
549
755
  });
550
- this.decisions = new Decisions(http);
551
- this.artifacts = new Artifacts(http);
552
- this.governance = new Governance(http);
756
+ this.decisions = new Decisions(this.http);
757
+ this.artifacts = new Artifacts(this.http);
758
+ this.governance = new Governance(this.http);
553
759
  this.webhooks = new Webhooks();
554
760
  }
761
+ /**
762
+ * "Can I do this?" — the one verb, now covering your own artifacts too. Pass `authority` for a
763
+ * public Exchange artifact, or `artifact_id` for your own (Studio or the Exchange) — same
764
+ * return shape either way, so calling code never needs to know which rail answered.
765
+ *
766
+ * @example
767
+ * const client = new Nomos('nms_live_...');
768
+ * const verdict = await client.can({ artifact_id: 'loan_approval_v1', credit_score: 720 });
769
+ */
770
+ async can(params, opts) {
771
+ return canAuthenticated(this.http, params, opts);
772
+ }
555
773
  };
556
774
 
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`;
775
+ // src/verify-chain.ts
776
+ var CHAIN_PROTOCOL_VERSION2 = "1";
777
+ var DEFAULT_BASE = "https://www.nomosprotocol.com";
778
+ async function verifyChain(params, opts) {
779
+ if (!params.root_public_key_pem) {
780
+ throw new Error("verifyChain(): `root_public_key_pem` is required \u2014 there is no default root.");
781
+ }
782
+ const base = (opts?.baseUrl ?? DEFAULT_BASE).replace(/\/$/, "");
783
+ const url = `${base}/api/v1/chain-of-trust/verify`;
567
784
  let res;
568
785
  try {
569
786
  res = await fetch(url, {
570
787
  method: "POST",
571
788
  headers: { "Content-Type": "application/json" },
572
- body: JSON.stringify({ inputs: facts, action }),
789
+ body: JSON.stringify({
790
+ version: CHAIN_PROTOCOL_VERSION2,
791
+ artifact: params.artifact,
792
+ key_certs: params.key_certs,
793
+ root_public_key_pem: params.root_public_key_pem
794
+ }),
573
795
  signal: opts?.signal
574
796
  });
575
797
  } catch (err) {
@@ -578,44 +800,16 @@ async function can(params, opts) {
578
800
  }
579
801
  const requestId = res.headers.get("x-request-id") ?? "";
580
802
  const data = await res.json().catch(() => null);
581
- if (!res.ok) {
582
- const message = data?.error ?? data?.message ?? `NOMOS query failed (HTTP ${res.status})`;
583
- switch (res.status) {
584
- case 401:
585
- throw new NomosAuthenticationError(message, requestId);
586
- case 403:
587
- throw new NomosAuthorizationError(message, requestId);
588
- case 404:
589
- throw new NomosNotFoundError(message, requestId);
590
- case 422:
591
- case 400:
592
- throw new NomosValidationError(message, data?.details, requestId);
593
- case 429:
594
- throw new NomosRateLimitError(
595
- message,
596
- res.headers.get("retry-after") ? parseInt(res.headers.get("retry-after"), 10) : void 0,
597
- requestId
598
- );
599
- default:
600
- throw new NomosAPIError(message, res.status, requestId);
601
- }
803
+ if (data && typeof data.decision === "string") {
804
+ return data;
602
805
  }
603
- return {
604
- allowed: data.verdict === "AUTHORIZED",
605
- verdict: data.verdict,
606
- reason: data.verdict_description,
607
- rule: data.matched_rule_id ?? null,
608
- query_id: data.query_id,
609
- url: data.url,
610
- authority: {
611
- id: data.artifact?.id ?? authority,
612
- title: data.artifact?.title ?? "",
613
- standing: data.artifact?.standing ?? ""
614
- },
615
- missing_inputs: data.missing_inputs ?? []
616
- };
806
+ const message = data?.error ?? `Chain verification request failed (HTTP ${res.status})`;
807
+ if (res.status === 429) {
808
+ throw new NomosRateLimitError(message, res.headers.get("retry-after") ? parseInt(res.headers.get("retry-after"), 10) : void 0, requestId);
809
+ }
810
+ if (res.status === 400) throw new NomosValidationError(message, data?.details, requestId);
811
+ throw new NomosAPIError(message, res.status, requestId);
617
812
  }
618
- var nomos = { can };
619
813
  // Annotate the CommonJS export names for ESM import in node:
620
814
  0 && (module.exports = {
621
815
  Nomos,
@@ -623,12 +817,15 @@ var nomos = { can };
623
817
  NomosAuthenticationError,
624
818
  NomosAuthorizationError,
625
819
  NomosError,
820
+ NomosIssuerNotTrustedError,
626
821
  NomosNetworkError,
627
822
  NomosNotFoundError,
823
+ NomosPaymentRequiredError,
628
824
  NomosRateLimitError,
629
825
  NomosValidationError,
630
826
  NomosWebhookSignatureError,
631
827
  can,
632
- nomos
828
+ nomos,
829
+ verifyChain
633
830
  });
634
831
  //# sourceMappingURL=index.cjs.map