@nomosprotocol/sdk 2.3.2 → 2.6.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
@@ -122,9 +122,55 @@ It abstracts three different authority situations behind one call and one verdic
122
122
  | A public authority published on the NOMOS Exchange | `nomos.can({ authority, ...facts })` | No |
123
123
  | Your organization's sealed artifact | `client.can({ artifact_id, ...facts })` | Yes |
124
124
  | A sealed artifact presented directly by an agent or system | `nomos.can({ artifact, key_certs, root_public_key_pem, ...facts })` | No |
125
+ | An act, and you don't know which laws govern it | `nomos.can({ context, ...facts })` | No |
125
126
 
126
127
  The caller does not need to know which evaluation rail is being used. `can()` routes internally based on what you provide.
127
128
 
129
+ ### 0. Ask by context: let NOMOS find the laws (2.6.0)
130
+
131
+ Naming a law lets the caller choose which law is checked, and leave out the strict one. Describe
132
+ the act instead, and NOMOS works out which laws govern it, asks every one, and combines the answers:
133
+ any deny denies, and a yes needs every law to say yes.
134
+
135
+ ```ts
136
+ import { can } from '@nomosprotocol/sdk';
137
+
138
+ const v = await can({
139
+ context: {
140
+ jurisdiction: 'AE', // where the act takes effect
141
+ established_in: 'DIFC', // where your organisation is incorporated or established
142
+ subjects_in: 'AE', // where the people it concerns are
143
+ output_used_in: 'AE', // where any AI output is used
144
+ },
145
+ action: 'share_customer_data_with_analytics_vendor',
146
+ processing_fair_and_transparent: false,
147
+ });
148
+
149
+ v.mode; // 'resolution'
150
+ v.verdict; // 'DENIED': a DIFC firm acting in mainland Dubai answers to DIFC law
151
+ v.answered_under; // ['difc-data-protection-law-difc-law-no-5-of-2020']
152
+ v.coverage; // what the answer covers, in words
153
+ v.members; // each law's own answer, each a signed record (query_id)
154
+ ```
155
+
156
+ What can come back besides a verdict:
157
+
158
+ - `INSUFFICIENT_DATA` with `open_facts`: a fact that decides which law applies was not stated
159
+ (for example `established_in`). It is asked for, never assumed.
160
+ - `INSUFFICIENT_DATA` for a place the registrar does not cover (Saudi Arabia, the US, an ADGM or
161
+ QFC firm today). "No law found" is never read as "no law applies".
162
+ - `CONFLICT`: two registrars each claim exclusive precedence. A person decides.
163
+
164
+ Which laws apply where comes from the NOMOS registrar: a signed, append-only list in the public
165
+ log at `/.well-known/nomos-exchange-log`, verifiable offline. Each claim states the provision it
166
+ rests on. It currently covers the EU AI Act, Qatar Law No. 13 of 2016, UAE Federal Decree-Law
167
+ No. 45 of 2021 and DIFC Law No. 5 of 2020, and lists only its own laws, so other law may also
168
+ apply. Only want the list of laws? `POST /api/v1/resolve { context }` returns it without asking
169
+ them, and records nothing.
170
+
171
+ `accept()` still verifies the one authority an Act carries. Resolving inside `accept()`, from
172
+ registrars the receiving system pins, is the next step of NOMOS-SPEC-014.
173
+
128
174
  ### 1. Public authority
129
175
 
130
176
  Ask a published authority directly:
