@nervur-org/dock 0.3.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/GETTING_STARTED.md +128 -0
- package/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +37 -0
- package/api/quo-api.md +196 -0
- package/api/route.ts +227 -0
- package/beings/GLOSSARY.md +118 -0
- package/beings/WORLDS.md +567 -0
- package/beings/avatar.ts +101 -0
- package/beings/carry.ts +159 -0
- package/beings/clock.ts +552 -0
- package/beings/courier.ts +107 -0
- package/beings/desk.ts +128 -0
- package/beings/doorbell.ts +129 -0
- package/beings/envoy.ts +218 -0
- package/beings/index.ts +16 -0
- package/beings/join.ts +51 -0
- package/beings/link.ts +104 -0
- package/beings/look.ts +139 -0
- package/beings/porter.ts +43 -0
- package/beings/presence.ts +39 -0
- package/beings/quo-app-bells.md +280 -0
- package/beings/quo-dock.md +1942 -0
- package/beings/setup.ts +45 -0
- package/beings/side.ts +60 -0
- package/beings/user.ts +459 -0
- package/beings/way.ts +65 -0
- package/cli/bundles.ts +20 -0
- package/cli/client.ts +73 -0
- package/cli/daemon.ts +581 -0
- package/cli/estate/Caddyfile +33 -0
- package/cli/estate/quo.service +35 -0
- package/cli/estate/systems.quo.plist +44 -0
- package/cli/estate.ts +48 -0
- package/cli/folder.ts +27 -0
- package/cli/oauth.ts +32 -0
- package/cli/pilot.ts +38 -0
- package/cli/quo-dock.md +1942 -0
- package/cli/quo.ts +203 -0
- package/cli/stand.ts +160 -0
- package/dist/api/route.d.ts +21 -0
- package/dist/api/route.js +193 -0
- package/dist/beings/avatar.d.ts +32 -0
- package/dist/beings/avatar.js +100 -0
- package/dist/beings/carry.d.ts +12 -0
- package/dist/beings/carry.js +174 -0
- package/dist/beings/clock.d.ts +161 -0
- package/dist/beings/clock.js +528 -0
- package/dist/beings/courier.d.ts +50 -0
- package/dist/beings/courier.js +96 -0
- package/dist/beings/desk.d.ts +53 -0
- package/dist/beings/desk.js +135 -0
- package/dist/beings/doorbell.d.ts +78 -0
- package/dist/beings/doorbell.js +133 -0
- package/dist/beings/envoy.d.ts +54 -0
- package/dist/beings/envoy.js +211 -0
- package/dist/beings/index.d.ts +14 -0
- package/dist/beings/index.js +16 -0
- package/dist/beings/join.d.ts +15 -0
- package/dist/beings/join.js +42 -0
- package/dist/beings/link.d.ts +14 -0
- package/dist/beings/link.js +91 -0
- package/dist/beings/look.d.ts +30 -0
- package/dist/beings/look.js +104 -0
- package/dist/beings/porter.d.ts +14 -0
- package/dist/beings/porter.js +41 -0
- package/dist/beings/presence.d.ts +8 -0
- package/dist/beings/presence.js +21 -0
- package/dist/beings/setup.d.ts +3 -0
- package/dist/beings/setup.js +39 -0
- package/dist/beings/side.d.ts +32 -0
- package/dist/beings/side.js +37 -0
- package/dist/beings/user.d.ts +315 -0
- package/dist/beings/user.js +462 -0
- package/dist/beings/way.d.ts +19 -0
- package/dist/beings/way.js +28 -0
- package/dist/cli/bundles.d.ts +5 -0
- package/dist/cli/bundles.js +17 -0
- package/dist/cli/client.d.ts +15 -0
- package/dist/cli/client.js +58 -0
- package/dist/cli/daemon.d.ts +50 -0
- package/dist/cli/daemon.js +584 -0
- package/dist/cli/estate/Caddyfile +33 -0
- package/dist/cli/estate/quo.service +35 -0
- package/dist/cli/estate/systems.quo.plist +44 -0
- package/dist/cli/estate.d.ts +5 -0
- package/dist/cli/estate.js +50 -0
- package/dist/cli/folder.d.ts +2 -0
- package/dist/cli/folder.js +25 -0
- package/dist/cli/oauth.d.ts +2 -0
- package/dist/cli/oauth.js +32 -0
- package/dist/cli/pilot.d.ts +5 -0
- package/dist/cli/pilot.js +22 -0
- package/dist/cli/quo.d.ts +2 -0
- package/dist/cli/quo.js +201 -0
- package/dist/cli/stand.d.ts +37 -0
- package/dist/cli/stand.js +110 -0
- package/dist/dock/agents.d.ts +102 -0
- package/dist/dock/agents.js +105 -0
- package/dist/dock/apns.d.ts +27 -0
- package/dist/dock/apns.js +98 -0
- package/dist/dock/console.d.ts +6 -0
- package/dist/dock/console.js +20 -0
- package/dist/dock/dialers.d.ts +56 -0
- package/dist/dock/dialers.js +75 -0
- package/dist/dock/faculty.d.ts +24 -0
- package/dist/dock/faculty.js +142 -0
- package/dist/dock/fcm.d.ts +13 -0
- package/dist/dock/fcm.js +77 -0
- package/dist/dock/grants.d.ts +41 -0
- package/dist/dock/grants.js +99 -0
- package/dist/dock/http2.d.ts +4 -0
- package/dist/dock/http2.js +68 -0
- package/dist/dock/index.d.ts +41 -0
- package/dist/dock/index.js +113 -0
- package/dist/dock/local.d.ts +13 -0
- package/dist/dock/local.js +44 -0
- package/dist/dock/location.d.ts +19 -0
- package/dist/dock/location.js +11 -0
- package/dist/dock/notifications.d.ts +38 -0
- package/dist/dock/notifications.js +60 -0
- package/dist/dock/push.d.ts +79 -0
- package/dist/dock/push.js +132 -0
- package/dist/dock/service.d.ts +4 -0
- package/dist/dock/service.js +107 -0
- package/dist/dock/timer.d.ts +52 -0
- package/dist/dock/timer.js +149 -0
- package/dist/dock/webpush.d.ts +12 -0
- package/dist/dock/webpush.js +144 -0
- package/dist/harbor/browser.d.ts +15 -0
- package/dist/harbor/browser.js +82 -0
- package/dist/harbor/capacitor.d.ts +28 -0
- package/dist/harbor/capacitor.js +218 -0
- package/dist/harbor/disk.d.ts +27 -0
- package/dist/harbor/disk.js +249 -0
- package/dist/harbor/edge/alarm.d.ts +10 -0
- package/dist/harbor/edge/alarm.js +38 -0
- package/dist/harbor/edge/edge.d.ts +54 -0
- package/dist/harbor/edge/edge.js +370 -0
- package/dist/harbor/edge/exercise.d.ts +7 -0
- package/dist/harbor/edge/exercise.js +232 -0
- package/dist/harbor/edge/given.d.ts +26 -0
- package/dist/harbor/edge/given.js +42 -0
- package/dist/harbor/edge/platform.d.ts +39 -0
- package/dist/harbor/edge/storage.d.ts +30 -0
- package/dist/harbor/edge/storage.js +180 -0
- package/dist/harbor/edge/worker.d.ts +17 -0
- package/dist/harbor/edge/worker.js +67 -0
- package/dist/harbor/files.d.ts +15 -0
- package/dist/harbor/files.js +220 -0
- package/dist/harbor/http.d.ts +19 -0
- package/dist/harbor/http.js +177 -0
- package/dist/harbor/idb.d.ts +15 -0
- package/dist/harbor/idb.js +0 -0
- package/dist/harbor/quo.d.ts +10 -0
- package/dist/harbor/quo.js +42 -0
- package/dist/harbor/resolve.d.ts +16 -0
- package/dist/harbor/resolve.js +17 -0
- package/dist/harbor/seal.d.ts +3 -0
- package/dist/harbor/seal.js +25 -0
- package/dist/harbor/sealed.d.ts +35 -0
- package/dist/harbor/sealed.js +162 -0
- package/dist/harbor/tauri.d.ts +16 -0
- package/dist/harbor/tauri.js +59 -0
- package/dist/harbor/ward.d.ts +9 -0
- package/dist/harbor/ward.js +15 -0
- package/dist/human/app.d.ts +32 -0
- package/dist/human/app.js +527 -0
- package/dist/human/apps.d.ts +10 -0
- package/dist/human/apps.js +29 -0
- package/dist/human/dom.d.ts +2 -0
- package/dist/human/dom.js +15 -0
- package/dist/human/door.d.ts +19 -0
- package/dist/human/door.js +92 -0
- package/dist/human/guest.d.ts +3 -0
- package/dist/human/guest.js +33 -0
- package/dist/human/html.d.ts +55 -0
- package/dist/human/html.js +451 -0
- package/dist/human/local.d.ts +7 -0
- package/dist/human/local.js +10 -0
- package/dist/human/mark.d.ts +8 -0
- package/dist/human/mark.js +47 -0
- package/dist/human/move.d.ts +17 -0
- package/dist/human/move.js +90 -0
- package/dist/human/screen.d.ts +19 -0
- package/dist/human/screen.js +119 -0
- package/dist/human/style.d.ts +2 -0
- package/dist/human/style.js +60 -0
- package/dist/human/tab.bundle.txt +4775 -0
- package/dist/human/tab.d.ts +15 -0
- package/dist/human/tab.js +409 -0
- package/dist/human/tree.d.ts +50 -0
- package/dist/human/tree.js +157 -0
- package/dist/human/wake.d.ts +34 -0
- package/dist/human/wake.js +179 -0
- package/dist/human/web.d.ts +33 -0
- package/dist/human/web.js +184 -0
- package/dist/human/worker.bundle.txt +3868 -0
- package/dist/human/worker.d.ts +1 -0
- package/dist/human/worker.js +81 -0
- package/dist/human/worlds.d.ts +24 -0
- package/dist/human/worlds.js +99 -0
- package/dist/mcp/agent.d.ts +10 -0
- package/dist/mcp/agent.js +78 -0
- package/dist/mcp/allow.d.ts +20 -0
- package/dist/mcp/allow.js +88 -0
- package/dist/mcp/http.d.ts +27 -0
- package/dist/mcp/http.js +92 -0
- package/dist/mcp/oauth.d.ts +91 -0
- package/dist/mcp/oauth.js +241 -0
- package/dist/mcp/route.d.ts +15 -0
- package/dist/mcp/route.js +42 -0
- package/dist/mcp/runner.d.ts +45 -0
- package/dist/mcp/runner.js +99 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.js +102 -0
- package/dock/agents.ts +121 -0
- package/dock/apns.ts +138 -0
- package/dock/console.ts +23 -0
- package/dock/dialers.ts +85 -0
- package/dock/faculty.ts +148 -0
- package/dock/fcm.ts +103 -0
- package/dock/grants.ts +111 -0
- package/dock/http2.ts +70 -0
- package/dock/index.ts +133 -0
- package/dock/local.ts +48 -0
- package/dock/location.ts +22 -0
- package/dock/notifications.ts +67 -0
- package/dock/push.ts +161 -0
- package/dock/quo-app-bells.md +280 -0
- package/dock/quo-dock.md +1942 -0
- package/dock/service.ts +100 -0
- package/dock/timer.ts +174 -0
- package/dock/webpush.ts +182 -0
- package/harbor/browser.ts +97 -0
- package/harbor/capacitor.ts +223 -0
- package/harbor/disk.ts +237 -0
- package/harbor/edge/alarm.ts +48 -0
- package/harbor/edge/edge.ts +407 -0
- package/harbor/edge/exercise.ts +241 -0
- package/harbor/edge/given.ts +106 -0
- package/harbor/edge/platform.d.ts +39 -0
- package/harbor/edge/storage.ts +179 -0
- package/harbor/edge/text.d.ts +15 -0
- package/harbor/edge/worker.ts +70 -0
- package/harbor/edge/wrangler.toml +30 -0
- package/harbor/files.ts +221 -0
- package/harbor/http.ts +188 -0
- package/harbor/idb.ts +0 -0
- package/harbor/quo-harbor.md +850 -0
- package/harbor/quo.ts +44 -0
- package/harbor/resolve.ts +31 -0
- package/harbor/seal.ts +26 -0
- package/harbor/sealed.ts +183 -0
- package/harbor/tauri.ts +79 -0
- package/harbor/ward.ts +28 -0
- package/human/app.ts +580 -0
- package/human/apps.ts +47 -0
- package/human/dom.ts +21 -0
- package/human/door.ts +113 -0
- package/human/guest.ts +34 -0
- package/human/html.ts +490 -0
- package/human/local.ts +30 -0
- package/human/mark.ts +53 -0
- package/human/move.ts +122 -0
- package/human/quo-human.md +1042 -0
- package/human/screen.ts +136 -0
- package/human/style.ts +63 -0
- package/human/tab.bundle.txt +4775 -0
- package/human/tab.ts +446 -0
- package/human/tree.ts +217 -0
- package/human/wake.ts +191 -0
- package/human/web.ts +259 -0
- package/human/worker.bundle.txt +3868 -0
- package/human/worker.ts +124 -0
- package/human/worlds.ts +127 -0
- package/mcp/agent.ts +94 -0
- package/mcp/allow.ts +131 -0
- package/mcp/http.ts +92 -0
- package/mcp/oauth.ts +270 -0
- package/mcp/quo-mcp.md +343 -0
- package/mcp/route.ts +45 -0
- package/mcp/runner.ts +136 -0
- package/mcp/server.ts +101 -0
- package/package.json +121 -0
package/dock/quo-dock.md
ADDED
|
@@ -0,0 +1,1942 @@
|
|
|
1
|
+
# The dock
|
|
2
|
+
|
|
3
|
+
This is the trunk of the dock: what every estate on Quo needs and nobody writes
|
|
4
|
+
twice. `quo/SPEC.md` is the protocol and knows nothing of this folder, and
|
|
5
|
+
`papers/quo-kit.md` is what the library under `packages/quo/src` chose; the
|
|
6
|
+
five documents under `packages/dock/` are the truth of their folders in the
|
|
7
|
+
same way, and where any of them disagrees with `quo/SPEC.md`, the spec wins
|
|
8
|
+
and the document is rewritten. Nothing
|
|
9
|
+
here adds a word to harbor, ward or being. `papers/WORLDS.md` is how those
|
|
10
|
+
words are proposed to a person and to an organisation, and this document
|
|
11
|
+
follows it: the unit is the org, an org has one home whose own being is the
|
|
12
|
+
org's being, the user being is that being when the org is one human, a
|
|
13
|
+
member of an org is an id at its beings and never a home on its box, a
|
|
14
|
+
route says the grammar and never the rung, and an envoy is a ward the org
|
|
15
|
+
does not run. `papers/GLOSSARY.md` holds every word of both.
|
|
16
|
+
|
|
17
|
+
Three layers, one repository, and the words for them:
|
|
18
|
+
|
|
19
|
+
| layer | folder | package | published |
|
|
20
|
+
| --------- | ----------------- | -------------------- | --------------- |
|
|
21
|
+
| Quo | `packages/quo/` | `@nervur-org/nervur` | yes, Apache 2.0 |
|
|
22
|
+
| the dock | `packages/dock/` | `@nervur-org/dock` | yes, Apache 2.0 |
|
|
23
|
+
| an estate | the owner's own | none | never |
|
|
24
|
+
|
|
25
|
+
**An estate is all of one owner's harbors.** The droplet, the edge and the
|
|
26
|
+
Mac, with the desk, the user being and the avatars living across them: that
|
|
27
|
+
is one estate, and an estate's folder is one such folder. One owner, and
|
|
28
|
+
a root each,
|
|
29
|
+
never one root over the three: a root is one harbor's socket and stops at
|
|
30
|
+
that device, so owning your own second box is a standing, minted there and
|
|
31
|
+
taken here, exactly as owning anyone else's would be. What makes them one
|
|
32
|
+
estate is who holds those standings, not a key any two of them share. A second
|
|
33
|
+
domain is a second estate. An adopter's company is an estate in their own
|
|
34
|
+
repository, and it looks exactly like ours. An estate folder is one folder
|
|
35
|
+
per harbor, each the quo directory that device runs minus what it mints,
|
|
36
|
+
its class bodies, its routes, its agents, its dial list, plus that device's
|
|
37
|
+
unit; a package file that depends on the dock and nothing else; secrets by
|
|
38
|
+
reference; and one document. Nothing else may be needed to run it, and the
|
|
39
|
+
proof is mechanical: an estate folder copied into an empty repository, with
|
|
40
|
+
one install of the dock, boots. If it does not, the missing piece belongs
|
|
41
|
+
in the dock.
|
|
42
|
+
|
|
43
|
+
**The dock is what a harbor is fitted with to receive people and models**:
|
|
44
|
+
the daemon and the `quo` command, the front desk, the user being and the
|
|
45
|
+
avatar, the harbors on real terrains, the model sides and the screen, the
|
|
46
|
+
credential exchange, and the faculties of the dock ward, one being per
|
|
47
|
+
thing a box can do. It is general by two rules, under
|
|
48
|
+
"Generality", and it is published so an adopter installs it once and writes
|
|
49
|
+
only their estate.
|
|
50
|
+
|
|
51
|
+
The five documents of the dock, and the folders each answers for:
|
|
52
|
+
|
|
53
|
+
| document | folders | owns |
|
|
54
|
+
| ---------------------- | -------------------------- | ------------------------------------------------ |
|
|
55
|
+
| `papers/quo-dock.md` | `beings/`, `dock/`, `cli/` | this: the beings and rules every side shares |
|
|
56
|
+
| `papers/quo-human.md` | `human/` | the human side: a blueprint spoken as HTML |
|
|
57
|
+
| `papers/quo-mcp.md` | `mcp/` | the model side: a blueprint spoken as tools |
|
|
58
|
+
| `papers/quo-api.md` | `api/` | the api route: a blueprint spoken as JSON |
|
|
59
|
+
| `papers/quo-harbor.md` | `harbor/` | transports: store, directory, doors, listener |
|
|
60
|
+
|
|
61
|
+
Five documents and seven folders, because the trunk answers for three: the
|
|
62
|
+
beings every side shares, the dock ward that holds a box's faculties, and
|
|
63
|
+
the daemon and the `quo` command that stand a device up. Each document
|
|
64
|
+
decides only its own folders, and names another side's word only to say
|
|
65
|
+
where the boundary runs: the human document decides no transport, the model
|
|
66
|
+
document decides no screen, the harbor document decides nothing about what
|
|
67
|
+
a being is. Anything two sides need is decided here.
|
|
68
|
+
|
|
69
|
+
## Getting started
|
|
70
|
+
|
|
71
|
+
An adopter alone with this paper stands a box and puts a phone in a world
|
|
72
|
+
in six commands. The dock installs from npm and brings the library with
|
|
73
|
+
it; `quo` is its one command, run as `npx quo` in the folder it was
|
|
74
|
+
installed in, and every command takes `--dir` for the harbor folder, or
|
|
75
|
+
reads `QUO_DIR`, or takes `~/.quo`.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install @nervur-org/dock
|
|
79
|
+
npx quo init --dir ~/.quo --ward acme --user razvan --domain acme.com --default --show
|
|
80
|
+
npx quo serve --dir ~/.quo --http 8787
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`init` mints the ward `acme` at `~/.quo/wards/acme/`, boots razvan's user
|
|
84
|
+
being, her doorbell and the desk in it, marks it the default ward, the one
|
|
85
|
+
a caller means when naming none, shows it at the web route, and writes
|
|
86
|
+
`routes.json`: the four routes `mcp.`, `web.`, `quo.` and `api.` under the
|
|
87
|
+
domain, over https, for a proxy to map onto the one loopback port. A
|
|
88
|
+
harbor with one ward has it as the default with no mark; with two, one is
|
|
89
|
+
marked or the daemon refuses to serve by name. Unshown, a ward's page is
|
|
90
|
+
the plain door and the root of `web.` does not list it. `routes.json` is
|
|
91
|
+
read at serve and may be rewritten before; without `--domain`, write it
|
|
92
|
+
yourself in the harbor folder, naming the four routes, on a Mac four
|
|
93
|
+
paths at one loopback address:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{ "mcp": "http://127.0.0.1:8787/mcp", "web": "http://127.0.0.1:8787/web", "quo": "http://127.0.0.1:8787/quo", "api": "http://127.0.0.1:8787/api" }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
A link minted here is the web route with the invitation in its fragment,
|
|
100
|
+
and a far harbor reaches this one at the quo route the invitation names,
|
|
101
|
+
so the routes are the box's addresses and nothing else is. `serve` is the
|
|
102
|
+
daemon and the only process ever over that folder; every other command is
|
|
103
|
+
its client over the daemon's local socket. It prints the wards
|
|
104
|
+
it hosts, and one of them is `dock`, the box's own, which it boots and no
|
|
105
|
+
init made. From a second terminal:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npx quo invite --dir ~/.quo '{"being":"razvan","id":"phone"}'
|
|
109
|
+
npx quo census --dir ~/.quo
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The invite's answer is the invitation, `ward`, `heir` and `secret`, and
|
|
113
|
+
`link`, the ward's page, `web./acme`, with the invitation in its fragment.
|
|
114
|
+
Opened on the phone, the tab boots a harbor of its own, joins as that
|
|
115
|
+
device, and is in: no account, nothing typed. The same `id` a second time
|
|
116
|
+
answers `id taken` and exits non-zero. A census is the ward's describe:
|
|
117
|
+
her `asks`, the seven of the owner, `boot`, `public`, `invite`, `knock`,
|
|
118
|
+
`remove`, `unboot` and `ask`, and `notes`, the ward's `pk` and her
|
|
119
|
+
`beings` by key. A second `init` with another `--ward` hosts a second ward on the
|
|
120
|
+
same daemon; `--ward` names it on any command, and naming none means the
|
|
121
|
+
default.
|
|
122
|
+
|
|
123
|
+
The root reaches into a being of her own ward with `quo ask <being>
|
|
124
|
+
[method] [json]`; with no method the answer is the being's describe. A
|
|
125
|
+
being is booted from the classes the box holds, `quo boot
|
|
126
|
+
'{"key":"clock","class":"Clock"}'`, and the classes are the dock's own,
|
|
127
|
+
`User`, `Desk`, `Clock`, `Courier`, `Avatar`, `Presence` and the
|
|
128
|
+
faculties, plus what `classes/index.ts` beside the wards exports by name,
|
|
129
|
+
empty as init leaves it: a class nobody holds answers `no such class, or
|
|
130
|
+
key taken`, as a key already booted does.
|
|
131
|
+
|
|
132
|
+
Everything the box does on its own is a row on a faculty of the dock ward,
|
|
133
|
+
placed by the root with `quo ask --ward dock`, kept in her cells and back
|
|
134
|
+
at every boot; the daemon reads no recipe. Two rows every estate meets:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx quo ask --dir ~/.quo --ward dock dialers keep '{"url":"http://127.0.0.1:8788/quo"}'
|
|
138
|
+
npx quo ask --dir ~/.quo --ward dock agents keep '{"id":"bot","ward":"acme","url":"http://127.0.0.1:1234/v1","model":"qwen"}'
|
|
139
|
+
npx quo ask --dir ~/.quo --ward dock agents list
|
|
140
|
+
npx quo ask --dir ~/.quo --ward dock agents drop '{"id":"bot"}'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
A dialer row is a socket this box holds to that quo route from now and
|
|
144
|
+
from every boot, so the harbor there delivers here through it, the way a
|
|
145
|
+
Mac behind a router reaches a droplet. An agent row is an avatar admitted
|
|
146
|
+
into `acme` as `bot`, run by a model at a url that speaks function calling,
|
|
147
|
+
or by a process on its stdio, `{"id","ward","command","args","dir"}`.
|
|
148
|
+
Each faculty answers `keep`, `list` and `drop`; `drop` takes what names
|
|
149
|
+
the row, `id` for an agent, `url` for a dialer.
|
|
150
|
+
|
|
151
|
+
Two boxes are two harbors, and one owns the other across the wire the way
|
|
152
|
+
it owns itself. B reaches A by a dialer row naming A's quo route, as
|
|
153
|
+
above, or by one hint for its directory. Then the root of A invites an
|
|
154
|
+
owner on A's own ward, naming the ward's pk as the being, and the root of
|
|
155
|
+
B knocks for B's user being with the invitation A answered, the three
|
|
156
|
+
fields without the `link`:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npx quo reach --dir B <pk of A> http://127.0.0.1:8787/quo
|
|
160
|
+
npx quo invite --dir A '{"being":"<pk of A>","id":"b"}'
|
|
161
|
+
npx quo knock --dir B '{"being":"razvan","id":"a","invitation":{"ward":"…","heir":"…","secret":"…"}}'
|
|
162
|
+
npx quo census --dir B --via a
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The knock answers `taken`, the standing's name, and A's describe for an
|
|
166
|
+
owner. From then on every owner command with `--via a` at B is a sealed
|
|
167
|
+
ask at A's door, answered as A answers an owner: a boot lands in A and
|
|
168
|
+
stays through A's restarts, an invite mints on a being of A, and A's own
|
|
169
|
+
pk answers `no such being`, since a carried key pilots and does not hand
|
|
170
|
+
piloting on. What an estate stands beyond the
|
|
171
|
+
init, its front, its clock, the standings between its wards, is one
|
|
172
|
+
script, `stand.ts`, under "What a device stands up".
|
|
173
|
+
|
|
174
|
+
**On the edge** the harbor is one Durable Object and the worker is the
|
|
175
|
+
dock's, reached by name from a two-line `worker.ts`; there is no process
|
|
176
|
+
of your own and no socket, so the root is whoever holds the platform
|
|
177
|
+
secret `QUO_ROOT`, and the root's asks arrive as one POST under it. The
|
|
178
|
+
other secret, `QUO_SEED_KEY`, thirty-two bytes as hex, seals the seeds in
|
|
179
|
+
the object's storage. `wrangler dev --var QUO_SEED_KEY:… --var
|
|
180
|
+
QUO_ROOT:…` runs the same worker on a Mac's loopback.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
export { Quo } from '@nervur-org/dock/harbor/edge/worker';
|
|
184
|
+
export { default } from '@nervur-org/dock/harbor/edge/worker';
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```toml
|
|
188
|
+
name = "acme"
|
|
189
|
+
main = "worker.ts"
|
|
190
|
+
compatibility_date = "2026-09-01"
|
|
191
|
+
workers_dev = true
|
|
192
|
+
|
|
193
|
+
[[durable_objects.bindings]]
|
|
194
|
+
name = "HARBOR"
|
|
195
|
+
class_name = "Quo"
|
|
196
|
+
|
|
197
|
+
[[migrations]]
|
|
198
|
+
tag = "v1"
|
|
199
|
+
new_sqlite_classes = ["Quo"]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npx wrangler secret put QUO_SEED_KEY
|
|
204
|
+
npx wrangler secret put QUO_ROOT
|
|
205
|
+
npx wrangler deploy
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
A ward is `<origin>/h/<name>`, its routes under it, `/quo`, `/web`,
|
|
209
|
+
`/mcp`, `/api`, and its root door `/root`, which answers a POST under the
|
|
210
|
+
bearer and `401` to everything else. The root's first ask is the init,
|
|
211
|
+
once, answering `name`, `pk` and `user` and `409` after; every later one
|
|
212
|
+
is an owner ask by name, or `{}` for the census, and what the ward
|
|
213
|
+
answers comes wrapped as `result`, an error of hers included, under
|
|
214
|
+
`200`. The edge holds `User`, `Desk` and `Avatar`, and no clock:
|
|
215
|
+
what runs on a schedule runs on a box that has one.
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
R='http://127.0.0.1:8931/h/main/root'; H='authorization: Bearer <secret>'
|
|
219
|
+
curl -s -X POST $R -H "$H" -H 'content-type: application/json' -d '{"init":{"ward":"main","user":"razvan"}}'
|
|
220
|
+
curl -s -X POST $R -H "$H" -H 'content-type: application/json' -d '{}'
|
|
221
|
+
curl -s -X POST $R -H "$H" -H 'content-type: application/json' -d '{"method":"invite","args":{"being":"<pk>","id":"mac"}}'
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The invite answers the three fields and no link, since the edge composes
|
|
225
|
+
none. The first thing a root on the edge does is that invite on the
|
|
226
|
+
ward's own pk: a box reaches the edge with `quo reach <pk>
|
|
227
|
+
<origin>/h/main/quo`, knocks with the three fields, and from then on
|
|
228
|
+
pilots it with `--via`, as any ward, and the secret is not used again.
|
|
229
|
+
|
|
230
|
+
## The picture
|
|
231
|
+
|
|
232
|
+
A device runs one harbor, and a harbor is nothing but the device's own
|
|
233
|
+
secrets, data and code offered to Quo. Every world on that device is a ward:
|
|
234
|
+
a seed, a partition and a body of code, with an origin the harbor
|
|
235
|
+
remembers. An org's home is one such ward, on a box it trusts, and its own
|
|
236
|
+
being is the org's being: a family's, a company's, or a human's user being
|
|
237
|
+
when the org is one person. The org's devices, its models, its accounts,
|
|
238
|
+
its members and its memberships all meet there, and it moves to a box of
|
|
239
|
+
its own whole, seed and partition, with every standing intact. The device's
|
|
240
|
+
own capabilities, GPS, camera, disk, calendar, are beings in a ward that is
|
|
241
|
+
the device owner's. Every person and every model is an occupant, reaching
|
|
242
|
+
a being through an avatar under a gate. Every SaaS and every foreign API
|
|
243
|
+
is an envoy, a being of the org's standing in for a ward it does not run.
|
|
244
|
+
Every relation, to acme, to a model, to the phone's GPS, is the same
|
|
245
|
+
relation: an invitation, a knock, a standing, keys that rotate, the three
|
|
246
|
+
words.
|
|
247
|
+
|
|
248
|
+
So the app on a desk and the app in a pocket are browsers of worlds: one
|
|
249
|
+
harbor, many wards, one per world, each in its own runtime, switching
|
|
250
|
+
between them a screen act and nothing more. A world opened there is a
|
|
251
|
+
presence, born on the device's seed with the world's code, and never has to
|
|
252
|
+
be moved home, because home is elsewhere. The droplet is the world that
|
|
253
|
+
never sleeps. Identity is relation: a session is a standing, a token is an
|
|
254
|
+
invitation used once, login is a knock, logout is remove, and there is no
|
|
255
|
+
session store anywhere. Ownership is the ward: one daemon holds the harbor,
|
|
256
|
+
the socket is the root's door, touched once per home, and from then on the
|
|
257
|
+
user being owns her world through a standing at its door, from any device
|
|
258
|
+
that may mint.
|
|
259
|
+
|
|
260
|
+
## The one law
|
|
261
|
+
|
|
262
|
+
Nobody outside Quo is a being, and nobody outside Quo is a ward. A human at
|
|
263
|
+
a screen, a model at an MCP client, a model in an API loop, a SaaS behind an
|
|
264
|
+
API: every one of them is an **occupant**, reached through a being, named by
|
|
265
|
+
an id that being minted, seeing exactly what that being's gate shows it.
|
|
266
|
+
Everything under `packages/dock/` is that sentence applied to screens, models,
|
|
267
|
+
transports and devices.
|
|
268
|
+
|
|
269
|
+
Three roles exist, and they are held by where a thing runs, not by config:
|
|
270
|
+
|
|
271
|
+
| role | what it can do | who can hold it |
|
|
272
|
+
| --------- | ------------------------------------------- | --------------------------------------------------- |
|
|
273
|
+
| occupant | ask a being what her gate shows this asker | anyone, local or remote |
|
|
274
|
+
| owner | reach into a being; boot is every being's | the root, on the device; or a ward the root invited |
|
|
275
|
+
| developer | write a being class the harbor will hold | only a process on the device |
|
|
276
|
+
|
|
277
|
+
Whoever holds all three keeps them apart: the owner creates and places, and
|
|
278
|
+
never does the work. The work is done through an avatar under a gate, so
|
|
279
|
+
that every arrival is named and judged.
|
|
280
|
+
|
|
281
|
+
## Vocabulary
|
|
282
|
+
|
|
283
|
+
- **Org being.** Home's own being: one per org, in the org's own ward,
|
|
284
|
+
running day and night on a dialable harbor. Holds the org's standings,
|
|
285
|
+
its members as occupants, and its own ward's door as an owner.
|
|
286
|
+
Everything anyone does for the org goes through her. Her class is the
|
|
287
|
+
estate's own for an organisation, and `User` for an org of one.
|
|
288
|
+
- **User being.** The org being of an org of one: a human's own, holding
|
|
289
|
+
her standings, acme, calendar, bank, the house, and her devices as
|
|
290
|
+
occupants. The dock's `User` class is her, and a membership the desk
|
|
291
|
+
makes is of the same class or an estate's subclass, in the org's ward,
|
|
292
|
+
holding no door and no ward of its own.
|
|
293
|
+
- **Avatar.** A being that is the voice of someone outside Quo. Her `answer`
|
|
294
|
+
turns to that someone over a **side**, and her methods let that someone
|
|
295
|
+
ask her one standing. The side is hers, not Quo's. A screen and a model
|
|
296
|
+
are two sides of one avatar class.
|
|
297
|
+
- **Front desk.** A being strangers and fresh clients meet first. It holds
|
|
298
|
+
a standing to the ward's own being and to every being it made, and can
|
|
299
|
+
ask one for a device invitation. It is the being `quo init` marks public
|
|
300
|
+
in a fresh ward, and nothing more: a ward has one public being of any
|
|
301
|
+
class, an estate's front in place of the desk, a board or a corpus in a
|
|
302
|
+
ward of its own, and the mark is the owner's to move. Public is not a
|
|
303
|
+
kind of being: she is an ordinary being who chose to describe herself to
|
|
304
|
+
strangers and answer them, and what she keeps is hers like anyone's. The
|
|
305
|
+
desk keeps little because that is the desk's job, not because Quo asks
|
|
306
|
+
it of her.
|
|
307
|
+
- **Dock ward.** The ward a harbor boots and roots for itself, holding what
|
|
308
|
+
that box can do. Never a home, never a presence, and never moved.
|
|
309
|
+
- **Faculty.** One being of the dock ward, standing for one thing the box
|
|
310
|
+
can do. Reached by `lend`, held as an ordinary standing, and she wakes
|
|
311
|
+
whoever invited her. She holds the connection, the arming or the route,
|
|
312
|
+
and begins holding it at birth.
|
|
313
|
+
- **Envoy.** A faculty standing in for a ward nobody here runs: a thing that
|
|
314
|
+
speaks no Quo. Her methods call it and her describe is its surface,
|
|
315
|
+
narrowed; what it sends back unasked, a webhook or a callback or a
|
|
316
|
+
reading, reaches her own body and she turns it into one ask on one
|
|
317
|
+
standing. One being per vendor and one relation per account. A SaaS, an
|
|
318
|
+
MCP server someone else runs, an Arduino behind serial: each enters a
|
|
319
|
+
world as a being like any other.
|
|
320
|
+
- **Porter** and **Doorbell.** The bell's two beings, `beings/porter.ts` and
|
|
321
|
+
`beings/doorbell.ts`. The porter is public and boots a device that says
|
|
322
|
+
hello a doorbell of its own; the doorbell holds that device's platform
|
|
323
|
+
token, admits the worlds the device allows, and hands a ring on.
|
|
324
|
+
- **Timer.** The faculty who reads the box's clock, and the one being on a
|
|
325
|
+
device that may. She arms for the earliest moment any watcher of hers says
|
|
326
|
+
she is next due, wakes them then, and arms nothing when none of them names
|
|
327
|
+
a moment.
|
|
328
|
+
- **Clock.** The being who wakes other beings at a time. She reads no clock:
|
|
329
|
+
the moment arrives as an ask from the timer she invited, and she wakes
|
|
330
|
+
everyone else on standings the root placed.
|
|
331
|
+
- **Gate.** The `for(occupant, asker)` decision on each ask of a being. What
|
|
332
|
+
she shows is what she can be asked. An occupant's entire permission model
|
|
333
|
+
is the gates of the beings it is an occupant of.
|
|
334
|
+
- **Client identity.** What a credential exchange establishes: this human at
|
|
335
|
+
this browser, this OAuth client for this human, this process on this
|
|
336
|
+
machine. One client identity is one avatar, for life.
|
|
337
|
+
- **Carrier.** A being who shows the asks of the standings she holds as asks
|
|
338
|
+
of her own and forwards. The user being is one: a device the human let
|
|
339
|
+
reach sees acme's asks on her, and acme sees her.
|
|
340
|
+
- **Look.** What a being may say about how she is presented, as values: a
|
|
341
|
+
name, a logo, colours, a font, and a hint per ask. One optional ask,
|
|
342
|
+
`look`, answers it, and every side reads the part it understands. A
|
|
343
|
+
second optional ask, `page`, answers her page as a tree of values in the
|
|
344
|
+
screen's closed grammar; only a screen reads it.
|
|
345
|
+
- **Route.** A hostname a reverse proxy sends to one process: `web.`, `quo.`,
|
|
346
|
+
`mcp.`, `api.`. Routes are deployment, never boundaries.
|
|
347
|
+
|
|
348
|
+
## The ids
|
|
349
|
+
|
|
350
|
+
Every relation here is named by an id one being minted, and the dock's own
|
|
351
|
+
are these. They are conventions of this package and nothing of Quo's; a
|
|
352
|
+
different estate could choose others and change no word of the spec.
|
|
353
|
+
|
|
354
|
+
| id | who holds it | what it names |
|
|
355
|
+
| ------------------- | -------------- | ---------------------------------------- |
|
|
356
|
+
| `desk` | the org being | the front desk, her privileged occupant |
|
|
357
|
+
| `user:<name>` | the front desk | its standing to the being of that name |
|
|
358
|
+
| `<client>` | the user being | one device, by its client identity |
|
|
359
|
+
| `to:<client>` | the user being | her way back to that device |
|
|
360
|
+
| `user` | an avatar | her one standing, at the user being |
|
|
361
|
+
| `user:push` | an avatar | the user being as her occupant, to push |
|
|
362
|
+
| `avatar:<identity>` | the ward | the being key one client identity boots |
|
|
363
|
+
| `<name>` | any being | her standing at a faculty of her own box |
|
|
364
|
+
| `<name>:wake` | any being | that faculty, as her occupant |
|
|
365
|
+
| `bell:<client>` | the user being | that device's own doorbell, at the bell |
|
|
366
|
+
| `doorbell` | the box being | this device's doorbell, on its own ward |
|
|
367
|
+
| `device` | a doorbell | the device it belongs to |
|
|
368
|
+
| `world:<n>` | a doorbell | one world that device allowed |
|
|
369
|
+
| `home` | the org being | her own ward's door, as an owner |
|
|
370
|
+
| `home` | the ward | the org being, an owner at its door |
|
|
371
|
+
|
|
372
|
+
A device is two relations, because a relation in Quo is one direction: its
|
|
373
|
+
way in, the occupant, and her way back, `to:<client>`. Neither implies the
|
|
374
|
+
other and the door never joins them. The user being joins them herself, as
|
|
375
|
+
her own rule: she does not push to a device she no longer admits, and
|
|
376
|
+
`forget` drops both in one act. Her way back is handed over at hello and
|
|
377
|
+
taken when it can be: a knock back that is unreached, a tab whose pk the
|
|
378
|
+
far harbor has not bound yet or a phone in a tunnel, spends nothing, so she
|
|
379
|
+
keeps the invitation and takes it at the next push. A refusal is final.
|
|
380
|
+
|
|
381
|
+
**And she joins a world, `join`.** An invitation and a name, and she
|
|
382
|
+
knocks, hands back a way for that world to reach her, and keeps the
|
|
383
|
+
standing under that name, so what she joined is one more thing she carries.
|
|
384
|
+
It is gated to a device that may mint and wants no word of its own: `mint`
|
|
385
|
+
is what shows her own door to a device, so one that holds it can boot and
|
|
386
|
+
invite in her world already, and adding one standing to what she carries is
|
|
387
|
+
less than that. Such a device holds `reach` too in any sensible placement,
|
|
388
|
+
since `reach` is what carries her standings to a device at all and one that
|
|
389
|
+
joined a world it could not then see would be doing it for somebody else.
|
|
390
|
+
|
|
391
|
+
This is the day a homeless org gains a home, and it is why WORLDS can say
|
|
392
|
+
nothing is converted and no id changes. An org whose whole existence was
|
|
393
|
+
standings in other orgs' worlds boots a home on a box it trusts, and that
|
|
394
|
+
home joins each of those from there. From the far side nothing unusual
|
|
395
|
+
happens at all: the home is one more thing of hers holding a standing,
|
|
396
|
+
exactly as her phone is, minted by `device` on her being there like any
|
|
397
|
+
other. There is no migration because there is nothing to migrate.
|
|
398
|
+
|
|
399
|
+
**And she lists them, `devices`.** Two of her asks name a device, `push`
|
|
400
|
+
and `forget`, and nobody who has just lost a phone remembers what it was
|
|
401
|
+
called, so an ask that names one and no ask that shows them is an ask no
|
|
402
|
+
human can use. It answers her devices by the names they were given, which
|
|
403
|
+
of them is asking, and which words each was granted. It is read-only and
|
|
404
|
+
holds nothing secret: a device here is a name and what it may do, and every
|
|
405
|
+
key is in the relations rather than in this answer. Any device of hers may
|
|
406
|
+
ask it, since these are her own devices and a device that could not see the
|
|
407
|
+
others could not be told which one to take out.
|
|
408
|
+
|
|
409
|
+
A device the root minted may also call her: her `name` ask, gated to
|
|
410
|
+
root-minted devices alone, since the root already trusts that device with
|
|
411
|
+
its id and its reach. A being does not know her own key, so a name is
|
|
412
|
+
always given, by the desk's first hello at setup or by such a device, and
|
|
413
|
+
she keeps the last one. A name is a word, held to the same shape as a key.
|
|
414
|
+
|
|
415
|
+
`home` is the root stepping back. At setup the root invites an owner on the
|
|
416
|
+
ward's own pk and she knocks for it, so the seven owner asks of her ward,
|
|
417
|
+
boot, public, invite, knock, remove, unboot and ask, are standings' asks she
|
|
418
|
+
carries as `home-boot` and the rest. She carries them to a device that may
|
|
419
|
+
mint and to nobody else: `mint` is the word for a device trusted to change
|
|
420
|
+
who is in her world, and owning the world from that device is that trust
|
|
421
|
+
said in full. A device that reaches sees acme and the house and no door.
|
|
422
|
+
What an owner at the door is refused, she is refused there too: the ward
|
|
423
|
+
itself cannot be invited on through her, so a carried key pilots and does
|
|
424
|
+
not hand piloting on. The same standing a user being of another ward takes
|
|
425
|
+
to own this one, `--via`; here it is her own.
|
|
426
|
+
|
|
427
|
+
## Architecture
|
|
428
|
+
|
|
429
|
+
One droplet, one harbor, four routes. Every other placement is a subset.
|
|
430
|
+
|
|
431
|
+
```
|
|
432
|
+
acme.com, or a family's domain: the dialable part of an estate
|
|
433
|
+
reverse proxy
|
|
434
|
+
web. one page per world, /<ward>, the credential exchange, ends with an invitation
|
|
435
|
+
quo. the harbor's socket door: the rendezvous for every dialer
|
|
436
|
+
mcp. the model side over HTTP, a credential exchange in front
|
|
437
|
+
api. a describe as plain JSON: the public being for a stranger, the avatar under a bearer
|
|
438
|
+
quo serve: one process, one harbor, the ask pointer
|
|
439
|
+
unix socket owner asks, local only, never behind the proxy
|
|
440
|
+
localhost ports the routes above
|
|
441
|
+
ward main the estate's home: its org being, the desk, the bell, its envoys, its members, its avatars
|
|
442
|
+
ward <human> one per human whose home this box hosts: her user being, her desk, her envoys, her rules, her avatars
|
|
443
|
+
ward <world> any other world the estate runs: a corpus, a board, with a public being of its own
|
|
444
|
+
|
|
445
|
+
mac
|
|
446
|
+
quo serve (laptop, dialer, holds a socket to quo.)
|
|
447
|
+
unix socket owner asks, local only
|
|
448
|
+
ward main the device's own: the human's local beings and the avatars of local sides
|
|
449
|
+
a local agent
|
|
450
|
+
quo side -> an avatar over stdio occupant
|
|
451
|
+
quo pilot -> the ask pointer over the sock owner, here
|
|
452
|
+
quo pilot --via droplet -> a standing at the droplet's ward, sealed owner, there
|
|
453
|
+
the editor -> being classes on disk developer
|
|
454
|
+
|
|
455
|
+
iphone, or a tab
|
|
456
|
+
harbor (dialer, holds a socket to quo., woken by push where there is one)
|
|
457
|
+
ward <world> one presence per world joined, her avatar for that world in it
|
|
458
|
+
the human at the screen occupant
|
|
459
|
+
|
|
460
|
+
droplet, unattended
|
|
461
|
+
an agent under cron -> the owner pilot owner: the estate
|
|
462
|
+
-> an avatar over stdio occupant: reports, chores
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Wards are split by ownership, and by nothing else. An org's home is a ward
|
|
466
|
+
of its own because a ward is what moves whole: the day a human hosted on
|
|
467
|
+
the family's box has a box of her own, her seed and partition go there and
|
|
468
|
+
every standing she holds survives, while the household's other homes stay.
|
|
469
|
+
An avatar lives in the ward of the world she speaks for, keyed by the
|
|
470
|
+
client identity she is, because she is one of that world's relations and
|
|
471
|
+
moves with it. An organisation's members are not homes on its box: they
|
|
472
|
+
are ids at its beings, ten thousand of them in one ward if it has ten
|
|
473
|
+
thousand, and their homes are elsewhere or do not exist yet.
|
|
474
|
+
|
|
475
|
+
## The avatar
|
|
476
|
+
|
|
477
|
+
One avatar per client identity, not per session or per page load. Her
|
|
478
|
+
standing to the user being is minted once and persists in the partition; a
|
|
479
|
+
side opening and closing is a reconnect, and the keys carry on. Reconnect
|
|
480
|
+
never re-invites.
|
|
481
|
+
|
|
482
|
+
An avatar is one standing, and a harbor holds as many avatars as it holds
|
|
483
|
+
relations; a ward only groups them. The droplet keeps each in the ward she
|
|
484
|
+
speaks for, keyed `avatar:<identity>`. A tab groups by world, one local
|
|
485
|
+
ward per far world and one avatar per relation in it, so a fresh
|
|
486
|
+
invitation is always a fresh avatar and never a join on one that already
|
|
487
|
+
holds hers: two invitations into one world are two avatars, and two humans
|
|
488
|
+
on one family tablet are two relations. Which avatar a side speaks through
|
|
489
|
+
is a switch and nothing more.
|
|
490
|
+
|
|
491
|
+
**A device holds a ward per world because each world gets a version of the
|
|
492
|
+
human, and the versions do not join up.** One person is an employee at one
|
|
493
|
+
world, a consultant at a second and family at a third, and the switcher on
|
|
494
|
+
a device is the switch between those. It is not a convenience: a ward is a
|
|
495
|
+
seed, so two wards are two pks, and each world binds its own key to the
|
|
496
|
+
device it admitted. Two worlds on one harbor holding one phone bind two
|
|
497
|
+
different heirs and two different keys, each rotating on its own count,
|
|
498
|
+
with nothing in either record the other could compare. The droplet is the
|
|
499
|
+
opposite on purpose: it is the human's named identity, one pk, where a user
|
|
500
|
+
being carrying acme and the calendar and the house is the point.
|
|
501
|
+
|
|
502
|
+
Nothing of that is visible to a world's beings in any arrangement, since
|
|
503
|
+
an occupant sees pks never and a world knows the device by an id it minted
|
|
504
|
+
itself. The one place a device could be joined up is the wire, which
|
|
505
|
+
carries ward pks by necessity: one harbor is one socket, so an operator
|
|
506
|
+
hosting two of a human's worlds sees two pks arrive together. Two worlds on
|
|
507
|
+
two estates share no socket, and there is nothing to see.
|
|
508
|
+
|
|
509
|
+
One class for every side. It has:
|
|
510
|
+
|
|
511
|
+
- `answer`: the user being asks her only to push, and she forwards the push
|
|
512
|
+
to her side as a notification if the side has one, and drops it if not.
|
|
513
|
+
She describes herself with one ask, `notify`, gated to the user being.
|
|
514
|
+
- methods that mirror what her side asked for: each call from the side
|
|
515
|
+
becomes `standings.user.ask(name, args)`, with the time the side leaves.
|
|
516
|
+
|
|
517
|
+
She never holds a second standing. Someone who should reach acme reaches the
|
|
518
|
+
user being, whose gate for that asker shows `acme.book`, and the user being
|
|
519
|
+
forwards on her own acme standing. Acme sees the human's id. The occupant
|
|
520
|
+
never sees acme's invitation, never holds a key at acme, and cannot keep a
|
|
521
|
+
relation when the human removes it.
|
|
522
|
+
|
|
523
|
+
## The side
|
|
524
|
+
|
|
525
|
+
The one interface every side implements, so that one avatar serves all:
|
|
526
|
+
|
|
527
|
+
```
|
|
528
|
+
side
|
|
529
|
+
describe(blueprint) the avatar's describe, spoken as this side speaks: a form, a tool list
|
|
530
|
+
call(name, args, time?) one ask in, from the side; the avatar forwards it
|
|
531
|
+
push(object) one notification out, to every side she has; dropped if there is none
|
|
532
|
+
three words object, error object, silence, and the ward's words, each rendered as the side renders it
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Sides in this repository: the human at a screen, an MCP server, a runner for
|
|
536
|
+
function-calling APIs, an event-driven agent. Each is one folder and one
|
|
537
|
+
suite; none of them knows another exists.
|
|
538
|
+
|
|
539
|
+
A side is those three things, and its lifetime is its own. Two lifetimes
|
|
540
|
+
exist, each with two users, so each earns its shape: a **connected** side
|
|
541
|
+
lives until closed, the screen and the MCP server; a **run** side lives by
|
|
542
|
+
turns, the runner and the agent, each turn from a trigger to the model's
|
|
543
|
+
last word. A push is the trigger of a turn, never a line smuggled into one:
|
|
544
|
+
a push that arrives while a turn is running waits in her cells and starts
|
|
545
|
+
the next turn when this one ends. Nothing is dropped and nothing is held;
|
|
546
|
+
the ask that carried the push was answered the moment it landed. Between
|
|
547
|
+
turns, the world reaches a model as `papers/quo-mcp.md` says: an
|
|
548
|
+
ask that starts a run, and a callback when it ends.
|
|
549
|
+
|
|
550
|
+
One describe, many renderings. A screen is the user being's describe spoken
|
|
551
|
+
as forms; a tool list is the same describe spoken as tools. Nobody builds a
|
|
552
|
+
UI and then a tool layer: they build asks, both fall out, and neither side
|
|
553
|
+
can do a thing the other cannot, because the gate is one decision for
|
|
554
|
+
describe and for dispatch.
|
|
555
|
+
|
|
556
|
+
## Carrying
|
|
557
|
+
|
|
558
|
+
A device sees one describe, its avatar's one standing, and the human's
|
|
559
|
+
world is many standings: acme, the calendar, the house. The user being
|
|
560
|
+
carries them. Her describe for a device the human allowed to reach shows,
|
|
561
|
+
after her own asks, every ask of every standing she holds, each named
|
|
562
|
+
`<id>-<name>`, and answering one is an ask on that standing in her name. Acme
|
|
563
|
+
sees her id and never the device; the device sees acme without holding a
|
|
564
|
+
key anywhere; the human removes acme once, at her, and every device loses
|
|
565
|
+
it at once. Invariant 1 holds as written: the occupant still holds one
|
|
566
|
+
standing.
|
|
567
|
+
|
|
568
|
+
Carrying is a base, `beings/carry.ts`, and nothing in it knows what a user
|
|
569
|
+
is: a house being could carry its rooms, a company its suppliers. A subclass
|
|
570
|
+
says who may see what she carries, and the user being says a device with the
|
|
571
|
+
`reach` note, written when the human ticked the box at the exchange. The
|
|
572
|
+
notes of a carrier's describe say which asks are whose, how each standing
|
|
573
|
+
looks and which carried ask is her page, `standings: { id: { asks, look?,
|
|
574
|
+
page? } }`, so a screen draws one section per standing, paints her page in
|
|
575
|
+
it when she has one, and a model side keeps the flat list it needs with no
|
|
576
|
+
page in it; the mapping back from a carried name to the standing is in her
|
|
577
|
+
cells and never parsed from the name. Her own asks come first and are never
|
|
578
|
+
shadowed. She asks a standing's describe again when its digest moved and its
|
|
579
|
+
`look` once per digest, so a describe of hers may cost one ask per stale
|
|
580
|
+
standing, the price of a page that is never more than one ask behind.
|
|
581
|
+
|
|
582
|
+
**A carrier's shape is by name and never by history.** She carries her
|
|
583
|
+
standings in the order of their ids, not the order she took them: which lend
|
|
584
|
+
answered first, when a device came back, whether a relation was dropped and
|
|
585
|
+
taken again, are facts about her past and no fact about her. A shape that
|
|
586
|
+
followed them would differ between two runs that made her the same way, and
|
|
587
|
+
her digest with it, so everyone holding a standing on her would be told she
|
|
588
|
+
changed when nothing about her did. That is a promise and not an
|
|
589
|
+
implementation, because the sort is the whole of it and removing it as
|
|
590
|
+
tidying would put the fault back.
|
|
591
|
+
|
|
592
|
+
## The dock ward
|
|
593
|
+
|
|
594
|
+
A device lends its beings what it has, and it lends it as beings. The
|
|
595
|
+
harbor boots one ward of its own, the **dock ward**, and keeps its ask
|
|
596
|
+
pointer rather than handing it out, so it is that ward's root. In it stands
|
|
597
|
+
one being per thing the box can do, a **faculty**, `dock/`. A being of any
|
|
598
|
+
other ward on the box comes to hold a standing at one through `lend`, the
|
|
599
|
+
seventh member of the ground. The faculties below are the ones the dock
|
|
600
|
+
ships, and they are examples of the shape and not a list: `lend` reaches
|
|
601
|
+
any being of the dock ward by her key, so an estate boots a faculty of its
|
|
602
|
+
own there, `quo boot --ward dock`, of a class in its own `classes/` that
|
|
603
|
+
extends `Faculty`, holding whatever its terrain allows, and lends it the
|
|
604
|
+
same way.
|
|
605
|
+
|
|
606
|
+
```
|
|
607
|
+
the dock ward
|
|
608
|
+
timer the box's clock now()
|
|
609
|
+
location where the box is where()
|
|
610
|
+
notifications its notification center notify(title, body)
|
|
611
|
+
push waking a device by token ring(token), one ring per kind
|
|
612
|
+
console the box as one being carries the four above
|
|
613
|
+
local the box's own way in verify(proof); only where a daemon vouches
|
|
614
|
+
grants the bearers its routes honour only where a terrain serves routes
|
|
615
|
+
agents the processes it runs on an event keep(row), drop(id), list(); only where a daemon runs
|
|
616
|
+
dialers the sockets it holds to other harbors keep(url), drop(url), list(); dialed again at every boot
|
|
617
|
+
... a line to a store, an account at a vendor, anything it has
|
|
618
|
+
|
|
619
|
+
watch(invitation, method, args?) every faculty has it, and only it
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
The index of `dock/` is bundled for a tab, since the browser harbor
|
|
623
|
+
stands its dock ward from it, so nothing under it that the index reaches
|
|
624
|
+
imports a module of node's. The faculty only a terrain serving routes holds,
|
|
625
|
+
`grants`, is not in the index at all, so a tab carries no route's notion:
|
|
626
|
+
that terrain hands her class to its harbor, as the daemon hands its own
|
|
627
|
+
push, and reaches her by name. She is the same code on every one of them,
|
|
628
|
+
the web crypto and nothing of node, because the daemon and the worker at the
|
|
629
|
+
edge both stand her. `test/web.test.ts` fetches
|
|
630
|
+
the tab bundle and refuses anything of node in it, because the failure is
|
|
631
|
+
silent otherwise: a tab paints nothing, on every box at once.
|
|
632
|
+
|
|
633
|
+
**A faculty is a being and nothing else.** She has a class the harbor
|
|
634
|
+
holds, cells the partition keeps, a blueprint she declares, a gate that
|
|
635
|
+
reads her occupants, and relations that count and rotate. She is authored
|
|
636
|
+
once, proven against the memory harbor with no device under her, and booted
|
|
637
|
+
wherever her class is held. What makes her a faculty is where she stands
|
|
638
|
+
and not what she is, so one blueprint has as many bodies as there are
|
|
639
|
+
terrains, and one suite accepts them all.
|
|
640
|
+
|
|
641
|
+
**She holds what the box holds, and begins at birth.** A being runs only
|
|
642
|
+
while she is answering, so nothing happens in a world that nobody asked for
|
|
643
|
+
unless some being holds a clock, a line or a socket. That being is a
|
|
644
|
+
faculty. Her constructor opens the connection, registers the worker and
|
|
645
|
+
claims the route, and from then on her own code runs when the world outside
|
|
646
|
+
moves. She is born whether or not the thing she holds is
|
|
647
|
+
reachable, and answers an error object while it is not: absence is for a
|
|
648
|
+
class the harbor does not hold, never for a line that is late.
|
|
649
|
+
|
|
650
|
+
**Reaching her is `lend`, and it hands back an id.** The ward asks the
|
|
651
|
+
ground for the name, the harbor's root mints an invitation on that being,
|
|
652
|
+
and the ward knocks and takes it under the id she gave, inside that same
|
|
653
|
+
call. She never sees the invitation, because it is the device's capability
|
|
654
|
+
and a value she could copy is one she could hand on. A ward that could not
|
|
655
|
+
take what was minted says so there, and the root removes the occupant it
|
|
656
|
+
made, so a refused lend leaves no row on a faculty. A being who wants to
|
|
657
|
+
lend her device access to another does it in the open, by an ask that forwards
|
|
658
|
+
to her standing, where her gate reads who is asking and she can stop.
|
|
659
|
+
|
|
660
|
+
**Waking is a second relation, and only she can give it.**
|
|
661
|
+
|
|
662
|
+
```js
|
|
663
|
+
await this.lend('timer', 'timer');
|
|
664
|
+
const back = await this.invite('timer:wake');
|
|
665
|
+
await this.standings.timer.ask('watch', { invitation: back, method: 'tick' });
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
`watch` is the one subscribe verb, on every faculty and named the same on
|
|
669
|
+
each, so a being who can be woken by one can be woken by any and one suite
|
|
670
|
+
holds them all to it. `dock/faculty.ts` is where it is written, once.
|
|
671
|
+
|
|
672
|
+
Two things birth decides. A being is not live from inside her own
|
|
673
|
+
constructor, because her ward sets her door after it returns, so the join
|
|
674
|
+
yields a turn before it asks for anything. And birth is synchronous while
|
|
675
|
+
taking a standing is not, so a terrain waits on `settled()` before it says
|
|
676
|
+
the box is up: a box that answered sooner would be one whose first tick woke
|
|
677
|
+
nobody.
|
|
678
|
+
|
|
679
|
+
Two ids and not one, because standings and occupants are one namespace and
|
|
680
|
+
invite refuses a name a standing holds. `<name>` is the way out and
|
|
681
|
+
`<name>:wake` is the way back, and a being who wants two subscriptions to
|
|
682
|
+
one faculty mints two of the second.
|
|
683
|
+
|
|
684
|
+
From then on every tick is an ordinary ask at her door as the occupant
|
|
685
|
+
`timer:wake`, judged by her gate, counted once, rotating like anything else.
|
|
686
|
+
`occupants.remove('timer:wake')` is the whole of unsubscribe: the faculty's next
|
|
687
|
+
wake hears `removed` and drops her row. A tick, a delivery, a push and a
|
|
688
|
+
stranger's ask are one act at a door, told apart by the id her ward puts on
|
|
689
|
+
them and by nothing else.
|
|
690
|
+
|
|
691
|
+
**The relation is born at every birth.** A standing points at a being and
|
|
692
|
+
not at a box, so one she wakes up holding names a being on the box she was
|
|
693
|
+
on when it was made. A restart and a migration are the same silent event to
|
|
694
|
+
her, so she does the same lines in her constructor every time: remove, lend,
|
|
695
|
+
invite. `dock/faculty.ts` holds them written once, for a being to extend.
|
|
696
|
+
|
|
697
|
+
**Per caller in the notes, per thing in her cells.** The occupant's notes
|
|
698
|
+
say what this caller is, a prefix on a store, an account at a vendor,
|
|
699
|
+
seeded by `invite` and read by her gate. Her own cells say what the thing
|
|
700
|
+
is: a connection string, a schedule, a set of tokens. A caller names
|
|
701
|
+
neither and cannot: she has no way to spell another caller's prefix, which
|
|
702
|
+
makes the notes a capability rather than a permission.
|
|
703
|
+
|
|
704
|
+
**A faculty's blueprint is her boundary.** `get`, `set` and a scoped scan
|
|
705
|
+
are safe by construction, because the prefix is read off the relation. One
|
|
706
|
+
ask that takes a raw command hands over the whole store and leaves the
|
|
707
|
+
notes as decoration.
|
|
708
|
+
|
|
709
|
+
**Nothing is lent off the box.** `lend` is a member of the ground and the
|
|
710
|
+
ground is the object the booting harbor passes, so it reaches this box and
|
|
711
|
+
no other. A being who wants another box's GPS asks the being who lives on
|
|
712
|
+
that box, on a standing she already holds, and that being asks her own
|
|
713
|
+
faculty. A harbor need not announce the dock ward's pk on any line, since
|
|
714
|
+
an announce is a claim it chooses to make, and being reachable grants
|
|
715
|
+
nothing in any case: the only way in is a heir a faculty minted, and the
|
|
716
|
+
only source of one is `lend`.
|
|
717
|
+
|
|
718
|
+
**A vendor is a faculty like any other.** Speaking Stripe's HTTP is
|
|
719
|
+
knowledge and the box has it; the account is somebody's and rides on the
|
|
720
|
+
relation. So there is one being per vendor and one relation per account:
|
|
721
|
+
her cells hold the credentials, the occupant's notes name which account a
|
|
722
|
+
caller is, and a caller never holds either. This is what a refresh flow
|
|
723
|
+
requires rather than a preference, since a rotated refresh token has to be
|
|
724
|
+
written down by whoever holds it, and only she can write her own cells. The
|
|
725
|
+
account is placed by an ask, gated to whoever may place it, never by a file
|
|
726
|
+
and never in the args of every call. A home that moves box leaves its
|
|
727
|
+
credentials behind and places them again where it lands, which is what
|
|
728
|
+
moving machines does to every account anywhere.
|
|
729
|
+
|
|
730
|
+
## Reaching another box
|
|
731
|
+
|
|
732
|
+
A faculty is lent to the beings of its own harbor and to nobody else, so a
|
|
733
|
+
world that wants what another box has asks the being who lives on that box.
|
|
734
|
+
That being is the **Presence**, a class of `beings/` in a file of her own,
|
|
735
|
+
and she is what an app boots in a presence ward when a world is joined. At
|
|
736
|
+
her birth she lends every name the box lends, as the user being and the
|
|
737
|
+
Clock do, and holds each as a standing named for the faculty, `location`,
|
|
738
|
+
`notifications`, whatever the box has; a name the box does not lend is
|
|
739
|
+
simply not held, and
|
|
740
|
+
she asks again at every birth since a restart and a move are one event to
|
|
741
|
+
her. She is a Carrier over those standings, so the being who holds a
|
|
742
|
+
standing at her sees `location-where` and `notifications-notify` in her
|
|
743
|
+
describe and asks them in her name, and she keeps the Avatar's side, since
|
|
744
|
+
the human at the screen speaks through her too. Her gate shows a faculty to
|
|
745
|
+
the standing that joined her and to nobody else. Home holds a standing at
|
|
746
|
+
her, its way back `to:<id>`, and carries through it what she carries and
|
|
747
|
+
nothing of her own: `to:phone-location-where` on the user being is the
|
|
748
|
+
phone's location, shown to a device or a model that may reach, while a
|
|
749
|
+
way back to a bare avatar carries nothing. A model asks home where the
|
|
750
|
+
human is, home asks her, she asks her location, and acme never learns a
|
|
751
|
+
phone was involved.
|
|
752
|
+
|
|
753
|
+
That is the carrier, and it is the only way across a box. Nothing of the
|
|
754
|
+
box is handed to a world: no invitation to a faculty, no standing at one on
|
|
755
|
+
a far being, no callback in a stance. It is better than a faculty that
|
|
756
|
+
routed: her gate decides what her world may ask of that device, one ask at
|
|
757
|
+
a time, and removing one standing at home is the whole of taking a device
|
|
758
|
+
away.
|
|
759
|
+
|
|
760
|
+
A live ask is answered live, and what a world wants while she sleeps she
|
|
761
|
+
sends. A device in a hand is reached only while it dials, a ring wakes the
|
|
762
|
+
app for seconds and answers no ask, so a world that wants the phone's
|
|
763
|
+
location at night holds the last one she sent. Sending is a rule of the
|
|
764
|
+
Presence, on a schedule or on a change, an ask of her own on the standing
|
|
765
|
+
she holds at home, gated by the same gate that answers the live ask.
|
|
766
|
+
|
|
767
|
+
The platform's own permission prompt belongs to the faculty's first ask,
|
|
768
|
+
and a refusal is the error object every ask after it answers. Which of a
|
|
769
|
+
presence being's asks a shell draws as forms is which standings she holds:
|
|
770
|
+
a bare tab holds none and a phone holds two.
|
|
771
|
+
|
|
772
|
+
## The doorbell
|
|
773
|
+
|
|
774
|
+
A ward on a dialer is reached only while its harbor holds a line, so a
|
|
775
|
+
closed tab, a phone asleep and an app in the background hear nothing. How
|
|
776
|
+
such a device is reached is its platform's, and the two platforms are not
|
|
777
|
+
alike.
|
|
778
|
+
|
|
779
|
+
A browser is the world's own to ring. The user being holds a standing at
|
|
780
|
+
the `push` faculty of her box and rings the endpoint the tab handed her,
|
|
781
|
+
under a VAPID keypair the world minted itself: an endpoint answers only to
|
|
782
|
+
the secret its subscription was made under, so an endpoint at one world
|
|
783
|
+
cannot be rung by another, and there is nothing in it to hand on.
|
|
784
|
+
|
|
785
|
+
A phone is not. A store's token is the device's and the same at every
|
|
786
|
+
world, so a world that held one could wake her forever, disallowing it
|
|
787
|
+
would depend on that world's honesty, and a rotation would be one write per
|
|
788
|
+
world. So a phone hands no token to a world at all. It holds a doorbell of
|
|
789
|
+
its own at a bell, `beings/porter.ts` and `beings/doorbell.ts`, keeps the
|
|
790
|
+
platform's token there, and hands each world it admits a standing at that
|
|
791
|
+
doorbell and the banner key the doorbell never learns. The world rings the
|
|
792
|
+
standing, with the line sealed to that key and nothing else in the ask, and
|
|
793
|
+
`papers/quo-app-bells.md` is the whole of it. A device with no bell behind
|
|
794
|
+
it holds no doorbell and hands a world nothing, which is the same silence a
|
|
795
|
+
browser with no push service answers with.
|
|
796
|
+
|
|
797
|
+
The three rings are not three of a kind, and the difference decides an
|
|
798
|
+
architecture. The three grounds stand beside the push faculty, under
|
|
799
|
+
`dock/`, since a ring is what the box can do and not a being of anyone's.
|
|
800
|
+
Web Push, `dock/webpush.ts`, is a world's own: it mints its keypair
|
|
801
|
+
and rings a browser's push service itself, no account and nobody's
|
|
802
|
+
permission. APNs and FCM, `dock/apns.ts` and `dock/fcm.ts`, accept a push
|
|
803
|
+
only signed with the key of the team that published the app being woken,
|
|
804
|
+
so a world cannot ring a phone however much its human wants it to. The key
|
|
805
|
+
is the publisher's. So the ring is handed on, a token and a sealed banner,
|
|
806
|
+
to a bell service the publisher runs, `dock/service.ts`, which is both ends
|
|
807
|
+
of that: the
|
|
808
|
+
ring the bell's own box holds, `handedTo`, and the door that holds the keys
|
|
809
|
+
and rings, `ringing`. The service sees a token, a ring and ciphertext it
|
|
810
|
+
holds no key for, keeps nothing, and knows no world. A ring with no banner
|
|
811
|
+
is a background wake, and the device drains what it missed and composes its
|
|
812
|
+
own line. An estate that ships its own app under its own name
|
|
813
|
+
holds its own keys and puts the two rings straight into the push faculty of
|
|
814
|
+
its own box, needing no service from anybody.
|
|
815
|
+
|
|
816
|
+
Three things that shape are: a token names its bell, the address of the
|
|
817
|
+
service that holds the keys of the app that minted it, set by that app's
|
|
818
|
+
shell from its build the way `sandbox` is, so a box rings the service the
|
|
819
|
+
token names and rings a phone running an app it never shipped, while the
|
|
820
|
+
service the root kept on the push faculty, `quo ask --ward dock push keep
|
|
821
|
+
'{"service":"https://bell.example"}'`, is a world's default for a token
|
|
822
|
+
that names none and never an address in this package, since whose service it is
|
|
823
|
+
belongs to an estate; who may ring it is the token itself, because a device
|
|
824
|
+
token exists only where a device installed that app, a ring wakes a phone
|
|
825
|
+
and says nothing to it, and an account would be the registry of worlds the
|
|
826
|
+
app is built not to have, and a service refuses a token of an app it holds
|
|
827
|
+
no key for with an error object; and the APNs provider API is HTTP/2 only
|
|
828
|
+
while Node's fetch is undici, which speaks none, so a box whose fetch
|
|
829
|
+
speaks it rings Apple with `dock/apns.ts` and a Node box rings the same
|
|
830
|
+
thing with `dock/http2.ts`, which is that ring over `node:http2` and is
|
|
831
|
+
named by the Node terrain alone. A Node box is a bell when it is handed
|
|
832
|
+
Apple's key, `QUO_APNS_KEY` and the three beside it, and every box handed
|
|
833
|
+
none routes to the service instead. Web Push is the world's own key and
|
|
834
|
+
names no service.
|
|
835
|
+
|
|
836
|
+
What a push carries with nothing to show is nothing on all three. Apple's
|
|
837
|
+
is a background push, `content-available` at priority five, and Google's is
|
|
838
|
+
data only with no `notification` key, which Android would otherwise display
|
|
839
|
+
itself, out of the app's hands and the human's. Both are best effort by
|
|
840
|
+
their platform's own rules, throttled and undelivered to an app the human
|
|
841
|
+
force quit; a ring with a line to show is an alert instead, `mutable-content`
|
|
842
|
+
at priority ten, which the device's own extension opens before the banner is
|
|
843
|
+
drawn. A token names the device and, on Apple,
|
|
844
|
+
which of the two hosts minted it, since a development token means nothing
|
|
845
|
+
to the production host and nothing in the token itself says which.
|
|
846
|
+
|
|
847
|
+
The doorbell carries nothing anyone but the device can read. A ring is the
|
|
848
|
+
device's token and, beside it, the banner sealed to that device's banner
|
|
849
|
+
key: a title and a line, opened on the device itself and by nothing in
|
|
850
|
+
between, not this box, not the bell service and not the platform. The
|
|
851
|
+
object is not in it and never is. `papers/quo-app-bells.md` holds the whole
|
|
852
|
+
of it, and this is the half the dock does. So a push that comes back
|
|
853
|
+
unreached is three moves at the user being. The object waits in her outbox
|
|
854
|
+
under that device, bounded and oldest out, since a device gone for a month
|
|
855
|
+
is told what is recent and never everything. She rings: a phone on the
|
|
856
|
+
`bell:<client>` standing that device handed her, with the line sealed to
|
|
857
|
+
the banner key it handed her beside it, and a browser on its endpoint
|
|
858
|
+
through the `push` faculty of her own box, with the line itself and no
|
|
859
|
+
second seal, since Web Push encrypts a body under RFC 8291 to the keys of
|
|
860
|
+
that subscription alone. And she answers the pusher with
|
|
861
|
+
both facts, `{ unreached: true, rang }`, because a pusher that heard only
|
|
862
|
+
"unreached" would retry and one that heard only "rang" would not know it
|
|
863
|
+
was not delivered.
|
|
864
|
+
|
|
865
|
+
The device's half is two asks of hers, each gated to a device. Nothing of
|
|
866
|
+
what the device can do is handed to her: its faculties are beings of its own
|
|
867
|
+
dock ward, and what a world reaches is the device, on the standing it holds.
|
|
868
|
+
|
|
869
|
+
```
|
|
870
|
+
bell({}) what she already holds for this device
|
|
871
|
+
bell({ invitation, banner }) a standing at its doorbell, and the key
|
|
872
|
+
bell({ token }) a browser endpoint with its keys, a browser's alone
|
|
873
|
+
missed() hand over what it missed, and empty it
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
A phone hands a doorbell over once, because a standing does not rotate: the
|
|
877
|
+
device asks what she holds and says nothing when she holds one, which is
|
|
878
|
+
what keeps the doorbell's list of the worlds it allows a list a human can
|
|
879
|
+
read and cut. A browser says its endpoint every time, because a push
|
|
880
|
+
service may hand out a new one at any moment and a stale one rings an empty
|
|
881
|
+
house. A store's token is refused where a human can read why. A device says
|
|
882
|
+
`missed` when it is woken and when it comes back, and what comes over lands
|
|
883
|
+
on its avatar exactly where a push would have landed. So the notification a
|
|
884
|
+
human reads is either the line the world sealed, opened on the device and
|
|
885
|
+
by nothing between, or one composed on the device out of objects that
|
|
886
|
+
crossed sealed; the platform that carried the ring learned that a device
|
|
887
|
+
was woken and never why. `forget` drops the doorbell standing with the way
|
|
888
|
+
in, the way back and the outbox, in one act.
|
|
889
|
+
|
|
890
|
+
Her doorbell standings and her standing at the push faculty are plumbing
|
|
891
|
+
and carry nothing: a device that may reach what she holds sees acme and the
|
|
892
|
+
house, and never what wakes it.
|
|
893
|
+
|
|
894
|
+
## Moving a world between devices
|
|
895
|
+
|
|
896
|
+
A device holds one local ward per world it has joined, on its own seed, and
|
|
897
|
+
everything it is in that world is in there: the avatars, their standings,
|
|
898
|
+
what each was told. So a human who wants a world on another device moves
|
|
899
|
+
that ward, whole, and nothing else happens. The far world is never asked
|
|
900
|
+
and never told, because a pk is permanent by arithmetic and every standing
|
|
901
|
+
still points at the same ward. Nobody is invited twice, no account is
|
|
902
|
+
transferred, and there is nothing to log into.
|
|
903
|
+
|
|
904
|
+
The being is the **Courier**, `beings/courier.ts`, in a ward of her own on
|
|
905
|
+
each device, and the device **giving** is the one that shows the offer.
|
|
906
|
+
That is not a preference: a device is reachable only through a listener it
|
|
907
|
+
dials, and a device that has joined no world dials nothing at all, so it
|
|
908
|
+
cannot be knocked. The device already in the world holds a socket to that
|
|
909
|
+
world's rendezvous, so the offer carries that reach note beside the
|
|
910
|
+
invitation, as one link, and the device taking dials it and knocks.
|
|
911
|
+
|
|
912
|
+
Two asks over one standing, because the answer to the first can be lost.
|
|
913
|
+
`hand` answers with the ward, seed, partition and record, and deletes
|
|
914
|
+
nothing; the device that took it puts the ward on its own harbor and then
|
|
915
|
+
asks `done`, which is where the copy goes. Interrupted between them, both
|
|
916
|
+
devices hold a ward nobody has touched since it was saved and the human
|
|
917
|
+
offers it again; deleting first and losing the answer would leave none.
|
|
918
|
+
Nothing of the ward is kept in the courier on either side: what is given
|
|
919
|
+
comes from the shell when it is asked for, and what arrives goes back to
|
|
920
|
+
the shell, since only a shell puts a ward on a harbor. `human/move.ts` is
|
|
921
|
+
that shell half, and both shells wear both ends: the tab offers on every
|
|
922
|
+
world page, the app offers on its device page and takes on every page,
|
|
923
|
+
so a world goes tab to app, app to app on one device, and device to
|
|
924
|
+
device by the one path.
|
|
925
|
+
|
|
926
|
+
## The look
|
|
927
|
+
|
|
928
|
+
Three sides, one describe, and nothing hard-wired for any of them. A being
|
|
929
|
+
who wants to say how she is presented answers one more ask, `look`, with
|
|
930
|
+
values and no more, and every side reads the part it understands. The
|
|
931
|
+
vocabulary is closed, in `beings/look.ts`, and a token that fails its shape
|
|
932
|
+
is dropped, not fixed:
|
|
933
|
+
|
|
934
|
+
| token | shape | a screen | a model side |
|
|
935
|
+
| ---------------------- | ------------------------------------- | --------------------------------- | -------------------------- |
|
|
936
|
+
| name | text, no tags | her heading | nothing |
|
|
937
|
+
| logo | an image as a data URI, never a URL | beside the heading | nothing |
|
|
938
|
+
| accent, background, fg | hex colours | CSS variables on her section only | nothing |
|
|
939
|
+
| font | a font stack, plain characters | her section | nothing |
|
|
940
|
+
| radius | 0 to 40 | her corners | nothing |
|
|
941
|
+
| order | her ask names | which come first | tool order |
|
|
942
|
+
| stands | up to 8 origins, parsed and reduced | where a link of hers is vouched | nothing |
|
|
943
|
+
| asks.NAME.title | text | the button | the tool's title |
|
|
944
|
+
| asks.NAME.icon | one short string | on the button | nothing |
|
|
945
|
+
| asks.NAME.readOnly | boolean | run on open, nothing to type | `readOnlyHint` |
|
|
946
|
+
| asks.NAME.destructive | boolean | ask once more | `destructiveHint` |
|
|
947
|
+
| asks.NAME.idempotent | boolean | offer a retry after silence | `idempotentHint` |
|
|
948
|
+
|
|
949
|
+
A look is not capability: it is never in a blueprint or a digest, Quo never
|
|
950
|
+
sees it, and a being without one renders and lists exactly as before. The
|
|
951
|
+
`look` ask itself is presentation and is neither a form nor a tool, and so
|
|
952
|
+
is `page`, the one other presentation ask, which a screen alone reads and
|
|
953
|
+
`papers/quo-human.md` defines. A carrier keeps each standing's
|
|
954
|
+
look beside her asks in the notes, since a look moves with the digest, and
|
|
955
|
+
carries her page as an ask named there, since a page moves with every call;
|
|
956
|
+
so acme's section on the human's page is painted as acme asked, her look
|
|
957
|
+
and her page, inside that section and nowhere else. No token carries a
|
|
958
|
+
stylesheet or code and an image is a data URI, so a far being cannot paint
|
|
959
|
+
over the page or reach a server through her look.
|
|
960
|
+
|
|
961
|
+
**`stands` is the one token that names places, and it is inert.** An envoy
|
|
962
|
+
speaks for a vendor whose pages are that vendor's own, so a human she sends
|
|
963
|
+
to a checkout or a consent screen goes somewhere the org's world does not
|
|
964
|
+
serve. She says here which places those are. Nothing in the dock ever
|
|
965
|
+
fetches one: a side reads the list to decide whether to vouch for an
|
|
966
|
+
address the being's own page named, and `papers/quo-human.md` holds what a
|
|
967
|
+
screen does with it. It is in her look rather than in an answer on purpose.
|
|
968
|
+
A look is asked once per digest and is the same for every answer she gives,
|
|
969
|
+
so what she stands in for is a standing claim a human could be shown, and
|
|
970
|
+
never a destination that changes under one reply. That is what it bounds: a
|
|
971
|
+
vendor's response relayed through an envoy, carrying an address nobody in
|
|
972
|
+
the org wrote. It does not bound a being who is hostile herself, and
|
|
973
|
+
nothing on a page could; what bounds her is that a human chose to hold a
|
|
974
|
+
standing at her. A being who
|
|
975
|
+
marks an ask read-only and then writes has lied to her own page, and nothing
|
|
976
|
+
enforces it, the way nothing enforces that her empty ask is safe to repeat.
|
|
977
|
+
|
|
978
|
+
## Asks only
|
|
979
|
+
|
|
980
|
+
There is no listening in Quo, and none under `packages/dock/`. A being who wants
|
|
981
|
+
another's events invites her, the other takes the standing, and asks her
|
|
982
|
+
whenever something happens. An event is an ask in the other direction, and
|
|
983
|
+
it is the same act next door and across planets; only latency and
|
|
984
|
+
reachability change. It follows that two beings never share memory, not
|
|
985
|
+
even in one ward: a callback, a stream or a bus between them is a link that
|
|
986
|
+
breaks the day one of them is somewhere else, and Quo exists so that nothing
|
|
987
|
+
breaks when she is. Only a ward moves; a being is placed once, at boot.
|
|
988
|
+
|
|
989
|
+
Two consequences every side obeys:
|
|
990
|
+
|
|
991
|
+
- **Pushing to a dialer is asking a dialer.** The server asks the tab, so
|
|
992
|
+
the tab's being must have invited the server's, and the tab's harbor must
|
|
993
|
+
hold a socket for the ask to arrive on.
|
|
994
|
+
- **Slow work acknowledges.** An ask waits thirty seconds by default and
|
|
995
|
+
five minutes at the ceiling, and a late reply is not read. Anything that
|
|
996
|
+
takes longer answers at once with an acknowledgement and asks back on its
|
|
997
|
+
own standing when it is done. A run never holds an ask open.
|
|
998
|
+
|
|
999
|
+
## The clock
|
|
1000
|
+
|
|
1001
|
+
A being reads no clock and must not: a being who could read one would be a
|
|
1002
|
+
being who could not be moved, since the moment she read would be her box's
|
|
1003
|
+
and no other. So the moment reaches a being the one way anything reaches a
|
|
1004
|
+
being, as an ask: the box's **Timer**, a faculty of the dock ward, asks her
|
|
1005
|
+
at her door with the moment in the args. The Timer is the one being on a
|
|
1006
|
+
device that reads a clock, and she arms for one moment at a time: the
|
|
1007
|
+
earliest moment any watcher of hers says she is next due. A box holding no
|
|
1008
|
+
schedule anywhere never ticks.
|
|
1009
|
+
|
|
1010
|
+
The being who is asked and does the rest is the **Clock**,
|
|
1011
|
+
`beings/clock.ts`: at her birth she lends the timer and watches her, and
|
|
1012
|
+
every tick is one ask, `tick({ now })`.
|
|
1013
|
+
|
|
1014
|
+
Her cells hold what she was told to do and the last fire of each. Five asks:
|
|
1015
|
+
|
|
1016
|
+
```
|
|
1017
|
+
tick({ now }) fire everything due at that moment, and answer the tally
|
|
1018
|
+
and when she is next due
|
|
1019
|
+
list({ now }) every schedule, its last fire and when it is next
|
|
1020
|
+
next({ now }) the earliest schedule due after that moment
|
|
1021
|
+
keep({ id, ask, cron | at, … }) add one, or replace the one of that id
|
|
1022
|
+
drop({ id }) take one out, with what it last fired
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
The Timer has three of her own:
|
|
1026
|
+
|
|
1027
|
+
```
|
|
1028
|
+
watch({ invitation, method, args? }) wake me when I say I am next due
|
|
1029
|
+
due({ at }) bring my next round forward to this moment
|
|
1030
|
+
now() this box's moment
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
**One way to declare a schedule.** What the device always does, a morning
|
|
1034
|
+
sweep, is placed once by the root with `keep` when the estate is stood up,
|
|
1035
|
+
and what something decided while the device was running, a call back in
|
|
1036
|
+
three days, is placed by the same ask. Both land in her cells, both outlive
|
|
1037
|
+
the process because the partition does, and each stays until it is dropped.
|
|
1038
|
+
A second `keep` under an id replaces the first, which is how a schedule the
|
|
1039
|
+
device always does is changed: placed again, never edited in a file. The
|
|
1040
|
+
daemon reads no schedule from its folder, and nothing about her is set on
|
|
1041
|
+
the class.
|
|
1042
|
+
|
|
1043
|
+
`tick` is the timer's alone, gated on the id she minted for her when she
|
|
1044
|
+
invited her: a wake is the timer's to cause and nobody else's, not even the
|
|
1045
|
+
root's, and nobody else can wear that id because she minted it and one id
|
|
1046
|
+
names one record. `keep` and `drop` are the owner's alone,
|
|
1047
|
+
gated on `OWNER`, a word the ward refuses the same way: a schedule anyone at
|
|
1048
|
+
a door could add is a wake anyone could cause. `list` and `next` are the
|
|
1049
|
+
owner's or whoever the root invited, so what she holds is watched through a
|
|
1050
|
+
door like anything else.
|
|
1051
|
+
|
|
1052
|
+
**A placement says who may in its own `asks`, and that is the whole of what a
|
|
1053
|
+
placement is.** A subclass replaces the map rather than merging into it, so
|
|
1054
|
+
opening `keep` to occupants is one line, and it is the only line that matters:
|
|
1055
|
+
a clock serving other roots is this being with that gate widened, plus the
|
|
1056
|
+
namespace that letting strangers in requires. Nothing about waking changes,
|
|
1057
|
+
which is why the being woken cannot tell the two apart.
|
|
1058
|
+
|
|
1059
|
+
**A side fills the moment, and never asks a human for one.** A property whose
|
|
1060
|
+
schema says `format: "moment"` is a side's to answer: a screen draws no field
|
|
1061
|
+
for it, fills it from the device's own clock, and a read-only ask with nothing
|
|
1062
|
+
left to type then runs on open and shows a table where a form would have been.
|
|
1063
|
+
A being may not read a clock; a screen is not a being, it runs on a device,
|
|
1064
|
+
and a device has one. This is read off the input schema and never off a look,
|
|
1065
|
+
because it is not a choice about how she is drawn: the ask says what the
|
|
1066
|
+
argument is, and every side decides for itself what to do with that.
|
|
1067
|
+
|
|
1068
|
+
**The ask carries the moment.** She reads no clock, so the one thing on a
|
|
1069
|
+
device that reads one is the daemon's timer; a tick can be replayed exactly;
|
|
1070
|
+
and the suite needs no fake timers. A being reading the wall clock to stamp
|
|
1071
|
+
an answer is fine; a being reading it to decide what happens is not.
|
|
1072
|
+
|
|
1073
|
+
**One timer on a box, and she is a being of its dock ward.** She reads the
|
|
1074
|
+
device's clock and asks each being who invited her at that being's own door.
|
|
1075
|
+
The Clock wakes everyone else through their doors, on standings the root
|
|
1076
|
+
placed, so every wake is named, counted, once-only and judged at a gate. No
|
|
1077
|
+
other class here opens a timer, and no estate keeps a cron line for work a
|
|
1078
|
+
clock can do.
|
|
1079
|
+
|
|
1080
|
+
**Nobody is woken without a schedule saying so.** Every tick answers when
|
|
1081
|
+
that watcher is next due, the Timer arms for the earliest such moment across
|
|
1082
|
+
every watcher, and she arms nothing at all when they all say null. A moment
|
|
1083
|
+
is floored to the minute with half a second after it, because a Clock floors
|
|
1084
|
+
the moment she is handed and a round that fired a hair early would floor to
|
|
1085
|
+
the minute before. A round never overlaps the one before it: a second asked
|
|
1086
|
+
for while one is in flight is handed the one already running.
|
|
1087
|
+
|
|
1088
|
+
Three things move the arming, and they are the whole of it. A round, which
|
|
1089
|
+
sets it to what that round learned. `due({ at })`, which only ever brings it
|
|
1090
|
+
forward, because what fires is decided at the tick in a Clock's own cells and
|
|
1091
|
+
an arming moved later on somebody's word would be a schedule somebody else
|
|
1092
|
+
held silenced; a Clock asks it whenever a schedule of hers is kept or
|
|
1093
|
+
dropped, on the standing her birth gave her. And a watcher just filed, who is
|
|
1094
|
+
ticked at once: one move covers a box booting, a home moving in and a Clock
|
|
1095
|
+
born long after the box was up, and none of them waits for a round nobody
|
|
1096
|
+
scheduled.
|
|
1097
|
+
|
|
1098
|
+
A moment already gone is no arming. Every watcher works out when she is next
|
|
1099
|
+
due from the moment she was handed, so a round fired by hand at a moment that
|
|
1100
|
+
is not the box's answers one that is not the box's either, and a round at the
|
|
1101
|
+
box's own moment always names one still ahead.
|
|
1102
|
+
|
|
1103
|
+
**The terrain supplies the arming and nothing else.** One method of the Timer
|
|
1104
|
+
knows a platform, be woken at this moment or at no moment at all, and a
|
|
1105
|
+
terrain replaces that method alone: a `setTimeout` on Node, capped at a day
|
|
1106
|
+
and armed again, since a timeout further out is not trusted, and the object's
|
|
1107
|
+
own storage alarm on a worker, `harbor/edge/alarm.ts`. The same class, the
|
|
1108
|
+
same door and the same suite, as every faculty is built: one body per
|
|
1109
|
+
terrain and one behaviour everywhere. A phone and a tab lend no timer at all,
|
|
1110
|
+
because nothing runs in a hand at night and a faculty that promised a wake
|
|
1111
|
+
nothing would fire is worse than a name the box does not hold.
|
|
1112
|
+
|
|
1113
|
+
The root places what the device always does from a shell, once,
|
|
1114
|
+
`quo ask clock keep '{"id":"sweep","ask":"corpus.sweep","cron":"0 3 * * *"}'`,
|
|
1115
|
+
one line per schedule, and the estate's paper holds those lines, so the
|
|
1116
|
+
paper still describes the whole device. A schedule that will not read is
|
|
1117
|
+
refused in the answer to that line, where it was written, and is never kept
|
|
1118
|
+
quietly. The estate's stand script is where she is booted and where the
|
|
1119
|
+
standings a schedule names are placed, so a device wires its own clock with
|
|
1120
|
+
no human copying an invitation between two calls.
|
|
1121
|
+
|
|
1122
|
+
**Every clock on a device holds her own schedules**, in her own cells, and
|
|
1123
|
+
each invites the one timer to wake her. A second clock in a second ward is a
|
|
1124
|
+
second set of schedules, fired by the same timer on two relations.
|
|
1125
|
+
|
|
1126
|
+
**One class, two placements.** On its own daemon she wakes beings of her own
|
|
1127
|
+
harbor, on standings the root placed. On a service she wakes beings in other
|
|
1128
|
+
estates, across a wire, on standings their roots placed. The woken being
|
|
1129
|
+
cannot tell which one woke her, and should not: moving from one to the other
|
|
1130
|
+
is a standing taken and a line moved, never a class rewritten. One suite runs
|
|
1131
|
+
against every placement, as every general piece here does.
|
|
1132
|
+
|
|
1133
|
+
**What a schedule costs to answer does not depend on how often it fires.** A
|
|
1134
|
+
cron naming every minute of every hour names 1440 moments in a day, and the
|
|
1135
|
+
question is only ever which is the first one still ahead. On an ordinary day
|
|
1136
|
+
the zone's offset holds from midnight to midnight, so a civil name and a
|
|
1137
|
+
moment differ by one constant and the first name at or after the moment is the
|
|
1138
|
+
answer, found with two reads of the zone. Only the day a zone moves is built
|
|
1139
|
+
and sorted, because an hour the clocks skipped lands on the hour that replaced
|
|
1140
|
+
it and ascending civil stops being ascending UTC there. So one tick costs the
|
|
1141
|
+
same whether a device declares a morning sweep or a heartbeat every minute.
|
|
1142
|
+
|
|
1143
|
+
A schedule is `{ id, ask, args?, note?, cron?, at?, tz?, time?, catchUp? }`.
|
|
1144
|
+
`ask` is `<standing>.<name>`, split at the last dot, since a standing id may
|
|
1145
|
+
carry dots and an ask name may not. `cron` is five fields read in `tz`,
|
|
1146
|
+
which is UTC when nothing says otherwise, because the device's own zone
|
|
1147
|
+
changes under a running daemon and a civil hour somewhere names the where;
|
|
1148
|
+
`at` is a moment written out and fires once. `time` is what the wake may
|
|
1149
|
+
spend. `note` is a value she stores and hands back untouched, read by nobody,
|
|
1150
|
+
the way a ward never reads notes.
|
|
1151
|
+
|
|
1152
|
+
The rules that hold each other up:
|
|
1153
|
+
|
|
1154
|
+
- **A wake answers fast, so nothing is written after the answer.** The save
|
|
1155
|
+
is part of the ask: it happens before the answer is sealed, which is what
|
|
1156
|
+
lets a refused save be the ask's own error. So cells written an hour
|
|
1157
|
+
later belong to no ask, are saved by nothing, and are lost at the next
|
|
1158
|
+
restart. A wake that starts long work
|
|
1159
|
+
acknowledges at once, as slow work already must, and keeps its state in a
|
|
1160
|
+
keep. Because every wake is fast, one `tick` fires everything due inside
|
|
1161
|
+
one ask and answers the tally, so the clock herself never has work
|
|
1162
|
+
outliving her answer.
|
|
1163
|
+
- **A long job is chunked, not held.** The consequence, and better than what
|
|
1164
|
+
cron gives: the woken being takes one chunk per wake and keeps her cursor.
|
|
1165
|
+
A cron job that dies at minute forty of an hour starts the hour again; a
|
|
1166
|
+
chunked one resumes at the next minute.
|
|
1167
|
+
- **Retry is the three words and nothing else.** `unreached` is tried again
|
|
1168
|
+
at the next minute, because nothing was delivered. `late` is not, because
|
|
1169
|
+
the far door may have heard and be working still. Silence is not, by the
|
|
1170
|
+
eighth invariant. Every scheduler ever written guesses this from an exit
|
|
1171
|
+
code; this one is told, by the door, and writes the reason down beside the
|
|
1172
|
+
fire.
|
|
1173
|
+
- **One relation is one lane.** Every send on one standing waits for the one
|
|
1174
|
+
before it, so an estate with fifty schedules at one minute serializes on
|
|
1175
|
+
its own lane and delays nobody else. This is not a queue anyone built; it
|
|
1176
|
+
is what a relation already is.
|
|
1177
|
+
- **A minute fires once.** A tick replayed at a minute already fired fires
|
|
1178
|
+
nothing, which is what makes handing the moment in worth doing. Catch-up
|
|
1179
|
+
fires once for a gap however long it was, never once per minute the box
|
|
1180
|
+
was down, and a schedule without it lets a missed hour go.
|
|
1181
|
+
- **A promise outlives the process.** Nothing a schedule needs is in memory:
|
|
1182
|
+
every schedule, the root's own and an occupant's alike, is in the ward's
|
|
1183
|
+
partition beside the standing it will be woken
|
|
1184
|
+
on, and the last fire of each is written there when the tick returns. The
|
|
1185
|
+
arming is not state: it is armed again at every boot, from the tick every
|
|
1186
|
+
watcher answers when the box builds her. So an `at` a year out
|
|
1187
|
+
survives every restart between now and then, and a box that was down when
|
|
1188
|
+
the moment passed fires it on the first tick after it comes back. What this
|
|
1189
|
+
buys is at-least-once and not exactly-once: a process killed between the
|
|
1190
|
+
wake being delivered and the partition being saved wakes again next minute,
|
|
1191
|
+
which is the side to fail on for a promise, and it is why the work a wake
|
|
1192
|
+
starts must be safe to repeat.
|
|
1193
|
+
|
|
1194
|
+
What the dock's clock cannot do is written down rather than worked around:
|
|
1195
|
+
wake a being in another world, be watched from anywhere but its own box, or
|
|
1196
|
+
say in the morning that nothing fired. An estate that needs those takes a
|
|
1197
|
+
standing at a clock that has them.
|
|
1198
|
+
|
|
1199
|
+
## What a device stands up
|
|
1200
|
+
|
|
1201
|
+
A device has furniture: beings it boots for its own working, and relations
|
|
1202
|
+
between them. The desk and the user being, the bell, an estate's front, the
|
|
1203
|
+
clock with her keep and her voice. None of these is a world anyone keeps on
|
|
1204
|
+
the device; each is the device's own, and each is placed once by the root,
|
|
1205
|
+
from the estate's own `stand.ts`, on real invitations. The wards are the
|
|
1206
|
+
whole truth about what stands: every being, occupant and standing is in a
|
|
1207
|
+
ward's own partition and comes back from its folder at every boot, and the
|
|
1208
|
+
daemon reads no recipe. A script is what the root runs once to get there,
|
|
1209
|
+
and never a second record kept in step with the wards.
|
|
1210
|
+
|
|
1211
|
+
```ts
|
|
1212
|
+
import { stand } from '@nervur-org/dock/stand';
|
|
1213
|
+
|
|
1214
|
+
await stand(dir, {
|
|
1215
|
+
boot: [{ ward: 'corpus', key: 'echo', class: 'Echo', public: true }],
|
|
1216
|
+
wire: [{ from: 'main.clock', to: 'corpus.echo', as: 'corpus', notes: {} }],
|
|
1217
|
+
});
|
|
1218
|
+
```
|
|
1219
|
+
|
|
1220
|
+
`stand` boots the harbor folder with no daemon over it, which the lease
|
|
1221
|
+
refuses otherwise, so the recipe is stop, stand, start, like an init. A
|
|
1222
|
+
boot names a ward, a key and a class, and may say `public`: that being is
|
|
1223
|
+
the one of her ward that answers strangers, an estate's front in place of
|
|
1224
|
+
the desk init put there. A wire is one arrow. `from` ends holding the
|
|
1225
|
+
standing, `to` ends holding the occupant, because a relation is one
|
|
1226
|
+
direction and the answer rides the ask back. `as` names the standing on
|
|
1227
|
+
the near side, defaulting to the far key, since that is the name a schedule
|
|
1228
|
+
will write; `known` names the occupant on the far side, defaulting to the
|
|
1229
|
+
near key; `notes` are the terms the occupant is minted under, which is how
|
|
1230
|
+
a gate that reads notes is fed without a line of code.
|
|
1231
|
+
|
|
1232
|
+
**The default ward is the one a caller means when naming none.** It is not
|
|
1233
|
+
a kind of ward: its agents boot there, its door answers for it when a
|
|
1234
|
+
caller names no ward, and a command with no `--ward` is on it, and that is
|
|
1235
|
+
all. A harbor hosting one ward has it as the default with nothing said; a
|
|
1236
|
+
harbor hosting several marks one, `quo init --default` or `quo default
|
|
1237
|
+
<ward>`, a mark kept in that ward's own folder so it moves with the ward,
|
|
1238
|
+
and with none marked is refused at boot, by the names it hosts, rather
|
|
1239
|
+
than coming up on a guess. No name is a default, and the dock ward, which
|
|
1240
|
+
every harbor boots for its faculties, is never a candidate.
|
|
1241
|
+
|
|
1242
|
+
Five rules, and the first is what the rest protect.
|
|
1243
|
+
|
|
1244
|
+
- **It places ordinary relations.** An invite on the far ward's owner pointer
|
|
1245
|
+
and a knock on the near one: real keys, rotating, in both beings' cells,
|
|
1246
|
+
removable afterwards like any other. Never a channel the harbor grants
|
|
1247
|
+
itself. The moment a standing exists because the harbor said so rather than
|
|
1248
|
+
because an invitation was minted and taken, a being's occupants stop being
|
|
1249
|
+
the whole truth about who can reach her, and every gate here rests on that
|
|
1250
|
+
being true.
|
|
1251
|
+
- **It is idempotent, and it reads that off the mint.** A being is whatever
|
|
1252
|
+
answers, a `Being` or a bare object with one method, so there is no shape to
|
|
1253
|
+
reach into: what says a wire already stands is that the far being will not
|
|
1254
|
+
mint that occupant twice. Held to a word the ward does not reserve, `invite`
|
|
1255
|
+
answers null for one reason only, once the two names are known to be
|
|
1256
|
+
free under the other kind. Standings and occupants are one namespace, so
|
|
1257
|
+
before a heir is spent the script reads the partition the harbor holds: a
|
|
1258
|
+
`known` the far being already holds as a standing, or an `as` the near
|
|
1259
|
+
being already holds as an occupant, is a collision and never this wire,
|
|
1260
|
+
and it stops the script with the name in the word rather than passing
|
|
1261
|
+
for a wire already placed. Twice is once.
|
|
1262
|
+
- **Same harbor only.** Both ends name a ward this device hosts. Across
|
|
1263
|
+
harbors an invitation is not bookkeeping but consent, and there is no shared
|
|
1264
|
+
root to automate it with, so those relations stay a person's to make. This
|
|
1265
|
+
is the line: **a stand script wires a device to itself, an invitation wires
|
|
1266
|
+
it to another root.**
|
|
1267
|
+
- **It adds, never removes, and never unseats.** A line taken out leaves its
|
|
1268
|
+
relation standing, unlike a schedule, because silently unplacing a relation
|
|
1269
|
+
is a wake that stops with nobody told. And half a wire is not healed by
|
|
1270
|
+
guessing: a standing removed while the far occupant stands is not replaced,
|
|
1271
|
+
because minting a second under that name would mean unseating an occupant
|
|
1272
|
+
the root did not place. Both halves out, and the next run places it whole.
|
|
1273
|
+
- **It fails loudly.** A line that will not place stops the script where it
|
|
1274
|
+
is, and a harbor half stood is said, never served.
|
|
1275
|
+
|
|
1276
|
+
## The front desk
|
|
1277
|
+
|
|
1278
|
+
Public, for the first hello only, and everything after is behind an
|
|
1279
|
+
invitation. Its asks:
|
|
1280
|
+
|
|
1281
|
+
```
|
|
1282
|
+
hello() -> { asks it will answer, and how to get in }
|
|
1283
|
+
send({ kind, to }) -> what the way in says only where a kind sends one
|
|
1284
|
+
device({ proof }) -> invitation | { error } proof is what a route produced
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
`proof` is somebody's vouch for whoever is at the door. One kind is the
|
|
1288
|
+
daemon's vouch for a process it saw itself: a nonce it minted for a side
|
|
1289
|
+
that reached its local socket, `local`. Another is a world's own vouch for a
|
|
1290
|
+
mailbox or a number it sent a way in to. The front desk asks the way in of
|
|
1291
|
+
that kind who the proof is for, finds the user being, asks her
|
|
1292
|
+
`invite({ client })`, and hands back what she minted. The front desk never
|
|
1293
|
+
mints an invitation itself: only the user being invites into her own world,
|
|
1294
|
+
and she keeps the client identity in the occupant's notes.
|
|
1295
|
+
|
|
1296
|
+
**A way in is a being, and the desk holds a standing to each.** The id is
|
|
1297
|
+
`way:<kind>`, and that id is the whole of what makes a being a way in: there
|
|
1298
|
+
is no table, no registry and nothing set on a class, so one is placed and
|
|
1299
|
+
taken away by the root's knock and by remove, like every other relation
|
|
1300
|
+
here. Two hold one:
|
|
1301
|
+
|
|
1302
|
+
- the world's own, in the world's ward, where it keeps world state, as an
|
|
1303
|
+
estate's post holding the addresses does. The root wires it, and the
|
|
1304
|
+
estate's stand script is where that line is written.
|
|
1305
|
+
- the box's, lent by the dock ward, as `local` is: the desk takes it at her
|
|
1306
|
+
birth, by the name the harbor lends it under, so a box with no local side
|
|
1307
|
+
offers no such kind and nothing had to be turned off.
|
|
1308
|
+
|
|
1309
|
+
**A kind is a word an org chose, and the dock knows none of them.** There is
|
|
1310
|
+
no list of kinds anywhere, no registry, no flag on a class: `way:<kind>` is
|
|
1311
|
+
an id like any other, and a way in is a being an org wrote or took from
|
|
1312
|
+
somewhere. Mail, a message, a code shown on a screen, a letter, a voice on
|
|
1313
|
+
a call, a sign-in a person already holds at some other company: each of
|
|
1314
|
+
those is one kind, and none of them is named in this package. An org that
|
|
1315
|
+
builds a way in nobody here imagined boots a being, has the root knock her
|
|
1316
|
+
onto the desk, and changes no other line. That is the whole extension
|
|
1317
|
+
point, and it is deliberately the same act as every other relation.
|
|
1318
|
+
|
|
1319
|
+
A way in answers `verify({ proof })` with who it is for, or with nothing. A
|
|
1320
|
+
kind whose proof the world has to hand out first answers `send({ to })`
|
|
1321
|
+
beside it, and the desk's `send` is where a stranger asks for one: an
|
|
1322
|
+
address of that kind's own shape, a way in carried to it with the proof in
|
|
1323
|
+
a link's fragment, and coming back with it is the proof of control. A kind
|
|
1324
|
+
whose proof arrives from somewhere else answers `verify` alone and stands
|
|
1325
|
+
at no door. The desk judges neither. She does not know what an address is,
|
|
1326
|
+
what carried it, whether one was sent to before, or what a link says. Her
|
|
1327
|
+
`send` is shown only where a standing of hers says the being behind it can
|
|
1328
|
+
be asked to send, so a world that hands out nothing shows no such form, and
|
|
1329
|
+
the kinds it names are the kinds that send.
|
|
1330
|
+
|
|
1331
|
+
**A kind she holds no way in for is refused in the same words as one that
|
|
1332
|
+
said no.** The `send` form names the kinds that send for themselves, and it
|
|
1333
|
+
names no others: a way in that only verifies, as `local` does, stands at no
|
|
1334
|
+
door and is written down nowhere a stranger reads. Two different answers at
|
|
1335
|
+
`device` would name it to anyone who guessed the word, and a world's ways in
|
|
1336
|
+
would be a list that could be walked from outside by trying words. So there
|
|
1337
|
+
is one refusal, and it says which of the two it was to nobody.
|
|
1338
|
+
|
|
1339
|
+
**`revoke` is its own word and never `mint`'s.** A device that mints adds
|
|
1340
|
+
devices, which is noisy and stays visible in the world. A device that
|
|
1341
|
+
revokes takes the human's own devices out of it, so a stolen phone holding
|
|
1342
|
+
both would lock her out before she reached a laptop. It behaves like `mint`
|
|
1343
|
+
in every other way: never a default, held only where it was granted, handed
|
|
1344
|
+
on by a device that holds it and no further. A world that granted it to
|
|
1345
|
+
nobody has `forget` out of reach and the root's own removals instead, which
|
|
1346
|
+
is a choice an org makes rather than one the dock makes for it.
|
|
1347
|
+
|
|
1348
|
+
**`user` is a person and `client` is a device, and the difference decides
|
|
1349
|
+
what happens when one is lost.** The user being is found by `user`, so a way
|
|
1350
|
+
in that identifies a person hands her back the being she had, however many
|
|
1351
|
+
times she comes, so a way in keyed to a mailbox makes that mailbox the same
|
|
1352
|
+
person for life. The device standing is minted under `client`, and a name already
|
|
1353
|
+
standing is refused, `id taken`. So a way in that returns one fixed
|
|
1354
|
+
`client` per person gives that person exactly one device for life: the day
|
|
1355
|
+
she loses it she cannot come back until the old standing is taken out, and
|
|
1356
|
+
the world cannot tell her lost phone from a quiet one until she does. A way
|
|
1357
|
+
in that varies the client lets her return at once and leaves the dead
|
|
1358
|
+
standing for her to remove. Neither is wrong and the dock chooses neither,
|
|
1359
|
+
but an org that has not chosen has chosen the first.
|
|
1360
|
+
|
|
1361
|
+
A way in may also say `make`. That is the world saying this is a way in
|
|
1362
|
+
that brings its own people: a proof for a user nobody has booted makes her,
|
|
1363
|
+
and the desk holds her standing because the being is the desk's to make. A
|
|
1364
|
+
way in that stays quiet about it reaches an existing household and nothing
|
|
1365
|
+
else, which is what `local` on a device does and what a world with a fixed
|
|
1366
|
+
household must never change. What is made is the org's being about that
|
|
1367
|
+
person, in the org's ward, a membership and never a home: it holds no door
|
|
1368
|
+
and no ward of its own, and `true` makes it of the dock's `User` class
|
|
1369
|
+
while a class name makes it of the estate's own, a subscriber, a member,
|
|
1370
|
+
a customer.
|
|
1371
|
+
|
|
1372
|
+
## The credential exchange
|
|
1373
|
+
|
|
1374
|
+
The only piece that looks like today's SaaS, and it is deliberately kept as
|
|
1375
|
+
thin as one call. Nothing is ever typed to get in but an invitation: there
|
|
1376
|
+
is no password anywhere in the dock, because login is a knock. Every way in
|
|
1377
|
+
ends the same way:
|
|
1378
|
+
|
|
1379
|
+
1. Somebody who may invite mints an invitation for the client identity.
|
|
1380
|
+
The root does, on the box, with `quo invite` on the user being, and
|
|
1381
|
+
sends it as a link or a line; a device of hers that may mint does it
|
|
1382
|
+
from where it stands, on her `device` ask, so only a human's first
|
|
1383
|
+
device ever has to come from the box. On the device, a side that reached the
|
|
1384
|
+
daemon's side socket is vouched for by the daemon itself, the device's
|
|
1385
|
+
own **proof**: `local`, a nonce the daemon minted for that connection on
|
|
1386
|
+
the box's own way in, the `local` faculty of its dock ward, honoured
|
|
1387
|
+
once there, so the desk asks the user being to mint for it. File
|
|
1388
|
+
permissions on the socket are the device's own rule, which the spec
|
|
1389
|
+
allows. A world that
|
|
1390
|
+
admits anyone with a mailbox vouches for one the same way: the human
|
|
1391
|
+
asks the desk to `send` a way in, and what comes back through her
|
|
1392
|
+
mailbox is a proof of that kind, worth one trade and good for as long
|
|
1393
|
+
as the way in said.
|
|
1394
|
+
2. The invitation reaches the avatar for that identity: handed over at the
|
|
1395
|
+
allow page of the model side's exchange, opened as a link in a tab, or
|
|
1396
|
+
given by the desk to a local side. On a reconnect the avatar already
|
|
1397
|
+
holds her standing, nothing is handed and nothing is minted.
|
|
1398
|
+
3. The avatar knocks. The heir dies. From here there is no token anywhere,
|
|
1399
|
+
and the box's grant only remembers which avatar a client speaks
|
|
1400
|
+
through: `grants`, one faculty of the dock ward, `dock/grants.ts`,
|
|
1401
|
+
honoured on every route, since a route says the grammar and never the
|
|
1402
|
+
rung. Her tokens are her cells, kept by the dock ward's partition under
|
|
1403
|
+
the harbor's one secret and by no file of her own, and the daemon
|
|
1404
|
+
reaches her as it reaches `local`, in this process, never at a door.
|
|
1405
|
+
The OAuth dance on the model route and the trade on the api route both
|
|
1406
|
+
end there.
|
|
1407
|
+
|
|
1408
|
+
Three words travel with a minting: whether this device may **wake** the
|
|
1409
|
+
human's other devices, whether it may **reach** what she holds, and whether
|
|
1410
|
+
it may **mint** the next device. The user being writes each as a note on the
|
|
1411
|
+
occupant she mints, and her gate reads the note: `push` is shown to the desk
|
|
1412
|
+
and to a device with the `wake` note, her standings are carried for a device
|
|
1413
|
+
with the `reach` note, and `device` is shown to the desk and to a device with
|
|
1414
|
+
the `mint` note, and to nobody else. A device that may wake can hand an agent
|
|
1415
|
+
an event through the user being, and she sees who did; a device that may
|
|
1416
|
+
reach sees acme on her, and a model sees acme only because the human said it
|
|
1417
|
+
may; a device that may mint brings her next device in from where it stands,
|
|
1418
|
+
which is how a human adds a phone without going back to the box.
|
|
1419
|
+
|
|
1420
|
+
What a device holds is also the most it may write on a device it mints: it
|
|
1421
|
+
asks for the words it wants, and she writes only those it holds itself, so
|
|
1422
|
+
a chain of devices never gains a power along the way. The
|
|
1423
|
+
notes are written when the occupant is minted; to change one, remove the
|
|
1424
|
+
occupant and mint again, because reconnect mints nothing. A device the
|
|
1425
|
+
root mints on her directly, with `quo invite`, carries no note: the id the
|
|
1426
|
+
root chose is its client identity, and it reaches and may wake, since the
|
|
1427
|
+
root owns everything she holds and chose to mint it. `mint` is never
|
|
1428
|
+
implied, not even there, because it admits somebody else rather than
|
|
1429
|
+
reaching what the root already owns: the root says it out loud, by minting
|
|
1430
|
+
with `{ notes: { mint: true } }`, which the ward's owner `invite` passes to
|
|
1431
|
+
her untouched. So a harbor with no desk hands out no minting until its root
|
|
1432
|
+
means to, and the first device of a family is exactly as far as the box
|
|
1433
|
+
lets it go. An identity the user
|
|
1434
|
+
being forgot and lets in again on a fresh invitation is the same avatar:
|
|
1435
|
+
her old standing hears `removed`, she drops it, and knocks anew.
|
|
1436
|
+
|
|
1437
|
+
The lifetimes: a pending request and a code live ten minutes, an access
|
|
1438
|
+
token one hour, a refresh token thirty days, and refresh rotates. A
|
|
1439
|
+
registered client lives ten minutes, and as long as its refresh
|
|
1440
|
+
token once the human allowed it, since the register door is open to anyone
|
|
1441
|
+
and what nobody allowed must not stay on disk. A code is spent the first
|
|
1442
|
+
time it is offered, right or wrong. The client identity is chosen by the
|
|
1443
|
+
human at the allow page and never taken from the client's registration: a
|
|
1444
|
+
client may register afresh on every connect, and the avatar outlives that.
|
|
1445
|
+
The world the client lands in is the world the invitation names, so the
|
|
1446
|
+
page offers no choice of world.
|
|
1447
|
+
|
|
1448
|
+
Tokens, cookies and grants exist for the length of step 1 above and are never
|
|
1449
|
+
stored on a Quo side. Revocation is `occupants.remove(id)` on the user being:
|
|
1450
|
+
the client's next ask, under the key the door bound for its avatar, hears
|
|
1451
|
+
`removed`, and the side speaks that in its own vocabulary: the MCP route
|
|
1452
|
+
answers 401 at the next session and forgets the grant.
|
|
1453
|
+
|
|
1454
|
+
## The daemon and the CLI
|
|
1455
|
+
|
|
1456
|
+
One command, `quo`, under `packages/dock/cli/`. It imports the four library exports
|
|
1457
|
+
and nothing else, holds no class body, and never speaks to a ward except
|
|
1458
|
+
through the ask pointer or an avatar, the two doors everyone has. The
|
|
1459
|
+
daemon's folder holds the process and its two sockets, and nothing under
|
|
1460
|
+
`packages/dock/` imports it: a folder that took the daemon to write a route
|
|
1461
|
+
could be neither read nor replaced alone.
|
|
1462
|
+
|
|
1463
|
+
**A route is written against the harbor's door and never against the
|
|
1464
|
+
daemon.** The HTTP listener is the disk terrain's, `harbor/http.ts`, beside
|
|
1465
|
+
the harbor whose door it is, and the contract every side takes from it is
|
|
1466
|
+
`Handler`, `readAll` and `Quo`. Each route lives in the folder whose truth
|
|
1467
|
+
it is, `human/web.ts`, `mcp/route.ts`, `harbor/quo.ts`, `api/route.ts`, and
|
|
1468
|
+
the daemon only mounts them. The owner pilot is the other side of the same
|
|
1469
|
+
rule: what it is made of is the daemon's socket, so it stands in `cli/`, and
|
|
1470
|
+
`mcp/` holds the model side it is handed to.
|
|
1471
|
+
|
|
1472
|
+
```
|
|
1473
|
+
quo init [--dir D] [--ward W] [--user U] [--class C] [--default] [--show] [--domain DOM] mint a seed, boot a ward over a disk store, write both; C is the class of its home being, User unless said, an org's for an org's ward; --default marks it the default ward; --show shows it at the web route; --domain writes the four routes under DOM
|
|
1474
|
+
quo default <ward> mark one ward the default, the one a caller means when naming none; a harbor with one ward needs no mark
|
|
1475
|
+
quo show|hide <ward> show a ward at the web route, listed at its root and its public being's page painted for a stranger; hidden at init
|
|
1476
|
+
quo domain <domain> write routes.json by the convention, mcp., web., quo. and api. under the domain, over https
|
|
1477
|
+
quo serve [--dir D] the daemon: harbor from disk, the unix socket, the routes' listeners, the agents, a socket held to every route the dialers faculty keeps
|
|
1478
|
+
quo pilot [--dir D] [--via S] the owner pilot over stdio: the ward's describe as tools; as the root, or via a standing at another ward
|
|
1479
|
+
quo side [--dir D] --as NAME an avatar over stdio for a local client; NAME is the client identity
|
|
1480
|
+
quo run [--dir D] --as NAME --url URL --model NAME a human at a terminal talking to a model through an avatar; a line in, the final text out
|
|
1481
|
+
quo census [--dir D] print the census once and exit
|
|
1482
|
+
quo boot|public|invite|knock|remove|unboot six of the seven owner asks from a shell, for scripts and for agents under cron; --via S sends them to another ward
|
|
1483
|
+
quo ask <being> [method] [json] the seventh: the root reaching into a being of its own ward, as the device
|
|
1484
|
+
quo reach <pk> <url> a hint for the harbor's directory: that world's ward lives at that quo. route
|
|
1485
|
+
quo estate <dir> --domain D an estate folder of the shape every estate has: a droplet's quo directory, its unit and Caddyfile, a Mac's launchd agent and dial list, a package on this dock
|
|
1486
|
+
```
|
|
1487
|
+
|
|
1488
|
+
**The owner does two things, and the ward says both.** Six of the seven make
|
|
1489
|
+
and place beings: boot one, mark one public, mint an invitation, knock,
|
|
1490
|
+
remove a relation, take a being out. The seventh, `ask`, is the other half,
|
|
1491
|
+
reaching into a being, which is what the roles table above has always meant
|
|
1492
|
+
by owner. It is strictly less than `unboot`, which the same pointer speaks,
|
|
1493
|
+
since a root that may take a being out with every relation she holds may
|
|
1494
|
+
certainly ask her something. The asker is `OWNER`, the word a ward refuses at
|
|
1495
|
+
every mint, so her gate reads the device itself and never anybody at a door,
|
|
1496
|
+
and an ask gated that way has a caller at last. Without it a being could hold
|
|
1497
|
+
an ask only the daemon's own code could reach, which is an ask nobody can
|
|
1498
|
+
make.
|
|
1499
|
+
|
|
1500
|
+
It is the ward's ask and not the socket's, so nothing on this dock holds a
|
|
1501
|
+
being's answer and judges it again. `quo ask` and the socket's `being` line
|
|
1502
|
+
forward it; the api route asks it with no being named for a stranger,
|
|
1503
|
+
which is the public being asked as nobody; and a being of the tab's own
|
|
1504
|
+
ward is a subject over it, since the tab holds that ward's pointer and is
|
|
1505
|
+
its root. Each of those
|
|
1506
|
+
was once a hand-written door with an asker of its own making, an allowance
|
|
1507
|
+
copied from the spec and a save written after it, and there is one now.
|
|
1508
|
+
|
|
1509
|
+
**One process owns the harbor.** `quo serve` is the only process that ever
|
|
1510
|
+
holds the seed, the partition and the ask pointer, on every device, the Mac
|
|
1511
|
+
included. Two processes over one partition are two wards with one pk, and
|
|
1512
|
+
they diverge silently. `init` is the one exception: it creates with no
|
|
1513
|
+
daemon running, and exits. Its setup, `beings/setup.ts`, boots the desk
|
|
1514
|
+
public, the ward's home being and her doorbell, knocks the desk on her with
|
|
1515
|
+
`hello` and her name, and knocks her on her own ward's door as its owner,
|
|
1516
|
+
under `home`: a being does not know her own key, so the desk's first word
|
|
1517
|
+
names her, and she keeps the name from then on; and the root steps back,
|
|
1518
|
+
so the world is hers to place in from any device that may mint. The home
|
|
1519
|
+
being is a `User` unless `--class` says which: an organisation's ward has
|
|
1520
|
+
an org's home being, of the estate's own class, and setup places the same
|
|
1521
|
+
three things on her.
|
|
1522
|
+
|
|
1523
|
+
**Everyone else connects to it locally.** `pilot` and the bare owner asks
|
|
1524
|
+
are clients of a unix socket the daemon opens with permissions only the
|
|
1525
|
+
device's user can read, and the daemon hands the ask pointer to that
|
|
1526
|
+
socket. `side` is a client of a second local socket, for occupants: the
|
|
1527
|
+
first line names a client identity, every line after is MCP, and the model
|
|
1528
|
+
side runs in the daemon beside the avatar, so the process on the other end
|
|
1529
|
+
is a pipe to stdio and knows nothing but newlines. Reaching that socket is
|
|
1530
|
+
the proof for a local side: the daemon mints a nonce the desk honours
|
|
1531
|
+
once, and a `local` proof arriving any other way is refused. `quo/SPEC.md`
|
|
1532
|
+
allows this: the harbor hands the ask to one holder by the device's own
|
|
1533
|
+
rules, and file permissions are the device's rules. The ward stays
|
|
1534
|
+
in-process with its harbor; only the pointer crosses, across a
|
|
1535
|
+
boundary the harbor guards.
|
|
1536
|
+
|
|
1537
|
+
**Two kinds of door on one daemon, never mixed.** The unix socket is for
|
|
1538
|
+
the owner and is never behind the proxy; there is no `cli.` route. The
|
|
1539
|
+
listeners on localhost are for occupants and sit behind the proxy as the
|
|
1540
|
+
routes. The socket is the root. Owning a droplet from elsewhere is a
|
|
1541
|
+
standing: the root there invites an owner on the ward's own pk, the root
|
|
1542
|
+
here knocks for the user being with it, and she takes the droplet's ward
|
|
1543
|
+
under a name, `droplet`. Owning is one of the human's standings, so she
|
|
1544
|
+
holds it as she holds acme. From then on `quo pilot --via droplet`, and the
|
|
1545
|
+
bare owner asks with `--via droplet`, are sealed asks at the droplet's door,
|
|
1546
|
+
counted, rotated, removable: the daemon here asks on her standing
|
|
1547
|
+
in-process, as it drives an avatar for a side, and saves. The answers are
|
|
1548
|
+
the droplet's, as it answers an owner at its door. SSH is for the day the
|
|
1549
|
+
root itself must act. No key is ever carried but an invitation, and an
|
|
1550
|
+
owner at the door cannot mint another.
|
|
1551
|
+
|
|
1552
|
+
**The CLI stays thin.** Each subcommand picks a directory, builds the
|
|
1553
|
+
store, reaches the daemon, and hands off to a being or a side that already
|
|
1554
|
+
exists. Logic that appears in a subcommand was missing from a being or a
|
|
1555
|
+
side, and moves there.
|
|
1556
|
+
|
|
1557
|
+
## Envoys: a ward she does not run
|
|
1558
|
+
|
|
1559
|
+
Every account at a thing that speaks no Quo, an API, a SaaS, an MCP server
|
|
1560
|
+
run by someone else, a device behind a syscall, is a ward she does not run
|
|
1561
|
+
on a harbor she does not own, and she cannot knock at it. So a being of
|
|
1562
|
+
hers is booted that stands in its place: the envoy. Two questions decide
|
|
1563
|
+
her whole shape, and nothing else does.
|
|
1564
|
+
|
|
1565
|
+
**Does it call in?** If not, the relation is one way: the user being holds
|
|
1566
|
+
a standing at the envoy and is her occupant, and `calendly.slots` is that
|
|
1567
|
+
standing asked. If it does, a booking, a payment, a mail, the envoy also
|
|
1568
|
+
holds a standing back at whoever wants waking, and the second way is the
|
|
1569
|
+
harder one. Asking out is one method calling one API. Being called in is
|
|
1570
|
+
the same being read backwards: what arrives is that system's bytes in that
|
|
1571
|
+
system's vocabulary, signed the way that system signs, retried the way that
|
|
1572
|
+
system retries, and none of it is Quo. The harbor owns the wire, so it takes
|
|
1573
|
+
the bytes and asks the envoy `deliver` as the owner; she checks the
|
|
1574
|
+
signature, keeps the delivery id so a retry lands once, and turns it into
|
|
1575
|
+
one ask on one standing. That is the envoy's whole job, and it is where
|
|
1576
|
+
every line of code that knows the outside thing's name belongs. Whatever
|
|
1577
|
+
she wakes afterwards knows nothing of Revolut, a signature or a payload
|
|
1578
|
+
shape: it was woken through its own door, named and counted, exactly as the
|
|
1579
|
+
clock wakes it.
|
|
1580
|
+
|
|
1581
|
+
**Whose account is it?** One account is one envoy, never one per vendor,
|
|
1582
|
+
and she lives in the home of whoever owns the account: a human's Calendly
|
|
1583
|
+
in her home ward, where it moves with her; the estate's Revolut in `main`,
|
|
1584
|
+
with the humans' user beings as its occupants. Her credential is in her
|
|
1585
|
+
cells, the same as rule 1 below: what the vendor gave her, a key, a
|
|
1586
|
+
sending address, where the vendor answers, set through one ask of hers and
|
|
1587
|
+
taken out through another, and never a value in anything she answers.
|
|
1588
|
+
|
|
1589
|
+
**Who may place it is the owner or an occupant the org marked**,
|
|
1590
|
+
`credential` in that standing's notes, placed with the standing the way
|
|
1591
|
+
every other note is and never a default. That is WORLDS' rule that placing
|
|
1592
|
+
an envoy is an ask on home, shown by the gate to the org's presences and to
|
|
1593
|
+
its model,
|
|
1594
|
+
with the box never logged into for it; an org that marked nobody keeps the
|
|
1595
|
+
root's shell, which is a choice it made rather than one made for it.
|
|
1596
|
+
|
|
1597
|
+
**Where the vendor answers is the class's and not a field.** A form that
|
|
1598
|
+
could set `at` would be a form that points this box at any address and
|
|
1599
|
+
makes it fetch there carrying the account's own key, so `at` from anyone
|
|
1600
|
+
but the owner is dropped rather than refused, and what stands is the one
|
|
1601
|
+
already placed or the class's own. The class is what speaks that vendor and
|
|
1602
|
+
is the thing that knows where it lives. `CREDENTIAL` is how a class says
|
|
1603
|
+
what its credential is as fields, so a screen draws the form a human fills
|
|
1604
|
+
from the vendor's own page, and `at` cannot be among them.
|
|
1605
|
+
|
|
1606
|
+
The partition is where every standing's keys already live,
|
|
1607
|
+
wrapped under the harbor's one secret, so a token beside them is protected
|
|
1608
|
+
exactly as well and moves with her ward; the box's environment holds what
|
|
1609
|
+
the box keeps, a file's path, a route's page, and no account of anyone's.
|
|
1610
|
+
An envoy in a presence, a Calendly on the phone, is one way by
|
|
1611
|
+
construction, because nothing calls in to a phone.
|
|
1612
|
+
|
|
1613
|
+
Her class is written by someone who has never met the human. An author
|
|
1614
|
+
ships a package with the class; an estate's class file re-exports it; the
|
|
1615
|
+
root, or the user being through her own door, boots one per account. The
|
|
1616
|
+
author sees no human, no token and no partition.
|
|
1617
|
+
|
|
1618
|
+
**Writing one.** An envoy is one class in one file, and every envoy is the
|
|
1619
|
+
same seven things. This is the shape an author is held to, and the shape
|
|
1620
|
+
the base holds, `beings/envoy.ts`, so that a class on it is the vendor's
|
|
1621
|
+
surface, the vendor's signature and the vendor's vocabulary, and nothing
|
|
1622
|
+
of the discipline.
|
|
1623
|
+
|
|
1624
|
+
1. **Her credential is in her cells, and she gets it herself.** The
|
|
1625
|
+
partition already holds every standing's keys, the credentials that let
|
|
1626
|
+
anyone be her, wrapped under the harbor's one secret; a token beside
|
|
1627
|
+
them is protected exactly as well, and it moves with her ward, where a
|
|
1628
|
+
token in the box's environment stays on the box. An OAuth account she
|
|
1629
|
+
begins over `fetch` and completes when the redirect lands at her door;
|
|
1630
|
+
an API key the owner pastes into one form of hers, gated to the owner.
|
|
1631
|
+
Rotation is hers, on `fetch`; revocation is the owner asking her to
|
|
1632
|
+
forget. The one discipline is that a secret in her cells is never a
|
|
1633
|
+
value in her describe, her look, her page or her answers.
|
|
1634
|
+
2. **Her asks out are the vendor's surface, narrowed.** `slots`, `book`,
|
|
1635
|
+
`cancel`: one method, one call to the API, over plain fetch and no SDK,
|
|
1636
|
+
the vendor's answer reshaped into I-JSON values and nothing passed
|
|
1637
|
+
through raw. Each is gated to her occupants, and what an occupant may
|
|
1638
|
+
ask is her gate alone.
|
|
1639
|
+
3. **`deliver` is her one ask in, gated to the owner.** The daemon asks it
|
|
1640
|
+
with the raw body as a string and the headers as an object, and nothing
|
|
1641
|
+
else, since the vendor signed the bytes and a reparsed body fails the
|
|
1642
|
+
check. She verifies the signature, refuses what fails with an error
|
|
1643
|
+
object, keeps the delivery id and answers `{ ok }` at once for one she
|
|
1644
|
+
has seen, so a retry lands once. Then she decodes in the vendor's
|
|
1645
|
+
vocabulary and finds who the delivery is about.
|
|
1646
|
+
4. **Her cells are what the vendor told her**, values only, all safe in the
|
|
1647
|
+
partition and all rebuildable from the vendor: the map from the vendor's
|
|
1648
|
+
id, an order, an invitee, a customer, to the standing it is about,
|
|
1649
|
+
written when she asked out and read when she is called in; the delivery
|
|
1650
|
+
ids she has seen, bounded and oldest out; whatever she caches of the
|
|
1651
|
+
surface.
|
|
1652
|
+
5. **Her way back is a standing placed like any other.** Whoever wants
|
|
1653
|
+
waking holds the way in, an occupant of hers, and hands her the way
|
|
1654
|
+
back in the same knock, so she holds `to:<id>` for each, `<id>` being
|
|
1655
|
+
her own id for that occupant. A delivery is one ask on one of those,
|
|
1656
|
+
`booked`, `paid`, `arrived`, in her own words, and the being woken
|
|
1657
|
+
cannot tell it from a clock. A being's ids are one namespace, standings
|
|
1658
|
+
and occupants alike, so the occupant who holds her as `booking` knows
|
|
1659
|
+
her back by another id, and an invite on an id already held answers
|
|
1660
|
+
null.
|
|
1661
|
+
6. **She answers the vendor before she wakes anyone.** `{ ok }` goes back
|
|
1662
|
+
the moment the delivery is kept, and the wake goes out after; the ward
|
|
1663
|
+
saves on return and the vendor retries on anything but a quick 200. A
|
|
1664
|
+
wake that meets silence is written down in her cells and not retried
|
|
1665
|
+
by her, since silence is never retried by a side.
|
|
1666
|
+
7. **She is proven with the vendor stubbed and never reached.** On the
|
|
1667
|
+
memory harbor, her dock handed a stub where the platform would be,
|
|
1668
|
+
every ask out against a recorded answer, every delivery from a recorded
|
|
1669
|
+
body and signature, and the estate's suite never opens a socket to the
|
|
1670
|
+
vendor. Live, she is proven on the bench, and the sandbox host is what
|
|
1671
|
+
the bench sets.
|
|
1672
|
+
|
|
1673
|
+
What she never does: hold a standing at a third party on an occupant's
|
|
1674
|
+
behalf, since an occupant holds one standing; show a token; parse a body
|
|
1675
|
+
before the signature is checked; retry a wake; or know who is behind the
|
|
1676
|
+
occupant that asked her.
|
|
1677
|
+
|
|
1678
|
+
**The base.** `Envoy` in `beings/envoy.ts` is the seven things as one
|
|
1679
|
+
class, and a vendor's envoy extends it. This is its whole surface, and a
|
|
1680
|
+
class on it is written from this block and nothing else:
|
|
1681
|
+
|
|
1682
|
+
```ts
|
|
1683
|
+
import { Envoy, owner, occupant, type Delivery, type Wake } from '@nervur-org/dock';
|
|
1684
|
+
import type { Asker, JsonObject } from '@nervur-org/nervur';
|
|
1685
|
+
|
|
1686
|
+
export class Booking extends Envoy {
|
|
1687
|
+
// cells and asks are statics, as on every being. Spread the base's asks,
|
|
1688
|
+
// credential and forget for the owner; spread Envoy.IN, deliver, only if
|
|
1689
|
+
// the vendor calls in; then your asks out, each { description, input, for }.
|
|
1690
|
+
static override cells = { ...Envoy.cells, mine: 0 };
|
|
1691
|
+
static override asks = {
|
|
1692
|
+
...Envoy.asks,
|
|
1693
|
+
...Envoy.IN,
|
|
1694
|
+
book: { description: 'book a slot', input: { type: 'object', properties: { when: { type: 'string' } }, required: ['when'] }, for: occupant },
|
|
1695
|
+
prices: { description: 'the price list', input: { type: 'object' }, for: owner },
|
|
1696
|
+
};
|
|
1697
|
+
|
|
1698
|
+
// 1. What the owner's credential must hold, as flat fields: a reason, or null.
|
|
1699
|
+
// The owner asks `credential` with { key, at, secret }; you read it as this.held.
|
|
1700
|
+
protected override check(c: JsonObject): string | null {
|
|
1701
|
+
return typeof c.key === 'string' && typeof c.at === 'string' && typeof c.secret === 'string' ? null : 'key, at and secret';
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// 2. An ask out: one fetch through `out`, from `at` in the credential with `key`
|
|
1705
|
+
// as the bearer, the answer as { ok, status, body }; reshape it, pass nothing raw.
|
|
1706
|
+
// A method's arguments are (args, asker); asker.id is who holds the standing.
|
|
1707
|
+
async book(args: JsonObject, asker: Asker): Promise<JsonObject> {
|
|
1708
|
+
const h = await this.out('/book', { body: { when: String(args.when) } });
|
|
1709
|
+
if (!h.ok) return this.refused(h);
|
|
1710
|
+
// 4. remember the vendor's id for whom it was, with what the wake will need
|
|
1711
|
+
this.remember(String(h.body.id), asker.id ?? '', { when: String(h.body.when) });
|
|
1712
|
+
return { booked: h.body.id };
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
// 3. The vendor's signature over the raw body as sent. `hmac(secret, text)` is
|
|
1716
|
+
// hex SHA-256; `fresh(ts)` takes seconds or milliseconds, five minutes.
|
|
1717
|
+
protected override async verify(body: string, headers: Record<string, string>): Promise<boolean> {
|
|
1718
|
+
const ts = headers['x-vendor-timestamp'] ?? '';
|
|
1719
|
+
return this.fresh(ts) && headers['x-vendor-signature'] === `sha256=${await this.hmac(String(this.held?.secret), `${ts}.${body}`)}`;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// The vendor's vocabulary, from the raw body, after the signature held:
|
|
1723
|
+
// its id for the delivery, its kind, and which vendor id it is about.
|
|
1724
|
+
protected override decode(body: string): Delivery | { error: string } {
|
|
1725
|
+
const ev = JSON.parse(body) as { event: string; booking_id: string };
|
|
1726
|
+
return { id: ev.booking_id, kind: ev.event, about: ev.booking_id, data: {} };
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
// 5. What a delivery wakes: { method, args } on the way back `to:<by>` for whom
|
|
1730
|
+
// the vendor id was remembered, `about` being what remember kept; or null.
|
|
1731
|
+
protected override wake(d: Delivery, about?: JsonObject & { by: string }): Wake | null {
|
|
1732
|
+
return d.kind === 'booking.confirmed' ? { method: 'confirmed', args: { id: d.id, when: about?.when ?? null } } : null;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
```
|
|
1736
|
+
|
|
1737
|
+
What the base does around that, and the class never writes: `deliver`,
|
|
1738
|
+
the owner's one ask in, checks `verify` before anything is parsed, keeps
|
|
1739
|
+
the kind and id bounded so a retry answers `{ ok }` and wakes nobody
|
|
1740
|
+
twice, answers `{ ok }` and only then wakes, after the answer and not
|
|
1741
|
+
before, so a suite yields a turn before it looks; the wake goes on the
|
|
1742
|
+
standing `to:<by>`, which the occupant who wants waking hands her at the
|
|
1743
|
+
knock, and what could not be woken, no way back, silence, a word, or a
|
|
1744
|
+
wake shaped wrong, is in `unwoken` in her cells and never retried. An
|
|
1745
|
+
empty body at `deliver` is a redirect landing, handed to `landed(query,
|
|
1746
|
+
headers)`, for a class that begins its account over OAuth. A gate is
|
|
1747
|
+
`(occupant, asker) => boolean` and nothing more, and an ask out is asked
|
|
1748
|
+
with `(args, asker)`. `out` with no options is a GET, a query written in
|
|
1749
|
+
the path passes through, the bearer crosses as `Authorization: Bearer`,
|
|
1750
|
+
and `body` in what it heard is the vendor's JSON already parsed, `{}`
|
|
1751
|
+
when there was none. `test/envoy.test.ts`
|
|
1752
|
+
runs the seven against two fixtures on the base, `test/envoys.ts`, a mail
|
|
1753
|
+
account one way and a payment account both ways, with the vendors
|
|
1754
|
+
stubbed on loopback and never reached.
|
|
1755
|
+
|
|
1756
|
+
**What is not an envoy: a faculty.** An envoy stands in for a ward she does
|
|
1757
|
+
not run, on a harbor she does not own, that speaks no Quo: an account,
|
|
1758
|
+
somewhere else, with a credential, a schema and a signature. What this
|
|
1759
|
+
device has is nobody's account and lives nowhere else: its GPS, its
|
|
1760
|
+
notification center, its timer, its push service keys. Those are faculties,
|
|
1761
|
+
lent in the dock, and no boundary being stands in front of them. The Clock
|
|
1762
|
+
is the case that makes this sharp. She looks like an event source and is not
|
|
1763
|
+
one: the moment reaches her as a value from a faculty of her own device,
|
|
1764
|
+
with no vocabulary of anyone else's, no credential, no schema and no
|
|
1765
|
+
signature. She is an ordinary being doing arithmetic on a number she was
|
|
1766
|
+
handed. So the whole test is where the thing lives: on this device, a
|
|
1767
|
+
faculty; on any other harbor, an envoy. It decides every case that will
|
|
1768
|
+
come.
|
|
1769
|
+
|
|
1770
|
+
**A faculty arrives in the stance; an event arrives as an ask.** Those are
|
|
1771
|
+
two doors and the second one is easy to get wrong. What a terrain can do is
|
|
1772
|
+
in the dock a being is handed at her birth, and she calls it when she is
|
|
1773
|
+
asked: that is pull, and the ward's own ground is six of the same kind,
|
|
1774
|
+
every one of them called by the ward and none of them calling in. An event
|
|
1775
|
+
is the other direction, and it may not arrive as a callback into a being,
|
|
1776
|
+
because the harbor saves a partition when an ask returns and a callback that
|
|
1777
|
+
fires at three in the morning writes into memory the next restart throws
|
|
1778
|
+
away. So whatever holds the subscription, a socket, a plugin, an HTTP
|
|
1779
|
+
route, the timer, turns what it heard into one ask at her door, as the
|
|
1780
|
+
occupant of its own name, through the way back the ward gave the faculty
|
|
1781
|
+
when it bound her. That is why the timer asks `tick` rather than pushing
|
|
1782
|
+
the moment in, and it is why the clock's fire log survives a restart at
|
|
1783
|
+
all. The being woken at the end of it cannot tell a timer from a webhook
|
|
1784
|
+
from a syscall, and must not be able to.
|
|
1785
|
+
|
|
1786
|
+
So a source of wakes is three parts and only one of them is ever an envoy:
|
|
1787
|
+
the envoy at the edge, foreign in both directions; the register of who wants
|
|
1788
|
+
waking, on what, on which standing; and the waking itself, which is splitting
|
|
1789
|
+
an ask at its last dot, sending it on a standing, and writing down the word
|
|
1790
|
+
that came back. The clock is the lower two, triggered by a faculty. A webhook
|
|
1791
|
+
is all three. The lower two are the same in both, and they rise into this
|
|
1792
|
+
folder the day a second thing needs them, shaped by both rather than guessed
|
|
1793
|
+
from the clock alone.
|
|
1794
|
+
|
|
1795
|
+
## Scenes
|
|
1796
|
+
|
|
1797
|
+
The estate is proven by scenes, not features. Each one below uses nothing
|
|
1798
|
+
but what is written above, and each names the one place a convention had
|
|
1799
|
+
to be decided.
|
|
1800
|
+
|
|
1801
|
+
1. **A link on WhatsApp.** An acme employee's being minted an id for you
|
|
1802
|
+
and got an invitation; the link is that value next to a reach hint. It
|
|
1803
|
+
is single-use and unowned: whoever knocks first is bound, so links are
|
|
1804
|
+
minted per person. The hint is a harbor convention.
|
|
1805
|
+
2. **You have a world.** Your root does one owner knock: a being of yours,
|
|
1806
|
+
existing or booted on the spot, knocks with the invitation, takes acme,
|
|
1807
|
+
and puts its own invitation in the args so acme can push back. Two
|
|
1808
|
+
invitations, two relations. You never open acme's page.
|
|
1809
|
+
3. **You have no world.** The link opens a tab; the bundle boots a harbor,
|
|
1810
|
+
a ward and an avatar on a seed minted into the browser's store; the
|
|
1811
|
+
avatar knocks and takes acme; the tab's screen is her side. A world in a
|
|
1812
|
+
rented room: yours, on your seed, booted by acme's code.
|
|
1813
|
+
4. **Urgency now, home later.** Book in the tab. At home, the tab exports
|
|
1814
|
+
seed and partition and deletes its copy in one gesture; your harbor
|
|
1815
|
+
boots the same seed, the same pk, and acme's rendezvous rebinds it on the
|
|
1816
|
+
next connect. Migration is three moves and nobody is told.
|
|
1817
|
+
5. **A model books for you.** Claude is an occupant of your user being
|
|
1818
|
+
through an avatar; your gate shows it `acme.book` and `calendar.free`;
|
|
1819
|
+
your being forwards on her own standings. Acme sees your id and never
|
|
1820
|
+
learns a model was involved. Remote Claude is an occupant and nothing
|
|
1821
|
+
else; local Claude may also be the owner and the developer, kept apart.
|
|
1822
|
+
6. **An agent woken by events.** Your user being asks the agent's avatar;
|
|
1823
|
+
the avatar starts a run in its folder and acknowledges; the run asks back
|
|
1824
|
+
with the result. One run at a time. What it may do is the gate; how it
|
|
1825
|
+
behaves is its constitution.
|
|
1826
|
+
7. **A phone in a tunnel.** The socket drops, the rendezvous forgets the
|
|
1827
|
+
reach, asks to the phone are unreached, and nothing else moves. Seed,
|
|
1828
|
+
partition and keys are on the phone. Reconnect rebinds the same pk under
|
|
1829
|
+
the same keys and count. Signal is lost, identity never.
|
|
1830
|
+
8. **A ward on another device, piloted from here.** The root there invites
|
|
1831
|
+
an owner on the ward; your being knocks; every owner ask from now on is a
|
|
1832
|
+
sealed ask on that standing, counted, rotated, removable. No key is ever
|
|
1833
|
+
carried but an invitation, and an owner at the door cannot mint another.
|
|
1834
|
+
|
|
1835
|
+
## Generality
|
|
1836
|
+
|
|
1837
|
+
Eight general pieces exist under `packages/dock/`, and everything concrete is
|
|
1838
|
+
an implementation of one of them. An estate implements none of them: it holds
|
|
1839
|
+
class bodies and placement, and reaches every piece through the dock.
|
|
1840
|
+
|
|
1841
|
+
| piece | one interface for | implementations |
|
|
1842
|
+
| ------------ | ------------------------------------------ | ------------------------------------------------ |
|
|
1843
|
+
| harbor core | partitions, directory, own doors, forward | one, on every terrain |
|
|
1844
|
+
| reach | carrying bytes to a pk | request, socket |
|
|
1845
|
+
| store | keeping partitions by seed | memory, disk, IndexedDB, edge storage |
|
|
1846
|
+
| side | an avatar speaking to someone outside | screen, MCP server, runner, agent |
|
|
1847
|
+
| way in | a being who says who a proof is for | the box's `local` faculty; a world's own post |
|
|
1848
|
+
| faculty | one thing a device lends its beings | location, notifications, push, timer; per terrain|
|
|
1849
|
+
| envoy | an account elsewhere, as a being of hers | a vendor's account; a world's way in by mail |
|
|
1850
|
+
| carrier | a being showing her standings as her own | the user being; any estate being that extends it |
|
|
1851
|
+
| look | how a being is presented, as values | read by the screen and the model side |
|
|
1852
|
+
|
|
1853
|
+
Two rules keep them general:
|
|
1854
|
+
|
|
1855
|
+
1. A piece exists only with two users. No interface is written for one
|
|
1856
|
+
implementation; what one terrain alone needs stays inside that terrain.
|
|
1857
|
+
2. Every interface has one suite, run against every implementation. A new
|
|
1858
|
+
implementation is accepted when the shared suite passes untouched.
|
|
1859
|
+
|
|
1860
|
+
Harbor core, reach and store are the library's, decided by `quo/SPEC.md`
|
|
1861
|
+
and written under `packages/quo/src`, because a harbor is a word of the
|
|
1862
|
+
protocol and not a convention of ours. What is the dock's of those three is
|
|
1863
|
+
each terrain's store, loader, lease and listener, which is a device's
|
|
1864
|
+
business and no ward's. The other six pieces are dock conventions and are
|
|
1865
|
+
decided here.
|
|
1866
|
+
|
|
1867
|
+
The export map is the third rule, and it is the estate's surface, not a
|
|
1868
|
+
directory of the folders. A subpath stands in `package.json` because an
|
|
1869
|
+
estate or the app imports it by that name; the folders reach each other by
|
|
1870
|
+
relative path and need no export to do it. So a subpath nobody outside the
|
|
1871
|
+
package imports is not a smaller thing than an interface with one
|
|
1872
|
+
implementation, it is the same fault at the package boundary, and it comes
|
|
1873
|
+
off the map. What an adopter needs is added the day an estate needs it, and
|
|
1874
|
+
never against a reader we imagine.
|
|
1875
|
+
|
|
1876
|
+
## Invariants
|
|
1877
|
+
|
|
1878
|
+
1. An occupant holds one standing: its avatar's standing to the user being.
|
|
1879
|
+
It never holds a standing at a third party.
|
|
1880
|
+
2. An occupant sees keys, invitations, pks and partitions never. What
|
|
1881
|
+
crosses a side is asks, args, answers and the three words.
|
|
1882
|
+
3. The gate is the permission model. There is no second list.
|
|
1883
|
+
4. Credentials live for one call and are never stored on a Quo side.
|
|
1884
|
+
Revocation is remove.
|
|
1885
|
+
5. The root is held by the device's own rule and no route of a world
|
|
1886
|
+
exposes it: the ask pointer on a daemon's local socket, and on the edge,
|
|
1887
|
+
where there is no local process, a platform secret. Every other owner is
|
|
1888
|
+
an occupant of the ward, minted by the root alone.
|
|
1889
|
+
6. The owner creates and places. The work goes through an avatar.
|
|
1890
|
+
7. One avatar per client identity. Reconnect is not re-invite.
|
|
1891
|
+
8. Silence is never retried by a side.
|
|
1892
|
+
9. A faculty's credentials are in her cells, and her cells are in the dock
|
|
1893
|
+
ward, which never moves. Nothing that migrates carries a credential.
|
|
1894
|
+
10. Nothing outside a package reaches inside it. `packages/dock/` imports
|
|
1895
|
+
the library by its four exports and never a path under
|
|
1896
|
+
`packages/quo/src`; an estate and an app import the two packages by
|
|
1897
|
+
their exported names, the root and the subpaths the export map offers,
|
|
1898
|
+
and never a file that is not one. A folder of a package reaches its
|
|
1899
|
+
sibling folders by relative path, which is inside and not a breach.
|
|
1900
|
+
11. Nothing here changes a harbor, a ward or a being.
|
|
1901
|
+
12. An estate needs nothing the dock does not give it. Its folder, copied
|
|
1902
|
+
into an empty repository with one install, boots.
|
|
1903
|
+
13. Nothing reaches a being but an ask at her door, from someone she
|
|
1904
|
+
invited, under the id she minted. No callback into her frame, no static
|
|
1905
|
+
set on her class from outside, no asker a device made up.
|
|
1906
|
+
14. What a box can do is beings. A device that lends anything boots a dock
|
|
1907
|
+
ward, and everything in it is reached by `lend` and held as a standing.
|
|
1908
|
+
|
|
1909
|
+
## Where the dock stands
|
|
1910
|
+
|
|
1911
|
+
The one place under `packages/dock/` where a gap between these documents and
|
|
1912
|
+
the folders is named. Each line is a debt to close, not a note to keep.
|
|
1913
|
+
|
|
1914
|
+
- **The dock holds no org base.** An estate's org being extends `Being`
|
|
1915
|
+
and writes its members, their ways back and its telling by hand; the
|
|
1916
|
+
org base rises the day a second estate writes the same.
|
|
1917
|
+
- **A carried describe is stale until the next ask through the standing.**
|
|
1918
|
+
After a far being's gate changes, a carrier's describe still lists the
|
|
1919
|
+
old asks once, and the next lists them right. Whether a carried
|
|
1920
|
+
blueprint refreshes on describe or only on an ask is a decision for the
|
|
1921
|
+
carrying chapter, not yet taken.
|
|
1922
|
+
- **The bell keeps a token of a kind the box never rings.** `bell` takes
|
|
1923
|
+
`{ kind: 'test' }` and refuses it only at ring time; whether the door
|
|
1924
|
+
refuses a kind no ring is lent for is a decision for the doorbell
|
|
1925
|
+
chapter, not yet taken.
|
|
1926
|
+
|
|
1927
|
+
Two rules hold:
|
|
1928
|
+
|
|
1929
|
+
- **The ward and the being are asserted on the memory harbor, and every
|
|
1930
|
+
harbor by the conformance suite.** The first keeps the network from
|
|
1931
|
+
hiding a fault in the words: the memory harbor records its wire and
|
|
1932
|
+
stands in for weather, so a door test that fails there fails at the door.
|
|
1933
|
+
The second is how a harbor on any terrain is accepted, untouched, and not
|
|
1934
|
+
before. Neither binds an adopter; they bind this bench.
|
|
1935
|
+
- **One language, TypeScript, until 1.0.0.** A second kit is a second thing to
|
|
1936
|
+
keep in step with every change, and the shelf under `packages/quo/protocol/`
|
|
1937
|
+
are the hand that will be extended when the time comes.
|
|
1938
|
+
|
|
1939
|
+
## Glossary
|
|
1940
|
+
|
|
1941
|
+
Every word of this document and of its sides is in `papers/GLOSSARY.md`,
|
|
1942
|
+
beside the spec, with every other word above it.
|