nervur 0.3.0 → 0.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/LICENSE CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  Apache License
3
2
  Version 2.0, January 2004
4
3
  http://www.apache.org/licenses/
package/NOTICE ADDED
@@ -0,0 +1,2 @@
1
+ Nervur
2
+ Copyright 2026 Razvan Gherghina
package/README.md CHANGED
@@ -1,53 +1,338 @@
1
- # nervur
1
+ # Nervur
2
2
 
3
- The nervur CLI — the operator's command-line face over a running carcass.
4
- It is a client, never the runtime: every verb speaks to the carcass's HTTP
5
- API, and the same verbs exist at parity on the nervur console, the
6
- machine-level face the carcass serves on loopback.
3
+ The library. **A distributed system of sovereign beings**: an address is its
4
+ own verification key, authority is a signature, and every relationship is
5
+ one ref with declared rights — so a reference cannot be forged and no
6
+ ambient authority exists anywhere. A ground is one sovereign process holding
7
+ many beings — it creates them and it destroys them, and it exposes exactly
8
+ one handler where every message for every being arrives. A being is one
9
+ function closed over its own cells; the door asks by whose authority and
10
+ refuses every other question. The lineage is the object-capability
11
+ tradition — CapTP, the actor model — with one honest departure: the door
12
+ verifies who is calling, and rights are declared per reference. Pure
13
+ Crockford JavaScript — factory functions, closures for privacy, no `this`,
14
+ no classes, frozen surfaces — on `node:` built-ins, plus `graphql` for the
15
+ one thing that would be madness to hand-roll: the schema and its execution.
7
16
 
8
- nervur is neutral ground for inter-company work: two companies are two
9
- cryptographic identities meeting in one shared datastore that neither hosts
10
- and both can read. Humans and AI agents are first-class colleagues on it.
17
+ ## Quickstart
11
18
 
12
- ## Install
19
+ `npm install nervur` — what ships is this source, byte for byte, one
20
+ dependency (`graphql`). The package is a library and carries no command;
21
+ the `nervur` command on an operator's PATH belongs to the
22
+ [`nervurd`](../nervurd/README.md) package, its name twin. Construction is
23
+ synchronous; every call through a door — `pointer`, `mcp`, `gql`, and each
24
+ function they hand back — is async. This runs today, lifted from the
25
+ bench's own passing suites:
13
26
 
