@nomosprotocol/sdk 2.5.0 → 2.7.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
@@ -97,6 +97,39 @@ can return their own chains with what they sign (`keyCerts`). On the relying par
97
97
  (NOMOS-SPEC-007 rev. 1.10). Revocations are never forgotten, and without a current list
98
98
  `accept()` answers 503 instead of committing.
99
99
 
100
+ **Which laws govern the act (2.7.0).** An Act carries one authority, so on its own `accept()` checks
101
+ the law the carrier chose. Pin registrars and `accept()` works out every law that governs the act here,
102
+ now, and commits only if the Act carries every one (NOMOS-SPEC-014, Draft):
103
+
104
+ ```ts
105
+ import { accept, registrarSource } from '@nomosprotocol/sdk';
106
+
107
+ const platform = registrarSource({
108
+ id: 'platform',
109
+ url: 'https://www.nomosprotocol.com',
110
+ publicKeyPem: PLATFORM_KEY_PEM, // pinned by you; heads signed by any other key are refused
111
+ });
112
+
113
+ const rp = accept({
114
+ // ...relyingPartyId, root, authorities (with registrarName), commit, as above
115
+ resolution: {
116
+ registrars: [platform],
117
+ // From YOUR knowledge and state, never from the Act: a carrier that stated its own
118
+ // jurisdiction would choose the law again.
119
+ context: (act, localFacts) => ({ jurisdiction: 'AE', established_in: 'DIFC', subjects_in: 'AE', output_used_in: 'AE' }),
120
+ },
121
+ });
122
+ ```
123
+
124
+ If another law also governs the act, the Act is held (`422 INCOMPLETE`, `reason_code:
125
+ 'laws_not_presented'`, the laws named) and nothing is committed. A context fact you leave out holds it
126
+ too (`resolution_incomplete`, naming the fact). A registrar that cannot be read, a head older than
127
+ `maxHeadAgeSeconds`, a head signed by a key you did not pin, or a log whose history was rewritten
128
+ answers 503 with Retry-After. Receipts record the resolution (status, context, laws, log heads).
129
+ Create each `registrarSource()` once and reuse it: the check that a registrar's history was only ever
130
+ added to spans the source's lifetime (pass `initialHead` to carry the last head across restarts).
131
+ Without `resolution`, `accept()` is unchanged.
132
+
100
133
  **The other roles** are one call each:
101
134
  - `issuer()` publishes your sealed policies and certificates;
102
135
  - `witness()` turns an existing lookup into signed statements;
@@ -122,9 +155,55 @@ It abstracts three different authority situations behind one call and one verdic
122
155
  | A public authority published on the NOMOS Exchange | `nomos.can({ authority, ...facts })` | No |
123
156
  | Your organization's sealed artifact | `client.can({ artifact_id, ...facts })` | Yes |
124
157
  | A sealed artifact presented directly by an agent or system | `nomos.can({ artifact, key_certs, root_public_key_pem, ...facts })` | No |
158
+ | An act, and you don't know which laws govern it | `nomos.can({ context, ...facts })` | No |
125
159
 
126
160
  The caller does not need to know which evaluation rail is being used. `can()` routes internally based on what you provide.
127
161
 
162
+ ### 0. Ask by context: let NOMOS find the laws (2.6.0)
163
+
164
+ Naming a law lets the caller choose which law is checked, and leave out the strict one. Describe
165
+ the act instead, and NOMOS works out which laws govern it, asks every one, and combines the answers:
166
+ any deny denies, and a yes needs every law to say yes.
167
+
168
+ ```ts
169
+ import { can } from '@nomosprotocol/sdk';
170
+
171
+ const v = await can({
172
+ context: {
173
+ jurisdiction: 'AE', // where the act takes effect
174
+ established_in: 'DIFC', // where your organisation is incorporated or established
175
+ subjects_in: 'AE', // where the people it concerns are
176
+ output_used_in: 'AE', // where any AI output is used
177
+ },
178
+ action: 'share_customer_data_with_analytics_vendor',
179
+ processing_fair_and_transparent: false,
180
+ });
181
+
182
+ v.mode; // 'resolution'
183
+ v.verdict; // 'DENIED': a DIFC firm acting in mainland Dubai answers to DIFC law
184
+ v.answered_under; // ['difc-data-protection-law-difc-law-no-5-of-2020']
185
+ v.coverage; // what the answer covers, in words
186
+ v.members; // each law's own answer, each a signed record (query_id)
187
+ ```
188
+
189
+ What can come back besides a verdict:
190
+
191
+ - `INSUFFICIENT_DATA` with `open_facts`: a fact that decides which law applies was not stated
192
+ (for example `established_in`). It is asked for, never assumed.
193
+ - `INSUFFICIENT_DATA` for a place the registrar does not cover (Saudi Arabia, the US, an ADGM or
194
+ QFC firm today). "No law found" is never read as "no law applies".
195
+ - `CONFLICT`: two registrars each claim exclusive precedence. A person decides.
196
+
197
+ Which laws apply where comes from the NOMOS registrar: a signed, append-only list in the public
198
+ log at `/.well-known/nomos-exchange-log`, verifiable offline. Each claim states the provision it
199
+ rests on. It currently covers the EU AI Act, Qatar Law No. 13 of 2016, UAE Federal Decree-Law
200
+ No. 45 of 2021 and DIFC Law No. 5 of 2020, and lists only its own laws, so other law may also
201
+ apply. Only want the list of laws? `POST /api/v1/resolve { context }` returns it without asking
202
+ them, and records nothing.
203
+
204
+ The system that commits the change can resolve too, from registrars it pins and a context it supplies
205
+ itself: see `accept({ resolution })` above (2.7.0).
206
+
128
207
  ### 1. Public authority
129
208
 
130
209
  Ask a published authority directly:
@@ -187,7 +266,11 @@ import { Nomos } from "@nomosprotocol/sdk";
187
266
 
188
267
  const client = new Nomos(process.env.NOMOS_API_KEY!);
189
268
  const r = await client.can(
190
- { authority: "data-protection-law-difc-law-no-5-of-2020", days_since_access_request: 45 },
269
+ { authority: "difc-data-protection-law-difc-law-no-5-of-2020",
270
+ ...allTwelveNo, // the law's intake questions, listed on its page
271
+ q_individual_request: true, // "yes": a request from a person
272
+ access_request_received: true, days_since_access_request: 45,
273
+ identity_doubt_unresolved: false, response_period_extended_with_notice: false },
191
274
  { awaitApproval: { timeoutMs: 10 * 60_000, onApproval: (a) => console.log("waiting on", a.url) } },
192
275
  );
193
276
 
@@ -196,6 +279,14 @@ else console.log(r.verdict, r.approval?.status); // DENIED, or rejected / timed_
196
279
  ```
197
280
 
198
281
  The approval is bound to the authority, the action and the facts: different facts need a new one.
282
+
283
+ A large law carries a few yes/no **intake questions** (listed on its page). Each "no" rules out the
284
+ articles it covers, so an ordinary action gets a firm answer in one call: on the DIFC Data Protection
285
+ Law, all twelve "no" is `AUTHORIZED`.
286
+
287
+ When a listing is replaced by a newer version and retired, calls to the old address throw a
288
+ `NomosAPIError` with status 410: the message names the new version's address. Move to it; the old
289
+ address is never answered from the old rules.
199
290
  When the law gets a new version, you get an email with what changed. Turn either email off on the
200
291
  same page.
201
292