nervur 0.11.0 → 0.13.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.
@@ -0,0 +1,111 @@
1
+ # Getting started — two castles
2
+
3
+ Picture a castle: one gate, its own residents, its own law. A **ground** is
4
+ the castle — one sovereign process, one handler where every letter arrives.
5
+ Its residents are **beings**; its capabilities are **modules**; the ground
6
+ under it is a **floor** — a filesystem, or a browser's IndexedDB — and the
7
+ stance it takes toward the world is its **terrain**. This page raises two
8
+ castles on two terrains and has them speak: a **mountain** (a server, always
9
+ reachable, always remembering) and a **house of cards** (a browser tab,
10
+ raised in seconds, holding what it can afford to lose). Same castle, same
11
+ law, different ground under it.
12
+
13
+ Everything below runs today — the bench drives this exact story in
14
+ `bench/worlds/story/`.
15
+
16
+ ## The mountain
17
+
18
+ The machine's raise is one config and one daemon —
19
+ [`nervurd`](https://www.npmjs.com/package/nervurd) is the host, and its
20
+ README is the operator's full page. The five-minute shape:
21
+
22
+ ```sh
23
+ npm install -g nervurd
24
+ nervur self mint --self ./hand.json --heir ./heir.json
25
+ nervur init --config ./castle.json --self ./hand.json
26
+ NERVUR_SECRET=... nervurd --config ./castle.json
27
+ ```
28
+
29
+ The config names the shelf, the door, the secret's environment variable,
30
+ and the modules — the castle's capabilities, chosen by you:
31
+
32
+ ```json
33
+ {
34
+ "cells": "/var/lib/nervur/castle.json",
35
+ "listen": { "host": "127.0.0.1", "port": 7001 },
36
+ "secret": "NERVUR_SECRET",
37
+ "modules": {}
38
+ }
39
+ ```
40
+
41
+ Keep the heir off the machine. Whoever takes the machine takes what the
42
+ castle holds today; the heir is why they can never become it tomorrow.
43
+
44
+ ## The house of cards
45
+
46
+ The browser's raise is two calls from
47
+ [`@nervur-org/floor-web`](https://www.npmjs.com/package/@nervur-org/floor-web),
48
+ because a tab closes constantly and the two acts must not blur:
49
+
50
+ ```js
51
+ import { found, wake } from '@nervur-org/floor-web';
52
+
53
+ // Once, ever: mint the identity, stand the castle, get custody back.
54
+ let kept;
55
+ const castle = found({ keep: (dump) => (kept = dump) });
56
+ // castle.address — the name; castle.custody — the hand, the heir, the
57
+ // secret: yours to keep (IndexedDB, or a mountain you trust), never the
58
+ // ground's.
59
+
60
+ // Every session after: same name, same residents, no identity act.
61
+ // wake hands back the standing ground itself — woken.address is the name.
62
+ const woken = wake({ held: kept, secret: castle.custody.secret });
63
+ ```
64
+
65
+ Where `kept` and `custody` live is your choice, under this terrain's honest
66
+ limit: a browser may evict what it keeps — a card castle holds what it can
67
+ afford to lose, and keeps what is precious in its relationship with a
68
+ mountain.
69
+
70
+ ## They speak
71
+
72
+ A letter is a letter on every terrain: one signed envelope, carried
73
+ however the two castles can reach each other — here, `fetch` to the
74
+ mountain's door. First the mountain's keeper invites the card castle's
75
+ founder (one signed act at the admin door — the CLI, or any client the
76
+ keeper holds), and then the founder asks from the browser:
77
+
78
+ ```js
79
+ import { Client, Tools, Voice } from 'nervur';
80
+
81
+ const me = Voice.revive(castle.custody.hand);
82
+ const talk = Client({
83
+ voice: me,
84
+ deliver: async (envelope) => {
85
+ const res = await fetch('http://127.0.0.1:7001', {
86
+ body: JSON.stringify(envelope),
87
+ headers: { 'content-type': 'application/json' },
88
+ method: 'POST',
89
+ });
90
+ return res.status === 200 ? res.json() : undefined;
91
+ },
92
+ });
93
+
94
+ const heard = await talk.ask(mountainAddress, '{ census { active } }');
95
+ ```
96
+
97
+ The client verifies every answer from the address alone — the mountain's
98
+ word arrives signed, and a broken or missing signature is dropped exactly
99
+ like silence. A stranger who was never invited gets silence too, and cannot
100
+ tell refusal from absence. That is the whole protocol: **say who you are,
101
+ name whom you seek, and the castle answers or keeps quiet.**
102
+
103
+ ## Where to go next
104
+
105
+ - [`nervur`'s README](README.md) — the library underneath both castles.
106
+ - [`nervurd`'s README](../nervurd/README.md) — the operator's page: checks,
107
+ rotation, backups, the door's full behaviour.
108
+ - [`@nervur-org/floor-web`](../floor-web/README.md) — the web floor's
109
+ terrain contract, founding and waking in full.
110
+ - [`@nervur-org/species`](../species/README.md) — blueprints your castles
111
+ grow beings from.
package/README.md CHANGED
@@ -1,23 +1,40 @@
1
1
  # Nervur
2
2
 
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.
3
+ The library. Picture a castle: one gate, its own residents, its own law.
4
+ **A ground is the castle** — one sovereign process holding many beings,
5
+ exposing exactly one handler where every message for every resident
6
+ arrives. You arrive at the gate, say _I am X_ with your signature, and
7
+ whom you seek travels inside the letter; the castle answers or keeps
8
+ silence, and a resident that refuses you looks exactly like one that
9
+ never lived there. The castle's capabilities are its **modules** — a
10
+ resident uses only what its blueprint declares — and the terrain under
11
+ the castle is a **floor**: a browser tab, a Linux daemon, a phone, an
12
+ edge worker. The castle is a castle wherever it is built.
13
+
14
+ Beneath the picture, the mechanics: an address is its own verification
15
+ key, authority is a signature, and every relationship is one ref with
16
+ declared rights — so a reference cannot be forged and no ambient
17
+ authority exists anywhere. The door asks by whose authority and refuses
18
+ every other question. The lineage is the object-capability tradition —
19
+ CapTP, the actor model — with one honest departure: the door verifies who
20
+ is calling, and rights are declared per reference. Pure Crockford
21
+ JavaScript — factory functions, closures for privacy, no `this`, no
22
+ classes, frozen surfaces — with no `node:` import anywhere, so the twelve
23
+ files run wherever JavaScript does; three dependencies (`graphql` for the
24
+ one thing that would be madness to hand-roll, `@noble/curves` and
25
+ `@noble/hashes` for the arithmetic every host must agree on).
16
26
 
17
27
  ## Quickstart
18
28
 
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;
29
+ New to the whole thing? [GETTING-STARTED.md](GETTING-STARTED.md) raises
30
+ two castles on two terrains — a server and a browser tab — and has them
31
+ speak; the bench drives that exact story. What follows here is the
32
+ library alone.
33
+
34
+ `npm install nervur` — what ships is this source, byte for byte. The
35
+ package is a library and carries no command; a ground also needs a floor
36
+ (`@nervur-org/floor-machine` here) — the library is the law and the
37
+ shape, and a host is what it stands on;
21
38
  the `nervur` command on an operator's PATH belongs to the
22
39
  [`nervurd`](../nervurd/README.md) package, its name twin. Construction is
23
40
  synchronous; every call through a door — `pointer`, `mcp`, `gql`, and each
@@ -25,7 +42,8 @@ function they hand back — is async. This runs today, lifted from the
25
42
  bench's own passing suites:
26
43
 
27
44
  ```js
28
- import { Cells, Ground, Keychain, Tools, Voice } from 'nervur';
45
+ import { Cells, Cipher, Keychain } from '@nervur-org/floor-machine';
46
+ import { Ground, Tools, Voice } from 'nervur';
29
47
 
30
48
  const DIARY = `({
31
49
  name: 'acme.diary',
@@ -49,7 +67,11 @@ const DIARY = `({
49
67
  })`;
50
68
 
51
69
  const secret = Buffer.from(process.env.NERVUR_SECRET, 'base64'); // 32 bytes, minted once with Tools.secret()
52
- const shelf = { cells: Cells({ file: 'ground.json' }), keychain: Keychain(secret) };
70
+ const shelf = {
71
+ cells: Cells({ file: 'ground.json' }),
72
+ cipher: Cipher(),
73
+ keychain: Keychain(secret),
74
+ };
53
75
 
54
76
  const heir = Voice(); // keep this one somewhere the machine is not
55
77
  const self = Voice(Tools.digest(heir.pk)); // and hand the ground only this
@@ -122,7 +144,11 @@ three things while hiding everything it is made of:
122
144
  ground arrives here and routes on the envelope's `to`. An address the
123
145
  ground does not hold is silence. A host module that listens on a port, or
124
146
  an app shell that calls in process, hands its envelopes to this and
125
- nothing else — the library never opens a socket.
147
+ nothing else — the library never opens a socket. `receive` answers the
148
+ signed `{ body, sig }` or `undefined` for silence; over HTTP, carry the
149
+ answer as the 200 response body and silence however you like (`nervurd`
150
+ uses an empty `204`) — no status code carries meaning, and a caller
151
+ trusts nothing but a verified signed answer.
126
152
  - `serve(voice)` — authority established once, and every face inherits it.
127
153
 
128
154
  A fresh ground has no identity, and no door for anyone to reach first: it
@@ -134,6 +160,17 @@ after. Both are custody's own calls on the shelf, never doors — which is
134
160
  what running-is-custody looks like from the outside, stated rather than
135
161
  hidden.
136
162
 
163
+ `unproven()` is custody's third call, and it is a read: the addresses whose
164
+ writes have stopped proving, with when each was first and last seen and how
165
+ many writes have been refused. A write is proven against the key on the
166
+ being's `chain/pk` cell, so whoever holds the shelf can overwrite that cell
167
+ and freeze a being's memory while it answers on, correctly signed and
168
+ indistinguishable to every caller. The ground records that in its own host
169
+ rows under `ground/` — no being reads them, nothing goes over the wire — and
170
+ this is where a host reads them back. Show it to whoever operates the
171
+ machine; a ground that has stopped proving its own writes is a custody
172
+ incident, not a bug.
173
+
137
174
  **`serve` is the client, and it has four faces of one door.** `pointer(at)`
138
175
  is a plain object with one function per field, generated from the schema that
139
176
  caller is allowed to see. `gql(at)` is the raw document. `mcp(at)` is the
@@ -215,7 +252,8 @@ silence, and silence is something local delivery never answers with.
215
252
  activates and destroys, reachable only through the admin being.
216
253
  - [admin.mjs](admin.mjs) — the administrative program every ground installs
217
254
  for itself, aliased `Ground`: census, create, activate, destroy, upgrade,
218
- alias, attest, and behind `SELF` alone invite, drop and extend. Upgrade is
255
+ alias, attest, invite, drop, wear, and behind `SELF` alone extend; `wears`
256
+ answers any stranger with the livery being the ground wears. Upgrade is
219
257
  custody moving the one cell that was always the core's to move: the
220
258
  program is replaced in place — address, cells, refs and chain surviving —
221
259
  refused whole when a cell the being holds would go undeclared, and visible
package/SECURITY.md ADDED
@@ -0,0 +1,11 @@
1
+ # Security
2
+
3
+ This package answers one question — by whose authority — and a hole in that
4
+ answer is the most serious bug it can have.
5
+
6
+ Report vulnerabilities privately to **<security@nervur.com>**. Do not open a
7
+ public report. State what you ran, what you sent, and what answered; a
8
+ proof of concept is welcome but not required.
9
+
10
+ A person reads every report. Fixes ship as releases on the live 0.x line,
11
+ and the report is credited if you want it to be.
package/admin.mjs CHANGED
@@ -1,7 +1,10 @@
1
1
  const ADMIN = `({
2
2
  name: 'nervur.ground',
3
3
  needs: { modules: [{ module: 'memory', contract: 'nervur.memory' }] },
4
- memory: { 'aliases/*': { type: 'String', class: 'data' } },
4
+ memory: {
5
+ 'aliases/*': { type: 'String', class: 'data' },
6
+ wears: { type: 'String', class: 'data' },
7
+ },
5
8
  interface: \`
6
9
  input RefInput { address: String!, nextPkHash: String, rights: String }
7
10
  type Resident { address: String!, alias: String, digest: String, active: Boolean! }
@@ -11,6 +14,7 @@ const ADMIN = `({
11
14
  aliasOf(name: String!): String @rights(is: [SELF, MEMBER])
12
15
  attest(module: String!): Boolean @rights(is: [SELF, MEMBER])
13
16
  opened: Boolean @rights(is: [SELF, MEMBER, STRANGER])
17
+ wears: String @rights(is: [SELF, MEMBER, STRANGER])
14
18
  }
15
19
  type Mutation {
16
20
  invite(address: String!, nextPkHash: String, rights: String): Boolean @rights(is: [SELF, MEMBER])
@@ -22,6 +26,7 @@ const ADMIN = `({
22
26
  destroy(address: String!): String @rights(is: [SELF, MEMBER])
23
27
  upgrade(address: String!, program: String!): Boolean @rights(is: [SELF, MEMBER])
24
28
  alias(name: String!, address: String!): Boolean @rights(is: [SELF, MEMBER])
29
+ wear(address: String): Boolean @rights(is: [SELF, MEMBER])
25
30
  }
26
31
  \`,
27
32
  resolvers: {
@@ -37,6 +42,7 @@ const ADMIN = `({
37
42
  aliasOf: (_, { name }, { modules }) => modules.memory.read('aliases/' + name),
38
43
  attest: (_, { module }, { registry }) => registry.attest(module),
39
44
  opened: (_, __, { modules }) => modules.memory.list('refs/').length > 0,
45
+ wears: (_, __, { modules }) => modules.memory.read('wears') ?? null,
40
46
  },
41
47
  Mutation: {
42
48
  invite: (_, { address, nextPkHash, rights }, { modules }) =>
@@ -66,6 +72,10 @@ const ADMIN = `({
66
72
  registry.upgrade({ address, program }) !== undefined,
67
73
  alias: (_, { name, address }, { modules }) =>
68
74
  modules.memory.write('aliases/' + name, address) !== undefined,
75
+ wear: (_, { address }, { modules }) => {
76
+ modules.memory.write('wears', address ?? undefined);
77
+ return modules.memory.read('wears') === (address ?? undefined);
78
+ },
69
79
  },
70
80
  },
71
81
  })`;