@nomosprotocol/sdk 1.3.1 → 1.3.2
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 +283 -74
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
1
|
# @nomosprotocol/sdk
|
|
2
2
|
|
|
3
|
-
Official TypeScript SDK for [NOMOS](https://www.nomosprotocol.com) —
|
|
3
|
+
Official TypeScript SDK for [NOMOS](https://www.nomosprotocol.com/) — a programmable authority layer for AI agents and software.
|
|
4
|
+
|
|
5
|
+
NOMOS lets software ask a simple question:
|
|
6
|
+
|
|
7
|
+
> **Can I do this?**
|
|
8
|
+
|
|
9
|
+
Give `can()` an authority, an action, and the relevant facts. NOMOS verifies the authority, evaluates its rules, and returns a deterministic verdict:
|
|
10
|
+
|
|
11
|
+
**AUTHORIZED · DENIED · ESCALATED**
|
|
4
12
|
|
|
5
13
|
```bash
|
|
6
14
|
npm install @nomosprotocol/sdk
|
|
7
15
|
```
|
|
8
16
|
|
|
9
|
-
## `can()` — one function
|
|
17
|
+
## `can()` — one function for authority
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
`can()` is the primary query interface to NOMOS.
|
|
12
20
|
|
|
13
|
-
|
|
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 |
|
|
21
|
+
It abstracts three different authority situations behind one call and one verdict vocabulary:
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
| You have... | Call | API key? |
|
|
24
|
+
| ---------------------------------------------------------- | ------------------------------------------------------------------- | -------: |
|
|
25
|
+
| A public authority published on the NOMOS Exchange | `nomos.can({ authority, ...facts })` | No |
|
|
26
|
+
| Your organization's sealed artifact | `client.can({ artifact_id, ...facts })` | Yes |
|
|
27
|
+
| A sealed artifact presented directly by an agent or system | `nomos.can({ artifact, key_certs, root_public_key_pem, ...facts })` | No |
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
The caller does not need to know which evaluation rail is being used. `can()` routes internally based on what you provide.
|
|
30
|
+
|
|
31
|
+
### 1. Public authority
|
|
32
|
+
|
|
33
|
+
Ask a published authority directly:
|
|
22
34
|
|
|
23
35
|
```ts
|
|
24
36
|
import { nomos } from "@nomosprotocol/sdk";
|
|
@@ -28,86 +40,201 @@ const verdict = await nomos.can({
|
|
|
28
40
|
ai_categorises_biometric_data_for_sensitive_attributes: true,
|
|
29
41
|
});
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
console.log(verdict.verdict);
|
|
44
|
+
// "DENIED"
|
|
45
|
+
|
|
46
|
+
console.log(verdict.reason);
|
|
47
|
+
// explanation from the authority
|
|
48
|
+
|
|
49
|
+
console.log(verdict.query_id);
|
|
50
|
+
// permanent transcript ID
|
|
36
51
|
```
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
No API key or account is required for public Exchange authorities.
|
|
54
|
+
|
|
55
|
+
Facts can also be passed explicitly:
|
|
39
56
|
|
|
40
57
|
```ts
|
|
41
58
|
const verdict = await nomos.can({
|
|
42
59
|
authority: "eu-ai-act.nomos",
|
|
43
|
-
inputs: {
|
|
44
|
-
|
|
60
|
+
inputs: {
|
|
61
|
+
ai_categorises_biometric_data_for_sensitive_attributes: true,
|
|
62
|
+
},
|
|
63
|
+
action: "screen a new feature before launch",
|
|
45
64
|
});
|
|
46
65
|
```
|
|
47
66
|
|
|
48
|
-
|
|
67
|
+
If a paid authority has not been purchased, NOMOS throws `NomosPaymentRequiredError` with the price and purchase URL attached.
|
|
68
|
+
|
|
69
|
+
NOMOS never auto-charges on your behalf.
|
|
49
70
|
|
|
50
|
-
### Your
|
|
71
|
+
### 2. Your organization's authority
|
|
51
72
|
|
|
52
|
-
|
|
73
|
+
Use an authenticated client for artifacts belonging to your organization:
|
|
53
74
|
|
|
54
75
|
```ts
|
|
55
76
|
import { Nomos } from "@nomosprotocol/sdk";
|
|
56
77
|
|
|
57
|
-
const client = new Nomos("nms_live_...");
|
|
78
|
+
const client = new Nomos("nms_live_...");
|
|
58
79
|
|
|
59
80
|
const verdict = await client.can({
|
|
60
81
|
artifact_id: "loan_approval_v1",
|
|
61
82
|
credit_score: 720,
|
|
62
83
|
loan_amount: 50_000,
|
|
63
84
|
});
|
|
85
|
+
|
|
86
|
+
if (verdict.allowed) {
|
|
87
|
+
// proceed
|
|
88
|
+
}
|
|
64
89
|
```
|
|
65
90
|
|
|
66
|
-
|
|
91
|
+
The same verdict vocabulary is returned.
|
|
92
|
+
|
|
93
|
+
The difference is that the artifact is private and accessed through your authenticated NOMOS account.
|
|
94
|
+
|
|
95
|
+
### 3. A portable authority presented by an agent
|
|
67
96
|
|
|
68
|
-
|
|
97
|
+
This is the keyless, portable authority rail.
|
|
98
|
+
|
|
99
|
+
An agent can carry a sealed `.nomos` artifact with it and present that authority to another system — even if NOMOS has never seen or registered the artifact.
|
|
69
100
|
|
|
70
101
|
```ts
|
|
71
102
|
const verdict = await nomos.can({
|
|
72
|
-
artifact:
|
|
73
|
-
key_certs:
|
|
103
|
+
artifact: presentedArtifact,
|
|
104
|
+
key_certs: [rootToIntermediate, intermediateToLeaf],
|
|
74
105
|
root_public_key_pem: yourPinnedRootKey,
|
|
75
106
|
credit_score: 720,
|
|
76
107
|
});
|
|
77
108
|
```
|
|
78
109
|
|
|
79
|
-
|
|
110
|
+
NOMOS performs the entire verification locally:
|
|
111
|
+
|
|
112
|
+
1. Walk the presented certificate chain from the root key you've pinned.
|
|
113
|
+
2. Verify each link's signature and check it hasn't expired.
|
|
114
|
+
3. Check that no key in the chain has been revoked.
|
|
115
|
+
4. Verify the artifact's own cryptographic seal against the resolved key.
|
|
116
|
+
5. Evaluate the artifact's rules against the supplied facts.
|
|
117
|
+
6. Return `AUTHORIZED`, `DENIED`, or `ESCALATED`.
|
|
118
|
+
|
|
119
|
+
(NOMOS-SPEC-006 artifact-version revocation — an issuer withdrawing one specific sealed version — is checked on the Exchange rail today; wiring the same check into this rail is a known gap, not yet done.)
|
|
120
|
+
|
|
121
|
+
No prior artifact lookup is required.
|
|
122
|
+
|
|
123
|
+
This means authority can **travel with the agent**.
|
|
124
|
+
|
|
125
|
+
The relying party does not have to trust the AI vendor, the model, or a pre-existing NOMOS registry entry. It can independently verify the authority presented to it.
|
|
126
|
+
|
|
127
|
+
The root key is deliberately supplied by the relying party. NOMOS does not define a universal default root of trust.
|
|
128
|
+
|
|
129
|
+
This rail is currently a working prototype and is not yet a numbered NOMOS specification. A second independent implementation is still required before treating the wire format as interoperable infrastructure.
|
|
130
|
+
|
|
131
|
+
## `.nomos` — the portable authority artifact
|
|
80
132
|
|
|
81
|
-
|
|
133
|
+
A `.nomos` file is a sealed, machine-executable representation of institutional authority.
|
|
82
134
|
|
|
83
|
-
|
|
135
|
+
It can contain the rules, constraints, permissions, obligations, provenance, issuer information, version, and other governance metadata required to evaluate an action.
|
|
84
136
|
|
|
85
|
-
|
|
137
|
+
The artifact is cryptographically sealed.
|
|
86
138
|
|
|
87
|
-
|
|
88
|
-
|---|---|---|
|
|
89
|
-
| `allowed` | `boolean` | `true` only when `verdict === "AUTHORIZED"` |
|
|
90
|
-
| `verdict` | `"AUTHORIZED" \| "DENIED" \| "ESCALATED"` | The authority's own answer |
|
|
91
|
-
| `reason` | `string` | Human-readable explanation |
|
|
92
|
-
| `rule` | `string \| null` | The rule id that decided it, if any |
|
|
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 |
|
|
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 |
|
|
139
|
+
Changing the authority changes the seal.
|
|
103
140
|
|
|
104
|
-
|
|
141
|
+
That makes the authority:
|
|
105
142
|
|
|
106
|
-
|
|
143
|
+
* **machine-executable** — software can evaluate it;
|
|
144
|
+
* **portable** — it can move between systems;
|
|
145
|
+
* **verifiable** — another party can check its authenticity;
|
|
146
|
+
* **versioned** — the exact authority being evaluated can be identified;
|
|
147
|
+
* **auditable** — decisions can be tied back to the authority that produced them.
|
|
107
148
|
|
|
108
|
-
|
|
149
|
+
The important distinction is:
|
|
109
150
|
|
|
110
|
-
|
|
151
|
+
> The AI does not have to carry the policy in its prompt and be trusted to obey it.
|
|
152
|
+
|
|
153
|
+
The authority can exist outside the model as a verifiable artifact.
|
|
154
|
+
|
|
155
|
+
The model reasons.
|
|
156
|
+
|
|
157
|
+
**NOMOS determines what the authority permits.**
|
|
158
|
+
|
|
159
|
+
## The verdict
|
|
160
|
+
|
|
161
|
+
`can()` normalizes every rail into the same core result:
|
|
162
|
+
|
|
163
|
+
| Field | Type | Meaning |
|
|
164
|
+
| ------------------- | ------------------------------------ | -------------------------------------------------------- |
|
|
165
|
+
| `allowed` | `boolean` | `true` only when the verdict is `AUTHORIZED` |
|
|
166
|
+
| `verdict` | `AUTHORIZED \| DENIED \| ESCALATED` | The authority's decision |
|
|
167
|
+
| `reason` | `string` | Human-readable explanation |
|
|
168
|
+
| `rule` | `string \| null` | Rule that determined the result, when applicable |
|
|
169
|
+
| `authority` | `{ id, title, standing, attestation? }` | The authority that answered, its standing, and — when a regulator/auditor/issuer has co-signed this exact version — who, in what role, and their statement |
|
|
170
|
+
| `version` | `string` | Authority version, when declared |
|
|
171
|
+
| `missing_inputs` | `string[]` | Facts required for an `ESCALATED` decision |
|
|
172
|
+
| `obligations` | array | Additional duties triggered alongside the verdict |
|
|
173
|
+
| `revoked` | `boolean` | Whether the evaluated authority version has been revoked |
|
|
174
|
+
| `revocation_reason` | `string` | Reason for revocation, when applicable |
|
|
175
|
+
| `max_age` | `number` | Recommended period before re-checking the verdict |
|
|
176
|
+
| `audit` | `{ hash, timestamp }` | Present only on your own artifact rail — your own provenance record, distinct from the Exchange rail's public transcript |
|
|
177
|
+
|
|
178
|
+
### `ESCALATED` is not `DENIED`
|
|
179
|
+
|
|
180
|
+
An escalation means the authority does not yet have enough information to decide.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
if (verdict.verdict === "ESCALATED") {
|
|
184
|
+
console.log(verdict.missing_inputs);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
This distinction matters in institutional workflows.
|
|
189
|
+
|
|
190
|
+
A missing fact is different from a prohibition.
|
|
191
|
+
|
|
192
|
+
An untrusted issuer is different from a rule saying "no."
|
|
193
|
+
|
|
194
|
+
NOMOS keeps those failure classes separate.
|
|
195
|
+
|
|
196
|
+
## Authority trust is separate from authorization
|
|
197
|
+
|
|
198
|
+
The portable-authority rail deliberately distinguishes two questions:
|
|
199
|
+
|
|
200
|
+
**"Do I trust this authority?"**
|
|
201
|
+
|
|
202
|
+
and
|
|
203
|
+
|
|
204
|
+
**"Does this authority permit this action?"**
|
|
205
|
+
|
|
206
|
+
If the presented chain cannot be established, NOMOS throws `NomosIssuerNotTrustedError`.
|
|
207
|
+
|
|
208
|
+
It does **not** return `DENIED`.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
try {
|
|
212
|
+
const verdict = await nomos.can({
|
|
213
|
+
artifact,
|
|
214
|
+
key_certs,
|
|
215
|
+
root_public_key_pem,
|
|
216
|
+
credit_score: 720,
|
|
217
|
+
});
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (err instanceof NomosIssuerNotTrustedError) {
|
|
220
|
+
// The authority itself was not established as trustworthy.
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
A `DENIED` verdict means:
|
|
226
|
+
|
|
227
|
+
> **The authority was established, and its rules said no.**
|
|
228
|
+
|
|
229
|
+
An issuer-trust error means:
|
|
230
|
+
|
|
231
|
+
> **The authority itself was not established.**
|
|
232
|
+
|
|
233
|
+
Those are fundamentally different claims.
|
|
234
|
+
|
|
235
|
+
## Errors
|
|
236
|
+
|
|
237
|
+
NOMOS uses typed errors rather than generic failures:
|
|
111
238
|
|
|
112
239
|
```ts
|
|
113
240
|
import {
|
|
@@ -119,64 +246,146 @@ import {
|
|
|
119
246
|
} from "@nomosprotocol/sdk";
|
|
120
247
|
|
|
121
248
|
try {
|
|
122
|
-
const verdict = await nomos.can({
|
|
249
|
+
const verdict = await nomos.can({
|
|
250
|
+
authority: "eu-ai-act.nomos",
|
|
251
|
+
...facts,
|
|
252
|
+
});
|
|
123
253
|
} catch (err) {
|
|
124
254
|
if (err instanceof NomosNotFoundError) {
|
|
125
|
-
//
|
|
255
|
+
// Authority does not exist.
|
|
126
256
|
} else if (err instanceof NomosPaymentRequiredError) {
|
|
127
|
-
//
|
|
257
|
+
// Paid authority that has not been purchased.
|
|
258
|
+
console.log(err.priceUsd);
|
|
259
|
+
console.log(err.purchaseUrl);
|
|
128
260
|
} else if (err instanceof NomosIssuerNotTrustedError) {
|
|
129
|
-
//
|
|
130
|
-
|
|
261
|
+
// Portable authority could not be established.
|
|
262
|
+
console.log(err.reason);
|
|
263
|
+
// ISSUER_NOT_RECOGNIZED
|
|
264
|
+
// KEY_REVOKED
|
|
265
|
+
// SEAL_INVALID
|
|
131
266
|
} else if (err instanceof NomosNetworkError) {
|
|
132
|
-
//
|
|
267
|
+
// Network failure or timeout.
|
|
133
268
|
} else {
|
|
134
269
|
throw err;
|
|
135
270
|
}
|
|
136
271
|
}
|
|
137
272
|
```
|
|
138
273
|
|
|
139
|
-
|
|
274
|
+
## Permanent decision transcripts
|
|
275
|
+
|
|
276
|
+
For public Exchange authorities, every `can()` request produces a permanent transcript.
|
|
277
|
+
|
|
278
|
+
The transcript records what was asked, which authority version answered, and what it decided.
|
|
279
|
+
|
|
280
|
+
The returned `query_id` can be used to retrieve the transcript:
|
|
140
281
|
|
|
141
282
|
```ts
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
283
|
+
console.log(verdict.query_id);
|
|
284
|
+
console.log(verdict.url);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The portable and private artifact rails are stateless by design and do not invent a server-side transcript ID.
|
|
288
|
+
|
|
289
|
+
## What `can()` does — and does not do
|
|
290
|
+
|
|
291
|
+
`can()` answers one question:
|
|
292
|
+
|
|
293
|
+
> **Is this action authorized under this authority, given these facts, right now?**
|
|
294
|
+
|
|
295
|
+
It deliberately does not become the interface for everything NOMOS does. Other capabilities stay on the authenticated client, because they answer different questions:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
await client.can({ artifact_id, ...facts }) // Is this action authorized?
|
|
299
|
+
await verifyChain({ artifact, key_certs, root_public_key_pem }) // Is this artifact's chain of custody genuine?
|
|
300
|
+
await client.artifacts.list({ band, sort }) // What artifacts are available?
|
|
301
|
+
await client.artifacts.retrieve(id) // What is inside this artifact?
|
|
302
|
+
await client.governance.generate({ policies, decision_logs }) // Create a new sealed artifact.
|
|
303
|
+
await client.governance.detectContradictions({ rules }) // Find conflicts in a rule set.
|
|
149
304
|
```
|
|
150
305
|
|
|
151
|
-
|
|
306
|
+
The simplicity comes from giving each operation one meaning.
|
|
307
|
+
|
|
308
|
+
## Authenticated client
|
|
152
309
|
|
|
153
|
-
|
|
310
|
+
The authenticated `Nomos` client exposes the broader platform:
|
|
154
311
|
|
|
155
312
|
```ts
|
|
156
313
|
import { Nomos } from "@nomosprotocol/sdk";
|
|
157
314
|
|
|
158
|
-
const client = new Nomos("nms_live_...");
|
|
315
|
+
const client = new Nomos("nms_live_...");
|
|
316
|
+
|
|
317
|
+
// Authorization
|
|
318
|
+
await client.can({
|
|
319
|
+
artifact_id: "your_artifact_id",
|
|
320
|
+
...facts,
|
|
321
|
+
});
|
|
159
322
|
|
|
160
|
-
// Lower-level
|
|
161
|
-
|
|
162
|
-
const result = await client.decisions.verify({
|
|
323
|
+
// Lower-level decision verification
|
|
324
|
+
await client.decisions.verify({
|
|
163
325
|
artifact_id: "your_artifact_id",
|
|
164
|
-
decision_context: {
|
|
326
|
+
decision_context: { ...facts },
|
|
165
327
|
});
|
|
166
328
|
|
|
329
|
+
// Discover artifacts
|
|
167
330
|
for await (const artifact of client.artifacts.listAutoPaging()) {
|
|
168
331
|
console.log(artifact.id);
|
|
169
332
|
}
|
|
170
333
|
|
|
171
|
-
|
|
334
|
+
// Verify webhook events
|
|
335
|
+
const event = await client.webhooks.constructEvent(
|
|
336
|
+
payload,
|
|
337
|
+
signatureHeader,
|
|
338
|
+
webhookSecret,
|
|
339
|
+
);
|
|
172
340
|
```
|
|
173
341
|
|
|
174
|
-
|
|
342
|
+
`client.decisions.verify()` remains available when the caller needs the lower-level evaluation response rather than the normalized `can()` result.
|
|
343
|
+
|
|
344
|
+
## Why this exists
|
|
345
|
+
|
|
346
|
+
AI agents can reason.
|
|
347
|
+
|
|
348
|
+
They can plan.
|
|
349
|
+
|
|
350
|
+
They can call tools.
|
|
351
|
+
|
|
352
|
+
But reasoning about authority is not the same thing as possessing authority.
|
|
353
|
+
|
|
354
|
+
A system prompt can tell an agent:
|
|
355
|
+
|
|
356
|
+
> "You may approve loans below this threshold."
|
|
357
|
+
|
|
358
|
+
That does not independently prove that the instruction is authentic, current, untampered with, or actually authoritative.
|
|
359
|
+
|
|
360
|
+
NOMOS moves that authority outside the model.
|
|
361
|
+
|
|
362
|
+
The authority becomes a machine-readable artifact.
|
|
363
|
+
|
|
364
|
+
The artifact can be verified.
|
|
365
|
+
|
|
366
|
+
The rules can be evaluated deterministically.
|
|
367
|
+
|
|
368
|
+
And the result can be checked independently of the model that requested the action.
|
|
369
|
+
|
|
370
|
+
The long-term goal is simple:
|
|
371
|
+
|
|
372
|
+
> **AI agents should not have to be trusted to understand their authority. Their authority should be something other systems can verify.**
|
|
175
373
|
|
|
176
374
|
## Requirements
|
|
177
375
|
|
|
178
|
-
Node.js 18
|
|
376
|
+
* Node.js 18+
|
|
377
|
+
* TypeScript supported
|
|
378
|
+
* ESM and CommonJS builds
|
|
379
|
+
* Full TypeScript declarations
|
|
179
380
|
|
|
180
381
|
## License
|
|
181
382
|
|
|
182
383
|
MIT
|
|
384
|
+
|
|
385
|
+
## Links
|
|
386
|
+
|
|
387
|
+
* [NOMOS Protocol](https://www.nomosprotocol.com/)
|
|
388
|
+
* [Documentation](https://www.nomosprotocol.com/docs)
|
|
389
|
+
* [NOMOS Exchange](https://www.nomosprotocol.com/exchange)
|
|
390
|
+
* [NOMOS Specification](https://www.nomosprotocol.com/spec)
|
|
391
|
+
* [GitHub](https://github.com/nomos-spec/spec)
|