@@ -176,6 +222,41 @@ If a paid authority has not been purchased, NOMOS throws `NomosPaymentRequiredEr
176
222
 
177
223
  NOMOS never auto-charges on your behalf.
178
224
 
225
+ ### Escalations that reach a person
226
+
227
+ Click **Use this law** on an authority's Exchange page (signed in). From then on, when that law
228
+ answers ESCALATED to a call made with your API key, your approvers get an email and approve on a
229
+ page. An API key can never approve, so an agent can't approve itself. Your code can wait for the answer:
230
+
231
+ ```ts
232
+ import { Nomos } from "@nomosprotocol/sdk";
233
+
234
+ const client = new Nomos(process.env.NOMOS_API_KEY!);
235
+ const r = await client.can(
236
+ { authority: "difc-data-protection-law-difc-law-no-5-of-2020",
237
+ ...allTwelveNo, // the law's intake questions, listed on its page
238
+ q_individual_request: true, // "yes": a request from a person
239
+ access_request_received: true, days_since_access_request: 45,
240
+ identity_doubt_unresolved: false, response_period_extended_with_notice: false },
241
+ { awaitApproval: { timeoutMs: 10 * 60_000, onApproval: (a) => console.log("waiting on", a.url) } },
242
+ );
243
+
244
+ if (r.allowed) proceed(); // AUTHORIZED, or a person approved this exact call (spent once)
245
+ else console.log(r.verdict, r.approval?.status); // DENIED, or rejected / timed_out
246
+ ```
247
+
248
+ The approval is bound to the authority, the action and the facts: different facts need a new one.
249
+
250
+ A large law carries a few yes/no **intake questions** (listed on its page). Each "no" rules out the
251
+ articles it covers, so an ordinary action gets a firm answer in one call: on the DIFC Data Protection
252
+ Law, all twelve "no" is `AUTHORIZED`.
253
+
254
+ When a listing is replaced by a newer version and retired, calls to the old address throw a
255
+ `NomosAPIError` with status 410: the message names the new version's address. Move to it; the old
256
+ address is never answered from the old rules.
257
+ When the law gets a new version, you get an email with what changed. Turn either email off on the
258
+ same page.
259
+
179
260
  ### 2. Your organization's authority
180
261
 
181
262
  Use an authenticated client for artifacts belonging to your organization:
package/dist/index.cjs CHANGED
@@ -191,7 +191,7 @@ function isRetryable(status) {
191
191
  }
192
192
 
193
193
  // src/http-client.ts
194
- var SDK_VERSION = "2.3.2";
194
+ var SDK_VERSION = "2.6.0";
195
195
  var HttpClient = class {
196
196
  constructor(opts) {
197
197
  this.opts = opts;
@@ -721,7 +721,9 @@ function normalizeAuthorityQueryResult(data, authority) {
721
721
  // NOMOS-SPEC-006 — present only when the issuer has actually revoked this exact sealed
722
722
  // version; omitted (not false) when revocation doesn't structurally apply here, so "checked
723
723
  // and clean" and "couldn't check" never look the same to a caller.
724
- ...data.revoked === true ? { revoked: true, revocation_reason: data.revocation_reason } : {}
724
+ ...data.revoked === true ? { revoked: true, revocation_reason: data.revocation_reason } : {},
725
+ ...data.signature ? { signature: data.signature } : {},
726
+ ...data.evaluator_version ? { evaluator_version: data.evaluator_version } : {}
725
727
  };
726
728
  }
727
729
  function callerHeaders(c) {
@@ -751,9 +753,34 @@ async function canViaExchange(authority, facts, action, opts) {
751
753
  if (!res.ok) mapHttpError(res.status, data, requestId);
752
754
  return normalizeAuthorityQueryResult(data, authority);
753
755
  }
754
- async function canViaExchangeAuthenticated(http, authority, facts, action, caller) {
755
- const { data } = await http.post("/api/v1/can", { artifact: authority, action, ...facts }, { headers: callerHeaders(caller) });
756
- return normalizeAuthorityQueryResult(data, authority);
756
+ async function canViaExchangeAuthenticated(http, authority, facts, action, opts) {
757
+ const ask = async () => (await http.post("/api/v1/can", { artifact: authority, action, ...facts }, { headers: callerHeaders(opts?.caller) })).data;
758
+ const data = await ask();
759
+ const result = normalizeAuthorityQueryResult(data, authority);
760
+ const approval = data?.approval;
761
+ if (!approval) return result;
762
+ result.approval = { id: approval.id, url: approval.url, status: approval.status };
763
+ if (!opts?.awaitApproval || result.verdict !== "ESCALATED") return result;
764
+ return awaitApproval(http, result, typeof opts.awaitApproval === "object" ? opts.awaitApproval : {});
765
+ }
766
+ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
767
+ async function awaitApproval(http, result, o) {
768
+ const a = result.approval;
769
+ o.onApproval?.({ id: a.id, url: a.url });
770
+ const deadline = Date.now() + (o.timeoutMs ?? 6e5);
771
+ let status = a.status;
772
+ while (status === "pending" && Date.now() < deadline) {
773
+ await sleep2(Math.max(500, o.pollMs ?? 3e3));
774
+ status = (await http.get(`/api/v1/approvals/${a.id}`)).data?.status ?? "pending";
775
+ }
776
+ if (status === "pending") return { ...result, approval: { ...a, status: "timed_out" } };
777
+ if (status !== "approved") return { ...result, approval: { ...a, status: "rejected" } };
778
+ try {
779
+ await http.post(`/api/v1/approvals/${a.id}/consume`, {});
780
+ } catch {
781
+ return { ...result, approval: { ...a, status: "not_usable" } };
782
+ }
783
+ return { ...result, allowed: true, approval: { ...a, status: "approved" } };
757
784
  }
758
785
  async function canViaArtifact(http, artifactId, facts, action) {
759
786
  const isPublic = artifactId.startsWith("pub_");
@@ -831,6 +858,50 @@ async function canViaChain(artifact, keyCerts, rootPublicKeyPem, facts, action,
831
858
  if (data?.decision === "MALFORMED") throw new NomosValidationError(message, data, requestId);
832
859
  mapHttpError(res.status, data, requestId);
833
860
  }
861
+ function normalizeResolutionResult(data) {
862
+ return {
863
+ mode: "resolution",
864
+ allowed: data.verdict === "AUTHORIZED",
865
+ verdict: data.verdict,
866
+ reason: data.reason ?? "",
867
+ resolution: data.resolution ?? { status: "INCOMPLETE", proof: null },
868
+ members: Array.isArray(data.members) ? data.members : [],
869
+ deciding_authorities: Array.isArray(data.deciding_authorities) ? data.deciding_authorities : [],
870
+ escalations: Array.isArray(data.escalations) ? data.escalations : [],
871
+ open_facts: Array.isArray(data.open_facts) ? data.open_facts : [],
872
+ ...Array.isArray(data.answered_under) ? { answered_under: data.answered_under } : {},
873
+ ...typeof data.coverage === "string" ? { coverage: data.coverage } : {}
874
+ };
875
+ }
876
+ function resolutionBody(params) {
877
+ const { authority, artifact_id, artifact, key_certs, root_public_key_pem, action, inputs, context, ...flat } = params;
878
+ void authority;
879
+ void artifact_id;
880
+ void artifact;
881
+ void key_certs;
882
+ void root_public_key_pem;
883
+ const facts = inputs && typeof inputs === "object" ? inputs : flat;
884
+ return { context, ...action ? { action } : {}, ...facts };
885
+ }
886
+ async function canViaResolution(params, opts) {
887
+ const base = (opts?.baseUrl ?? DEFAULT_EXCHANGE_BASE).replace(/\/$/, "");
888
+ let res;
889
+ try {
890
+ res = await fetch(`${base}/api/v1/can`, {
891
+ method: "POST",
892
+ headers: { "Content-Type": "application/json", "User-Agent": `nomos-node/${SDK_VERSION}`, ...callerHeaders(opts?.caller) },
893
+ body: JSON.stringify(resolutionBody(params)),
894
+ signal: opts?.signal
895
+ });
896
+ } catch (err) {
897
+ if (err?.name === "AbortError") throw new NomosNetworkError("Request aborted");
898
+ throw new NomosNetworkError(err?.message ?? "Network error");
899
+ }
900
+ const requestId = res.headers.get("x-request-id") ?? "";
901
+ const data = await res.json().catch(() => null);
902
+ if (!res.ok) mapHttpError(res.status, data, requestId);
903
+ return normalizeResolutionResult(data);
904
+ }
834
905
  async function can(params, opts) {
835
906
  if (params.artifact !== void 0) {
836
907
  if (!params.key_certs || !params.root_public_key_pem) {
@@ -842,8 +913,11 @@ async function can(params, opts) {
842
913
  if (params.artifact_id) {
843
914
  throw new Error("can(): `artifact_id` requires an API key \u2014 use `client.can(...)` on an authenticated Nomos instance, not the free `can()` export.");
844
915
  }
916
+ if (!params.authority && params.context && typeof params.context === "object" && !Array.isArray(params.context)) {
917
+ return canViaResolution(params, opts);
918
+ }
845
919
  if (!params.authority || typeof params.authority !== "string") {
846
- throw new Error("can(): `authority` (an Exchange artifact id or slug), `artifact_id` (via client.can()), or `artifact` (chain-of-trust) is required.");
920
+ throw new Error("can(): `authority` (an Exchange artifact id or slug), `artifact_id` (via client.can()), `artifact` (chain-of-trust), or `context` (resolution, NOMOS-SPEC-014) is required.");
847
921
  }
848
922
  const { facts, action } = splitFacts(params);
849
923
  return canViaExchange(params.authority, facts, action, opts);
@@ -857,8 +931,12 @@ async function canAuthenticated(http, params, opts) {
857
931
  return canViaChain(params.artifact, params.key_certs, params.root_public_key_pem, facts, action, opts);
858
932
  }
859
933
  if (params.artifact_id) return canViaArtifact(http, params.artifact_id, facts, action);
860
- if (params.authority) return canViaExchangeAuthenticated(http, params.authority, facts, action, opts?.caller);
861
- throw new Error("can(): `authority` (a public Exchange artifact), `artifact_id` (your own artifact), or `artifact` (chain-of-trust) is required.");
934
+ if (params.authority) return canViaExchangeAuthenticated(http, params.authority, facts, action, opts);
935
+ if (params.context && typeof params.context === "object" && !Array.isArray(params.context)) {
936
+ const data = (await http.post("/api/v1/can", resolutionBody(params), { headers: callerHeaders(opts?.caller) })).data;
937
+ return normalizeResolutionResult(data);
938
+ }
939
+ throw new Error("can(): `authority` (a public Exchange artifact), `artifact_id` (your own artifact), `artifact` (chain-of-trust), or `context` (resolution, NOMOS-SPEC-014) is required.");
862
940
  }
863
941
  var nomos = { can };
864
942
 
package/dist/index.d.cts CHANGED
@@ -80,9 +80,77 @@ interface CanParams {
80
80
  /** The facts to evaluate. Either pass this explicitly, or spread the facts flat alongside
81
81
  * `authority`/`action` — both shapes are accepted (explicit `inputs` wins if both given). */
82
82
  inputs?: Record<string, unknown>;
83
+ /** Resolution rail (NOMOS-SPEC-014 Draft): the properties that attach law to an act —
84
+ * e.g. { jurisdiction: 'EU', sector: 'ai' }. With `context` and NO `authority`/`artifact_id`/
85
+ * `artifact`, the server first resolves WHICH authorities govern this context (from its own
86
+ * signed registrar chain — never from anything you present), asks every one of them, and
87
+ * combines strictly. Returns a CanResolutionResult instead of a CanResult. Keyless. */
88
+ context?: Record<string, unknown>;
83
89
  /** Flat-facts convenience: any extra keys are treated as inputs when `inputs` is omitted. */
84
90
  [key: string]: unknown;
85
91
  }
92
+ /** One governing authority the resolver found for a context (NOMOS-SPEC-014). */
93
+ interface ResolvedAuthorityRef {
94
+ authority: string;
95
+ version: string | null;
96
+ registrar: string;
97
+ scope: string;
98
+ }
99
+ /** One resolved authority's answer inside a resolution-rail can() call. `verdict` is null with
100
+ * `unavailable` set when the member could not be consulted — which holds the WHOLE combined
101
+ * answer open (INSUFFICIENT_DATA), never acts as a permit. */
102
+ interface CanResolutionMember {
103
+ authority: string;
104
+ verdict: string | null;
105
+ unavailable: string | null;
106
+ query_id?: string | null;
107
+ rule?: string | null;
108
+ reason?: string | null;
109
+ escalation_id?: string | null;
110
+ revoked?: boolean;
111
+ missing_facts?: string[];
112
+ }
113
+ /** The resolution rail's answer (NOMOS-SPEC-014): which authorities govern, each one's own
114
+ * sealed answer, and the strict combination. `verdict` adds two values CanResult never has:
115
+ * INSUFFICIENT_DATA (resolution or a member held open — named in `open_facts`/`resolution`)
116
+ * and CONFLICT (two registrars assert exclusive precedence; a person settles it). */
117
+ interface CanResolutionResult {
118
+ mode: 'resolution';
119
+ allowed: boolean;
120
+ verdict: 'AUTHORIZED' | 'DENIED' | 'ESCALATED' | 'INSUFFICIENT_DATA' | 'CONFLICT';
121
+ reason: string;
122
+ resolution: {
123
+ status: 'RESOLVED' | 'CONFLICT' | 'INCOMPLETE';
124
+ applicable?: ResolvedAuthorityRef[];
125
+ missing?: {
126
+ context_facts: string[];
127
+ unreachable_registrars: string[];
128
+ stale_closures: string[];
129
+ /** SPEC-014 0.2.0: a current closure names law the registrar's log no longer claims, or the reverse */
130
+ closure_disagreements?: Array<{
131
+ registrar: string;
132
+ authority: string;
133
+ reason: 'closed_but_not_claimed' | 'claimed_but_not_closed';
134
+ }>;
135
+ /** SPEC-014 0.2.0: registrars whose log copy is older than the relying party's bound */
136
+ stale_heads?: string[];
137
+ };
138
+ conflicts?: unknown[];
139
+ proof: unknown;
140
+ };
141
+ members: CanResolutionMember[];
142
+ deciding_authorities: string[];
143
+ escalations: Array<{
144
+ authority: string;
145
+ escalation_id: string | null;
146
+ }>;
147
+ open_facts: string[];
148
+ /** The laws this answer was computed under (the resolved set). */
149
+ answered_under?: string[];
150
+ /** What the answer covers, in words: the registrar lists only its own authorities, so other law
151
+ * may also apply. An empty set is never AUTHORIZED. */
152
+ coverage?: string;
153
+ }
86
154
  interface CanOptions {
87
155
  /** Override the API host (defaults to the public NOMOS Exchange). */
88
156
  baseUrl?: string;
@@ -92,6 +160,32 @@ interface CanOptions {
92
160
  * `purpose: 'test'` queries are answered and sealed but kept out of the authority's public
93
161
  * history and counts — set it from tests and CI. Exchange authorities only. */
94
162
  caller?: CanCaller;
163
+ /**
164
+ * Wait for a person when the answer is ESCALATED (client.can() with an API key, on a law you
165
+ * follow with "Use this law" on the Exchange). Your approvers get an email; when one approves this
166
+ * exact action and these facts, the approval is spent once and the result comes back with
167
+ * `allowed: true`. A rejection or the timeout returns `allowed: false`. `true` waits up to 10 minutes.
168
+ */
169
+ awaitApproval?: boolean | AwaitApprovalOptions;
170
+ }
171
+ interface AwaitApprovalOptions {
172
+ /** How long to wait for an answer, ms. Default 600000 (10 minutes). */
173
+ timeoutMs?: number;
174
+ /** How often to check, ms. Default 3000. */
175
+ pollMs?: number;
176
+ /** Called once when the request is made, with the page your approver answers on. */
177
+ onApproval?: (approval: {
178
+ id: number;
179
+ url: string;
180
+ }) => void;
181
+ }
182
+ /** A person's decision on an ESCALATED answer (see CanOptions.awaitApproval). */
183
+ interface CanApproval {
184
+ id: number;
185
+ /** The page a signed-in approver answers on. */
186
+ url: string;
187
+ /** pending until answered; approved means spent for this call; timed_out means nobody answered in time. */
188
+ status: 'pending' | 'approved' | 'rejected' | 'timed_out' | 'not_usable';
95
189
  }
96
190
  interface CanCaller {
97
191
  /** A stable name for your agent, e.g. "acme-credit-agent". */
@@ -101,8 +195,11 @@ interface CanCaller {
101
195
  purpose?: 'production' | 'test';
102
196
  }
103
197
  interface CanResult {
104
- /** True only for an AUTHORIZED verdict. DENIED and ESCALATED are both `false`. */
198
+ /** True for an AUTHORIZED verdict, or for an ESCALATED one a person approved for this exact call
199
+ * (awaitApproval; the approval is then spent). Otherwise `false`. */
105
200
  allowed: boolean;
201
+ /** Present when an ESCALATED answer became an approval request (a law you follow, with a key). */
202
+ approval?: CanApproval;
106
203
  /** The authority's own verdict, verbatim, in ONE consistent vocabulary regardless of which
107
204
  * rail answered it — the public Exchange or your own authenticated artifact. */
108
205
  verdict: CanVerdict;
@@ -167,6 +264,18 @@ interface CanResult {
167
264
  * `"unchecked"` (neither — e.g. the pinned root, which nothing else can vouch for). Omitted
168
265
  * (not a guess) on the Exchange/private-artifact rails, where this concept doesn't apply. */
169
266
  revocation_checked?: 'live' | 'staple' | 'unchecked';
267
+ /** Exchange rail: the platform's Ed25519 signature over what was decided, made when the query was
268
+ * answered (docs/design-notes/signed-query-records.md). Check the full record offline with
269
+ * `node verify-nomos.mjs <record.json> --pubkey <key.pem>` (record from `url`). Absent on records
270
+ * answered by a deployment with no signing key. */
271
+ signature?: {
272
+ algorithm: 'Ed25519';
273
+ kid: string;
274
+ value: string;
275
+ statement: Record<string, unknown>;
276
+ };
277
+ /** Exchange rail: the evaluator version that decided this, as named in the signed statement. */
278
+ evaluator_version?: string;
170
279
  }
171
280
  interface ConservedStateInfo {
172
281
  artifact_id: string;
@@ -795,6 +904,12 @@ declare class Nomos {
795
904
  can(params: CanParams, opts?: CanOptions): Promise<CanResult>;
796
905
  }
797
906
 
907
+ declare function can(params: CanParams & {
908
+ context: Record<string, unknown>;
909
+ authority?: undefined;
910
+ artifact_id?: undefined;
911
+ artifact?: undefined;
912
+ }, opts?: CanOptions): Promise<CanResolutionResult>;
798
913
  declare function can(params: CanParams, opts?: CanOptions): Promise<CanResult>;
799
914
  /**
800
915
  * Ready-to-use, keyless entry point matching the canonical `nomos.can(...)` form.
@@ -1635,4 +1750,4 @@ declare function approver(o: ApproverOptions): {
1635
1750
  pending: Map<string, Pending>;
1636
1751
  };
1637
1752
 
1638
- export { ACT_MEDIA_TYPE, type AcceptOptions, type AcceptStore, type AcceptTransaction, type AcceptedAuthority, type KeyCertificate as ActKeyCertificate, type ActOptions, type ActResult, type ActStatus, type Action, type Answer, type ApproverOptions, type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type BindingProposal, type BindingSuggestion, type BuildActOptions, type CanCaller, type CanOptions, type CanParams, type CanResult, type CanVerdict, type CertificateRegistry, type ChainVerdictDecision, type ConservedStateInfo, type ConservedStateReceipt, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type EnrolmentClaims, type FactBinding, type GenerateGovernanceParams, type GenerateGovernanceResult, type IssueOptions, type IssuerDocument, type IssuerOptions, type KeyCertificate$1 as KeyCertificate, type ListArtifactsParams, MAX_PRESENTED, Nomos, NomosAPIError, NomosActError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosDiscoveryError, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type PasskeyApproverOptions, type PasskeyCredential, type PasskeyStore, type PendingApproval, type PgClient, type PgPool, type PolicyField, type PostgresStoreOptions, type Receipt, RegistryError, type RegistryStore, type RelyingPartyDocument, type RequestOptions, type Reservation, type ReserveByActParams, type ReserveByQueryParams, type ReserveParams, type RevocationSourceOptions, RevocationUnavailable, type SignatureSuite, type Signer, type ToolBindings, type ToolDescription, type UnresolvedFact, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, WELL_KNOWN, type WithLastResponse, type WitnessDocument, type WitnessOptions, accept, act, applyToolBindings, approver, buildAct, can, certificateRegistry, certify, describeSuggestion, fetchDocument, generateSigner, issuer, kidOf, localSigner, memoryPasskeyStore, memoryRegistryStore, memoryStore, nomos, originOf, parseToolBindings, passkeyApprover, postgresPasskeyStore, postgresRegistryStore, postgresStore, presentedCertificates, readAttestation, reliedKids, revocationListCertificates, revocationSource, seal, seededSigner, signConsent, signEnrolmentToken, signInForce, signRevocation, signRevocationList, signTestimony, statusFor, suggestToolBindings, verifyChain, verifyEnrolmentToken, witness };
1753
+ export { ACT_MEDIA_TYPE, type AcceptOptions, type AcceptStore, type AcceptTransaction, type AcceptedAuthority, type KeyCertificate as ActKeyCertificate, type ActOptions, type ActResult, type ActStatus, type Action, type Answer, type ApproverOptions, type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type AwaitApprovalOptions, type BindingProposal, type BindingSuggestion, type BuildActOptions, type CanApproval, type CanCaller, type CanOptions, type CanParams, type CanResolutionMember, type CanResolutionResult, type CanResult, type CanVerdict, type CertificateRegistry, type ChainVerdictDecision, type ConservedStateInfo, type ConservedStateReceipt, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type EnrolmentClaims, type FactBinding, type GenerateGovernanceParams, type GenerateGovernanceResult, type IssueOptions, type IssuerDocument, type IssuerOptions, type KeyCertificate$1 as KeyCertificate, type ListArtifactsParams, MAX_PRESENTED, Nomos, NomosAPIError, NomosActError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosDiscoveryError, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type PasskeyApproverOptions, type PasskeyCredential, type PasskeyStore, type PendingApproval, type PgClient, type PgPool, type PolicyField, type PostgresStoreOptions, type Receipt, RegistryError, type RegistryStore, type RelyingPartyDocument, type RequestOptions, type Reservation, type ReserveByActParams, type ReserveByQueryParams, type ReserveParams, type ResolvedAuthorityRef, type RevocationSourceOptions, RevocationUnavailable, type SignatureSuite, type Signer, type ToolBindings, type ToolDescription, type UnresolvedFact, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, WELL_KNOWN, type WithLastResponse, type WitnessDocument, type WitnessOptions, accept, act, applyToolBindings, approver, buildAct, can, certificateRegistry, certify, describeSuggestion, fetchDocument, generateSigner, issuer, kidOf, localSigner, memoryPasskeyStore, memoryRegistryStore, memoryStore, nomos, originOf, parseToolBindings, passkeyApprover, postgresPasskeyStore, postgresRegistryStore, postgresStore, presentedCertificates, readAttestation, reliedKids, revocationListCertificates, revocationSource, seal, seededSigner, signConsent, signEnrolmentToken, signInForce, signRevocation, signRevocationList, signTestimony, statusFor, suggestToolBindings, verifyChain, verifyEnrolmentToken, witness };
package/dist/index.d.ts CHANGED
@@ -80,9 +80,77 @@ interface CanParams {
80
80
  /** The facts to evaluate. Either pass this explicitly, or spread the facts flat alongside
81
81
  * `authority`/`action` — both shapes are accepted (explicit `inputs` wins if both given). */
82
82
  inputs?: Record<string, unknown>;
83
+ /** Resolution rail (NOMOS-SPEC-014 Draft): the properties that attach law to an act —
84
+ * e.g. { jurisdiction: 'EU', sector: 'ai' }. With `context` and NO `authority`/`artifact_id`/
85
+ * `artifact`, the server first resolves WHICH authorities govern this context (from its own
86
+ * signed registrar chain — never from anything you present), asks every one of them, and
87
+ * combines strictly. Returns a CanResolutionResult instead of a CanResult. Keyless. */
88
+ context?: Record<string, unknown>;
83
89
  /** Flat-facts convenience: any extra keys are treated as inputs when `inputs` is omitted. */
84
90
  [key: string]: unknown;
85
91
  }
92
+ /** One governing authority the resolver found for a context (NOMOS-SPEC-014). */
93
+ interface ResolvedAuthorityRef {
94
+ authority: string;
95
+ version: string | null;
96
+ registrar: string;
97
+ scope: string;
98
+ }
99
+ /** One resolved authority's answer inside a resolution-rail can() call. `verdict` is null with
100
+ * `unavailable` set when the member could not be consulted — which holds the WHOLE combined
101
+ * answer open (INSUFFICIENT_DATA), never acts as a permit. */
102
+ interface CanResolutionMember {
103
+ authority: string;
104
+ verdict: string | null;
105
+ unavailable: string | null;
106
+ query_id?: string | null;
107
+ rule?: string | null;
108
+ reason?: string | null;
109
+ escalation_id?: string | null;
110
+ revoked?: boolean;
111
+ missing_facts?: string[];
112
+ }
113
+ /** The resolution rail's answer (NOMOS-SPEC-014): which authorities govern, each one's own
114
+ * sealed answer, and the strict combination. `verdict` adds two values CanResult never has:
115
+ * INSUFFICIENT_DATA (resolution or a member held open — named in `open_facts`/`resolution`)
116
+ * and CONFLICT (two registrars assert exclusive precedence; a person settles it). */
117
+ interface CanResolutionResult {
118
+ mode: 'resolution';
119
+ allowed: boolean;
120
+ verdict: 'AUTHORIZED' | 'DENIED' | 'ESCALATED' | 'INSUFFICIENT_DATA' | 'CONFLICT';
121
+ reason: string;
122
+ resolution: {
123
+ status: 'RESOLVED' | 'CONFLICT' | 'INCOMPLETE';
124
+ applicable?: ResolvedAuthorityRef[];
125
+ missing?: {
126
+ context_facts: string[];
127
+ unreachable_registrars: string[];
128
+ stale_closures: string[];
129
+ /** SPEC-014 0.2.0: a current closure names law the registrar's log no longer claims, or the reverse */
130
+ closure_disagreements?: Array<{
131
+ registrar: string;
132
+ authority: string;
133
+ reason: 'closed_but_not_claimed' | 'claimed_but_not_closed';
134
+ }>;
135
+ /** SPEC-014 0.2.0: registrars whose log copy is older than the relying party's bound */
136
+ stale_heads?: string[];
137
+ };
138
+ conflicts?: unknown[];
139
+ proof: unknown;
140
+ };
141
+ members: CanResolutionMember[];
142
+ deciding_authorities: string[];
143
+ escalations: Array<{
144
+ authority: string;
145
+ escalation_id: string | null;
146
+ }>;
147
+ open_facts: string[];
148
+ /** The laws this answer was computed under (the resolved set). */
149
+ answered_under?: string[];
150
+ /** What the answer covers, in words: the registrar lists only its own authorities, so other law
151
+ * may also apply. An empty set is never AUTHORIZED. */
152
+ coverage?: string;
153
+ }
86
154
  interface CanOptions {
87
155
  /** Override the API host (defaults to the public NOMOS Exchange). */
88
156
  baseUrl?: string;
@@ -92,6 +160,32 @@ interface CanOptions {
92
160
  * `purpose: 'test'` queries are answered and sealed but kept out of the authority's public
93
161
  * history and counts — set it from tests and CI. Exchange authorities only. */
94
162
  caller?: CanCaller;
163
+ /**
164
+ * Wait for a person when the answer is ESCALATED (client.can() with an API key, on a law you
165
+ * follow with "Use this law" on the Exchange). Your approvers get an email; when one approves this
166
+ * exact action and these facts, the approval is spent once and the result comes back with
167
+ * `allowed: true`. A rejection or the timeout returns `allowed: false`. `true` waits up to 10 minutes.
168
+ */
169
+ awaitApproval?: boolean | AwaitApprovalOptions;
170
+ }
171
+ interface AwaitApprovalOptions {
172
+ /** How long to wait for an answer, ms. Default 600000 (10 minutes). */
173
+ timeoutMs?: number;
174
+ /** How often to check, ms. Default 3000. */
175
+ pollMs?: number;
176
+ /** Called once when the request is made, with the page your approver answers on. */
177
+ onApproval?: (approval: {
178
+ id: number;
179
+ url: string;
180
+ }) => void;
181
+ }
182
+ /** A person's decision on an ESCALATED answer (see CanOptions.awaitApproval). */
183
+ interface CanApproval {
184
+ id: number;
185
+ /** The page a signed-in approver answers on. */
186
+ url: string;
187
+ /** pending until answered; approved means spent for this call; timed_out means nobody answered in time. */
188
+ status: 'pending' | 'approved' | 'rejected' | 'timed_out' | 'not_usable';
95
189
  }
96
190
  interface CanCaller {
97
191
  /** A stable name for your agent, e.g. "acme-credit-agent". */
@@ -101,8 +195,11 @@ interface CanCaller {
101
195
  purpose?: 'production' | 'test';
102
196
  }
103
197
  interface CanResult {
104
- /** True only for an AUTHORIZED verdict. DENIED and ESCALATED are both `false`. */
198
+ /** True for an AUTHORIZED verdict, or for an ESCALATED one a person approved for this exact call
199
+ * (awaitApproval; the approval is then spent). Otherwise `false`. */
105
200
  allowed: boolean;
201
+ /** Present when an ESCALATED answer became an approval request (a law you follow, with a key). */
202
+ approval?: CanApproval;
106
203
  /** The authority's own verdict, verbatim, in ONE consistent vocabulary regardless of which
107
204
  * rail answered it — the public Exchange or your own authenticated artifact. */
108
205
  verdict: CanVerdict;
@@ -167,6 +264,18 @@ interface CanResult {
167
264
  * `"unchecked"` (neither — e.g. the pinned root, which nothing else can vouch for). Omitted
168
265
  * (not a guess) on the Exchange/private-artifact rails, where this concept doesn't apply. */
169
266
  revocation_checked?: 'live' | 'staple' | 'unchecked';
267
+ /** Exchange rail: the platform's Ed25519 signature over what was decided, made when the query was
268
+ * answered (docs/design-notes/signed-query-records.md). Check the full record offline with
269
+ * `node verify-nomos.mjs <record.json> --pubkey <key.pem>` (record from `url`). Absent on records
270
+ * answered by a deployment with no signing key. */
271
+ signature?: {
272
+ algorithm: 'Ed25519';
273
+ kid: string;
274
+ value: string;
275
+ statement: Record<string, unknown>;
276
+ };
277
+ /** Exchange rail: the evaluator version that decided this, as named in the signed statement. */
278
+ evaluator_version?: string;
170
279
  }
171
280
  interface ConservedStateInfo {
172
281
  artifact_id: string;
@@ -795,6 +904,12 @@ declare class Nomos {
795
904
  can(params: CanParams, opts?: CanOptions): Promise<CanResult>;
796
905
  }
797
906
 
907
+ declare function can(params: CanParams & {
908
+ context: Record<string, unknown>;
909
+ authority?: undefined;
910
+ artifact_id?: undefined;
911
+ artifact?: undefined;
912
+ }, opts?: CanOptions): Promise<CanResolutionResult>;
798
913
  declare function can(params: CanParams, opts?: CanOptions): Promise<CanResult>;
799
914
  /**
800
915
  * Ready-to-use, keyless entry point matching the canonical `nomos.can(...)` form.
@@ -1635,4 +1750,4 @@ declare function approver(o: ApproverOptions): {
1635
1750
  pending: Map<string, Pending>;
1636
1751
  };
1637
1752
 
1638
- export { ACT_MEDIA_TYPE, type AcceptOptions, type AcceptStore, type AcceptTransaction, type AcceptedAuthority, type KeyCertificate as ActKeyCertificate, type ActOptions, type ActResult, type ActStatus, type Action, type Answer, type ApproverOptions, type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type BindingProposal, type BindingSuggestion, type BuildActOptions, type CanCaller, type CanOptions, type CanParams, type CanResult, type CanVerdict, type CertificateRegistry, type ChainVerdictDecision, type ConservedStateInfo, type ConservedStateReceipt, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type EnrolmentClaims, type FactBinding, type GenerateGovernanceParams, type GenerateGovernanceResult, type IssueOptions, type IssuerDocument, type IssuerOptions, type KeyCertificate$1 as KeyCertificate, type ListArtifactsParams, MAX_PRESENTED, Nomos, NomosAPIError, NomosActError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosDiscoveryError, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type PasskeyApproverOptions, type PasskeyCredential, type PasskeyStore, type PendingApproval, type PgClient, type PgPool, type PolicyField, type PostgresStoreOptions, type Receipt, RegistryError, type RegistryStore, type RelyingPartyDocument, type RequestOptions, type Reservation, type ReserveByActParams, type ReserveByQueryParams, type ReserveParams, type RevocationSourceOptions, RevocationUnavailable, type SignatureSuite, type Signer, type ToolBindings, type ToolDescription, type UnresolvedFact, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, WELL_KNOWN, type WithLastResponse, type WitnessDocument, type WitnessOptions, accept, act, applyToolBindings, approver, buildAct, can, certificateRegistry, certify, describeSuggestion, fetchDocument, generateSigner, issuer, kidOf, localSigner, memoryPasskeyStore, memoryRegistryStore, memoryStore, nomos, originOf, parseToolBindings, passkeyApprover, postgresPasskeyStore, postgresRegistryStore, postgresStore, presentedCertificates, readAttestation, reliedKids, revocationListCertificates, revocationSource, seal, seededSigner, signConsent, signEnrolmentToken, signInForce, signRevocation, signRevocationList, signTestimony, statusFor, suggestToolBindings, verifyChain, verifyEnrolmentToken, witness };
1753
+ export { ACT_MEDIA_TYPE, type AcceptOptions, type AcceptStore, type AcceptTransaction, type AcceptedAuthority, type KeyCertificate as ActKeyCertificate, type ActOptions, type ActResult, type ActStatus, type Action, type Answer, type ApproverOptions, type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type AwaitApprovalOptions, type BindingProposal, type BindingSuggestion, type BuildActOptions, type CanApproval, type CanCaller, type CanOptions, type CanParams, type CanResolutionMember, type CanResolutionResult, type CanResult, type CanVerdict, type CertificateRegistry, type ChainVerdictDecision, type ConservedStateInfo, type ConservedStateReceipt, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type EnrolmentClaims, type FactBinding, type GenerateGovernanceParams, type GenerateGovernanceResult, type IssueOptions, type IssuerDocument, type IssuerOptions, type KeyCertificate$1 as KeyCertificate, type ListArtifactsParams, MAX_PRESENTED, Nomos, NomosAPIError, NomosActError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosDiscoveryError, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type PasskeyApproverOptions, type PasskeyCredential, type PasskeyStore, type PendingApproval, type PgClient, type PgPool, type PolicyField, type PostgresStoreOptions, type Receipt, RegistryError, type RegistryStore, type RelyingPartyDocument, type RequestOptions, type Reservation, type ReserveByActParams, type ReserveByQueryParams, type ReserveParams, type ResolvedAuthorityRef, type RevocationSourceOptions, RevocationUnavailable, type SignatureSuite, type Signer, type ToolBindings, type ToolDescription, type UnresolvedFact, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, WELL_KNOWN, type WithLastResponse, type WitnessDocument, type WitnessOptions, accept, act, applyToolBindings, approver, buildAct, can, certificateRegistry, certify, describeSuggestion, fetchDocument, generateSigner, issuer, kidOf, localSigner, memoryPasskeyStore, memoryRegistryStore, memoryStore, nomos, originOf, parseToolBindings, passkeyApprover, postgresPasskeyStore, postgresRegistryStore, postgresStore, presentedCertificates, readAttestation, reliedKids, revocationListCertificates, revocationSource, seal, seededSigner, signConsent, signEnrolmentToken, signInForce, signRevocation, signRevocationList, signTestimony, statusFor, suggestToolBindings, verifyChain, verifyEnrolmentToken, witness };
package/dist/index.js CHANGED
@@ -107,7 +107,7 @@ function isRetryable(status) {
107
107
  }
108
108
 
109
109
  // src/http-client.ts
110
- var SDK_VERSION = "2.3.2";
110
+ var SDK_VERSION = "2.6.0";
111
111
  var HttpClient = class {
112
112
  constructor(opts) {
113
113
  this.opts = opts;
@@ -637,7 +637,9 @@ function normalizeAuthorityQueryResult(data, authority) {
637
637
  // NOMOS-SPEC-006 — present only when the issuer has actually revoked this exact sealed
638
638
  // version; omitted (not false) when revocation doesn't structurally apply here, so "checked
639
639
  // and clean" and "couldn't check" never look the same to a caller.
640
- ...data.revoked === true ? { revoked: true, revocation_reason: data.revocation_reason } : {}
640
+ ...data.revoked === true ? { revoked: true, revocation_reason: data.revocation_reason } : {},
641
+ ...data.signature ? { signature: data.signature } : {},
642
+ ...data.evaluator_version ? { evaluator_version: data.evaluator_version } : {}
641
643
  };
642
644
  }
643
645
  function callerHeaders(c) {
@@ -667,9 +669,34 @@ async function canViaExchange(authority, facts, action, opts) {
667
669
  if (!res.ok) mapHttpError(res.status, data, requestId);
668
670
  return normalizeAuthorityQueryResult(data, authority);
669
671
  }
670
- async function canViaExchangeAuthenticated(http, authority, facts, action, caller) {
671
- const { data } = await http.post("/api/v1/can", { artifact: authority, action, ...facts }, { headers: callerHeaders(caller) });
672
- return normalizeAuthorityQueryResult(data, authority);
672
+ async function canViaExchangeAuthenticated(http, authority, facts, action, opts) {
673
+ const ask = async () => (await http.post("/api/v1/can", { artifact: authority, action, ...facts }, { headers: callerHeaders(opts?.caller) })).data;
674
+ const data = await ask();
675
+ const result = normalizeAuthorityQueryResult(data, authority);
676
+ const approval = data?.approval;
677
+ if (!approval) return result;
678
+ result.approval = { id: approval.id, url: approval.url, status: approval.status };
679
+ if (!opts?.awaitApproval || result.verdict !== "ESCALATED") return result;
680
+ return awaitApproval(http, result, typeof opts.awaitApproval === "object" ? opts.awaitApproval : {});
681
+ }
682
+ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
683
+ async function awaitApproval(http, result, o) {
684
+ const a = result.approval;
685
+ o.onApproval?.({ id: a.id, url: a.url });
686
+ const deadline = Date.now() + (o.timeoutMs ?? 6e5);
687
+ let status = a.status;
688
+ while (status === "pending" && Date.now() < deadline) {
689
+ await sleep2(Math.max(500, o.pollMs ?? 3e3));
690
+ status = (await http.get(`/api/v1/approvals/${a.id}`)).data?.status ?? "pending";
691
+ }
692
+ if (status === "pending") return { ...result, approval: { ...a, status: "timed_out" } };
693
+ if (status !== "approved") return { ...result, approval: { ...a, status: "rejected" } };
694
+ try {
695
+ await http.post(`/api/v1/approvals/${a.id}/consume`, {});
696
+ } catch {
697
+ return { ...result, approval: { ...a, status: "not_usable" } };
698
+ }
699
+ return { ...result, allowed: true, approval: { ...a, status: "approved" } };
673
700
  }
674
701
  async function canViaArtifact(http, artifactId, facts, action) {
675
702
  const isPublic = artifactId.startsWith("pub_");
@@ -747,6 +774,50 @@ async function canViaChain(artifact, keyCerts, rootPublicKeyPem, facts, action,
747
774
  if (data?.decision === "MALFORMED") throw new NomosValidationError(message, data, requestId);
748
775
  mapHttpError(res.status, data, requestId);
749
776
  }
777
+ function normalizeResolutionResult(data) {
778
+ return {
779
+ mode: "resolution",
780
+ allowed: data.verdict === "AUTHORIZED",
781
+ verdict: data.verdict,
782
+ reason: data.reason ?? "",
783
+ resolution: data.resolution ?? { status: "INCOMPLETE", proof: null },
784
+ members: Array.isArray(data.members) ? data.members : [],
785
+ deciding_authorities: Array.isArray(data.deciding_authorities) ? data.deciding_authorities : [],
786
+ escalations: Array.isArray(data.escalations) ? data.escalations : [],
787
+ open_facts: Array.isArray(data.open_facts) ? data.open_facts : [],
788
+ ...Array.isArray(data.answered_under) ? { answered_under: data.answered_under } : {},
789
+ ...typeof data.coverage === "string" ? { coverage: data.coverage } : {}
790
+ };
791
+ }
792
+ function resolutionBody(params) {
793
+ const { authority, artifact_id, artifact, key_certs, root_public_key_pem, action, inputs, context, ...flat } = params;
794
+ void authority;
795
+ void artifact_id;
796
+ void artifact;
797
+ void key_certs;
798
+ void root_public_key_pem;
799
+ const facts = inputs && typeof inputs === "object" ? inputs : flat;
800
+ return { context, ...action ? { action } : {}, ...facts };
801
+ }
802
+ async function canViaResolution(params, opts) {
803
+ const base = (opts?.baseUrl ?? DEFAULT_EXCHANGE_BASE).replace(/\/$/, "");
804
+ let res;
805
+ try {
806
+ res = await fetch(`${base}/api/v1/can`, {
807
+ method: "POST",
808
+ headers: { "Content-Type": "application/json", "User-Agent": `nomos-node/${SDK_VERSION}`, ...callerHeaders(opts?.caller) },
809
+ body: JSON.stringify(resolutionBody(params)),
810
+ signal: opts?.signal
811
+ });
812
+ } catch (err) {
813
+ if (err?.name === "AbortError") throw new NomosNetworkError("Request aborted");
814
+ throw new NomosNetworkError(err?.message ?? "Network error");
815
+ }
816
+ const requestId = res.headers.get("x-request-id") ?? "";
817
+ const data = await res.json().catch(() => null);
818
+ if (!res.ok) mapHttpError(res.status, data, requestId);
819
+ return normalizeResolutionResult(data);
820
+ }
750
821
  async function can(params, opts) {
751
822
  if (params.artifact !== void 0) {
752
823
  if (!params.key_certs || !params.root_public_key_pem) {
@@ -758,8 +829,11 @@ async function can(params, opts) {
758
829
  if (params.artifact_id) {
759
830
  throw new Error("can(): `artifact_id` requires an API key \u2014 use `client.can(...)` on an authenticated Nomos instance, not the free `can()` export.");
760
831
  }
832
+ if (!params.authority && params.context && typeof params.context === "object" && !Array.isArray(params.context)) {
833
+ return canViaResolution(params, opts);
834
+ }
761
835
  if (!params.authority || typeof params.authority !== "string") {
762
- throw new Error("can(): `authority` (an Exchange artifact id or slug), `artifact_id` (via client.can()), or `artifact` (chain-of-trust) is required.");
836
+ throw new Error("can(): `authority` (an Exchange artifact id or slug), `artifact_id` (via client.can()), `artifact` (chain-of-trust), or `context` (resolution, NOMOS-SPEC-014) is required.");
763
837
  }
764
838
  const { facts, action } = splitFacts(params);
765
839
  return canViaExchange(params.authority, facts, action, opts);
@@ -773,8 +847,12 @@ async function canAuthenticated(http, params, opts) {
773
847
  return canViaChain(params.artifact, params.key_certs, params.root_public_key_pem, facts, action, opts);
774
848
  }
775
849
  if (params.artifact_id) return canViaArtifact(http, params.artifact_id, facts, action);
776
- if (params.authority) return canViaExchangeAuthenticated(http, params.authority, facts, action, opts?.caller);
777
- throw new Error("can(): `authority` (a public Exchange artifact), `artifact_id` (your own artifact), or `artifact` (chain-of-trust) is required.");
850
+ if (params.authority) return canViaExchangeAuthenticated(http, params.authority, facts, action, opts);
851
+ if (params.context && typeof params.context === "object" && !Array.isArray(params.context)) {
852
+ const data = (await http.post("/api/v1/can", resolutionBody(params), { headers: callerHeaders(opts?.caller) })).data;
853
+ return normalizeResolutionResult(data);
854
+ }
855
+ throw new Error("can(): `authority` (a public Exchange artifact), `artifact_id` (your own artifact), `artifact` (chain-of-trust), or `context` (resolution, NOMOS-SPEC-014) is required.");
778
856
  }
779
857
  var nomos = { can };
780
858
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomosprotocol/sdk",
3
- "version": "2.3.2",
3
+ "version": "2.6.0",
4
4
  "description": "NOMOS for TypeScript: accept() for the system that makes a change, act() for the agent that asks, the issuer, witness and approver roles, and the Exchange client. Verification is local (@nomosprotocol/verify).",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -32,7 +32,9 @@
32
32
  "build": "tsup",
33
33
  "dev": "tsup --watch",
34
34
  "clean": "rm -rf dist",
35
- "type-check": "tsc --noEmit"
35
+ "type-check": "tsc --noEmit",
36
+ "check:verify": "npm --prefix ../packages/verify run build && node scripts/check-bundled-verify.mjs",
37
+ "prepublishOnly": "npm run check:verify && npm run build"
36
38
  },
37
39
  "tsup": {
38
40
  "entry": [