@nervur-org/dock 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/quo-api.md DELETED
@@ -1,196 +0,0 @@
1
- # The api route
2
-
3
- This is the fourth rendering of one describe. The screen speaks a being's
4
- describe as forms, the model side speaks it as tools, the CLI speaks it as
5
- lines, and this route speaks it as plain HTTP and JSON. It is one route of
6
- the daemon described in `papers/quo-dock.md`, which owns the routes, the
7
- grants, the avatar and the shared invariants; this document assumes all of
8
- that and adds only what the route does. `papers/WORLDS.md` decides what
9
- every route is held to: a route says the grammar a caller speaks and never
10
- who the caller is, and the same credential is honoured on every route.
11
-
12
- ## What it is for
13
-
14
- A route hears every rung. A request carrying nothing is the stranger every
15
- route knows: an ask at the named ward's public being as `{}`, which is what
16
- every stranger is at every door, and what a browser fetch, a page
17
- rendering server side, a crawler and a tab on another origin are. A
18
- request carrying a bearer the box granted is an occupant: the identity's
19
- avatar in the world the grant names, asked through her one standing at the
20
- world's being. What a script sees with a bearer is what a model sees with
21
- the same bearer as tools and a tab sees as forms, because all three are
22
- one avatar's describe for one identity, and the gate is one decision for
23
- describe and for dispatch. The route judges nothing and names nobody: the
24
- public being answers a stranger by her own gate, the world's being answers
25
- an occupant by hers, and the route carries the answer.
26
-
27
- A grant is the box's and may die at any moment: a box wiped, an occupant
28
- removed at the world's being, thirty days without a refresh. Nothing of
29
- the world dies with it, since the avatar and her standing are the ward's;
30
- only the box's memory of which token names her is gone, and the holder
31
- trades again on a fresh invitation and lands on the same avatar. So a
32
- bearer is held by something with a human near it: a model client whose
33
- human clicks the allow page again, a script whose root mints again, a
34
- SaaS whose operator signs up again as at any vendor. A program of the
35
- org's that must run without a human is not a caller of this route. It is
36
- a harbor of the org with a ward and a standing, or an agent the daemon
37
- boots on the box, and either speaks Quo on keys that do not expire.
38
-
39
- A world is a ward, so the route names one. Every ward `init` made holds a
40
- desk, who is public for the first hello and answers a stranger nothing
41
- else, so a world's public reads live in a ward of their own, and the route is what
42
- makes a second ward reachable by name from outside: the corpus of a court
43
- watcher, a catalogue, a board. An occupant's world is the one her
44
- invitation named, and a bearer on any other world is refused.
45
-
46
- ## The mapping
47
-
48
- `packages/dock/api/route.ts` is the route, `Request` in and `Response` out
49
- and naming no terrain: the daemon mounts it under `/api` and the proxy maps
50
- it from the api. hostname, and the worker at the edge mounts the same file
51
- under its harbor's prefix. It asks its harbor for the wards it holds and for
52
- which of them a caller who names none is at, and for nothing else.
53
-
54
- | request | Quo |
55
- | ------------------------------------ | -------------------------------------------------------------------- |
56
- | `POST /api` `{identity, invitation}` | the trade: her avatar knocks with it, and a grant is issued |
57
- | `POST /api` `{refresh}` | the next grant; the old refresh token dies |
58
- | `GET /api` | the describe of the default ward, as `GET /api/<that ward>` |
59
- | `GET /api/<ward>` | the describe: the public being for nobody, the avatar under a bearer |
60
- | `GET /api/<ward>/<ask>?k=v` | that ask, the query as args, every value a string |
61
- | `POST /api/<ward>/<ask>` JSON body | that ask, the object as args |
62
- | `POST /api/<ward>/<being>/deliver` | a vendor's delivery, the raw body and the headers to that envoy |
63
- | `GET /api/<ward>/<being>/deliver` | the same with an empty body: a redirect lands here, query in headers |
64
- | `OPTIONS` | the preflight, any origin, `authorization` allowed |
65
-
66
- What a caller leaves unsaid follows `papers/WORLDS.md`, and none of it is
67
- a default the route picks. A credential names an identity in a world, so an
68
- occupant's ward is her grant's and a bearer on any other ward is refused.
69
- A stranger carries nothing that names a world, so a stranger who names no
70
- ward is at the ward the terrain says it is at, the one the box hosts or the
71
- one it marks, and `GET /api` is what a tab at the web
72
- route's root sees and a model's first tool list. A being is never named,
73
- because a caller
74
- does not choose who answers: naming a ward is naming a door, and behind it
75
- the ward decides, the public being as nobody for a stranger and the
76
- identity's avatar for an occupant. The one path that names a being is the
77
- delivery, and there it is not a choice either: the envoy claimed that path
78
- at her birth, and a delivery with no envoy named has nowhere to land and
79
- is 404, never the public being.
80
-
81
- The trade is the allow page of the model side without the page. Nobody at
82
- a page vouches for a program, so a trade is a knock that spends: the
83
- invitation is the one the root minted for that identity on the world's
84
- being with `nervur invite`, sent as a line, and the avatar knocks with it
85
- inside the trade. An identity already in is refused with `already in` and
86
- refreshes instead, or is removed at the world's being and invited again,
87
- and never re-enters on a spent invitation. What comes back is the box's
88
- grant, `dock/grants.ts`: an access token for an hour, a refresh token for
89
- thirty days, the same grant the OAuth dance issues, honoured on the model
90
- route too.
91
-
92
- What she says crosses as the model side crosses it:
93
-
94
- | she answered | the client gets |
95
- | ------------------------------ | ------------------------------------------------------ |
96
- | an object | 200 with that JSON |
97
- | `{ error }` | 400 with that JSON |
98
- | silence, a throw, a wait spent | 503 `{ error: 'silence' }`: the work may have happened |
99
- | an ask not hers for this asker | 400 `{ error: 'unknown ask' }`, her own answer |
100
-
101
- For a stranger, a ward with nobody at the door is 503 beside them, and
102
- not a 404 of its own: on the wire a stranger who asks a ward that has no
103
- public being hears one silence, and a device speaking for strangers says
104
- what the door would. A stranger learns which world exists here and nothing
105
- else about it. For an occupant the ward's words cross by name, `unreached`
106
- as 503 `{ error: 'unreached' }`, and `removed` is 401 `{ error: 'removed'
107
- }` with the grant forgotten at the box, so a script the world's being
108
- removed starts over with a fresh invitation, exactly as a model client
109
- does.
110
-
111
- And what the route says on its own: 404 for a ward not here and for a path
112
- that is not one of the shapes, 400 for a body that is not a JSON object,
113
- 400 `{ error: 'already in' }` for a trade on an identity that holds a live
114
- standing, 401 `{ error: 'unauthorized' }` for a bearer nobody issued or one
115
- that died, 403 `{ error: 'not your world' }` for a bearer on a world the
116
- grant does not name, 413 for a body past a megabyte, 405 for any other
117
- method. Every answer is JSON and open to any origin: the route carries
118
- nothing a stranger can use that she did not choose to say, and a bearer is
119
- the caller's to keep.
120
-
121
- The route holds none of the judgement. For a stranger it asks the ward the
122
- owner's `ask` with no being named, which is the public being asked as
123
- nobody, and the ward bounds the wait, names the three choices and saves
124
- before the pointer returns: a public being may write while answering, a
125
- tally, a board, and what she wrote is hers to keep. For an occupant it
126
- asks her avatar, `tools` for the describe and `call` for an ask, the two
127
- things every side asks of her, and saves the ward after.
128
-
129
- ## The delivery
130
-
131
- A vendor's webhook is not a stranger's ask and does not land as `{}`, and
132
- it is not an occupant's ask either: it is the owner asking the envoy
133
- `deliver`, as `papers/quo-dock.md` says of envoys, and the route carries
134
- the bytes and nothing of the judgement. `POST /api/<ward>/<being>/deliver`
135
- is its own row and not an ask: the body is read as a string and never
136
- parsed here, since the vendor signed the bytes and a reparsed body fails
137
- her check, and the rule that a body is a JSON object does not apply. The
138
- route asks the ward's owner pointer for that being's `deliver` with `{
139
- body, headers }`, and answers what she answers: `{ ok }` as 200, her error
140
- object as 400, silence as 503. No bearer is read on this path, because the
141
- rung is not the caller's to prove: the signature is hers to check, and
142
- what fails it is refused by her and never by the route.
143
-
144
- A `GET` on the same path is the same ask with an empty body, and is where
145
- an OAuth redirect lands when an envoy begins an account, the code in the
146
- query. The query crosses inside the headers object under the name
147
- `:query`, which no header on the wire can carry, so her one ask in stays
148
- one and an author writes no second door for redirects. What she answers
149
- crosses as JSON like any delivery.
150
-
151
- **And a browser gets a page.** This is a road for programs with exactly one
152
- human arrival, and it is somebody who has just paid for something: a vendor
153
- returns her by opening this URL in her tab, so without a page she ends a
154
- payment reading what an envoy says to a machine. A `GET` whose `accept`
155
- names HTML is answered with the human side's landing page, saying which of
156
- the two happened and offering the world's own page, since a tab a vendor
157
- opened has no history to go back through. What the envoy answered is never
158
- on it. Everything else is unchanged: a vendor asking for JSON is answered
159
- in JSON, a `GET` that says nothing about what it wants is answered in JSON,
160
- and the page carries no cross-origin header, since a page is for the
161
- browser that was sent here and never for a script somewhere else.
162
-
163
- On its own the route says 404 for a ward not here, for a being not hers
164
- and for a third segment that is not `deliver`, 413 past a megabyte and 405
165
- for any method but `POST` and `GET`. A vendor that retries on anything but a quick
166
- 200 lands once, because she keeps the delivery id and answers `{ ok }` for
167
- one she has seen before she wakes anyone.
168
-
169
- ## What is not here
170
-
171
- A ward's delivery reaching more than one envoy, and a vendor that cannot
172
- sign what it sends. Both stand when a first user does.
173
-
174
- ## The proof
175
-
176
- `packages/dock/test/api.test.ts`, over a real daemon on loopback. A
177
- stranger: a world with a public board described and asked by anyone, args
178
- by query and by body, what she wrote kept across a reboot, no ward named
179
- being the device's own, and every refusal. An occupant: an invitation
180
- traded for a grant, the describe under the bearer being the avatar's own
181
- with no ward named and with hers, an ask through her, another world
182
- refused, refresh rotating, removal at the world's being ending the grant,
183
- and the identity let in again on a fresh invitation. One identity, two
184
- grammars: a bearer the OAuth dance issued honoured here and its refresh
185
- rotating at the trade, a bearer the trade issued opening an MCP session,
186
- and the tool list being this route's describe with her look and page
187
- left out. The delivery: signed bytes landing on an envoy's `deliver` as
188
- the owner, a good signature kept and woken once, a retry landing once, a
189
- bad one her refusal as 400, no bearer read, what she kept outliving the
190
- daemon, and the route's own 404, 405 and 413.
191
-
192
- The same route on a second terrain is
193
- `packages/dock/test/terrain/edge.test.ts`, behind `npm run deep:dock`: the
194
- trade at a worker's `/api`, the ask through her avatar, and the answer
195
- compared against a Node daemon doing the same thing, because a rendering of
196
- one describe must not differ by terrain.
package/quo-app-bells.md DELETED
@@ -1,280 +0,0 @@
1
- # The bells of a Quo app
2
-
3
- A ward on a dialer is reachable only while it holds a line. A closed tab, a
4
- phone asleep, an app in the background: the world has something to say and
5
- nobody is listening. The bell is how such a device is reached, the one way
6
- its platform allows, by waking it.
7
-
8
- Everything here is four pieces, and none of them is any publisher's. An
9
- adopter who ships their own Quo app under their own name runs the same four
10
- with their own keys, changes nothing in the library and nothing in the dock,
11
- and is not asking anyone's permission.
12
-
13
- ## Why a service exists at all
14
-
15
- Apple and Google accept a push only when it is signed with the key of the
16
- team that published the app. A world's box holds no such key and never
17
- will, because the key belongs to whoever publishes the app and to nobody
18
- else. So a world hands the ring on to a service that publisher runs.
19
-
20
- This is the one place in Quo where a world does not reach a person
21
- directly, and it is not a design choice. It is the platform's rule, and the
22
- whole of this paper is about making that hop carry as little as it possibly
23
- can.
24
-
25
- Web Push needs no service. A world mints its own keypair and speaks to the
26
- browser's push service itself, so tabs and desks are free of this entirely.
27
-
28
- ## The porter
29
-
30
- A bell is a ward, and its public being is the porter. A phone nobody knows
31
- arrives at her as `{}`, which is the one place in Quo a stranger is
32
- answered by design.
33
-
34
- She does one thing. A device says hello, she brings a doorbell into being
35
- for it, and hands back the way in. The device knocks, and from then on it is
36
- a named occupant with a heir and a count, which is where anything that must
37
- happen once belongs. Her own answer is safe to repeat, as a public being's
38
- must be: saying hello twice is two doorbells and no harm.
39
-
40
- A stranger who keeps knocking is the harbor's, never hers and never the
41
- door's. A bell needs no attestation, no account and no door of its own
42
- invention, and it should have none.
43
-
44
- ## The doorbell
45
-
46
- One being per device. Two things live in it.
47
-
48
- **The platform token**, in her cells, written by the device and read by
49
- nobody. It leaves for nowhere. No world ever sees it, so no world can copy
50
- it, hand it on, or keep ringing after it is told to stop. When the platform
51
- hands the phone a new token, the phone writes it here once and not one world
52
- is told anything.
53
-
54
- **One occupant per allowed world.** The device asks the doorbell for a way
55
- in, hands it to a world it has joined, and that world knocks and holds an
56
- ordinary standing. That row is the whole of the permission, and it is held
57
- at the bell, by neither the world that rings nor the publisher who holds the
58
- key.
59
-
60
- So the pair is recorded independently: this device, that world. A device
61
- with presence in many worlds has many rows here, and every one of them
62
- stands or falls on its own.
63
-
64
- ## Allowing and disallowing
65
-
66
- **Allowing is an invitation the device mints.** A world can only ring a
67
- phone that chose it. There is no list of approved worlds anywhere, and there
68
- must not be: gating it would make the publisher the gatekeeper of every
69
- world's relation with its own member, which is the thing Quo exists to
70
- abolish, and it buys nothing, because the consent is already there in the
71
- act of handing over the invitation.
72
-
73
- **Disallowing is `remove`.** One occupant goes. The heir is forgotten and
74
- its last keys are kept apart, bounded, so that world hears `removed` once at
75
- the door and nobody else hears a thing. Her other worlds are untouched. It
76
- takes effect at the bell, so it does not depend on the removed world being
77
- honest about it, and there is nothing it could have kept that would still
78
- work.
79
-
80
- ## What a ring carries
81
-
82
- A world rings its standing. It names no device, no token and no world,
83
- because the doorbell reads both ends from the standing the ask arrived on.
84
- There is nothing in a ring to forge.
85
-
86
- It carries one thing: a payload sealed to the device's own key. Not to the
87
- bell's, not to the world's, not to anyone else's. The bell passes ciphertext
88
- it could not read if it tried, the platform carries ciphertext, and on the
89
- device a notification service extension unseals it before the banner is
90
- drawn. What the person sees is an ordinary, immediate notification.
91
-
92
- So the claim is not that the doorbell carries nothing. It is that the
93
- doorbell carries nothing anyone but her device can read, which is the
94
- stronger promise, because the weaker one bought its purity with the banner
95
- itself: a notification composed after the app wakes is one the platform may
96
- delay at its own discretion and one a person who force quit the app never
97
- sees at all.
98
-
99
- The payload is bounded by what the platform will carry, four kilobytes on
100
- APNs, and the doorbell refuses a seal that will not fit rather than letting
101
- a platform nobody is reading refuse it two hops later.
102
-
103
- Web Push carries the same line and seals it once rather than twice. The
104
- standard encrypts a body to keys only that browser holds, so the only party
105
- a banner key would hide it from is the push service, which that encryption
106
- already excludes. A second seal would buy nothing and would make every tab
107
- keep a banner key for it. So a world encrypts the line itself, the browser
108
- opens it, and the worker shows what it was handed.
109
-
110
- ## Where a bell runs
111
-
112
- Anywhere a harbor runs, and it is the same bell. A bell is a ward: the
113
- porter, a doorbell per device, and the push faculty of the ward it stands
114
- on. Nothing in it knows a platform.
115
-
116
- What differs is one thing, and it is the thing a faculty is for. The
117
- faculty's rings are built by the terrain out of what that terrain has: a
118
- worker's `fetch` speaks HTTP/2 and an edge bell rings APNs with it, a
119
- droplet speaks it with `node:http2` and rings the same service, and a
120
- terrain with neither holds no `apns` ring and says so. A publisher who
121
- starts on a worker and moves to a box moves the ward, keeps its pk, and
122
- neither a device's standing nor a world's is disturbed.
123
-
124
- So the edge is a convenience and never a requirement. It is a good first
125
- home because a bell keeps almost nothing and must answer whenever any world
126
- rings, and that is all it is.
127
-
128
- ## Nothing is configured
129
-
130
- A bell has no configuration file and wants none. What it holds, it holds the
131
- way every being holds anything.
132
-
133
- - **Who may ring** is an occupant, invited by the device.
134
- - **What is rung** is a token in a doorbell's cells, written by the device.
135
- - **Which service a world hands a ring to** is a cell on the push faculty,
136
- set by the root, because whose service it is belongs to an estate and not
137
- to a world.
138
- - **The platform keys** are the terrain's secrets and reach the faculty as
139
- its rings are built, never a file in a tree and never a cell.
140
-
141
- Rotation is the same answer twice. A heir rotates as it speaks, which is
142
- Quo's and needs nobody. A platform token rotates by the device writing it
143
- once, and because no world was ever given one, no world is told.
144
-
145
- ## The banner key
146
-
147
- A world seals to a key that opens banners and nothing else.
148
-
149
- It is the device's, one pair, and it lives in the cells of the box's
150
- notifications faculty, which is the being that shows a human a line. That is
151
- the source of truth and the only place the pair is kept. The public half
152
- goes to a world when the device admits it, beside the invitation to the
153
- doorbell. The secret half never leaves the device.
154
-
155
- Where a platform's own notification process can read it is the faculty's
156
- business, and this is the whole reason the key sits there rather than in a
157
- being of the library's. On a phone the extension that draws the banner is a
158
- second process, so the faculty puts the secret in the keychain group it
159
- shares with it. On a desk and in a tab the terrain has its own answer. A
160
- faculty is the terrain's hand, and a key that must be reachable by the
161
- platform belongs in the hand and not in the library.
162
-
163
- A tab keeps none. Web Push is encrypted to the subscription's own keys and
164
- a browser draws its own banners in its own worker, so there is neither a
165
- second process to reach nor a second seal to open.
166
-
167
- Minting the key is where the person is asked. A device that has just minted
168
- one is a device about to be rung, and the ring that carries a banner is an
169
- alert the platform draws for nobody who has not allowed it. Registering for
170
- the doorbell asked for nothing, deliberately, because a doorbell displays
171
- nothing; so the phone asks in the same act as it plants the key, and a
172
- refusal costs the line and not the wake. The key stays where it is, the
173
- world is admitted, and what arrives is a push the person reads when she
174
- opens the app.
175
-
176
- It is not the ward's own key, and that is deliberate. The ward's seed opens
177
- everything the person sees and is kept so that it never leaves the device at
178
- all. A banner key opens banners. If the process that draws notifications
179
- were ever turned against her, the second loses her a line of text and the
180
- first would lose her the world.
181
-
182
- ## What a bell knows, and what it cannot
183
-
184
- It knows which device, which world, and when. It cannot know what was said.
185
-
186
- That is the honest accounting, and the design holds it to the minimum: keep
187
- counters and no log of rings, name a doorbell by nothing that identifies a
188
- person, and hold occupants as keys with no world's name beside them. A bell
189
- that kept more would be a registry of worlds, and the app exists not to have
190
- one.
191
-
192
- The publisher cannot add a world to a device's doorbell. Only the device
193
- can, because only the device holds the standing that mints a way in. The
194
- list is held at the bell and it is not the bell's to write.
195
-
196
- ## The three kinds
197
-
198
- **Web.** A world mints its own keypair and rings the browser's push service
199
- itself. The line rides in the ring, encrypted end to end by the standard to
200
- the two keys of that subscription, no bell is involved, and no banner key is
201
- wanted.
202
-
203
- **iOS.** APNs, signed under the publisher's key, spoken over HTTP/2. The
204
- service extension is what makes a sealed payload into a banner, and it needs
205
- the device's key reachable from a second process.
206
-
207
- **Android.** The same shape under FCM, with a data message the app composes
208
- from.
209
-
210
- A bell that holds no key for a kind says so to whoever rings that kind,
211
- which is what a publisher who rings no Android phones says to an Android
212
- phone. It does not pretend it rang.
213
-
214
- ## What this refuses
215
-
216
- - **An account at the bell.** The standing is the identity. An account would
217
- be the registry of worlds, and there is no third thing it would buy.
218
- - **Approving worlds.** The device approves. Nobody else is asked.
219
- - **A token that travels.** The moment a world holds one, disallowing is a
220
- request rather than a fact.
221
- - **A payload the bell can read.** Including one it merely promises not to.
222
- - **A configuration file.** A bell is occupants, standings and cells. A
223
- second place to say who may ring would be a second answer to a question
224
- that already has one.
225
- - **A terrain in the design.** Edge or droplet is where the ward sleeps, and
226
- a bell that worked one way on one and another way on the other would be
227
- two bells.
228
- - **Anything publisher-specific in the packages.** Team ids, bundle ids, a
229
- bell's address and a push key are an estate's, carried in by its
230
- environment. What is published is the porter, the doorbell, the faculty
231
- and the service, and they name nobody.
232
-
233
- ## Where the tree stands
234
-
235
- The one place under this paper where a gap between it and the code is named.
236
-
237
- Each of the three grounds has drawn a line on a screen, and each in the
238
- shape this paper gives it. On an iPhone the app minted its banner key,
239
- planted the secret half where the extension reads it, took a device token
240
- from Apple, and a ring under the team's own key carried a hundred and
241
- thirty bytes of ciphertext that the extension opened. On Android the ring
242
- carried an empty data map and the device composed the line itself. In a
243
- real Chrome the world found the tab unreachable, rang its subscription, and
244
- the worker drew what it was handed.
245
-
246
- - **A simulator draws no sealed banner**, because it runs no notification
247
- service extension at all. It proves everything on either side of that
248
- move: the pair minted and kept across a reinstall, the secret half
249
- planted, and the seal opened by the extension's own code against a
250
- payload a world sealed to that device. The move itself is an iPhone's.
251
- - **A browser wants a real one.** The Chromium a bench drives answers
252
- `showNotification` neither way, so the line that draws it is the one line
253
- no test here runs. A browser also asks the human with a button and never
254
- on load, so the permission is a click and no suite can spring it.
255
- - **Android is pointed at a Firebase project by a file a build carries**
256
- and the tree ignores, since the file holds a key. The project is the
257
- publisher's own and holds the one app it ships. Its id is minted once and
258
- never moves, so a project opened for something else keeps that name for
259
- as long as it lives, which is a thing to know before opening one.
260
- - **The ring on real hardware holds past the first.** A phone with the app
261
- backgrounded draws the banner, and so does one locked with the screen
262
- off. Five rings two seconds apart all arrived, in order, none coalesced
263
- and none dropped, so the platform throttles nothing at that rate. And a
264
- ring lands after a full power cycle on an app that has not run since the
265
- device booted, which is custody at its strongest: the doorbell standing
266
- and the banner key are both kept, and nothing has to be launched to
267
- receive. A background push reaches no app the human force quit, by
268
- Apple's rule, so a phone under test is backgrounded and not closed.
269
- - **An Android device reaches its bell over `wss` and never `ws`.** The
270
- shell is served from `https://localhost` there, and that ground's WebView
271
- refuses a cleartext socket from an HTTPS page as mixed content, so a bell
272
- on a bare address on the same network cannot be dialled at all and the
273
- device comes away with no doorbell. A phone's WebView on the other ground
274
- allows it. So a bell under test wants a real name and a certificate,
275
- where the other ground is content with an address on the wire.
276
- - **A ring costs about 290ms on the box side**, measured from the ask to
277
- the answer that comes back only once the bell has handed it to Apple and
278
- Apple has taken it. It is steady under a burst: five rings measured 291,
279
- 294, 291, 295 and 295. What the platform then does with it before a
280
- screen lights is the platform's and is not ours to measure.