@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.
Files changed (285) hide show
  1. package/GETTING_STARTED.md +128 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +6 -0
  4. package/README.md +37 -0
  5. package/api/quo-api.md +196 -0
  6. package/api/route.ts +227 -0
  7. package/beings/GLOSSARY.md +118 -0
  8. package/beings/WORLDS.md +567 -0
  9. package/beings/avatar.ts +101 -0
  10. package/beings/carry.ts +159 -0
  11. package/beings/clock.ts +552 -0
  12. package/beings/courier.ts +107 -0
  13. package/beings/desk.ts +128 -0
  14. package/beings/doorbell.ts +129 -0
  15. package/beings/envoy.ts +218 -0
  16. package/beings/index.ts +16 -0
  17. package/beings/join.ts +51 -0
  18. package/beings/link.ts +104 -0
  19. package/beings/look.ts +139 -0
  20. package/beings/porter.ts +43 -0
  21. package/beings/presence.ts +39 -0
  22. package/beings/quo-app-bells.md +280 -0
  23. package/beings/quo-dock.md +1942 -0
  24. package/beings/setup.ts +45 -0
  25. package/beings/side.ts +60 -0
  26. package/beings/user.ts +459 -0
  27. package/beings/way.ts +65 -0
  28. package/cli/bundles.ts +20 -0
  29. package/cli/client.ts +73 -0
  30. package/cli/daemon.ts +581 -0
  31. package/cli/estate/Caddyfile +33 -0
  32. package/cli/estate/quo.service +35 -0
  33. package/cli/estate/systems.quo.plist +44 -0
  34. package/cli/estate.ts +48 -0
  35. package/cli/folder.ts +27 -0
  36. package/cli/oauth.ts +32 -0
  37. package/cli/pilot.ts +38 -0
  38. package/cli/quo-dock.md +1942 -0
  39. package/cli/quo.ts +203 -0
  40. package/cli/stand.ts +160 -0
  41. package/dist/api/route.d.ts +21 -0
  42. package/dist/api/route.js +193 -0
  43. package/dist/beings/avatar.d.ts +32 -0
  44. package/dist/beings/avatar.js +100 -0
  45. package/dist/beings/carry.d.ts +12 -0
  46. package/dist/beings/carry.js +174 -0
  47. package/dist/beings/clock.d.ts +161 -0
  48. package/dist/beings/clock.js +528 -0
  49. package/dist/beings/courier.d.ts +50 -0
  50. package/dist/beings/courier.js +96 -0
  51. package/dist/beings/desk.d.ts +53 -0
  52. package/dist/beings/desk.js +135 -0
  53. package/dist/beings/doorbell.d.ts +78 -0
  54. package/dist/beings/doorbell.js +133 -0
  55. package/dist/beings/envoy.d.ts +54 -0
  56. package/dist/beings/envoy.js +211 -0
  57. package/dist/beings/index.d.ts +14 -0
  58. package/dist/beings/index.js +16 -0
  59. package/dist/beings/join.d.ts +15 -0
  60. package/dist/beings/join.js +42 -0
  61. package/dist/beings/link.d.ts +14 -0
  62. package/dist/beings/link.js +91 -0
  63. package/dist/beings/look.d.ts +30 -0
  64. package/dist/beings/look.js +104 -0
  65. package/dist/beings/porter.d.ts +14 -0
  66. package/dist/beings/porter.js +41 -0
  67. package/dist/beings/presence.d.ts +8 -0
  68. package/dist/beings/presence.js +21 -0
  69. package/dist/beings/setup.d.ts +3 -0
  70. package/dist/beings/setup.js +39 -0
  71. package/dist/beings/side.d.ts +32 -0
  72. package/dist/beings/side.js +37 -0
  73. package/dist/beings/user.d.ts +315 -0
  74. package/dist/beings/user.js +462 -0
  75. package/dist/beings/way.d.ts +19 -0
  76. package/dist/beings/way.js +28 -0
  77. package/dist/cli/bundles.d.ts +5 -0
  78. package/dist/cli/bundles.js +17 -0
  79. package/dist/cli/client.d.ts +15 -0
  80. package/dist/cli/client.js +58 -0
  81. package/dist/cli/daemon.d.ts +50 -0
  82. package/dist/cli/daemon.js +584 -0
  83. package/dist/cli/estate/Caddyfile +33 -0
  84. package/dist/cli/estate/quo.service +35 -0
  85. package/dist/cli/estate/systems.quo.plist +44 -0
  86. package/dist/cli/estate.d.ts +5 -0
  87. package/dist/cli/estate.js +50 -0
  88. package/dist/cli/folder.d.ts +2 -0
  89. package/dist/cli/folder.js +25 -0
  90. package/dist/cli/oauth.d.ts +2 -0
  91. package/dist/cli/oauth.js +32 -0
  92. package/dist/cli/pilot.d.ts +5 -0
  93. package/dist/cli/pilot.js +22 -0
  94. package/dist/cli/quo.d.ts +2 -0
  95. package/dist/cli/quo.js +201 -0
  96. package/dist/cli/stand.d.ts +37 -0
  97. package/dist/cli/stand.js +110 -0
  98. package/dist/dock/agents.d.ts +102 -0
  99. package/dist/dock/agents.js +105 -0
  100. package/dist/dock/apns.d.ts +27 -0
  101. package/dist/dock/apns.js +98 -0
  102. package/dist/dock/console.d.ts +6 -0
  103. package/dist/dock/console.js +20 -0
  104. package/dist/dock/dialers.d.ts +56 -0
  105. package/dist/dock/dialers.js +75 -0
  106. package/dist/dock/faculty.d.ts +24 -0
  107. package/dist/dock/faculty.js +142 -0
  108. package/dist/dock/fcm.d.ts +13 -0
  109. package/dist/dock/fcm.js +77 -0
  110. package/dist/dock/grants.d.ts +41 -0
  111. package/dist/dock/grants.js +99 -0
  112. package/dist/dock/http2.d.ts +4 -0
  113. package/dist/dock/http2.js +68 -0
  114. package/dist/dock/index.d.ts +41 -0
  115. package/dist/dock/index.js +113 -0
  116. package/dist/dock/local.d.ts +13 -0
  117. package/dist/dock/local.js +44 -0
  118. package/dist/dock/location.d.ts +19 -0
  119. package/dist/dock/location.js +11 -0
  120. package/dist/dock/notifications.d.ts +38 -0
  121. package/dist/dock/notifications.js +60 -0
  122. package/dist/dock/push.d.ts +79 -0
  123. package/dist/dock/push.js +132 -0
  124. package/dist/dock/service.d.ts +4 -0
  125. package/dist/dock/service.js +107 -0
  126. package/dist/dock/timer.d.ts +52 -0
  127. package/dist/dock/timer.js +149 -0
  128. package/dist/dock/webpush.d.ts +12 -0
  129. package/dist/dock/webpush.js +144 -0
  130. package/dist/harbor/browser.d.ts +15 -0
  131. package/dist/harbor/browser.js +82 -0
  132. package/dist/harbor/capacitor.d.ts +28 -0
  133. package/dist/harbor/capacitor.js +218 -0
  134. package/dist/harbor/disk.d.ts +27 -0
  135. package/dist/harbor/disk.js +249 -0
  136. package/dist/harbor/edge/alarm.d.ts +10 -0
  137. package/dist/harbor/edge/alarm.js +38 -0
  138. package/dist/harbor/edge/edge.d.ts +54 -0
  139. package/dist/harbor/edge/edge.js +370 -0
  140. package/dist/harbor/edge/exercise.d.ts +7 -0
  141. package/dist/harbor/edge/exercise.js +232 -0
  142. package/dist/harbor/edge/given.d.ts +26 -0
  143. package/dist/harbor/edge/given.js +42 -0
  144. package/dist/harbor/edge/platform.d.ts +39 -0
  145. package/dist/harbor/edge/storage.d.ts +30 -0
  146. package/dist/harbor/edge/storage.js +180 -0
  147. package/dist/harbor/edge/worker.d.ts +17 -0
  148. package/dist/harbor/edge/worker.js +67 -0
  149. package/dist/harbor/files.d.ts +15 -0
  150. package/dist/harbor/files.js +220 -0
  151. package/dist/harbor/http.d.ts +19 -0
  152. package/dist/harbor/http.js +177 -0
  153. package/dist/harbor/idb.d.ts +15 -0
  154. package/dist/harbor/idb.js +0 -0
  155. package/dist/harbor/quo.d.ts +10 -0
  156. package/dist/harbor/quo.js +42 -0
  157. package/dist/harbor/resolve.d.ts +16 -0
  158. package/dist/harbor/resolve.js +17 -0
  159. package/dist/harbor/seal.d.ts +3 -0
  160. package/dist/harbor/seal.js +25 -0
  161. package/dist/harbor/sealed.d.ts +35 -0
  162. package/dist/harbor/sealed.js +162 -0
  163. package/dist/harbor/tauri.d.ts +16 -0
  164. package/dist/harbor/tauri.js +59 -0
  165. package/dist/harbor/ward.d.ts +9 -0
  166. package/dist/harbor/ward.js +15 -0
  167. package/dist/human/app.d.ts +32 -0
  168. package/dist/human/app.js +527 -0
  169. package/dist/human/apps.d.ts +10 -0
  170. package/dist/human/apps.js +29 -0
  171. package/dist/human/dom.d.ts +2 -0
  172. package/dist/human/dom.js +15 -0
  173. package/dist/human/door.d.ts +19 -0
  174. package/dist/human/door.js +92 -0
  175. package/dist/human/guest.d.ts +3 -0
  176. package/dist/human/guest.js +33 -0
  177. package/dist/human/html.d.ts +55 -0
  178. package/dist/human/html.js +451 -0
  179. package/dist/human/local.d.ts +7 -0
  180. package/dist/human/local.js +10 -0
  181. package/dist/human/mark.d.ts +8 -0
  182. package/dist/human/mark.js +47 -0
  183. package/dist/human/move.d.ts +17 -0
  184. package/dist/human/move.js +90 -0
  185. package/dist/human/screen.d.ts +19 -0
  186. package/dist/human/screen.js +119 -0
  187. package/dist/human/style.d.ts +2 -0
  188. package/dist/human/style.js +60 -0
  189. package/dist/human/tab.bundle.txt +4775 -0
  190. package/dist/human/tab.d.ts +15 -0
  191. package/dist/human/tab.js +409 -0
  192. package/dist/human/tree.d.ts +50 -0
  193. package/dist/human/tree.js +157 -0
  194. package/dist/human/wake.d.ts +34 -0
  195. package/dist/human/wake.js +179 -0
  196. package/dist/human/web.d.ts +33 -0
  197. package/dist/human/web.js +184 -0
  198. package/dist/human/worker.bundle.txt +3868 -0
  199. package/dist/human/worker.d.ts +1 -0
  200. package/dist/human/worker.js +81 -0
  201. package/dist/human/worlds.d.ts +24 -0
  202. package/dist/human/worlds.js +99 -0
  203. package/dist/mcp/agent.d.ts +10 -0
  204. package/dist/mcp/agent.js +78 -0
  205. package/dist/mcp/allow.d.ts +20 -0
  206. package/dist/mcp/allow.js +88 -0
  207. package/dist/mcp/http.d.ts +27 -0
  208. package/dist/mcp/http.js +92 -0
  209. package/dist/mcp/oauth.d.ts +91 -0
  210. package/dist/mcp/oauth.js +241 -0
  211. package/dist/mcp/route.d.ts +15 -0
  212. package/dist/mcp/route.js +42 -0
  213. package/dist/mcp/runner.d.ts +45 -0
  214. package/dist/mcp/runner.js +99 -0
  215. package/dist/mcp/server.d.ts +11 -0
  216. package/dist/mcp/server.js +102 -0
  217. package/dock/agents.ts +121 -0
  218. package/dock/apns.ts +138 -0
  219. package/dock/console.ts +23 -0
  220. package/dock/dialers.ts +85 -0
  221. package/dock/faculty.ts +148 -0
  222. package/dock/fcm.ts +103 -0
  223. package/dock/grants.ts +111 -0
  224. package/dock/http2.ts +70 -0
  225. package/dock/index.ts +133 -0
  226. package/dock/local.ts +48 -0
  227. package/dock/location.ts +22 -0
  228. package/dock/notifications.ts +67 -0
  229. package/dock/push.ts +161 -0
  230. package/dock/quo-app-bells.md +280 -0
  231. package/dock/quo-dock.md +1942 -0
  232. package/dock/service.ts +100 -0
  233. package/dock/timer.ts +174 -0
  234. package/dock/webpush.ts +182 -0
  235. package/harbor/browser.ts +97 -0
  236. package/harbor/capacitor.ts +223 -0
  237. package/harbor/disk.ts +237 -0
  238. package/harbor/edge/alarm.ts +48 -0
  239. package/harbor/edge/edge.ts +407 -0
  240. package/harbor/edge/exercise.ts +241 -0
  241. package/harbor/edge/given.ts +106 -0
  242. package/harbor/edge/platform.d.ts +39 -0
  243. package/harbor/edge/storage.ts +179 -0
  244. package/harbor/edge/text.d.ts +15 -0
  245. package/harbor/edge/worker.ts +70 -0
  246. package/harbor/edge/wrangler.toml +30 -0
  247. package/harbor/files.ts +221 -0
  248. package/harbor/http.ts +188 -0
  249. package/harbor/idb.ts +0 -0
  250. package/harbor/quo-harbor.md +850 -0
  251. package/harbor/quo.ts +44 -0
  252. package/harbor/resolve.ts +31 -0
  253. package/harbor/seal.ts +26 -0
  254. package/harbor/sealed.ts +183 -0
  255. package/harbor/tauri.ts +79 -0
  256. package/harbor/ward.ts +28 -0
  257. package/human/app.ts +580 -0
  258. package/human/apps.ts +47 -0
  259. package/human/dom.ts +21 -0
  260. package/human/door.ts +113 -0
  261. package/human/guest.ts +34 -0
  262. package/human/html.ts +490 -0
  263. package/human/local.ts +30 -0
  264. package/human/mark.ts +53 -0
  265. package/human/move.ts +122 -0
  266. package/human/quo-human.md +1042 -0
  267. package/human/screen.ts +136 -0
  268. package/human/style.ts +63 -0
  269. package/human/tab.bundle.txt +4775 -0
  270. package/human/tab.ts +446 -0
  271. package/human/tree.ts +217 -0
  272. package/human/wake.ts +191 -0
  273. package/human/web.ts +259 -0
  274. package/human/worker.bundle.txt +3868 -0
  275. package/human/worker.ts +124 -0
  276. package/human/worlds.ts +127 -0
  277. package/mcp/agent.ts +94 -0
  278. package/mcp/allow.ts +131 -0
  279. package/mcp/http.ts +92 -0
  280. package/mcp/oauth.ts +270 -0
  281. package/mcp/quo-mcp.md +343 -0
  282. package/mcp/route.ts +45 -0
  283. package/mcp/runner.ts +136 -0
  284. package/mcp/server.ts +101 -0
  285. package/package.json +121 -0