14
- ```sh
15
- npm create nervur
27
+ ```js
28
+ import { Cells, Ground, Keychain, Tools, Voice } from 'nervur';
29
+
30
+ const DIARY = `({
31
+ name: 'acme.diary',
32
+ needs: { modules: [{ module: 'memory', contract: 'nervur.memory' }] },
33
+ memory: { pages: { type: '[String!]', class: 'data' } },
34
+ interface: \`
35
+ type Query { pages: [String!] @rights(is: [SELF, MEMBER]) }
36
+ type Mutation { write(line: String!): Int @rights(is: [SELF, MEMBER]) }
37
+ \`,
38
+ resolvers: {
39
+ Query: { pages: (_, __, { modules }) => modules.memory.read('pages') ?? [] },
40
+ Mutation: {
41
+ write: (_, { line }, { modules }) => {
42
+ const kept = modules.memory.read('pages') ?? [];
43
+ kept.push(line);
44
+ modules.memory.write('pages', kept);
45
+ return kept.length;
46
+ },
47
+ },
48
+ },
49
+ })`;
50
+
51
+ const secret = Buffer.from(process.env.NERVUR_SECRET, 'base64'); // 32 bytes, minted once with Tools.secret()
52
+ const ground = Ground({ cells: Cells({ file: 'ground.json' }), keychain: Keychain(secret) });
53
+
54
+ const me = Voice(); // keep it: writeFileSync of me.pack(), or it dies with the process
55
+ const serve = ground.serve(me);
56
+ const stranger = await serve.pointer('Ground');
57
+ await stranger.init({ address: Tools.address(me.pk) }); // first boot only — once, ever
58
+
59
+ const admin = await serve.pointer('Ground'); // a pointer is your rights at mint — re-mint after they change
60
+ const diary = await admin.create({
61
+ program: DIARY,
62
+ refs: [{ address: Tools.address(me.pk), rights: 'MEMBER' }],
63
+ });
64
+ const mine = await serve.pointer(diary);
65
+ await mine.write({ line: 'first entry' });
66
+ console.log(await mine.pages()); // [ 'first entry' ]
16
67
  ```
17
68
 
18
- One command stands the whole thing: verifies Docker (binary · daemon ·
19
- permission, each failure a one-line fix), pulls the carcass image, lays down
20
- compose + volume under `~/.nervur`, and mints your first identity.
21
-
22
- ## Verbs
23
-
24
- ```text
25
- carcass family: install, up, down, reset, preflight, whoami, status,
26
- identity, use, add, list, remove
27
- being family: scaffold, deploy, species
28
-
29
- install Docker preflight, then stand a carcass at
30
- NERVUR_HOME (default ~/.nervur)
31
- whoami · status the carcass answers with its identity and state
32
- identity add <name> mint an identity on this machine (silent, idempotent);
33
- --key <file> adopts a brought ed25519 key instead —
34
- it never replaces an existing different owner
35
- identity key <name> print the identity's private key (base64url PKCS8),
36
- the exact shape --key accepts back
37
- identity list identities on this machine · name + public id
38
- identity remove <name> destroy an identity — refused while beings or
39
- sealed lineages remain (only an empty one goes)
40
- use [name] select which identity add/list act as
41
- add <kind> [--as name] add a being under the selected identity
42
- list · remove <name> the fleet registry · retire a being
43
- scaffold <name> mint a species repo (the DNA)
44
- deploy <name> <repo> <hash> grant + pin + run a species
45
- species list deployed species and their pins
69
+ The rights enum here is the default (`SELF MEMBER STRANGER`); declare your
70
+ own `enum Rights` in the interface to replace it. Reboot: construct the
71
+ same `Ground` from the same two arguments — it wakes remembering; `init`
72
+ refuses, because the ref survived in the cells.
73
+
74
+ **The wire, when a caller is remote.** An envelope is plain JSON:
75
+
76
+ ```json
77
+ {
78
+ "from": "G…",
79
+ "to": "G…",
80
+ "seq": 1755170000000,
81
+ "op": { "ask": { "source": "{ pages }", "variables": {} } },
82
+ "sig": "base64 over canon({from, op, seq, to})"
83
+ }
46
84
  ```
47
85
 
