@nomosprotocol/sdk 1.3.4 → 2.0.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 +103 -1
- package/dist/index.cjs +5350 -77
- package/dist/index.d.cts +559 -26
- package/dist/index.d.ts +559 -26
- package/dist/index.js +5320 -74
- package/package.json +30 -11
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -14,6 +14,83 @@ Give `can()` an authority, an action, and the relevant facts. NOMOS verifies the
|
|
|
14
14
|
npm install @nomosprotocol/sdk
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## Acts: your first verified, committed change in five minutes
|
|
18
|
+
|
|
19
|
+
An **Act** carries an exact change together with its proof: the sealed rules it acts under, facts
|
|
20
|
+
signed by the systems that know them, and the approvals of the people who must approve. The system
|
|
21
|
+
that makes the change verifies all of it itself, locally, and commits exactly that change or
|
|
22
|
+
nothing (NOMOS-SPEC-008, NOMOS-SPEC-009).
|
|
23
|
+
|
|
24
|
+
**1. Start a complete local world.** It includes a shop, a purchasing policy, a finance system that
|
|
25
|
+
states budgets, and a manager who approves. Every key is a labelled test key.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx @nomosprotocol/cli dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**2. Act as an agent.** One call discovers the shop, gathers the signed budget from finance,
|
|
32
|
+
submits, and returns the shop's verdict:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { act } from '@nomosprotocol/sdk';
|
|
36
|
+
|
|
37
|
+
const r = await act({
|
|
38
|
+
to: 'http://localhost:4747',
|
|
39
|
+
action: { type: 'purchase', subject: 'DEPT-17', params: { amount: 500, vendor: 'paperco' } },
|
|
40
|
+
});
|
|
41
|
+
console.log(r.status, r.summary); // committed Committed by shop.local.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Try `amount: 2000`: the manager must approve, and the approval link prints in the sandbox window.
|
|
45
|
+
Try `amount: 9000` (over budget), `vendor: 'shadyco'` (blocked), or `subject: 'DEPT-99'` (finance
|
|
46
|
+
knows nothing). `r.missing` then names exactly the fact that is missing, and why.
|
|
47
|
+
|
|
48
|
+
**3. Be the system that makes the change.** Wrap your existing handler with `accept()`. It runs the
|
|
49
|
+
verifier locally and then, in one transaction, records the nonce, re-reads your own facts, hands
|
|
50
|
+
`commit` exactly the verified action, and stores the receipt:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { accept } from '@nomosprotocol/sdk';
|
|
54
|
+
|
|
55
|
+
const rp = accept({
|
|
56
|
+
relyingPartyId: 'orders.acme.example',
|
|
57
|
+
root: ACME_ROOT_PEM, // the root you pin; there is no default
|
|
58
|
+
actEndpoint: 'https://orders.acme.example/nomos/acts',
|
|
59
|
+
authorities: [{ artifactId: 'acme-purchasing', issuer: 'https://policy.acme.example',
|
|
60
|
+
actions: ['purchase'], currencyPolicy: 'record', inForceSealHash: POLICY_SEAL_HASH }],
|
|
61
|
+
localFacts: async (act) => ({ vendor_blocked: await isBlocked(act.action.params.vendor) }),
|
|
62
|
+
commit: async (action, { tx }) => createOrder(tx, action), // your existing code
|
|
63
|
+
store: myPostgresStore, // nonces and receipts; memoryStore() for development
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
app.use(rp.express()); // Express, Connect, node:http
|
|
67
|
+
// or: export default { fetch: rp.fetch } — Hono, Next.js, Cloudflare Workers, Deno, Bun
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`accept()` serves your discovery document at `/.well-known/nomos-relying-party`, generated from this
|
|
71
|
+
same configuration, and answers every Act with the status NOMOS-SPEC-009 assigns.
|
|
72
|
+
|
|
73
|
+
| Verdict | Status |
|
|
74
|
+
|---|---|
|
|
75
|
+
| AUTHORIZED, committed | 201 |
|
|
76
|
+
| ESCALATED | 202 |
|
|
77
|
+
| INCOMPLETE | 422 |
|
|
78
|
+
| DENIED | 403 |
|
|
79
|
+
| REPLAYED | 409, with the original receipt |
|
|
80
|
+
| any other refusal | 400 |
|
|
81
|
+
|
|
82
|
+
**The other roles** are one call each:
|
|
83
|
+
- `issuer()` publishes your sealed policies and certificates;
|
|
84
|
+
- `witness()` turns an existing lookup into signed statements;
|
|
85
|
+
- `approver()` shows a person the exact change and signs their consent.
|
|
86
|
+
|
|
87
|
+
Keys are `Signer`s, so they can live in your KMS or HSM.
|
|
88
|
+
|
|
89
|
+
**Any MCP agent** can carry Acts with no code: the `nomos-mcp` server has an `act` tool.
|
|
90
|
+
|
|
91
|
+
Verification is `@nomosprotocol/verify`, re-exported here as `verifyAct`, `verifyArtifact` and
|
|
92
|
+
`verifySeal`. It runs entirely offline, in Node, browsers and edge runtimes.
|
|
93
|
+
|
|
17
94
|
## `can()` — one function for authority
|
|
18
95
|
|
|
19
96
|
`can()` is the primary query interface to NOMOS.
|
|
@@ -227,6 +304,31 @@ An untrusted issuer is different from a rule saying "no."
|
|
|
227
304
|
|
|
228
305
|
NOMOS keeps those failure classes separate.
|
|
229
306
|
|
|
307
|
+
### `max_age` — how long a verdict should be trusted
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
verdict.max_age; // 300, or absent
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`AUTHORIZED` is true of a moment, not forever. Between your `can()` call and whatever
|
|
314
|
+
the agent actually does, the authority could be amended, the fact set could go stale, or
|
|
315
|
+
the artifact could be revoked. A caller that treats a cached verdict as valid indefinitely
|
|
316
|
+
has quietly reintroduced the gap `can()` exists to close.
|
|
317
|
+
|
|
318
|
+
`max_age` (seconds) is NOMOS's answer: re-run `can()` if more than this many seconds have
|
|
319
|
+
passed since the decision you're about to act on. Today it is present — always `300` —
|
|
320
|
+
**only** on the public authority rail (`nomos.can({ authority, ...facts })`), and only
|
|
321
|
+
when the evaluated artifact is structurally revocable (Ed25519-sealed with a `kid`, on a
|
|
322
|
+
deployment with revocation configured). It is **absent** otherwise, on purpose: an
|
|
323
|
+
artifact NOMOS cannot meaningfully re-check should never look as trustworthy as one it
|
|
324
|
+
can, so absence is a signal to re-verify more, not less. Treat a missing `max_age` as "no
|
|
325
|
+
guidance was possible," never as "safe to cache indefinitely."
|
|
326
|
+
|
|
327
|
+
**Disclosed gap:** `client.can({ artifact_id, ... })` and the presented-artifact rail
|
|
328
|
+
don't emit `max_age` yet — the field exists only on the query path that backs the public
|
|
329
|
+
rail. Until that's closed, apply the same discipline manually on those two rails: don't
|
|
330
|
+
hold a verdict across a meaningful delay before acting on it without re-checking.
|
|
331
|
+
|
|
230
332
|
## Authority trust is separate from authorization
|
|
231
333
|
|
|
232
334
|
The portable-authority rail deliberately distinguishes two questions:
|
|
@@ -409,7 +511,7 @@ The long-term goal is simple:
|
|
|
409
511
|
|
|
410
512
|
## Requirements
|
|
411
513
|
|
|
412
|
-
* Node.js
|
|
514
|
+
* Node.js 20+
|
|
413
515
|
* TypeScript supported
|
|
414
516
|
* ESM and CommonJS builds
|
|
415
517
|
* Full TypeScript declarations
|