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 +0 -1
- package/NOTICE +2 -0
- package/README.md +328 -43
- package/admin.mjs +60 -0
- package/being.mjs +369 -0
- package/blueprint.mjs +281 -0
- package/client.mjs +43 -0
- package/contract.mjs +96 -0
- package/ground.mjs +196 -0
- package/index.mjs +10 -0
- package/modules.mjs +111 -0
- package/package.json +14 -12
- package/pointer.mjs +79 -0
- package/program.mjs +74 -0
- package/tools.mjs +141 -0
- package/voice.mjs +90 -0
- package/completion.js +0 -122
- package/nervur.js +0 -672
- package/preflight.js +0 -162
- package/scaffold.js +0 -50
- package/style.js +0 -82
package/LICENSE
CHANGED
package/NOTICE
ADDED
package/README.md
CHANGED
|
@@ -1,53 +1,338 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Nervur
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
318
|
+
Five rules, each load-bearing:
|
|
52
319
|
|
|
53
|
-
|
|
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 };
|