48
- No verb ever prompts — every fork is a flag or env var with a sane default,
49
- and destructive turns are their own explicitly named verbs.
86
+ The ops are `describe`, `introspect`, `ask`, `rotate`, `release`,
87
+ `acceptInvite`. An answer is `{ body, sig }`, signed by the being asked —
88
+ verify against `pkOf(to)`, which `Client` does for you. A refusal is no
89
+ answer at all; how silence rides your transport (a 204, an empty body) is
90
+ the host module's choice. The library never opens a socket: your host
91
+ listens however it likes and hands each envelope to `receive` —
92
+ [the bench's sky.mjs](../bench/sky.mjs) is the reference harness for
93
+ wiring grounds together.
94
+
95
+ **The runbook, honestly.** Back up two things: the cells and the keychain
96
+ secret — together they are the whole ground. A lost secret is permanent
97
+ loss: nothing decrypts. A stolen secret plus stolen cells is total
98
+ compromise, and rekey does not exist yet — destroy and recreate is
99
+ today's remedy. The bench README's honest limits carry every known gap;
100
+ read them before production.
101
+
102
+ ## The two halves
103
+
104
+ **The server** is `Ground(Modules)` — modules are the whole of what a ground
105
+ is handed, and the ground is exactly what they offer. The floor is `cells`
106
+ (storage, including the one boot cell) and `keychain` (boot power as two
107
+ operations, seal and unseal — the enclave's own shape once a phone hosts a
108
+ ground); `paths`, `wire`, `contracts` and `blueprints` are
109
+ taken if given; everything else is a module for programs. It installs itself
110
+ the first time and boots from the same two forever after, and hands back
111
+ three things while hiding everything it is made of:
112
+
113
+ - `address` — its own admin being, aliased `Ground`.
114
+ - `receive(envelope)` — the one handler. Every message for every being on the
115
+ ground arrives here and routes on the envelope's `to`. An address the
116
+ ground does not hold is silence. A host module that listens on a port, or
117
+ an app shell that calls in process, hands its envelopes to this and
118
+ nothing else — the library never opens a socket.
119
+ - `serve(voice)` — authority established once, and every face inherits it.
120
+
121
+ A fresh ground holds no reference, so whoever reaches it first takes the
122
+ first one: `pointer('Ground').init({ address })` writes the first MEMBER ref
123
+ on the admin being — the same ref row as any other — exactly once, refusing
124
+ everyone after. That window is what running-is-custody looks like from the
125
+ outside, and it is stated rather than hidden.
126
+
127
+ **`serve` is the client, and it has four faces of one door.** `pointer(at)`
128
+ is a plain object with one function per field, generated from the schema that
129
+ caller is allowed to see. `gql(at)` is the raw document. `mcp(at)` is the
130
+ same schema as tools, filtered the same way, so an agent gets exactly what
131
+ its rights allow — a projection into JSON Schema, so scalar fields travel
132
+ whole while unions and custom scalars flatten; the rights filtering happens
133
+ on the SDL before the projection, so what flattens is shape, never
134
+ permission. `client` is the bare envelope underneath all three. Each
135
+ takes an address or an alias; each signs as the identity `serve` was given
136
+ and verifies every answer against the address it asked.
137
+
138
+ Delivery is local or remote through one API — a local answer and a remote
139
+ one carry the same shape, and the calling code does not branch. Failure
140
+ stays observable, as it always is: a missing endpoint or a dead ground is
141
+ silence, and silence is something local delivery never answers with.
142
+
143
+ ## The files
144
+
145
+ - [tools.mjs](tools.mjs) — the world's arithmetic, one frozen literal, and it
146
+ holds nothing: `digest`, `verifies`, `seal`/`unseal`/`secret`, `canon`
147
+ (canonical bytes), `envelope` (the signed message, numbered), `derive` (a
148
+ class key from a root), `address`/`pkOf` (StrKey, so every key is a Stellar
149
+ account and an address is a verification key). Same input, same output, any
150
+ machine — which is why an attacker computes all of it exactly as well as
151
+ you do. Nothing here holds a key: `envelope` assembles and numbers the
152
+ message and asks the voice it is handed to sign. Beside the literal stands
153
+ `Stamp`, the one stateful thing in the file: each caller mints one and
154
+ numbers its own envelopes, strictly rising. `Client` and a being's port
155
+ keep one lane per counterparty, so concurrent calls to the same door
156
+ land in order on their own; only a bare-envelope caller orders its own.
157
+ - [voice.mjs](voice.mjs) — the identity: three private keys shut in a closure
158
+ and never reachable from outside. `Voice()` mints one already committed to
159
+ its own successor, `Voice.revive` wakes a packed one, `Voice.sealTo` seals
160
+ to a voice's box key and `voice.open` is the only thing that opens it.
161
+ `succeed()` retires both hands at once — the committed key starts speaking
162
+ and a fresh box starts reading. The re-wrap is the handover's own
163
+ choreography: the retiring voice still stands when `succeed()` returns, so
164
+ the holder opens with the old box and seals to the new one in the same
165
+ act, then discards the old voice — after which it opens nothing that
166
+ remains. `pack()` is the one door out — the ground's custody, and
167
+ precisely why hosting is custody.
168
+ - [blueprint.mjs](blueprint.mjs) — the blueprint law. A program is compiled
169
+ from one serialisable source with no identity, no ground and no ambient
170
+ power in it: forbidden names are refused where they are read as powers and
171
+ shadowed at evaluation, the modules and bindings it needs are declared and
172
+ typed, the cells it keeps are declared with their class, its rights are its
173
+ own and every door declares them — a root field without `@rights`, or one
174
+ naming a right the enum never declared, is refused at compile — and the
175
+ whole of it digests to one canonical hash, rights included. Reformatting
176
+ the schema does not move the digest; a resolver digests by its source
177
+ text, so reformatting the code mints a new program. The forbidden-name
178
+ scan is a code-only regex, not a parser — a bounded blast radius, stated
179
+ as such, never a jail.
180
+ - [program.mjs](program.mjs) — a blueprint made executable: every field
181
+ gated by the `@rights` it declares, resolver or no resolver, and
182
+ introspection curtained per caller exactly as describe is. Errors never
183
+ leave the house.
184
+ - [modules.mjs](modules.mjs) — what a ground offers. `Cells` is dumb storage,
185
+ optionally a file. `Memory` is the memory law: reads free, writes signed by
186
+ the compartment's current voice, one version per cell, the chain by
187
+ prove-and-replace. `Clock` and `Entropy` are pinnable, so a program need
188
+ never reach for the wall. `Paths` resolves an address to an endpoint and
189
+ vouches for nothing; `Wire` carries; `Dns` is the directory they share.
190
+ - [contract.mjs](contract.mjs) — an interface with a digest and nothing else.
191
+ What `needs.modules` cites, what a bound module's client is generated from,
192
+ and what `attest` judges a stander by: more than the contract asks is fine,
193
+ less is not.
194
+ - [pointer.mjs](pointer.mjs) — `Pointer` and `Mcp`, both read off an SDL, so
195
+ no client is ever written by hand for a program.
196
+ - [client.mjs](client.mjs) — the bare envelope client, usable on its own.
197
+ Signs as its voice and verifies every reply against `pkOf(to)`.
198
+ - [being.mjs](being.mjs) — the being. `Compartment` seals a cell by its
199
+ class, `Port` carries authority both ways, `Being` is the door — prove,
200
+ read rights, execute, sign — and `Registry` creates, takes custody of,
201
+ activates and destroys, reachable only through the admin being.
202
+ - [admin.mjs](admin.mjs) — the administrative program every ground installs
203
+ for itself, aliased `Ground`: census, create, activate, destroy, upgrade,
204
+ alias, attest. Upgrade is custody moving the one cell that was always the
205
+ core's to move: the program is replaced in place — address, cells, refs
206
+ and chain surviving — refused whole when a cell the being holds would go
207
+ undeclared, and visible to every peer because `describe` answers the
208
+ program's digest.
209
+ - [ground.mjs](ground.mjs) — the sovereign ground, its one handler and
210
+ `serve`.
211
+ - [index.mjs](index.mjs) — the surface an adopter imports.
212
+
213
+ ## The two reference tables
214
+
215
+ A being keeps one row per direction, and nothing anywhere keeps a shared
216
+ record of the relation.
217
+
218
+ - **`refs/`** — who refers to me, and with what rights. This is the inbound
219
+ reference table: each entry is one capability the being issued, plus the
220
+ highest envelope number seen from that holder — which is what makes a
221
+ captured envelope worthless. A holder drops its own row with `release`;
222
+ the door answers it like any op and the row is gone for good.
223
+ - **`handles/`** — whom I refer to, and under which face. Outbound, and the
224
+ face is a keypair minted per counterparty, so no two peers can correlate
225
+ the same being by its keys — timing and shape stay visible to whoever
226
+ already sees them.
227
+
228
+ ## Classes, not tiers
229
+
230
+ No super-user stands inside the system — no voice passes every door. The
231
+ host running the ground is custody, not a user: root on the live process
232
+ holds everything, and stands outside every claim made here (the bench's
233
+ honest limits say it whole). A `class` here is a cell's sealing class,
234
+ never a JavaScript class — the code has none. A being holds one root
235
+ secret, and every class of
236
+ cell seals with a key derived from it — so a leaked class key opens one
237
+ class. Today no path hands out a class key without the root: the partition
238
+ prices a future delegation of reading and bounds a bug, it does not defend
239
+ against a present leak. A blueprint declares which cells it keeps and under which class, and a
240
+ resolver may write only those, plus its own refs and handles. Its program,
241
+ its chain and its keys are the core's to move, never its own.
242
+
243
+ ## What an adopter brings
244
+
245
+ Their own modules, their own programs, their own keys. A program names the
246
+ contracts it needs and runs on any ground that stands them — as a module, or
247
+ as a being, on this ground or another. A ground missing one refuses the
248
+ creation rather than failing later. The regress ends at one cell: the ground
249
+ needs just enough local storage to hold its own seed and where everything
250
+ else lives.
251
+
252
+ ## The floor — what is mandatory, what is yours
253
+
254
+ `Ground(Modules)` reads exactly seven names from what you hand it; every
255
+ other name passes through untouched as a module for programs.
256
+
257
+ - **`cells` — mandatory.** The shelf: `peek`/`put`/`keys`/`drop`/`dump`,
258
+ key to value, nothing else. Ships as a Map, optionally mirrored to one
259
+ JSON file. Swap it for anything that keeps those five promises
260
+ **synchronously, to a single writing process** — SQLite through a
261
+ synchronous driver, localStorage in a tab — the memory law versions
262
+ writes, it does not lock them, so exactly one ground writes a shelf. A
263
+ shared or remote store is never a cells swap: it stands behind its own
264
+ door as a memory being (bench suite 8), whose own ground is the single
265
+ writer of its own shelf. What sits on it: sealed content, the
266
+ deliberately unsealed
267
+ blueprints, and the shape — names, sizes, versions, timing. Sealed
268
+ content is safe wherever the shelf lives; the shape and the program
269
+ source are readable by whoever holds it, so where it lives decides who
270
+ sees those.
271
+ - **`keychain` — mandatory.** Two operations, `seal` and `unseal`, guarding
272
+ the one cell that boots the ground — the ground hands its boot cell to
273
+ the keychain and never sees a secret at all. `Keychain(secret)` builds
274
+ one from 32 bytes; a KMS stands behind the same two operations as an API
275
+ call; the Secure Enclave stands behind them natively, because sealing
276
+ without exporting the key is exactly what such hardware does. Choosing
277
+ the keychain is the host's one security decision: a root-only file
278
+ restarts unattended and dies with the disk image; a KMS can refuse the
279
+ next boot — revocation stops tomorrow, it does not evict a thief who
280
+ already copied (rekey is owed; the bench's honest limits carry it); the
281
+ Enclave keeps the power in the device, and it never leaves. A keychain
282
+ that cannot unseal yields no ground at all.
283
+ - **`paths` — optional; defaults to a private in-memory map.** The
284
+ phonebook: `publish`/`find`, address to endpoint, and it vouches for
285
+ nothing — a poisoned phonebook makes a being unreachable, never
286
+ impersonated, because the address verifies every answer.
287
+ - **`wire` — optional; defaults to in-process delivery.** The courier:
288
+ `carry(endpoint, envelope)`. The library never opens a socket — a host
289
+ module listens on whatever transport it likes and hands envelopes to
290
+ `receive`; the wire carries the outbound ones.
291
+ - **`contracts` — optional; empty by default.** The dictionary of
292
+ interfaces this ground can attest. Required the moment a module is bound
293
+ by address, so that `memory` offers the same interface under the same
294
+ digest on every ground and a pretender is refused. A digest pins shape;
295
+ what the words mean is the contract author's prose to state.
296
+ - **`blueprints` — optional.** `blueprints.ground()` replaces the default
297
+ admin blueprint, which is how a host customises what administering this
298
+ ground even means.
299
+ - **`watch` — optional.** The custodian's eye: a function called at every
300
+ door decision with one frozen fact, `{ to, from, kind, outcome }`, and
301
+ the outcomes are a closed set — `no-door`, `unproven`, `replayed`,
302
+ `mute`, `answered`. It sees outcomes and outsides, never an op body and
303
+ never a cell — exactly as knowing as the host already is. The caller's
304
+ silence is untouched; a watch that throws changes nothing; no watch
305
+ costs nothing. Tracing, metrics and logs are a host package built on
306
+ this, never the library's.
307
+
308
+ Boot, in order: on first boot the ground installs itself — mints its admin
309
+ voice, writes the admin being, hands the packed voice to the keychain to
310
+ seal into one cell. Every boot after: the keychain unseals, the voice
311
+ revives, every being in custody is announced, the ground answers. The first voice to call `init` names the first
312
+ MEMBER — once, and never again. `Clock` and `Entropy` are not floor: they
313
+ are ordinary modules a program declares, pinnable so a program never
314
+ reaches for the wall.
315
+
316
+ ## Writing a module
50
317
 
51
- ## License
318
+ Five rules, each load-bearing:
52
319
 
53
- Apache-2.0 — see [LICENSE](./LICENSE).
320
+ 1. **A frozen object of functions and nothing else** — no classes, no
321
+ events, no state a being could share through it. State belongs behind a
322
+ door: a stateful module is a being with cells of its own, bound by
323
+ address under rule 5.
324
+ 2. **Params carry the authority.** The module holds no account, no key, no
325
+ ambient context: `stripe.charge({ account, apikey, amount })`. This is
326
+ what makes the same module safe to hand to every being on the ground —
327
+ without the context it opens nothing.
328
+ 3. **It knows nobody.** A module never learns which being calls; per-being
329
+ authority arrives through the blueprint's declared bindings, typed, with
330
+ a declared owner.
331
+ 4. **Undefined is refusal** — the same silence the door speaks, never an
332
+ error that narrates.
333
+ 5. **A module blueprints will name needs a contract** — the interface whose
334
+ fields mirror its calls, digested, so the same word means the same thing
335
+ on every ground. The ground attests a stander against it: more than the
336
+ contract asks is fine, less is not. A module a being stands remotely is
337
+ bound as `{ at, contract }`; the floor — cells, keychain, paths, wire —
338
+ is host code, because it must exist before any being can speak.
package/admin.mjs ADDED
@@ -0,0 +1,60 @@
1
+ const ADMIN = `({
2
+ name: 'nervur.ground',
3
+ needs: { modules: [{ module: 'memory', contract: 'nervur.memory' }] },
4
+ memory: { 'aliases/*': { type: 'String', class: 'data' } },
5
+ interface: \`
6
+ input RefInput { address: String!, nextPkHash: String, rights: String }
7
+ type Census { stored: Int!, active: Int! }
8
+ type Query {
9
+ census: Census @rights(is: [SELF, MEMBER])
10
+ aliasOf(name: String!): String @rights(is: [SELF, MEMBER])
11
+ attest(module: String!): Boolean @rights(is: [SELF, MEMBER])
12
+ opened: Boolean @rights(is: [SELF, MEMBER, STRANGER])
13
+ }
14
+ type Mutation {
15
+ init(address: String!, nextPkHash: String): Boolean @rights(is: [SELF, MEMBER, STRANGER])
16
+ create(program: String!, refs: [RefInput!], data: String, config: String): String
17
+ @rights(is: [SELF, MEMBER])
18
+ activate(address: String!): String @rights(is: [SELF, MEMBER])
19
+ destroy(address: String!): String @rights(is: [SELF, MEMBER])
20
+ upgrade(address: String!, program: String!): Boolean @rights(is: [SELF, MEMBER])
21
+ alias(name: String!, address: String!): Boolean @rights(is: [SELF, MEMBER])
22
+ }
23
+ \`,
24
+ resolvers: {
25
+ Query: {
26
+ census: (_, __, { registry }) => ({
27
+ active: registry.active(),
28
+ stored: registry.stored(),
29
+ }),
30
+ aliasOf: (_, { name }, { modules }) => modules.memory.read('aliases/' + name),
31
+ attest: (_, { module }, { registry }) => registry.attest(module),
32
+ opened: (_, __, { modules }) => modules.memory.list('refs/').length > 0,
33
+ },
34
+ Mutation: {
35
+ init: (_, { address, nextPkHash }, { modules }) =>
36
+ modules.memory.list('refs/').length > 0
37
+ ? false
38
+ : modules.memory.write('refs/' + address, {
39
+ nextPkHash: nextPkHash ?? null,
40
+ rights: 'MEMBER',
41
+ }) !== undefined,
42
+ create: (_, { program, refs, data, config }, { registry }) =>
43
+ registry.create({
44
+ config: config === undefined ? undefined : JSON.parse(config),
45
+ data: data ? JSON.parse(data) : {},
46
+ program,
47
+ refs: refs ?? [],
48
+ }),
49
+ activate: (_, { address }, { registry }) =>
50
+ registry.activate(address) === undefined ? null : address,
51
+ destroy: (_, { address }, { registry }) => registry.destroy(address) ?? null,
52
+ upgrade: (_, { address, program }, { registry }) =>
53
+ registry.upgrade({ address, program }) !== undefined,
54
+ alias: (_, { name, address }, { modules }) =>
55
+ modules.memory.write('aliases/' + name, address) !== undefined,
56
+ },
57
+ },
58
+ })`;
59
+
60
+ export { ADMIN };