package/api/route.ts ADDED
@@ -0,0 +1,227 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The api. route: HTTP as the fourth rendering of one describe. It is
3
+ // `Request` in and `Response` out and names no terrain, so the daemon mounts
4
+ // it under `/api` behind the proxy that maps the api. hostname, and the
5
+ // worker at the edge mounts the same route under its own prefix.
6
+ // Beside forms, tools and the CLI, it speaks a being's describe and asks as
7
+ // plain JSON, and it hears every rung a route hears. A request carrying no
8
+ // bearer is the stranger every route knows: the named ward's public being
9
+ // asked as `{}`, which is what a browser fetch, a crawler or a page
10
+ // rendering server side is. A request carrying a bearer the box granted is
11
+ // an occupant: the identity's avatar in the ward the grant names, asked
12
+ // through her one standing at the world's being, so what a script sees
13
+ // with a bearer is what a model sees with the same bearer as tools and a
14
+ // tab sees as forms. A world is a ward, so `/api/<ward>` is the describe
15
+ // for this asker and `/api/<ward>/<ask>` is one ask, args from the query on
16
+ // a GET or a JSON object on a POST. A caller who names no ward is at the
17
+ // device's own, as a tab at the web route's root is: `GET /api` is the
18
+ // describe of that ward, for a stranger and under a bearer alike.
19
+ //
20
+ // A program gets its bearer the way the allow page gives one to a model,
21
+ // without the page: `POST /api` with the identity and the invitation the
22
+ // root minted for it trades the invitation for tokens, the avatar knocks,
23
+ // and the invitation is spent. `POST /api` with a refresh token rotates.
24
+ //
25
+ // What she says crosses as the model side crosses it: an object is 200
26
+ // with that JSON, an error object she answered is 400 with that JSON, and
27
+ // no object at all is 503 `{ error: 'silence' }`. For a stranger, silence,
28
+ // a throw, a wait that ran out and a ward with nobody at the door are one
29
+ // answer here, as they are one answer on the wire: a stranger learns which
30
+ // world exists and nothing else about it. For an occupant the ward's
31
+ // words cross by name, and `removed` is 401 with the grant forgotten, so a
32
+ // script that was removed at the user being starts over with a fresh
33
+ // invitation. A world that is not here, and a path that is not one of the
34
+ // shapes, are the route's own 404 and never a being's.
35
+ import type { Handler } from '../harbor/http.ts';
36
+ import { readText } from '../harbor/http.ts';
37
+ import type { Hosted } from '@nervur-org/nervur/harbor';
38
+ import type { Avatar } from '../beings/avatar.ts';
39
+ import { invitationOf } from '../beings/link.ts';
40
+ import { landed, POLICY } from '../human/door.ts';
41
+ import { CSS } from '../human/style.ts';
42
+ import type { Grants } from '../dock/grants.ts';
43
+ import { isInvitation, isSilence, isWord, wordOf, type Invitation, type JsonObject } from '@nervur-org/nervur';
44
+
45
+ const open = { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'GET, POST, OPTIONS', 'access-control-allow-headers': 'content-type, authorization' };
46
+
47
+ // The harbor this route answers for, as the route needs it: the wards it
48
+ // holds, and which of them a caller who names none is at. The second is the
49
+ // terrain's word and never a guess of the route's: a disk reads the mark an
50
+ // operator wrote, a worker holds the one world in its object. A terrain with
51
+ // no answer says the empty string, and the path that named no ward is no
52
+ // such world.
53
+ export type Held = { wards: ReadonlyMap<string, Hosted>; here(): string };
54
+
55
+ export type Options = {
56
+ grants: Grants;
57
+ // Her avatar as she stands in a world, for a bearer: nothing is minted.
58
+ resolve: (identity: string, ward: string) => Promise<{ avatar?: Avatar; error?: string }>;
59
+ // Boot or find the avatar for this identity in that world, and knock.
60
+ join: (identity: string, invitation: Invitation, ward: string) => Promise<{ error?: string }>;
61
+ // Where the worlds' pages are, for the one arrival here that is a human:
62
+ // a redirect from somewhere a world sent her. Without it she still gets a
63
+ // page, with no way back offered on it.
64
+ web?: string;
65
+ };
66
+
67
+ const word = (s: unknown) => (typeof s === 'string' && /^[\w.-]{1,40}$/.test(s) ? s : null);
68
+
69
+ export function apiRoute(harbor: Held, o: Options): Handler {
70
+ return async (req, rest) => {
71
+ const json = (status: number, body: unknown) => Response.json(body, { status, headers: open });
72
+ // The one page this road serves, and it wears the one stylesheet every
73
+ // screen wears. No cross-origin header on it: a page is for the browser
74
+ // that was sent here and never for a script somewhere else. Its policy
75
+ // is the allow page's, and for the same reason: a page that runs no
76
+ // script of its own should be allowed none.
77
+ const page = (status: number, body: string) =>
78
+ new Response(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>quo</title>\n<style>${CSS}</style>\n</head><body>${body}</body></html>`, {
79
+ status,
80
+ headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'content-security-policy': POLICY, 'referrer-policy': 'no-referrer' },
81
+ });
82
+ // Where this world lives, as the daemon was told and not as the request
83
+ // said: a Host header is the caller's to write, and a page that printed
84
+ // one would print whatever a caller wanted a human to read. Falling back
85
+ // to it is for a box configured with no web address at all, where the
86
+ // request is the only thing that knows.
87
+ const hostOf = (r: Request) => {
88
+ try {
89
+ return o.web === undefined ? (r.headers.get('host') ?? '') : new URL(o.web).host;
90
+ } catch {
91
+ return r.headers.get('host') ?? '';
92
+ }
93
+ };
94
+ if (req.method === 'OPTIONS') return new Response(null, { status: 204, headers: open });
95
+ if (req.method !== 'GET' && req.method !== 'POST') return json(405, { error: 'GET or POST' });
96
+ const body = async (): Promise<JsonObject | null> => {
97
+ const raw = await readText(req);
98
+ if (raw === undefined) return null;
99
+ let parsed: unknown = {};
100
+ try {
101
+ parsed = raw.length === 0 ? {} : JSON.parse(raw);
102
+ } catch {
103
+ return null;
104
+ }
105
+ return parsed === null || typeof parsed !== 'object' || Array.isArray(parsed) ? null : (parsed as JsonObject);
106
+ };
107
+
108
+ // The trade: an invitation for a grant, or a refresh token for the next.
109
+ if ((rest === '' || rest === '/') && req.method === 'POST') {
110
+ const b = await body();
111
+ if (b === null) return json(400, { error: 'the body is a JSON object' });
112
+ if (typeof b.refresh === 'string') {
113
+ const t = await o.grants.refresh(b.refresh);
114
+ return t ? json(200, t) : json(400, { error: 'invalid_grant' });
115
+ }
116
+ const inv = typeof b.invitation === 'string' ? invitationOf(b.invitation) : isInvitation(b.invitation) ? b.invitation : null;
117
+ if (!inv) return json(400, { error: 'that is not an invitation' });
118
+ const world = [...harbor.wards].find(([, h]) => h.pk === inv.ward);
119
+ if (!world) return json(400, { error: 'that invitation is for a world that does not live here' });
120
+ const [ward, hosted] = world;
121
+ const identity = word(b.identity);
122
+ if (identity === null || identity === hosted.record.user || identity === 'desk') return json(400, { error: 'an identity is one word, and not the user or the desk' });
123
+ // Nobody at a page vouches for a program, so a trade is a knock that
124
+ // spends: an identity already in refreshes, or is removed at the user
125
+ // being and invited again, and never re-enters on a spent invitation.
126
+ if ((await o.resolve(identity, ward)).avatar) return json(400, { error: 'already in' });
127
+ const joined = await o.join(identity, inv, ward);
128
+ if (joined.error) return json(400, { error: joined.error });
129
+ return json(200, await o.grants.issue(identity, ward, 'api'));
130
+ }
131
+
132
+ // No ward named is the device's own ward, and never a default the route
133
+ // picks: the harbor points at one of its wards and says that one is the
134
+ // device's, and its door answers for it.
135
+ const [, named = '', ask = '', ...more] = rest.split('/');
136
+ const ward = named === '' ? harbor.here() : named;
137
+
138
+ // The delivery: a vendor's webhook to an envoy, `/api/<ward>/<being>/deliver`.
139
+ // Not a stranger's ask and not an occupant's: the owner asking her
140
+ // `deliver` with the raw bytes and the headers, and nothing else. The
141
+ // body is read as a string and never parsed here, since the vendor
142
+ // signed the bytes and a reparsed body fails her check; no bearer is
143
+ // read, since the rung is not the caller's to prove. She answers `{ ok }`
144
+ // or her error object, and the route carries it.
145
+ // A GET on the same path is the same ask with an empty body: where an
146
+ // OAuth redirect lands, the code in the query. The query crosses inside
147
+ // the headers under `:query`, a name no header on the wire can have, so
148
+ // her one ask in stays one.
149
+ if (more.length === 1 && more[0] === 'deliver') {
150
+ if (req.method !== 'POST' && req.method !== 'GET') return json(405, { error: 'POST, or GET for a redirect' });
151
+ const hosted = harbor.wards.get(ward);
152
+ if (!hosted || !/^[\w-]+$/.test(ask)) return json(404, { error: 'no such world' });
153
+ const raw = req.method === 'GET' ? '' : await readText(req);
154
+ if (raw === undefined) return json(413, { error: 'too big' });
155
+ const headers: Record<string, string> = {};
156
+ req.headers.forEach((v, k) => (headers[k] = v));
157
+ const query = new URL(req.url).search.slice(1);
158
+ if (query) headers[':query'] = query;
159
+ const out = await hosted.ask('ask', { being: ask, method: 'deliver', args: { body: raw, headers } });
160
+ await hosted.save();
161
+ // A GET here is a browser and a browser is a human: a vendor a world
162
+ // sent her to has opened this URL in her tab, so what she is looking
163
+ // at is whatever the envoy answered a machine. She gets a page
164
+ // instead, and only she does; a vendor asking for JSON is answered in
165
+ // JSON as it always was, and so is a GET that says nothing about what
166
+ // it wants.
167
+ const wants = req.method === 'GET' && (req.headers.get('accept') ?? '').includes('text/html');
168
+ if (wants) {
169
+ const ok = out !== null && typeof out === 'object' && !Array.isArray(out) && typeof (out as { error?: unknown }).error !== 'string';
170
+ return page(ok ? 200 : 400, landed({ world: ward, host: hostOf(req), ...(o.web ? { at: `${o.web.replace(/\/$/, '')}/${encodeURIComponent(ward)}` } : {}), ok }));
171
+ }
172
+ if (isSilence(out) || out === null || typeof out !== 'object' || Array.isArray(out)) return json(503, { error: 'silence' });
173
+ const said = (out as { error?: unknown }).error;
174
+ if (said === 'no such being' || said === 'unknown ask') return json(404, { error: 'no such envoy' });
175
+ if (said === 'silence' || said === 'threw' || said === 'late') return json(503, { error: 'silence' });
176
+ if (typeof said === 'string') return json(400, out);
177
+ return json(200, out);
178
+ }
179
+ if (!/^[\w.-]+$/.test(ward) || more.length > 0 || (ask !== '' && !/^[\w-]+$/.test(ask))) return json(404, { error: 'GET /api for the device’s ward, GET /api/<ward> for the describe, GET or POST /api/<ward>/<ask> for one ask, POST /api/<ward>/<being>/deliver for a delivery' });
180
+ const hosted = harbor.wards.get(ward);
181
+ if (!hosted) return json(404, { error: 'no such world' });
182
+ let args: JsonObject;
183
+ if (req.method === 'POST') {
184
+ const b = await body();
185
+ if (b === null) return json(400, { error: 'the body is a JSON object' });
186
+ args = b;
187
+ } else {
188
+ const url = new URL(req.url);
189
+ args = Object.fromEntries(url.searchParams.entries());
190
+ }
191
+
192
+ const carried = (req.headers.get('authorization') ?? '') !== '';
193
+ const grant = o.grants.bearer(req);
194
+ if (carried && grant === null) return json(401, { error: 'unauthorized' });
195
+ if (grant === null) {
196
+ // The owner's `ask` with no being named: the public being, asked as
197
+ // nobody, bounded and judged by the ward, which is the in-process twin
198
+ // of the bytes a stranger would send. The route holds none of that. A
199
+ // world with nobody at the door and a being who said nothing are one
200
+ // answer here, as they are one answer on the wire.
201
+ const out = await hosted.ask('ask', ask === '' ? {} : { method: ask, args });
202
+ if (isSilence(out) || out === null || typeof out !== 'object' || Array.isArray(out)) return json(503, { error: 'silence' });
203
+ const said = (out as { error?: unknown }).error;
204
+ if (said === 'silence' || said === 'threw' || said === 'late' || said === 'no such being') return json(503, { error: 'silence' });
205
+ if (typeof said === 'string') return json(400, out);
206
+ return json(200, out);
207
+ }
208
+
209
+ // An occupant: the grant names a world, and the path must name the same
210
+ // one, since an avatar is one relation in one world.
211
+ if (grant.ward !== ward) return json(403, { error: 'not your world' });
212
+ const found = await o.resolve(grant.identity, ward);
213
+ if (found.error === 'removed') {
214
+ await o.grants.revoke(grant.identity, ward);
215
+ return json(401, { error: 'removed' });
216
+ }
217
+ if (!found.avatar) return json(401, { error: found.error ?? 'not joined' });
218
+ const out = ask === '' ? await found.avatar.tools() : await found.avatar.call(ask, args);
219
+ await hosted.save();
220
+ if (isSilence(out)) return json(503, { error: 'silence' });
221
+ if (isWord(out)) return json(503, { error: wordOf(out) });
222
+ if (out === null || typeof out !== 'object' || Array.isArray(out)) return json(200, out);
223
+ const said = (out as { error?: unknown }).error;
224
+ if (typeof said === 'string' && !('asks' in out)) return json(400, out);
225
+ return json(200, out);
226
+ };
227
+ }
@@ -0,0 +1,118 @@
1
+ # GLOSSARY
2
+
3
+ Every word the papers above the spec use, in one place. The protocol's own
4
+ words, being, ward, harbor, standing, occupant, invitation, knock and the
5
+ rest, are in the glossary of `quo/SPEC.md`, which assumes nothing and
6
+ is not repeated here; what the library spells for itself is in
7
+ `papers/quo-kit.md`. A word here is a convention of the dock or of an
8
+ estate, and changes no word of the spec. `papers/WORLDS.md` is where the
9
+ first seven are reasoned; `papers/quo-dock.md` and the papers of its sides
10
+ are where the rest are built.
11
+
12
+ ## A person and a world
13
+
14
+ - **org**: the unit every "you" names: a company, a family, a club, a
15
+ service, and at the smallest one human alone, an org of one. An org owns
16
+ wards, holds standings, trusts boxes and is trusted. Never an avatar, an
17
+ account or a user id; each of those is a thing an org holds.
18
+ - **world**: a ward, as a human sees it: one seed, one partition, one body
19
+ of code, one owner.
20
+ - **home**: the one ward an org owns on a harbor it trusts; the org's own
21
+ being is its being. Also that being's standing at the ward's own door as
22
+ an owner, `home`. One per org, and an org may have none yet.
23
+ - **org being**: home's own being, one per org, running day and night,
24
+ holding the org's standings and its members as occupants. An estate's
25
+ own class for an organisation; the user being for an org of one.
26
+ - **presence**: a ward a person owns on a harbor in her hand, one per world
27
+ reached from that device. The tab, the phone and the desk are presences
28
+ of the same shape, and nothing in a hand is a home.
29
+ - **presence being**: the being of a world who lives on a device, booted in
30
+ the presence ward when the world is joined. She lends every faculty the
31
+ box has at her birth and carries them, so a world reaches the device only
32
+ by asking her.
33
+ - **app**: a browser of worlds, one harbor and a presence ward per world,
34
+ the same shell on every terrain. It has no account and no person of its
35
+ own; which app a person uses is settled at home, and an adopter's app is
36
+ only the first one its member meets.
37
+ - **membership**: an id in a ward someone else owns. An employee id, a
38
+ member id, a customer id. Never a ward of hers.
39
+ - **hub**: home, as the one place every relation of hers meets.
40
+ - **rule**: home answering one standing by asking another.
41
+ - **device**: a harbor in her hand, with a ward of its own holding what that
42
+ box can do. One OS user is one harbor, and the app has no people of its
43
+ own.
44
+
45
+ ## The dock
46
+
47
+ - **dock**: what every estate needs and nobody writes twice,
48
+ `packages/dock/`, published as `@nervur-org/dock`.
49
+ - **dock ward**: the ward a harbor boots and roots for itself, holding the
50
+ faculties that box lends. Never a home, and never migrated.
51
+ - **estate**: all of one owner's harbors, with what is theirs alone, in a
52
+ tree of the owner's own and never published.
53
+ - **daemon**: `quo serve`, the one process on a device that holds a harbor.
54
+ - **route**: a hostname a reverse proxy sends to one process: `web.`,
55
+ `quo.`, `mcp.`, `api.`.
56
+ - **user being**: the org being of an org of one: a human's own, holding
57
+ all of her standings. The dock's `User` class, which also serves a
58
+ membership the desk makes in another org's ward.
59
+ - **front desk**: the being every fresh client meets; hands out invitations
60
+ minted by the ward's being, and is what `quo init` marks public in a
61
+ fresh ward. Any ward may mark one being of any class public instead.
62
+ - **avatar**: a being that is someone's voice from outside Quo, one per
63
+ client identity.
64
+ - **side**: the interface an avatar speaks through to someone outside Quo:
65
+ a screen, an MCP server, a runner, an agent.
66
+ - **client identity**: what a credential exchange establishes; one avatar,
67
+ for life.
68
+ - **proof**: a vouch the front desk trades once for an invitation: the
69
+ daemon's nonce for a local side, or the link a world mailed to an address
70
+ it wants proof of. Everyone else arrives with an invitation.
71
+ - **way in**: a being who says who a proof of one kind is for, and who sends
72
+ the proof out to an address where the kind has one. The desk holds a
73
+ standing at each, under `way:<kind>`: the box's, lent by the dock ward,
74
+ and the world's own, in the world's ward.
75
+ - **carrier**: a being who shows her standings' asks as her own and
76
+ forwards. The user being and the device are two.
77
+ - **envoy**: a being of the org's standing in for a ward nobody here runs,
78
+ one being per vendor and one relation per account, on the base
79
+ `beings/envoy.ts`. Her asks out are the vendor's surface, narrowed; her
80
+ one ask in, `deliver`, is the owner's, the vendor's bytes reaching her
81
+ own body through the api route and never a door; her credential is in
82
+ her cells, placed by the owner and shown to nobody; and a delivery is one
83
+ ask on the way back her occupant handed her at the knock.
84
+ - **faculty**: a being of the dock ward, standing for one thing the box can
85
+ do: a timer, a line, a store, a vendor's API. Reached by `lend`, held as
86
+ an ordinary standing, and she wakes whoever invited her. She holds what
87
+ the device holds and begins holding it at birth.
88
+ - **ring**: one platform's way of waking a device by its token, held by the
89
+ push faculty: `web`, `apns`, `fcm`. It carries the banner sealed to that
90
+ device's banner key, and nothing else.
91
+ - **bell**: a ward that wakes devices, with a public being for a phone
92
+ nobody knows and one doorbell per device. `papers/quo-app-bells.md`.
93
+ - **doorbell**: one device's being at a bell. Its cells hold that device's
94
+ platform token, and its occupants are the worlds that device allowed to
95
+ wake it, one standing each.
96
+ - **porter**: a bell's public being, where a phone nobody knows arrives as
97
+ `{}` and comes away with a doorbell of its own.
98
+ - **banner key**: the pair a device keeps in the cells of its notifications
99
+ faculty. A world seals a notification to its public half; the secret half
100
+ opens banners and nothing else, and never the world.
101
+ - **timer**: the faculty that reads the box's clock, and the only thing on a
102
+ device that may. She wakes whoever invited her, every minute.
103
+ - **clock**: the being who wakes beings at a time, on standings the root
104
+ placed. She holds the schedules and invited the timer.
105
+ - **gate**: `for(occupant, asker)`; the whole permission model.
106
+ - **look**: how a being is presented, as values; one optional ask.
107
+ - **page**: a being's page as a tree of values in the screen's grammar; one
108
+ optional ask, read by a screen alone.
109
+ - **owner pilot**: `quo pilot`, the model side over a ward's owner asks,
110
+ reaching the daemon's socket; with `--via`, a ward elsewhere through a
111
+ standing. Under cron, the estate's own agent.
112
+
113
+ ## The model side
114
+
115
+ - **constitution**: the folder and instructions an autonomous agent runs
116
+ under; governance, never permission.
117
+ - **runner**: an inline MCP client for a model that speaks function
118
+ calling; it drives the loop the MCP client would, over one conversation.