@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/README.md +9 -10
- package/package.json +4 -13
- package/GETTING_STARTED.md +0 -138
- package/GLOSSARY.md +0 -118
- package/WORLDS.md +0 -569
- package/quo-api.md +0 -196
- package/quo-app-bells.md +0 -280
- package/quo-dock.md +0 -1945
- package/quo-harbor.md +0 -874
- package/quo-human.md +0 -1042
- package/quo-mcp.md +0 -343
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.
|