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