@nervur-org/dock 0.4.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/quo-harbor.md DELETED
@@ -1,874 +0,0 @@
1
- # Transports
2
-
3
- This is how a harbor lives on a real device and how two of them meet over a real
4
- network. It implements the harbor of `quo/SPEC.md` on every terrain and adds
5
- nothing a ward or a far harbor can observe: what is written here that the
6
- protocol does not say is a device's own half, a store, a lease, a loader, a
7
- core and a dialer's patience, and a second kit that answers the same bytes owes
8
- none of it. A harbor boots wards, keeps what they are made of, carries
9
- bytes to one pk, returns what came back or nothing, never reads them, and
10
- answers nothing only where it knows nothing was sent. This document names no
11
- being and no side; it knows seeds, partitions, code, sockets and disks.
12
-
13
- ## One harbor per device, one ward per world
14
-
15
- A device runs one harbor. It is what the device already has, offered to Quo,
16
- and there is one of it because a device has one keychain, one disk and one
17
- set of processes. Two harbors on one device are a curiosity, not a design.
18
-
19
- A harbor hosts many wards, and a ward is a world: a seed, a partition and a
20
- body of code, with an origin. The wards of the device's owner and wards
21
- whose code came from a stranger are hosted the same way. A native app is
22
- therefore a browser of worlds: one harbor, a ward per world, each in its own
23
- runtime, and the screen showing one at a time.
24
-
25
- A ward is whole only where all three of its parts are. The harbor keeps a
26
- **ward record** per ward, and it is the harbor's, never the ward's:
27
-
28
- ```
29
- ward record
30
- pk derived from the seed; the key of the record
31
- seed where it is, in the secret store
32
- partition where it is, in the data store
33
- code where the class bodies come from, and which version boots
34
- ```
35
-
36
- One of those wards is the harbor's own, the **dock ward**. A harbor with
37
- anything to lend boots it and keeps its ask pointer instead of handing it
38
- out, so it is that ward's root, and every faculty of the box is a being in
39
- it. It is a ward in every other respect: a seed the store keeps, a
40
- partition that persists, a door judging the thirteen cases. Two things are
41
- its own. It is never adopted or dropped as a migration, because its beings
42
- are the box's and die with it. And the harbor cleans up after a ward that
43
- leaves: it is the root, so when it drops or hands out a ward it removes the
44
- relations that ward's beings held at faculties, since a faculty's occupant
45
- rows are not bounded the way a knock record is. A faculty cleans too, from
46
- her own end, dropping a subscriber she has heard `unreached` from long
47
- enough; the two catch different failures and neither is redundant.
48
-
49
- `lend` is the last member of the ground, and it is built per ward: given
50
- a name, the harbor asks its own root to invite on that faculty and hands
51
- back the invitation, or null. Which ward may ask for which name is the
52
- harbor's own decision, taken where the ground is built, and a ward whose
53
- code came from a stranger is lent nothing. The ground is per ward and per
54
- harbor, so `lend` reaches this box and no other; a ward that migrates is
55
- handed the new harbor's, which is why a being asks again at every birth.
56
-
57
- A harbor need not announce the dock ward's pk on the lines it dials. An
58
- announce is a claim it chooses to make, and nothing is lost by withholding
59
- this one: the only way through that door is a heir a faculty minted, and
60
- the only source of one is `lend`.
61
-
62
- ## Three parts, three native places
63
-
64
- The harbor adapts nothing. Every terrain already has a place for secrets, a
65
- place for data and a place for code, and those are the three the harbor
66
- needs:
67
-
68
- | a ward is | Mac, iPhone | droplet | edge | browser tab |
69
- | ----------- | --------------------- | -------------------------- | ----------------------- | ---------------------- |
70
- | seed | a file, sealed | `.env` or a secrets file | the platform's secrets | IndexedDB, per origin |
71
- | partition | a file, sealed | a file on disk | Durable Object storage | IndexedDB, per origin |
72
- | the key | Keychain | the environment, if any | the platform's secrets | none |
73
- | code | the app bundle | a folder | the deployed worker | a bundle URL |
74
-
75
- Envoys read their own secrets from the same secret store as the seed. A
76
- device has one place for secrets and the harbor is one more user of it.
77
-
78
- The browser column is the honest weakness: a tab has no secret store for
79
- bytes, so the seed sits beside the partition, guarded by the origin and
80
- nothing more. That is the price of a tab, and why a tab is a rented room and
81
- a native app is not.
82
-
83
- ### The partition, split or wrapped
84
-
85
- Inside the partition one branch is secret, the bind table: each standing's
86
- current and next keys, and the heirs minted and not yet spoken. Everything
87
- else is data. That is why a side never holds one. What a harbor hands a side
88
- for a ward it serves is `Hosted`: the door and the ask, the pk, the name, the
89
- record, a save, and the beings it made, by key and as a list of keys. No
90
- partition. A side that wants to know which beings a ward has, or which one it
91
- marks public, asks the ward and reads the describe; `harbor/ward.ts` is that
92
- read, `beingsOf` and `publicKey`, and every side here uses it. Asking costs
93
- nothing to write: a ward that wrote nothing has nothing to save.
94
-
95
- The partition is values only and the harbor may persist it as
96
- it likes, so it may keep the secret branch apart from the data, by terrain:
97
-
98
- - **Split.** The bind branch goes to the secret store beside the seed, the
99
- rest to the data store. Fits a phone with a few relations; keychain items
100
- are small and a being with a thousand standings has a thousand key pairs.
101
- - **Wrapped.** The secret store holds one key per harbor; the data store
102
- holds each ward whole, seed, partition and record as one blob encrypted
103
- under it. Any size, one secret. The droplet and edge answer, and it
104
- works on the phone too.
105
-
106
- The ward never knows any of this. It hands values and gets values back, and
107
- split or wrapped, one store or two, is the terrain's business. The one thing
108
- it learns is yes or no: whether what it wrote was kept. A heir given away as
109
- an invitation is a live credential until it speaks, and on a device other
110
- apps share that is the argument for the secret store.
111
-
112
- Wrapped is what is built, and it is one piece, `seal.ts`: AES-GCM under a
113
- 32-byte key, a fresh nonce each time, nonce and ciphertext together as hex,
114
- on WebCrypto so it runs wherever a store does. The key is the one secret a
115
- harbor reads from its terrain's secret store, named `NERVUR_SEED_KEY` on every
116
- terrain: a platform secret on the edge, the environment of a daemon on a
117
- device, handed to it from the Keychain by the app that spawned it. A
118
- droplet gives no key and its folder stays plain under its file modes. A
119
- store speaks one form, the one its key decides, and refuses a ward kept in
120
- the other by name, so a daemon started without its key, or with one
121
- against a plain folder, fails loudly and boots nothing. Sealing a plain
122
- folder is a deliberate operator command, never a boot's doing.
123
-
124
- ### A save the store refuses
125
-
126
- A save is part of the ask that caused it. At the end of every arrival, after
127
- the being has answered and before the ward seals the reply, the ward asks the
128
- harbor to keep what it wrote. The harbor saves and says whether the store
129
- took it. That is the last moment anything can still be said: a reply sealed
130
- to an asker's lid cannot be unsaid, and only the ward can seal.
131
-
132
- A store that refused makes that arrival a failed ask, said with the words a
133
- failed ask already has, `threw` to a key the door holds and silence to
134
- everyone else. Never a reason. A full disk is an insides of this device, and
135
- the far side hears no more of it than it hears of anything else in here.
136
-
137
- A row the store would not take leaves the being standing at what is kept. The
138
- harbor knows what that is without asking anybody: it keeps the copy it handed
139
- the last save that succeeded, row by row, and puts the refused row back to it
140
- in the ward's memory, in place and behind the cells guard. Her next ask sees
141
- what the device sees and a smaller write after it is taken. She loses the
142
- writes that were refused, which is exactly what their askers were told. The
143
- row is written in place because a booted being holds her cells and her bind
144
- table by reference: a fresh object in their place would strand her on cells
145
- nothing keeps. A row the last successful save dropped is a being the store no
146
- longer keeps, and her row goes with it.
147
-
148
- The harbor never asks a store what it holds to find that out. The one moment
149
- it would ask is from inside a save the store has just refused, which is the
150
- read least likely to be true: the edge answers it with a partition missing its
151
- head row, and a ward put back from that loses its heirs and answers its own
152
- occupants with silence. What the copy costs is one copy of the rows of each
153
- save, taken before the store is asked and kept only if the store takes it,
154
- plus one copy of the partition when a ward is hosted, so that a being whose
155
- first write of the run is refused is put back too. No store is read and no
156
- extra write is made.
157
-
158
- The fault is counted too, and a box says how many saves it refused wherever a
159
- box says anything about itself: `/health` on the daemon and on the edge
160
- carries the count per ward, and a daemon journals one line the first time a
161
- ward's save is refused. A counter nobody reads is the thing this replaces.
162
- A harbor that keeps nothing is asked nothing, and a ward running over no
163
- store answers as though none of this were here. A store that refuses every
164
- save cannot keep a ward's first write either, so no ward stands on it at all.
165
-
166
- The reason this is worth a member of the ground: on a terrain where a being
167
- is one row with a ceiling, a refused save is not a rare disk fault. Past the
168
- ceiling every save of that being is refused, her memory runs ahead of what is
169
- kept, and everything written to her afterwards is lost at the next reload
170
- while every door goes on answering success. A door that says yes to what was
171
- never kept is the worse fault, and it is the one a counter cannot fix.
172
-
173
- ### A being's row has a ceiling
174
-
175
- A being is one row, and every store that keeps a row as one value has a
176
- ceiling on it. The store refuses the row itself, before the platform is
177
- asked, and it reads the ceiling from one place beside the rest of what that
178
- terrain is: two stores disagreeing about the limit is a row kept the day
179
- before and refused the day after with nothing in the tree saying why. Where a
180
- terrain publishes no number, its store has no ceiling of its own and a full
181
- disk is what refuses.
182
-
183
- The edge is the terrain that publishes one. A SQLite-backed Durable Object
184
- takes a key and a value of 2 MB together, which is `ROW_CEILING` in
185
- `harbor/edge/given.ts`, and a being whose cells and bind table pass it is
186
- refused by `DurableStorage` rather than by the platform. What lives beside a
187
- ward for values a partition cannot hold is a design of its own: a partition
188
- holds values, and a holiday video is not one.
189
-
190
- ### Custody is a lease
191
-
192
- `quo/SPEC.md` makes custody the harbor's vouch: two harbors over one
193
- seed are two wards with one pk, and a harbor refuses to boot a ward another
194
- running harbor holds. The dock makes that a property of the store, not a rule
195
- people follow: a lease beside the seed in the secret store, taken by the daemon
196
- that boots the ward, checked before any boot, refused while held. A restored
197
- backup meets a held lease and does not boot. A second tab on one origin meets a
198
- lock and is the held page, `papers/quo-human.md` says which, and so does the
199
- service worker on that origin meet it: it is
200
- one more claimant of the same lock and holds the harbor only when no page does.
201
- The edge is a Durable Object, which is the lease built in. None of it stops
202
- a deliberate copy, and nothing can; it stops every accident, and accidents
203
- are the whole population of this problem.
204
-
205
- ### Migration is three moves
206
-
207
- A ward moves between harbors by moving its three parts: the seed from one
208
- secret store to another, the partition from one data store to another, and
209
- the new harbor pointed at the same code. Same seed, same pk, every standing
210
- anyone holds still points at her. The old harbor deletes its copies first:
211
- two harbors holding one seed are two wards with one pk, diverging silently.
212
-
213
- What a move costs is not the three parts, which are bytes, but how many
214
- peers wrote down where the ward was. A pk is permanent by arithmetic; a
215
- location is soft state in every other harbor's directory, and only that goes
216
- stale. So the bill is proportional to who hard-coded it, and there are two
217
- kinds of ward.
218
-
219
- A ward whose harbor dials out is free. It announces what it holds when a
220
- line opens and whenever that changes, the listener proves each claim at the
221
- door and binds it, and a ward that arrives on a new box is reachable as soon
222
- as its harbor dials. Nobody is told and no peer acts.
223
-
224
- A ward that is dialed keeps a second name. Peers hold a hint, pk at a URL,
225
- in their own stores and across their own restarts, so that URL is a promise
226
- the same way the pk is. Moving boxes does not break it, because a hostname
227
- is not a location: DNS and the proxy point it wherever the harbor now runs,
228
- which is the whole reason the route is a hostname and not an address.
229
- Changing the URL itself is the move that costs, and it cannot be announced
230
- to a peer this ward cannot reach.
231
-
232
- When it must change, what is republished is a hint and never an invitation.
233
- The relation, its keys and its count are untouched, and the invitation is
234
- as good as it was: that is why the two travel separately, a link being one
235
- next to the other. Peers on a rendezvous this ward also dials relearn by
236
- themselves, since a binding is proven at the door and names no URL; peers
237
- holding a hint need a new one, and a hint is not a capability, so it may be
238
- published in the open to all of them at once. Nobody is invited twice.
239
-
240
- The rule, then: a dialable ward keeps two names and owes the second the same
241
- care as the first, and a ward that only dials keeps one, which is why a
242
- laptop, a tab and a phone move home without ceremony and the droplet does
243
- not.
244
-
245
- ### Code is a harbor decision
246
-
247
- A ward's code has an origin: a folder the owner wrote, a bundle a world
248
- published, a worker deployed. When the origin ships new bodies the harbor
249
- decides whether the ward reboots on them. A reboot is silent, the ward cannot
250
- tell old bodies from new, and a body the cells were not written for is an
251
- absence this run. So the harbor pins what a ward boots on, records it in the
252
- ward record, and moves it deliberately: on its own for the owner's own code,
253
- with the owner's word for a world's.
254
-
255
- ## The daemon, and the two cores
256
-
257
- There are two harbor cores, and every device runs one of them:
258
-
259
- - **The node daemon**, `nervur serve`. A disk or keychain store, a unix socket
260
- for the owner, listeners on localhost for the routes. Droplet, Mac, Linux
261
- box: wherever a home runs.
262
- - **The browser harbor.** A dialer inside a webview or a tab, an IndexedDB
263
- store or a sealed store handed in, one socket per world it is connected
264
- to. A tab, and every app, phone and desk alike, since an app is a
265
- presence and a presence dials.
266
-
267
- The two cores meet a ward they cannot open differently, and both are right.
268
- The core carries on and names it in `unbooted`, because a device with many
269
- worlds must not lose them all to one. The daemon stops there and says which
270
- ward and why: a daemon's folder is an operator's, every ward in it is meant
271
- to be served, and coming up quietly with one missing is that world gone dark
272
- with nobody told. It is the same rule that makes a sealed folder with no key
273
- loud rather than a daemon serving nothing.
274
-
275
- Tauri and Capacitor are packaging, not harbors. Both apps ship the browser
276
- harbor over a native sealed store, the phone with push and the desk without
277
- it, and each is its own owner for its one harbor. Neither ships the daemon:
278
- a home runs on a box that runs day and night, and a laptop is a presence
279
- like a phone. The edge is the daemon's core over a Durable Object, one
280
- single-writer object per ward, or it diverges.
281
-
282
- A wrapped app carries its own bundle and never loads it from a hostname.
283
- Only a plain tab fetches the bundle from a world's `web.`, because it has
284
- nowhere else to get it. A wrapped app visits a world's `web.` exactly once,
285
- in a system browser sheet, to do the credential exchange and come back with
286
- an invitation; after that it speaks to that world's `quo.` for bytes and
287
- never to its `web.` again. One app on a device meets many worlds, one ward
288
- each, and there is never an app per world.
289
-
290
- ## Listeners and dialers
291
-
292
- Two kinds of harbor exist in the world, and every pair reduces to them:
293
-
294
- | terrain | can be dialed | can dial | lifetime | wakes on |
295
- | ------------ | ------------- | -------- | ----------------------- | -------- |
296
- | droplet | yes | yes | as long as wanted | itself |
297
- | edge | yes | fetch | per request | request |
298
- | desktop app | no | yes | while running | itself |
299
- | phone app | no | yes | foreground, throttled | push |
300
- | browser tab | no | yes | while open | itself |
301
-
302
- A ward on a dialer is reached only if its harbor dialed first and something
303
- holds that line open. A dialer holds one socket to a URL, announces the pks
304
- this harbor holds when the line opens and again whenever they change, and
305
- dials again with a wait that backs off to a ceiling when the line drops; how
306
- far it backs off is this harbor's, and the wait goes back to the shortest one
307
- where the far side announced and was taken, not where the line merely opened,
308
- or a listener that opens and then refuses is dialed at one short wait for
309
- good. A harbor may dial several listeners, and a pk nobody here knows is
310
- tried down that list in order.
311
-
312
- A device that comes back from sleep tells its dialer so, `wake`: whatever
313
- line it holds, open or still opening, is put down and a new one is dialed
314
- now, the frozen wait called off. Nothing can tell a line the system closed
315
- under a sleeping device from a live one, and asking would cost a round trip
316
- the four words do not have; an announce written into a dead line sits in the
317
- send buffer and errors only when TCP gives up, minutes later, and for all of
318
- it every ask to a pk bound behind that line hears `late`. Dialing costs one
319
- handshake when the line was fine, and an ask made meanwhile waits on the new
320
- socket. A line put down this way closes when the system gets round to it and
321
- dials nothing: only the line the dialer still holds dials again. A phone's
322
- ask must survive a push wake-up, and a tab is a
323
- device, and one seed never lives in two tabs.
324
-
325
- ## Transport by necessity
326
-
327
- Transport is chosen by necessity and nothing else. Two wards in one harbor:
328
- no transport, the harbor calls its other door. Both harbors can listen: a
329
- request, plain HTTP or raw TCP, and a reverse ask is a request the other
330
- way. One of them cannot listen, behind NAT, in a tab, on a phone: a socket,
331
- opened by the one that can dial and held by the one that can listen, with
332
- both sides sending asks over it and a frame id matching replies. There is no
333
- fourth situation, so there is no fourth reach, and none is ever picked
334
- because it is nice. A being cannot tell which one carried her ask, and that
335
- is the proof the choice was free.
336
-
337
- A connection is a reach, never a harbor, and a ward outlives it. When a
338
- socket drops, the listener's directory entry for that pk goes with it, the
339
- listener answers nothing, the dialer's harbor turns that into unreached, and
340
- the relation is untouched: seed, partition and keys are on the dialer, not on
341
- the wire. When the dialer reconnects it announces its pks, the listener
342
- probes each one at its door and rebinds what proved, and the next ask goes
343
- through under the same keys and the same count. A phone in a tunnel loses
344
- signal, never identity.
345
-
346
- Untouched is the relation and not her record of it. An ask that reached
347
- nobody has still taken its number and staged the key it announced, because
348
- she must never offer one number twice; the count carries the gap and the far
349
- door honours any number above its mark. So a tunnel costs a number and a
350
- rotation, and nothing else.
351
-
352
- ## Reach, directory, rendezvous
353
-
354
- A harbor needs two reach kinds and nothing more, which is what
355
- `quo/SPEC.md` says and what `packages/nervur/src/harbor/reach.ts` is:
356
-
357
- - **request**: a URL. Send bytes, get bytes. Listener to listener.
358
- - **socket**: a held connection, opened by whichever side can dial, used in
359
- both directions with a frame id. A WebSocket, a WebRTC channel and a
360
- native socket are one socket reach.
361
-
362
- Two runtimes on one device are no third kind. Either they are one harbor,
363
- and it calls its other door with no transport at all, or they are two
364
- harbors, and one of them dials the other as any dialer does.
365
-
366
- Plus one forwarding rule: a pk whose reach is another harbor that holds a
367
- socket to it. That rule is the **rendezvous**, and with it every dialer is
368
- reachable: a phone holds one socket to `quo.acme.com`, acme's harbor binds
369
- the phone's pk to that socket, and a third harbor sending to the phone's pk
370
- sends to acme, which forwards the same bytes to the same pk. Nobody changes
371
- a pk or a byte, and an intermediary opens nothing.
372
-
373
- What a box can see is the wire and only the wire. Its `/health` shows every
374
- foreign pk and the socket it holds it on, and one phone is one harbor
375
- holding one socket, so a phone that joined two worlds hosted on that box
376
- announces both wards together: that is the box hosting both worlds, and
377
- two worlds on two estates share no socket and have nothing to correlate.
378
- No being of either world sees any of it, since an occupant sees pks never.
379
-
380
- What it does learn is worth writing down, because "pks and ciphertext" is
381
- true and reads as a smaller thing than it is. A harbor forwarding for
382
- someone else holds, for every ask it carries: the destination ward pk, in
383
- the clear, because that is the address and there is no forwarding without
384
- it; the socket it came in on, and so the dialer's own address; the size of
385
- the ask and of the reply, which are not padded and so move with the
386
- plaintext; the time each crossed, and which reply belongs to which ask,
387
- because it awaits the one to answer the other; and, from the announce
388
- frame, which ward pks are held by one device. So an operator of a
389
- rendezvous can keep who spoke to whom, when, and how much, for as long as
390
- it likes, and break no ciphertext to do it. What it cannot learn is the
391
- relation: the heir is sealed inside the ask, so one standing at a ward
392
- cannot be told from another, nor followed from one rendezvous to the next.
393
- Being reachable through someone costs the fact that you spoke. It does not
394
- cost what you said, nor who you are to the far being.
395
-
396
- A rendezvous is a listener and nothing more, so there is never one of them.
397
- A harbor may hold sockets to several and be reached through any: the
398
- directory is keyed by pk and a bind is per socket, so each listener binds
399
- the same pks independently. Plurality is the design and not a deployment
400
- habit, and a world that can only be reached through one listener chose
401
- that.
402
-
403
- A line also says which **wire suite** it speaks, one number that is not
404
- negotiated: in the text frame a socket announces itself with, and as the
405
- header `quo-suite` on a request. It is never on an ask, so an ask is bytes
406
- with nothing in front of them, and a harbor still knows before it carries
407
- anything whether the far side can open what it would send. A side meeting a
408
- suite it does not know closes the line with code 4001; a request door answers
409
- nothing delivered. A dialer refused that way waits out its ceiling before
410
- asking again, because a suite does not become speakable by asking sooner.
411
- `quo/SPEC.md` owns the number and says why it exists.
412
-
413
- The **directory** is the map from pk to reach. The memory harbor stubs it with
414
- linked peers. A real one is a table the harbor fills however it likes, and the
415
- one entry it must learn by itself is a dialer's: when a dialer connects it
416
- announces the ward pks it holds, and the listener binds to that socket each one
417
- whose door answered its probe; `quo/SPEC.md` says what the probe is.
418
- After a migration the pk is unchanged, only its reach, and the next connect
419
- rebinds it.
420
-
421
- ## The quo. route, as built
422
-
423
- The reach interface and its two implementations are the library's,
424
- `packages/nervur/src/harbor/reach.ts`, and pass its reach suite here: the request
425
- and a socket to a daemon in `packages/dock/test/wire.test.ts`, the request
426
- through the worker inside the edge. The route is one file on every terrain,
427
- `packages/dock/harbor/quo.ts`, `Request` in and `Response` out, mounted by
428
- the daemon under `/quo` and by the worker under its harbor's prefix; it asks
429
- its harbor for one thing, an ask delivered to a pk. The listener half is the
430
- terrain's own and is not in it: `ws` at the daemon's door in
431
- `packages/dock/harbor/http.ts`, a socket pair in the edge harbor.
432
-
433
- ```
434
- request POST <url>/<pk>, the sealed ask as the body, the sealed reply as the answer;
435
- <url> is the world's quo. route, which the proxy maps onto the daemon's /quo
436
- 404 is "no reach for that pk" and comes back as nothing; a line lost after
437
- sending answers nothing at all, and the ward's bound ends the ask
438
- at most 1 MiB is read, and a body over it is answered by that same 404: an
439
- ask too big for this door is nothing delivered, never a silence, so it is
440
- safe to send again and it will fail again. The number is this daemon's and
441
- is on no wire; a caller cannot ask for it and is never told which of the
442
- two a 404 was
443
- socket a WebSocket at <url>, held by the dialer, used both ways
444
- text frame { announce: [pk, ...] } the ward pks this side holds
445
- ask frame [0][id 4][pk 64][bytes] a frame id matches the reply
446
- reply frame [1][id 4][bytes]
447
- none frame [2][id 4] nothing was delivered
448
- directory pk -> reach, filled three ways: a socket a dialer holds, bound once proven
449
- and unbound at close; a hint from a link, `nervur reach <pk> <url>`, kept in
450
- <dir>/reach.json; and the harbor's own doors, always first
451
- ```
452
-
453
- One rule makes the rendezvous: bytes that arrive from the wire go to an own
454
- door or to a socket this harbor holds for that pk, and never onward by
455
- request. So a third harbor with a hint that a phone's pk is at acme's
456
- `quo.` sends there, and acme forwards down the socket the phone holds.
457
- A row on the dock ward's `dialers` faculty makes a daemon a dialer, placed
458
- once by the root, `nervur ask --ward dock dialers keep '{"url":"https://quo.acme.com"}'`,
459
- kept in her cells and dialed again at every boot: it holds one socket per
460
- URL, announces its wards on open, binds the listener's pks, and reconnects
461
- with backoff from a second to thirty when the line drops. `drop` closes
462
- the line and forgets it. A pk is 64 bytes on the wire, the ward key and
463
- the padlock.
464
-
465
- The proof is `packages/dock/test/wire.test.ts`: the library's conformance suite,
466
- untouched, against two disk harbors in two folders with HTTP doors on
467
- loopback, once with every ask a request and once with the second harbor
468
- dialing the first; down is the reach refused, migrate is the ward folder
469
- moved and adopted. Then the rendezvous on its own.
470
-
471
- Two rules the daemon obeys and a reader would otherwise rediscover: a harbor
472
- writes the partition before it lets a folder go, because a being driven
473
- in-process changes it without passing a door; and a proxy sends the route
474
- root as `/quo/`, with the trailing slash, which the door takes.
475
-
476
- ## The two cores, as built
477
-
478
- The harbor core is the library's, `packages/nervur/src/harbor/core.ts`, and so is
479
- the dialer, `packages/nervur/src/harbor/dial.ts`. Neither is a word of the
480
- protocol: `quo/SPEC.md` says what a harbor owes a ward and what a reach owes
481
- a far harbor, and a core and a dialer are how this kit pays both on every
482
- terrain. Every harbor here extends the core and hands it three things: a
483
- **store**, the library's interface, with `files.ts` on a disk, plain as three
484
- files per ward or sealed as one blob when the daemon holds `NERVUR_SEED_KEY`,
485
- and `idb.ts` in a tab, each passing the library's store suite, the files
486
- store in both forms; a **loader**, the code half, a
487
- module from a folder on a daemon and the bundle in a tab; and a **lease**, a pid
488
- file on disk and a web lock on the database name in a tab, so a second tab on
489
- one origin meets the lock and is the held page. The disk harbor, `disk.ts`, is the
490
- core plus files, the loader and the pid, and every socket dialed to it is held
491
- by its HTTP door, which is where this terrain's `ws` listener stands; the
492
- browser harbor, `browser.ts`, is the core plus
493
- IndexedDB, the built-in beings, the lock and one dialer per world.
494
-
495
- The harbor folder is the device's and not a package: it has no node_modules
496
- and is not meant to, and a class file in it imports the library by name like
497
- any code. So the disk harbor resolves a class file's packages from where the
498
- dock itself is installed, `resolve.ts`, a module resolution hook registered
499
- once before the first class loads: a bare name that does not resolve from a
500
- file outside any node_modules is resolved again from the dock's own place.
501
- A class sees the packages the daemon sees, the library and the dock's own,
502
- and one copy of each, so a `Being` in a class file is the daemon's `Being`.
503
-
504
- A tab's store keeps values through JSON, as a file does, because the ward
505
- hands its partition out through a guard that structured clone refuses.
506
- The daemon's `/quo` and `/health` answer any origin, with the preflight a
507
- binary POST needs, because a tab on one world's `web.` reaching another
508
- world's `quo.` is the ordinary case.
509
-
510
- `/health` asks for nothing and tells what this harbor hosts: the wards by
511
- name and pk, the sockets held, the directory, and the saves its store
512
- refused, per ward. That is a map of the
513
- device, so it is an operator's surface and not a route: the proxy maps the
514
- three hostnames onto their paths and nothing onto this one, and a request
515
- for it from outside meets a 404 at the proxy. It answers any origin because
516
- it is on loopback, and the day it is put behind a proxy it needs a reason
517
- and a door.
518
-
519
- The proof is `packages/dock/test/terrain/browser.test.ts`, behind
520
- `npm run deep:dock` beside the library's own browser chapter: the
521
- conformance suite, untouched, inside a real Chromium, against two browser
522
- harbors in one tab with two databases, both dialing a Node daemon on
523
- loopback, so every relation crosses the rendezvous; down is the reach
524
- refused on both, migrate lifts seed, partition and record from one database
525
- into the other and boots there, the tab-to-home scene. And the lock: a
526
- second harbor over one database does not boot.
527
-
528
- ### One thing a terrain's fetch decides
529
-
530
- APNs, the ring `dock/apns.ts` is, speaks HTTP/2 and refuses HTTP/1.1.
531
- Node's `fetch` is undici, which has no HTTP/2 client, so a daemon on Node
532
- rings Apple through `dock/http2.ts` instead, the same ring over
533
- `node:http2`. A worker's fetch reaches Apple over HTTP/2 and so does
534
- Deno's and Bun's, and those terrains use the `fetch` one. What the two
535
- send is one function in `dock/apns.ts`, the path, the headers and the
536
- body, because a phone rung two ways is rung the same way twice; what
537
- differs is the transport and nothing else. The Node one is its own file
538
- so that a worker bundle never pulls `node:http2` in, which is why nothing
539
- terrain neutral names it and `dock/index.ts` does not export it. All of
540
- this is here rather than in the being because a being names no terrain.
541
-
542
- ## The edge, as built
543
-
544
- `packages/dock/harbor/edge/` is the core over one Durable Object, and the object
545
- is the harbor. The platform runs one instance of it at a time, which is
546
- the lease and the single writer every ward needs. Its storage is the
547
- store, `storage.ts`: one row per ward, seed, partition and record, with the
548
- seed sealed by `seal.ts` under a key from the platform's secrets,
549
- `NERVUR_SEED_KEY`, so the storage holds ciphertext and the secret store holds
550
- the one key, as the table above says. The deployed worker is the code, the
551
- built-in beings and whatever it hands in. It is a listener and never a
552
- dialer: reached by request at `<origin>/h/<name>/quo`, holding the sockets
553
- dialers open to it on the platform's own socket pair, the rendezvous for
554
- them, awake per request and kept awake by a held socket.
555
-
556
- Its owner door is a route, not a socket, because the platform has no local
557
- process: the root is whoever holds `NERVUR_ROOT`, a platform secret, and the
558
- root's asks arrive as one POST at `<origin>/h/<name>/root` under it, the
559
- first of them `{ init: { ward, user } }`, the root's setup. That is the
560
- device's own rule on this terrain, as file permissions are on a disk. The
561
- first thing a root on the edge does is invite an owner on the ward's pk,
562
- and from then on the edge is piloted from elsewhere over a standing, with
563
- `--via`, like any ward.
564
-
565
- What a side mounted there is given is one shape, `given.ts`, and it arrives
566
- in two halves because the halves change at different times. The deploy
567
- carries the tab's bundle and the service worker's, as text modules a
568
- `type = "Text"` rule in the `wrangler.toml` names, since a built artefact of
569
- a deploy is not a row a ward keeps. What is worn or configured while the
570
- worker runs is rows in the object's own storage, written by the root's door:
571
- the public origins, the apps table and the bell key under `settings`, and the
572
- design a world wears one row per file. So the four routes run there with
573
- nothing of them changed. `<origin>/h/<name>/web/<ward>` is a world's page, and
574
- the routes that page hands its tab are the worker's own until a root names
575
- public ones; `<origin>/h/<name>/api` is a describe as JSON for a stranger or
576
- a bearer, and `<origin>/h/<name>/mcp` is the dance and the endpoint, with the
577
- dance's records kept in the same object under one key.
578
-
579
- A worker stands a box ward, like every other terrain: the box's state has one
580
- home everywhere, the dock ward's partition, and a terrain that cannot stand
581
- that ward does not get a second place to keep box state. What differs by
582
- terrain is which faculties the ward lends, and `given.ts` holds the edge's
583
- list with the reason for each name that is missing. It lends two. `grants`,
584
- the bearers its routes honour, which is a record in her cells and the same
585
- code here as on a disk. And `timer`, on the object's own storage alarm:
586
- `Alarm` in `edge/alarm.ts` is the box's timer with the one method that knows
587
- a terrain replaced, since a `setTimeout` dies with the request and an alarm
588
- does not. One harbor is one object and every ward of it lives there, so the
589
- alarm wakes the whole harbor and a tick from the timer to a home's Clock is
590
- in-process here as it is on a disk. The object's `alarm` handler opens the
591
- harbor, boots every ward from storage and runs the round, so a home on the
592
- edge with a sweep at three in the morning wakes for it with no request to
593
- cause it.
594
-
595
- The other two are not lent: no place and no screen, since a worker is at
596
- neither; and no ring, since a ring is signed under this world's own web
597
- origin and a worker learns its origin from the request and not at boot. A
598
- faculty the platform has no answer for is not lent, rather than lent and
599
- hollow.
600
-
601
- Which ward a caller who names none is at is the terrain's word too. On a disk
602
- it is the mark an operator wrote in the folder; at the edge it is the one
603
- world the object holds, and an object holding several says nothing rather
604
- than guessing.
605
-
606
- The proof is `packages/dock/test/terrain/edge.test.ts`, behind
607
- `npm run deep:dock`, in the platform's own runtime on loopback under
608
- `wrangler dev`: the conformance suite, untouched, inside the platform
609
- against two edge harbors in one object over two prefixed views of its
610
- storage, reaching each other by request through the worker; the root door
611
- refused without the secret and answering under it; the box configured after
612
- it is deployed, its settings merged and answered and a design file worn and
613
- taken off; a world's page served out of the deploy's bundles and those rows;
614
- an invitation traded for a bearer at the program side, an ask through the
615
- avatar answered with the same JSON a Node daemon answers, and the model side
616
- challenging a caller with no bearer; a home keeping a one-shot for the next
617
- minute and being woken for it with nothing asking, the fire written down at
618
- the minute it named and read back a minute later; and a Node daemon
619
- dialing the edge, its pk held there, reached by a third harbor through the
620
- edge, unreached when the line drops. Two platform rules shape it: one object
621
- may not touch another's storage, so the exercise's two harbors share one
622
- object the way two tabs share a page; and the platform's crypto refuses a
623
- Uint8Array over a shared buffer and wants a plain one.
624
-
625
- The `ws` question the road asked is answered by this terrain: the socket
626
- reach's framing and the dialer are written on the standard surface and
627
- hold on every terrain; the listener half is the terrain's own, `ws` on
628
- Node and the socket pair on the edge, and stays where the terrain is. What
629
- the library takes, when it takes the reach, is the framing and the dialer.
630
-
631
- ## The phone, as built
632
-
633
- `sealed.ts` is the store an app holds on a device in a hand, the phone and
634
- the desk alike: one key per harbor in the platform's secret store, marked
635
- this device only so it never travels in a backup, and one sealed file per
636
- ward, seed, partition and record as one blob, in a folder no backup
637
- copies. The seal is `seal.ts`, the same piece the daemon and the edge use.
638
- Opening the store is where custody is decided, since a backup is the one
639
- copy nobody makes on purpose: a key with no folder is a reinstall, and the
640
- stale key is deleted; a folder with no key is a restore to another device,
641
- and the unreadable files are deleted; either way the harbor starts fresh
642
- and there is never a twin. What a platform hands it is two small seams,
643
- files and secrets, and the rule is proven once on the two in memory,
644
- `test/sealed.test.ts`. `capacitor.ts` hands the phone's, the Filesystem
645
- plugin in the folder iCloud does not copy and a secure-storage plugin over
646
- the Keychain. The app that holds the store is `app/`, which the dock does
647
- not know. Its page is `app/phone/entry.ts`, terrain glue and nothing else:
648
- this harbor, the dock's shell over it, and the link the platform hands the
649
- app, as the launch URL and then on every link opened while it runs.
650
-
651
- The harbor on a phone is the browser harbor, `browser.ts`, over that
652
- store: the browser harbor takes a name, `nervur` unless one is given, which
653
- is an IndexedDB database under the web lock `nervur:<name>`, or a store
654
- handed in, whose lease is the process that
655
- holds it, since an app has one harbor and nothing else can open its
656
- files. `nativeHarbor` in `capacitor.ts` is that harbor booted and armed:
657
- the App plugin's foreground event tells it `wake`, which tells every
658
- dialer, because a phone asleep loses its sockets silently and the wake is
659
- what dials them back.
660
-
661
- On Android the same store stands on the same two plugins: the key in the
662
- Keystore-backed secure store, the files in the app's own data folder,
663
- which the manifest excludes from backup with `allowBackup` false, so a
664
- restore finds nothing there either.
665
-
666
- The phone's faculties are its plugins, geolocation and local
667
- notifications, and `capacitor.ts` is two being classes over them, booted
668
- into the phone's dock ward and lent by name: a position as latitude,
669
- longitude, accuracy and time, and a notification shown under a
670
- title. The platform's permission prompt is the first ask's; the terrain
671
- grants it ahead, since no script taps a prompt, with `simctl privacy` and a
672
- set location on iOS, and with `pm grant` and a geo fix on Android.
673
-
674
- On iOS the notifications faculty also plants the banner key, which is the
675
- one thing a second process needs and cannot ask a ward for. It goes
676
- through the same secure store the harbor keeps its own key in: an item
677
- written there lands in the app's first keychain group, which is the group
678
- the notification extension is in, so the two meet with nothing written to
679
- make them meet. A plugin of the app's own could not carry it, because the
680
- bridge builds the page's plugin headers before it hands control to the
681
- app, and the list that registers one earlier is a file `cap sync`
682
- rewrites.
683
-
684
- A notification is scheduled with `isExactNotification` false, which is not
685
- the plugin's default. A notification that wants an exact alarm and has no
686
- permission for one opens the Alarms and reminders settings screen on
687
- Android 12 and up, and the call never answers until a human comes back
688
- from it, so the ask goes `late` and nothing says why. Ours is shown now
689
- and waits for no clock, and the permission it would otherwise want is one
690
- a store asks an alarm app to justify.
691
-
692
- The phone's ear is one more function from the same file, `phoneEar`,
693
- handed to the shell rather than to a class, since the token is the box's
694
- and no being's; what the human reads after a wake is the Notifications
695
- faculty's ask, made by the shell through the harbor's own pointer, and no
696
- second body. The ear registers for
697
- remote notifications and answers the token, `apns` on one platform and
698
- `fcm` on the other, and it is never sprung: registering is what puts the
699
- permission prompt up, and an app that asks the moment it opens is an app a
700
- human refuses on principle, so it answers null until notifications are
701
- already allowed, which they are on the phone's own screen. The token
702
- arrives as an event rather than as an answer, so the registration is
703
- awaited once and kept, and the shell asks again on every foreground
704
- because the platform hands out a new one whenever it likes. Which of
705
- Apple's two push hosts minted it is the build's word and not the token's:
706
- `phone/build.ts` writes in whether this shell was built pointed at a
707
- world, which is the same signal that decides the link claim, and a build
708
- told nothing is the store build that speaks to the production host.
709
-
710
- The proof is two tests behind `npm run deep:app` on one stage,
711
- `packages/dock/test/terrain/phone.ts`: `ios.test.ts` inside the real app
712
- in the iOS Simulator and `android.test.ts` inside it in the Android
713
- emulator. Each runs the store suite, untouched, and the custody rule
714
- against a real secret store and a real folder; the whole conformance
715
- suite over two harbors on native stores, both dialing a daemon on the
716
- Mac's loopback through the tab's own probe; the wake, the app sent
717
- behind Settings and brought back, its dialer told and its socket held
718
- again; and the phone joining the daemon's world on an invitation, then a
719
- model on the daemon asking the user being where the phone is, reaching the
720
- phone's being on the standing home holds and her own location faculty
721
- behind it, and getting where the Simulator or the emulator was put.
722
- The app is synced with the stage's
723
- origin as its page, built with xcodebuild or gradle, installed fresh and
724
- launched with simctl or adb; the
725
- page loads the bundled exercise, `native.ts`, runs it and posts the list
726
- back. The device names the Mac `localhost`, the Simulator because it
727
- shares the Mac's network and the emulator because adb reverses the
728
- stage's two ports, and it has to be that name: a webview grants WebCrypto
729
- to a plain-http page only on `localhost`, the one origin it counts secure
730
- without TLS. Through adb's reversed port a socket that Node dropped stays
731
- open on the device's side, so the stage answers every request with the
732
- connection closed and every fetch the page makes carries a timeout. Two
733
- things the plugins taught, held in the store: the secure store keeps
734
- JSON, so a value is read with the call that parses; and mkdir refuses a
735
- folder that exists, recursive or not.
736
-
737
- ## The desk
738
-
739
- The desk app is the phone's shell on a desk: the same browser harbor, over
740
- the same sealed store, showing the same screen, and a presence like the
741
- phone. `app/tauri/` is the Tauri project, and its Rust core holds three
742
- small things and nothing else: the files under the app's own data folder,
743
- which no backup copies, refusing a path that climbs out of it; the keys in
744
- the OS keychain under the app's service name, this device only; and the
745
- notification center. `tauri.ts` hands the first two to the sealed store as
746
- its seams over the core's commands, and its notifications faculty asks
747
- the third. The window runs `human/app.ts` over that harbor, one ward per
748
- world, every world dialed, carries the dock's stylesheet in its bundle,
749
- and tells the harbor it is back whenever the window comes to the front,
750
- since a desk sleeps and wakes like a phone.
751
-
752
- The desk's dock ward lends what a desk has: notifications through the
753
- platform's notification center, and nothing else. Location is what the
754
- platform gives, which on a desk is nothing, so it is not lent rather than
755
- lent and dumb. No timer, because a desk holds presences and no home, so no
756
- Clock lives on it and a Timer there would arm for nobody; a Timer stands
757
- only on a box that hosts a home. It has no ear, since no store rings a
758
- desk, so a desk is reached while the app is open and drains what it
759
- missed when it opens. The one
760
- window the desk app has is a surface over the DOM like the tab's, and the
761
- screen runs in it: nothing of the desk app is a daemon, spawns one or
762
- attaches to one. A person who wants a home on a Mac runs `nervur serve` on it
763
- as an operator, and that is a home like any other and no concern of the
764
- app's.
765
-
766
- The desk has no honest link. macOS verifies an associated domain for an
767
- app on the store and Windows and Linux verify nothing, so a join link on a
768
- desk opens the browser, and the way a world reaches the desk app is the
769
- Courier: the tab offers, the desk app takes the offer typed or pasted in.
770
- The store and its custody rule are proven on the two seams in memory, and
771
- the desk's seams, the keychain and the folder, are proven live: the app
772
- built with cargo, coming up cold on the Console on the operator's Mac.
773
-
774
- ## The link
775
-
776
- An invitation carries the ward pk of the world that minted it, and not where
777
- that pk lives. A harbor that has never heard of that world cannot carry the
778
- first knock. So a link is an invitation next to a **reach hint**, the URL of
779
- that world's `quo.` route. The being receives the invitation opaque, as
780
- always; the hint goes to the directory. The link is single-use, because the
781
- heir dies at the first knock, and whoever opens it first is bound.
782
-
783
- ## Routes on a daemon
784
-
785
- ```
786
- device
787
- nervur serve one process, one harbor, the ask pointer
788
- nervur.sock owner asks; local only; never behind a proxy
789
- side.sock local sides; local only; never behind a proxy
790
- 127.0.0.1:8787 the HTTP door, loopback only; routes mount by path
791
- /health the wards it hosts, by name and pk, and the saves refused; harbor/http.ts
792
- /mcp the model side; mcp/route.ts
793
- /web the worlds' pages and the allow page; human/web.ts, mcp/allow.ts
794
- /quo the socket door: request in, sockets held, the rendezvous; harbor/quo.ts
795
- /api a describe as JSON, a stranger's or a bearer's; api/route.ts
796
- reverse proxy
797
- mcp.example.com -> 127.0.0.1:8787/mcp
798
- web.example.com -> 127.0.0.1:8787/web
799
- quo.example.com -> 127.0.0.1:8787/quo
800
- api.example.com -> 127.0.0.1:8787/api
801
- ```
802
-
803
- A route is `Request` in and `Response` out, the shapes every runtime speaks,
804
- and `packages/dock/harbor/http.ts` is that contract. A handler answering
805
- nothing declines and the mount tries what comes after it. `listenHttp` is
806
- the one place Node's own objects appear: its door turns an `IncomingMessage`
807
- into a `Request` and writes a `Response` back onto a `ServerResponse`, and
808
- no route names either. So the same route runs on this daemon, on a worker at
809
- the edge, in a browser's own service worker, on Deno and on Bun, and the
810
- terrain is the door and never the side.
811
-
812
- The daemon listens on loopback only, and only when asked, with `--http PORT`
813
- or `NERVUR_HTTP`. The proxy faces the world and terminates TLS; the daemon never
814
- does. `nervur estate` writes a systemd unit that runs the daemon as one user
815
- forever, a launchd agent that does the same on a Mac, and a Caddyfile that
816
- maps the hostnames onto the one port by path, from the templates in
817
- `packages/dock/cli/estate/`.
818
- Unattended, the daemon is that unit: restarted if it dies, the lease
819
- released on SIGTERM, and every ward rebooted from its folder on the next
820
- start with relations intact. A Mac is a dialer and not a listener: its
821
- agent opens no HTTP door, and the routes it holds a socket to are rows the
822
- root kept on its `dialers` faculty.
823
-
824
- The socket is the root owner. Owning a droplet from elsewhere is a standing
825
- at its ward, minted by the root, carried as a sealed ask like any relation.
826
- There is no `cli.` route.
827
-
828
- **Where the addresses come from, per terrain.** One convention holds
829
- everywhere: the four routes are `mcp.`, `web.`, `quo.` and `api.` under one
830
- domain, over https, and nobody types a hostname four times. On a droplet
831
- the domain is said once, `nervur init --domain acme.com` or `nervur domain
832
- acme.com`, and `<dir>/routes.json` is written by the convention; an estate
833
- whose names break it edits that file, and it is the one file that stands
834
- beside the wards by right, since a ward knows its pk and never its
835
- hostname, which is the proxy's fact. With no `routes.json` the daemon
836
- serves `/web`, `/api` and `/quo` on its loopback door, names that door in
837
- pages and links, and opens no model door, since OAuth needs a public
838
- issuer. The app types nothing: it opens a link, the link carries the
839
- world's `quo.` hint, and the world's `GET /web/` hands it the harbor's
840
- whole config. The edge is its own origin, `<origin>/h/<name>/quo`, known at
841
- deploy. A tab is the origin it was served from, with the `quo` address in
842
- the page's config script. So a domain is said in one place, on the box,
843
- and every other terrain learns the addresses from a link or a request.
844
-
845
- ## Acceptance
846
-
847
- A harbor here is accepted when it passes the library's conformance suite
848
- untouched, and by nothing else. Three do: the disk harbor over the wire
849
- under both reaches, the browser harbor through a daemon, and the edge harbor
850
- inside the platform's runtime. Every store passes the library's store suite,
851
- `packages/nervur/src/conformance/store.ts`: files in
852
- `packages/dock/test/store.test.ts`, IndexedDB and the edge's storage inside
853
- their terrains behind `deep:dock`. Every reach passes the library's reach
854
- suite, `packages/nervur/src/conformance/reach.ts`. The store, the reach and the
855
- core are the library's, and every harbor here is one of them over its own
856
- terrain.
857
-
858
- ## Where the tree stands
859
-
860
- The one place under this paper where a gap between it and the folders is
861
- named. Each line is a debt to close, not a note to keep.
862
-
863
- - **A torn write is read, not refused.** A plain store keeps a ward as
864
- rows written one after another, and a write interrupted between two of
865
- them leaves rows that do not belong to one another. The partition
866
- assembled from them boots; it should refuse to, and say which row is
867
- wrong, the way a shape it cannot read already does.
868
- - **A sealed store is one value per ward, and whether it should be rows is
869
- undecided.** The phone and the desk seal the whole ward under one key, so
870
- a being with much in her cells costs her neighbours there as she costs
871
- nobody on a droplet, in a tab or on the edge. Rows there are one seal per
872
- being and move the custody rule the sealed store is written around, and
873
- that is a question about what a restore means before it is one about
874
- speed. The human decides it, and not in passing.