@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
@@ -0,0 +1,3868 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // ../quo/dist/being/being.js
8
+ var RESERVED = /* @__PURE__ */ new Set(["answer", "describe", "stance", "cells", "standings", "occupants", "occupant", "invite", "knock", "take", "boot", "lend", "constructor"]);
9
+ var wrote = (self, name) => {
10
+ for (let p = Object.getPrototypeOf(self); p !== null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
11
+ if (Object.hasOwn(p, name))
12
+ return typeof p[name] === "function";
13
+ }
14
+ return false;
15
+ };
16
+ var Being = class {
17
+ // Her cells' defaults. Merged in at birth, only where a key is missing, so
18
+ // a restart keeps what she wrote.
19
+ static cells = {};
20
+ // What she can be asked. Declaration order is blueprint order, except a
21
+ // name that reads as an array index, which the language lists first.
22
+ static asks = {};
23
+ stance;
24
+ constructor(stance) {
25
+ this.stance = stance;
26
+ const C = this.constructor;
27
+ for (const name of Object.keys(C.asks)) {
28
+ if (RESERVED.has(name))
29
+ throw new Error(`ask '${name}' is a reserved name`);
30
+ if (!wrote(this, name))
31
+ throw new Error(`ask '${name}' has no method on the prototype`);
32
+ }
33
+ for (const [k, v] of Object.entries(C.cells))
34
+ if (!Object.hasOwn(stance.cells, k))
35
+ stance.cells[k] = structuredClone(v);
36
+ }
37
+ get cells() {
38
+ return this.stance.cells;
39
+ }
40
+ get standings() {
41
+ return this.stance.standings;
42
+ }
43
+ get occupants() {
44
+ return this.stance.occupants;
45
+ }
46
+ // A standing at one of the things this device can do, under an id of hers.
47
+ // The ward knocks and takes it for her; the invitation never reaches her.
48
+ lend(name, id) {
49
+ return this.stance.lend(name, id);
50
+ }
51
+ invite(id, notes) {
52
+ return this.stance.occupants.invite(id, notes);
53
+ }
54
+ knock(invitation, method, args, wanted) {
55
+ return this.stance.standings.knock(invitation, method, args, wanted);
56
+ }
57
+ take(id, invitation) {
58
+ return this.stance.standings.take(id, invitation);
59
+ }
60
+ // A new being of her ward, by class name, under a key she chooses. With an
61
+ // id, she holds a standing to the being she made, who knows her by her key.
62
+ boot(className, key, id) {
63
+ return this.stance.boot(className, key, id);
64
+ }
65
+ // The occupant record for whoever is at the door. Undefined at a public being.
66
+ occupant(asker) {
67
+ return asker.id !== void 0 && Object.hasOwn(this.cells.occupants, asker.id) ? this.cells.occupants[asker.id] : void 0;
68
+ }
69
+ // Her blueprint for this asker. Override to shape it by hand.
70
+ describe(asker) {
71
+ const C = this.constructor;
72
+ const rec = this.occupant(asker);
73
+ const asks = [];
74
+ for (const [name, spec] of Object.entries(C.asks)) {
75
+ if (spec.for && !spec.for(rec, asker))
76
+ continue;
77
+ const ask = { name, input: spec.input ?? { type: "object" } };
78
+ if (spec.description !== void 0)
79
+ ask.description = spec.description;
80
+ if (spec.output !== void 0)
81
+ ask.output = spec.output;
82
+ asks.push(ask);
83
+ }
84
+ return { asks, notes: {} };
85
+ }
86
+ // The one function. Override to wrap it; call super to keep the dispatch.
87
+ async answer(asker, method, args = {}) {
88
+ if (method === void 0)
89
+ return this.describe(asker);
90
+ const C = this.constructor;
91
+ const spec = Object.hasOwn(C.asks, method) ? C.asks[method] : void 0;
92
+ if (!spec || spec.for && !spec.for(this.occupant(asker), asker))
93
+ return { error: "unknown ask" };
94
+ const fn = this[method];
95
+ return fn.call(this, args ?? {}, asker);
96
+ }
97
+ };
98
+
99
+ // ../quo/dist/being/types.js
100
+ var OWNER = "OWNER";
101
+ var PUBLIC = "PUBLIC";
102
+ var RESERVED_IDS = [OWNER, PUBLIC];
103
+ var isInvitation = (v) => {
104
+ if (v === null || typeof v !== "object" || Array.isArray(v))
105
+ return false;
106
+ const o = v;
107
+ if (typeof o.ward !== "string" || !/^[0-9a-f]{128}$/.test(o.ward))
108
+ return false;
109
+ const heir = typeof o.heir === "string", secret = typeof o.secret === "string";
110
+ return heir && secret || !heir && !secret && !("heir" in o) && !("secret" in o);
111
+ };
112
+ var isBlueprint = (v) => {
113
+ if (v === null || typeof v !== "object" || Array.isArray(v))
114
+ return false;
115
+ const { asks, notes } = v;
116
+ if (!Array.isArray(asks) || notes === void 0)
117
+ return false;
118
+ return asks.every((a) => a !== null && typeof a === "object" && !Array.isArray(a) && typeof a.name === "string" && a.input !== null && typeof a.input === "object" && !Array.isArray(a.input));
119
+ };
120
+ var WORD_KEY = /* @__PURE__ */ Symbol.for("quo.word");
121
+
122
+ // ../quo/dist/being/silence.js
123
+ var silence = /* @__PURE__ */ Symbol.for("quo.silence");
124
+ var isSilence = (x) => x === silence;
125
+ var DOOR_WORDS = ["removed", "absent", "unannounced", "repeated", "threw"];
126
+ var isDoorWord = (s) => typeof s === "string" && DOOR_WORDS.includes(s);
127
+ var WORDS = /* @__PURE__ */ new Map();
128
+ var word = (name) => {
129
+ let w = WORDS.get(name);
130
+ if (!w)
131
+ WORDS.set(name, w = Object.freeze({ [WORD_KEY]: name }));
132
+ return w;
133
+ };
134
+ var isWord = (x) => x !== null && typeof x === "object" && typeof x[WORD_KEY] === "string";
135
+ var wordOf = (x) => x[WORD_KEY];
136
+ var unreached = () => word("unreached");
137
+
138
+ // ../quo/dist/being/digest.js
139
+ var absent = (v) => v === void 0 || typeof v === "function" || typeof v === "symbol";
140
+ var canonical = (v) => {
141
+ if (Array.isArray(v))
142
+ return `[${Array.from(v, (slot) => absent(slot) ? "null" : canonical(slot)).join(",")}]`;
143
+ if (v !== null && typeof v === "object") {
144
+ return `{${Object.keys(v).filter((k) => !absent(v[k])).sort().map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`).join(",")}}`;
145
+ }
146
+ return JSON.stringify(v);
147
+ };
148
+ var hex = (bytes) => Array.from(new Uint8Array(bytes), (b) => b.toString(16).padStart(2, "0")).join("");
149
+ var subtle = () => {
150
+ const s = globalThis.crypto?.subtle;
151
+ if (!s)
152
+ throw new Error("this terrain has no crypto.subtle: a being needs a secure context to be described");
153
+ return s;
154
+ };
155
+ var digest = async (blueprint) => hex(await subtle().digest("SHA-256", new TextEncoder().encode(canonical(blueprint))));
156
+
157
+ // beings/look.ts
158
+ var COLOUR = /^#[0-9a-fA-F]{3,8}$/;
159
+ var FONT = /^[\w\s,'"-]{1,80}$/;
160
+ var LOGO = /^data:image\/(svg\+xml|png|jpeg|webp);base64,[A-Za-z0-9+/=]{1,40000}$/;
161
+ var NAME = /^[^<>&]{1,60}$/;
162
+ var ICON = /^[^<>&"']{1,8}$/;
163
+ var STANDS = 8;
164
+ var ORIGIN = 200;
165
+ var str = (v, re) => typeof v === "string" && re.test(v) ? v : void 0;
166
+ function sanitise(v) {
167
+ if (v === null || v === void 0 || typeof v !== "object" || Array.isArray(v)) return {};
168
+ const l = {};
169
+ const name = str(v.name, NAME);
170
+ if (name !== void 0) l.name = name;
171
+ const logo = str(v.logo, LOGO);
172
+ if (logo !== void 0) l.logo = logo;
173
+ for (const k of ["accent", "background", "foreground"]) {
174
+ const c = str(v[k], COLOUR);
175
+ if (c !== void 0) l[k] = c;
176
+ }
177
+ const font = str(v.font, FONT);
178
+ if (font !== void 0) l.font = font;
179
+ if (typeof v.radius === "number" && Number.isFinite(v.radius) && v.radius >= 0 && v.radius <= 40) l.radius = Math.round(v.radius);
180
+ if (Array.isArray(v.order)) l.order = v.order.filter((x) => typeof x === "string");
181
+ if (Array.isArray(v.stands)) {
182
+ const stands = [];
183
+ for (const x of v.stands.slice(0, STANDS)) {
184
+ if (typeof x !== "string" || x.length > ORIGIN) continue;
185
+ try {
186
+ const u = new URL(x);
187
+ if ((u.protocol === "https:" || u.protocol === "http:") && !stands.includes(u.origin)) stands.push(u.origin);
188
+ } catch {
189
+ }
190
+ }
191
+ if (stands.length) l.stands = stands;
192
+ }
193
+ if (v.asks !== null && typeof v.asks === "object" && !Array.isArray(v.asks)) {
194
+ const asks = {};
195
+ for (const [ask, h] of Object.entries(v.asks)) {
196
+ if (h === null || typeof h !== "object" || Array.isArray(h)) continue;
197
+ const hint2 = {};
198
+ const title = str(h.title, NAME);
199
+ if (title !== void 0) hint2.title = title;
200
+ const icon = str(h.icon, ICON);
201
+ if (icon !== void 0) hint2.icon = icon;
202
+ for (const k of ["readOnly", "destructive", "idempotent"]) if (typeof h[k] === "boolean") hint2[k] = h[k];
203
+ if (Object.keys(hint2).length) asks[ask] = hint2;
204
+ }
205
+ if (Object.keys(asks).length) l.asks = asks;
206
+ }
207
+ return l;
208
+ }
209
+
210
+ // beings/carry.ts
211
+ function carriedBy(bp) {
212
+ const out = /* @__PURE__ */ new Set();
213
+ const notes = bp.notes;
214
+ const groups2 = notes !== null && typeof notes === "object" && !Array.isArray(notes) ? notes.standings : void 0;
215
+ if (!groups2 || typeof groups2 !== "object" || Array.isArray(groups2)) return out;
216
+ for (const g of Object.values(groups2)) {
217
+ if (Array.isArray(g?.asks)) {
218
+ for (const n of g.asks) if (typeof n === "string") out.add(n);
219
+ }
220
+ if (typeof g?.page === "string") out.add(g.page);
221
+ }
222
+ return out;
223
+ }
224
+ var Carrier = class extends Being {
225
+ // Who sees what she carries. Nobody, until a subclass says who.
226
+ static carries(_occupant, _asker) {
227
+ return false;
228
+ }
229
+ // Standings that are plumbing: their own asks are never carried, and
230
+ // what they carry is. A way back to a device is one, by the dock's own
231
+ // convention, so a bare avatar's inbox never shows on her and a presence
232
+ // being's faculties do, under `to:<id>-<name>`, which is how home carries
233
+ // her phone. A subclass may name more.
234
+ static hidden(id) {
235
+ return id.startsWith("to:");
236
+ }
237
+ // Standings she never reads for carrying at all: a presence being's
238
+ // standing at her own user being is one, since carrying it would carry
239
+ // the user being's carrying of her, and round. None, until a subclass
240
+ // says which.
241
+ static skips(_id) {
242
+ return false;
243
+ }
244
+ // Which of the standings she carries this asker may see. Every one,
245
+ // until a subclass keeps some for fewer: a user being shows her own door
246
+ // to a device that may mint and to nobody else.
247
+ static shows(_id, _occupant, _asker) {
248
+ return true;
249
+ }
250
+ get carried() {
251
+ return this.cells.carried ??= {};
252
+ }
253
+ get looks() {
254
+ return this.cells.looks ??= {};
255
+ }
256
+ // Her describe with her standings carried into it, for an asker who may
257
+ // see them. Every other asker gets her describe alone.
258
+ async blueprint(asker) {
259
+ const C = this.constructor;
260
+ const bp = this.describe(asker);
261
+ if (!C.carries(this.occupant(asker), asker)) return bp;
262
+ const own = new Set(bp.asks.map((a) => a.name));
263
+ const carried2 = {};
264
+ const groups2 = {};
265
+ const occ = this.occupant(asker);
266
+ for (const [id, rec] of [...Object.entries(this.cells.standings)].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
267
+ if (C.skips(id)) continue;
268
+ const st = this.standings[id];
269
+ if (!st) continue;
270
+ if (!rec.blueprint || rec.seen !== rec.digest) await st.ask();
271
+ if (!rec.blueprint) continue;
272
+ const through = C.hidden(id) ? carriedBy(rec.blueprint) : null;
273
+ if (through && through.size === 0) continue;
274
+ const shown = C.shows(id, occ, asker);
275
+ const names = [];
276
+ const group = { asks: names };
277
+ for (const a of rec.blueprint.asks) {
278
+ if (a.name === "look") continue;
279
+ if (through && !through.has(a.name)) continue;
280
+ const name = `${id}-${a.name}`;
281
+ if (own.has(name)) continue;
282
+ carried2[name] = { id, ask: a.name };
283
+ if (!shown) continue;
284
+ bp.asks.push({ ...a, name });
285
+ if (a.name === "page") group.page = name;
286
+ else names.push(name);
287
+ }
288
+ if (!shown) continue;
289
+ if (rec.blueprint.asks.some((a) => a.name === "look")) {
290
+ const kept = this.looks[id];
291
+ if (!kept || kept.digest !== rec.digest) {
292
+ const l = await st.ask("look");
293
+ this.looks[id] = { digest: rec.digest, look: isSilence(l) || isWord(l) ? {} : sanitise(l) };
294
+ }
295
+ group.look = this.looks[id].look;
296
+ }
297
+ groups2[id] = group;
298
+ }
299
+ for (const id of Object.keys(this.looks)) if (!(id in groups2)) delete this.looks[id];
300
+ this.cells.carried = carried2;
301
+ const notes = bp.notes !== null && typeof bp.notes === "object" && !Array.isArray(bp.notes) ? bp.notes : {};
302
+ return { asks: bp.asks, notes: { ...notes, standings: groups2 } };
303
+ }
304
+ async answer(asker, method, args = {}) {
305
+ if (method === void 0) return this.blueprint(asker);
306
+ const C = this.constructor;
307
+ const to = Object.hasOwn(this.carried, method) ? this.carried[method] : void 0;
308
+ const occ = this.occupant(asker);
309
+ if (to && C.carries(occ, asker) && C.shows(to.id, occ, asker)) {
310
+ const st = this.standings[to.id];
311
+ if (!st) return { error: "unknown ask" };
312
+ const out = await st.ask(to.ask, args);
313
+ if (isSilence(out)) return out;
314
+ if (isWord(out)) return { error: wordOf(out) };
315
+ return out;
316
+ }
317
+ return super.answer(asker, method, args);
318
+ }
319
+ };
320
+
321
+ // dock/faculty.ts
322
+ var Faculty = class extends Being {
323
+ static cells = { watched: {}, minted: 0 };
324
+ // Someone wants waking. She hands the invitation she minted and says what
325
+ // to ask her; this knocks it, takes it under an id of this being's own,
326
+ // and files it. The id never crosses: it is this side's name for the
327
+ // relation, and the caller's name for the same relation is her own.
328
+ //
329
+ // The empty ask is the knock, because every being answers one and asking
330
+ // for a named ask would make every subscriber owe a method she does not
331
+ // otherwise need.
332
+ async watch(args, asker) {
333
+ const inv = args.invitation;
334
+ if (!isInvitation(inv) || typeof args.method !== "string") return { error: "an invitation and a method" };
335
+ const extra = args.args;
336
+ if (extra !== void 0 && (extra === null || typeof extra !== "object" || Array.isArray(extra))) return { error: "args is one object" };
337
+ const out = await this.knock(inv);
338
+ if (isSilence(out) || isWord(out)) return { error: "no way back" };
339
+ const cells = this.cells;
340
+ const id = await this.take(`wake:${cells.minted += 1}`, inv);
341
+ if (id === null) return { error: "not taken" };
342
+ cells.watched[id] = { by: asker.id ?? "", method: args.method, args: extra ?? {} };
343
+ await this.welcomed(id);
344
+ return { watching: id };
345
+ }
346
+ // Somebody new is filed, and the answer to her `watch` waits for this. A
347
+ // faculty that owes a new subscriber something at once says it here: the
348
+ // timer wakes her, learns when she is next due and arms the box for it, so
349
+ // a being born long after the box was up is not waiting for a round nobody
350
+ // scheduled. Most faculties owe nothing, and this is why they say nothing.
351
+ async welcomed(_id) {
352
+ }
353
+ // One round of waking. Each subscriber is asked what she asked to be
354
+ // asked, with whatever this round adds. A subscriber who removed the
355
+ // occupant says `removed` once, and her row goes with the standing; there
356
+ // is nothing to retry and nothing to remember.
357
+ //
358
+ // `only` is one subscriber of the round's own choosing, named by this
359
+ // being's own id for the relation, and it is the same round down to one
360
+ // row: a faculty that wakes somebody just filed learns exactly what a full
361
+ // round would have learned about her.
362
+ //
363
+ // `dropped` is this being herself: her ward took her out, so the standings
364
+ // she holds reach nobody. Null says so, and whoever holds the interval puts
365
+ // it down rather than waking an empty room for as long as the box is up.
366
+ async woke(round = (w) => w.args, only) {
367
+ const cells = this.cells;
368
+ const out = [];
369
+ for (const [id, w] of Object.entries(cells.watched)) {
370
+ if (only !== void 0 && id !== only) continue;
371
+ const said2 = await this.standings[id]?.ask(w.method, round(w));
372
+ if (said2 === void 0 || isWord(said2) && wordOf(said2) === "dropped") return null;
373
+ if (isWord(said2) && wordOf(said2) === "removed") {
374
+ this.standings.remove(id);
375
+ delete cells.watched[id];
376
+ continue;
377
+ }
378
+ out.push({ id, by: w.by, out: said2 });
379
+ }
380
+ return out;
381
+ }
382
+ // Who is waiting, by this being's own id for the relation. For a device's
383
+ // own code and for a suite; it crosses no door.
384
+ get watching() {
385
+ return this.cells.watched;
386
+ }
387
+ };
388
+ var flight = /* @__PURE__ */ new Set();
389
+ var settled = async () => {
390
+ while (flight.size) {
391
+ const now = [...flight];
392
+ await Promise.allSettled(now);
393
+ for (const p of now) flight.delete(p);
394
+ }
395
+ };
396
+ function join(b, name, watch, id = name) {
397
+ const p = joining(b, name, watch, id);
398
+ flight.add(p);
399
+ void p.catch(() => {
400
+ });
401
+ return p;
402
+ }
403
+ async function joining(b, name, watch, id) {
404
+ await Promise.resolve();
405
+ b.standings.remove(id);
406
+ b.occupants.remove(`${id}:wake`);
407
+ const got = await b.lend(name, id);
408
+ if (got === null) return null;
409
+ if (!watch) return got;
410
+ const back = await b.invite(`${id}:wake`);
411
+ if (!back) return got;
412
+ await b.standings[id]?.ask("watch", { invitation: back, method: watch.method, ...watch.args ? { args: watch.args } : {} });
413
+ return got;
414
+ }
415
+
416
+ // dock/console.ts
417
+ var CONSOLE = "console";
418
+ var Console = class extends Carrier {
419
+ static carries(occupant2, _asker) {
420
+ return occupant2 !== void 0;
421
+ }
422
+ };
423
+
424
+ // beings/way.ts
425
+ var WAY = (kind) => `way:${kind}`;
426
+ var kindOf = (id) => id.startsWith("way:") ? id.slice(4) : null;
427
+ var VERIFY = { description: "who this proof is for", input: { type: "object", properties: { proof: { type: "object" } }, required: ["proof"] } };
428
+ function verified(out) {
429
+ if (out === null || typeof out !== "object" || Array.isArray(out)) return null;
430
+ const v = out;
431
+ if (typeof v.user !== "string" || typeof v.client !== "string") return null;
432
+ const make = v.make;
433
+ return {
434
+ user: v.user,
435
+ client: v.client,
436
+ ...v.wake === true ? { wake: true } : {},
437
+ ...v.reach === true ? { reach: true } : {},
438
+ ...v.mint === true ? { mint: true } : {},
439
+ ...v.revoke === true ? { revoke: true } : {},
440
+ ...make === true || typeof make === "string" ? { make } : {}
441
+ };
442
+ }
443
+
444
+ // dock/local.ts
445
+ var LOCAL = "local";
446
+ var Local = class extends Faculty {
447
+ static asks = { verify: VERIFY };
448
+ #nonces = /* @__PURE__ */ new Map();
449
+ // A nonce for one client this box saw itself, spent by the first proof
450
+ // that names it. The caller drops it when the admit is over: a nonce that
451
+ // was never spent is not a way in that stays open.
452
+ mint(nonce, who) {
453
+ this.#nonces.set(nonce, who);
454
+ }
455
+ drop(nonce) {
456
+ this.#nonces.delete(nonce);
457
+ }
458
+ verify(args) {
459
+ const proof = args.proof;
460
+ if (!proof || typeof proof !== "object" || Array.isArray(proof)) return null;
461
+ const nonce = proof.nonce;
462
+ const who = typeof nonce === "string" ? this.#nonces.get(nonce) : void 0;
463
+ if (!who) return null;
464
+ this.#nonces.delete(nonce);
465
+ return who;
466
+ }
467
+ };
468
+
469
+ // dock/location.ts
470
+ var Location = class extends Faculty {
471
+ static asks = {
472
+ where: { description: "where this box is", input: { type: "object" } }
473
+ };
474
+ // The platform's own permission prompt is the first ask's, and a refusal is
475
+ // the error object every ask after it answers.
476
+ where() {
477
+ return Promise.resolve({ error: "this box cannot say where it is" });
478
+ }
479
+ };
480
+
481
+ // ../quo/dist/ward/cells.js
482
+ var DEPTH = 64;
483
+ var BREADTH = 1 << 20;
484
+ var WELL_FORMED = /^(?:[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF])*$/;
485
+ function walk(v, path, seen2, sized, depth) {
486
+ if (v === null || typeof v === "boolean")
487
+ return 1;
488
+ if (typeof v === "string")
489
+ return WELL_FORMED.test(v) ? 1 : `${path} carries a lone surrogate, which is no text a harbor can write down`;
490
+ if (typeof v === "number") {
491
+ if (!Number.isFinite(v))
492
+ return `${path} is ${String(v)}, which no harbor can write down`;
493
+ return Object.is(v, -0) ? `${path} is minus zero, which comes back as zero from every harbor that writes JSON` : 1;
494
+ }
495
+ if (typeof v !== "object")
496
+ return `${path} is a ${typeof v}, which is not a value`;
497
+ if (seen2.has(v))
498
+ return `${path} refers back to itself`;
499
+ const counted = sized.get(v);
500
+ if (counted !== void 0)
501
+ return counted;
502
+ if (depth >= DEPTH)
503
+ return `${path} is nested past ${DEPTH} levels, which no harbor can keep`;
504
+ seen2.add(v);
505
+ try {
506
+ let size = 1;
507
+ const under2 = (where, x) => {
508
+ const r = walk(x, where, seen2, sized, depth + 1);
509
+ if (typeof r === "string")
510
+ return r;
511
+ size += r;
512
+ return size > BREADTH ? `${path} writes out to more than ${BREADTH} values, which no harbor can keep` : null;
513
+ };
514
+ if (Array.isArray(v)) {
515
+ for (let i = 0; i < v.length; i += 1) {
516
+ if (!(i in v))
517
+ return `${path}[${i}] is a hole, which no harbor can write down`;
518
+ const f = under2(`${path}[${i}]`, v[i]);
519
+ if (f)
520
+ return f;
521
+ }
522
+ sized.set(v, size);
523
+ return size;
524
+ }
525
+ const proto = Object.getPrototypeOf(v);
526
+ if (proto !== Object.prototype && proto !== null)
527
+ return `${path} is a ${v.constructor?.name ?? "object"}, which is not a value`;
528
+ for (const k of Object.keys(v)) {
529
+ if (k === "__proto__")
530
+ return `${path}.__proto__ is a key no harbor can keep`;
531
+ const f = under2(`${path}.${k}`, v[k]);
532
+ if (f)
533
+ return f;
534
+ }
535
+ sized.set(v, size);
536
+ return size;
537
+ } finally {
538
+ seen2.delete(v);
539
+ }
540
+ }
541
+ var cellFault = (v, path) => {
542
+ const out = walk(v, path, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Map(), 0);
543
+ return typeof out === "string" ? out : null;
544
+ };
545
+ var WARDS = /* @__PURE__ */ new Set(["standings", "occupants", "class"]);
546
+ var wrapped = /* @__PURE__ */ new WeakMap();
547
+ var targets = /* @__PURE__ */ new WeakMap();
548
+ var guards = /* @__PURE__ */ new WeakSet();
549
+ var unkeepable = (k) => k === "__proto__";
550
+ var refuse = (why) => {
551
+ throw new TypeError(`cells hold values: ${why}`);
552
+ };
553
+ function keyFault(t, k, v, path, root2) {
554
+ if (typeof k !== "string")
555
+ refuse(`${path} takes no symbol key`);
556
+ if (unkeepable(k))
557
+ refuse(`${path}.__proto__ is a key no harbor can keep`);
558
+ if (root2 && WARDS.has(k))
559
+ refuse(`${path}.${k} is the ward's to write`);
560
+ if (Array.isArray(t)) {
561
+ if (k === "length") {
562
+ if (typeof v !== "number" || v > t.length)
563
+ refuse(`${path}.length set past what she wrote would leave holes`);
564
+ return;
565
+ }
566
+ const i = Number(k);
567
+ if (Number.isInteger(i) && i > t.length)
568
+ refuse(`${path}[${i}] would leave a hole`);
569
+ }
570
+ }
571
+ function guard(target, path, wrote2, root2 = false) {
572
+ if (guards.has(target))
573
+ return target;
574
+ const had = wrapped.get(target);
575
+ if (had)
576
+ return had;
577
+ const p = new Proxy(target, {
578
+ get(t, k, r) {
579
+ const v = Reflect.get(t, k, r);
580
+ return v !== null && typeof v === "object" && !ArrayBuffer.isView(v) ? guard(v, `${path}.${String(k)}`, wrote2) : v;
581
+ },
582
+ set(t, k, v, r) {
583
+ keyFault(t, k, v, path, root2);
584
+ const f = cellFault(v, `${path}.${String(k)}`);
585
+ if (f)
586
+ refuse(f);
587
+ const ok = Reflect.set(t, k, v, r);
588
+ wrote2();
589
+ return ok;
590
+ },
591
+ defineProperty(t, k, d) {
592
+ if (!("value" in d))
593
+ refuse(`${path}.${String(k)} is an accessor, which no harbor can keep`);
594
+ keyFault(t, k, d.value, path, root2);
595
+ const f = cellFault(d.value, `${path}.${String(k)}`);
596
+ if (f)
597
+ refuse(f);
598
+ const ok = Reflect.defineProperty(t, k, d);
599
+ wrote2();
600
+ return ok;
601
+ },
602
+ deleteProperty(t, k) {
603
+ if (root2 && typeof k === "string" && WARDS.has(k))
604
+ refuse(`${path}.${k} is the ward's to keep`);
605
+ const ok = Reflect.deleteProperty(t, k);
606
+ wrote2();
607
+ return ok;
608
+ }
609
+ });
610
+ wrapped.set(target, p);
611
+ targets.set(p, target);
612
+ guards.add(p);
613
+ return p;
614
+ }
615
+ var guardCells = (cells, wrote2 = () => {
616
+ }) => guard(cells, "cells", wrote2, true);
617
+ var unguarded = (cells) => targets.get(cells) ?? cells;
618
+
619
+ // ../quo/dist/ward/partition.js
620
+ var VERSION = "pre-1.0.0";
621
+ var READS = [VERSION];
622
+ function upgrade(p) {
623
+ return p;
624
+ }
625
+ var GONE = 256;
626
+ var MINTED = 8;
627
+ var KNOCKS = 64;
628
+ var prune = (knocks, answered4) => {
629
+ const names = Object.keys(knocks);
630
+ if (names.length <= KNOCKS)
631
+ return;
632
+ const order = [...names.filter((n) => !answered4[n]), ...names.filter((n) => answered4[n])];
633
+ for (const old of order.slice(0, names.length - KNOCKS)) {
634
+ delete knocks[old];
635
+ delete answered4[old];
636
+ }
637
+ };
638
+ var fault = (path, want) => {
639
+ throw new Error(`partition ${path} is not ${want}`);
640
+ };
641
+ var table = (v, path) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : fault(path, "an object");
642
+ var list = (v, path) => Array.isArray(v) ? v : fault(path, "an array");
643
+ var text = (v, path) => void (typeof v === "string" || fault(path, "a string"));
644
+ var orNull = (v, path) => void (v === null || typeof v === "string" || fault(path, "a string or null"));
645
+ var whole = (v, path) => void (Number.isSafeInteger(v) || fault(path, "a whole number"));
646
+ var flag = (v, path) => void (typeof v === "boolean" || fault(path, "true or false"));
647
+ function read(p) {
648
+ for (const [key, cells] of Object.entries(table(p.beings, "beings"))) {
649
+ const c = table(cells, `beings.${key}`);
650
+ table(c.standings, `beings.${key}.standings`);
651
+ table(c.occupants, `beings.${key}.occupants`);
652
+ if (c.class !== void 0)
653
+ text(c.class, `beings.${key}.class`);
654
+ }
655
+ for (const [key, bind] of Object.entries(table(p.bind, "bind"))) {
656
+ const b = table(bind, `bind.${key}`);
657
+ for (const [id, keys] of Object.entries(table(b.standings, `bind.${key}.standings`))) {
658
+ const w = `bind.${key}.standings.${id}`;
659
+ const s = table(keys, w);
660
+ text(s.ward, `${w}.ward`);
661
+ orNull(s.heir, `${w}.heir`);
662
+ text(s.current, `${w}.current`);
663
+ orNull(s.next, `${w}.next`);
664
+ whole(s.seq, `${w}.seq`);
665
+ }
666
+ for (const [id, pk] of Object.entries(table(b.occupants, `bind.${key}.occupants`)))
667
+ text(pk, `bind.${key}.occupants.${id}`);
668
+ for (const [name, keys] of Object.entries(table(b.knocks, `bind.${key}.knocks`))) {
669
+ const w = `bind.${key}.knocks.${name}`;
670
+ const k = table(keys, w);
671
+ text(k.current, `${w}.current`);
672
+ orNull(k.next, `${w}.next`);
673
+ flag(k.spoke, `${w}.spoke`);
674
+ flag(k.sent, `${w}.sent`);
675
+ whole(k.seq, `${w}.seq`);
676
+ }
677
+ table(b.answered, `bind.${key}.answered`);
678
+ for (const [i, pk] of list(b.minted, `bind.${key}.minted`).entries())
679
+ text(pk, `bind.${key}.minted[${i}]`);
680
+ }
681
+ for (const [pk, heir] of Object.entries(table(p.heirs, "heirs"))) {
682
+ const h = table(heir, `heirs.${pk}`);
683
+ text(h.being, `heirs.${pk}.being`);
684
+ text(h.id, `heirs.${pk}.id`);
685
+ text(h.current, `heirs.${pk}.current`);
686
+ orNull(h.announced, `heirs.${pk}.announced`);
687
+ flag(h.fresh, `heirs.${pk}.fresh`);
688
+ whole(h.mark, `heirs.${pk}.mark`);
689
+ for (const [i, n] of list(h.spent, `heirs.${pk}.spent`).entries())
690
+ whole(n, `heirs.${pk}.spent[${i}]`);
691
+ }
692
+ for (const [pk, gone] of Object.entries(table(p.gone, "gone"))) {
693
+ const g = table(gone, `gone.${pk}`);
694
+ text(g.current, `gone.${pk}.current`);
695
+ orNull(g.announced, `gone.${pk}.announced`);
696
+ }
697
+ orNull(p.public, "public");
698
+ return p;
699
+ }
700
+ function open(memory) {
701
+ const p = memory;
702
+ if (Object.keys(memory).length > 0 && p.version !== VERSION) {
703
+ if (!READS.includes(p.version))
704
+ throw new Error(`partition is version ${p.version}, this ward reads ${READS.join(", ")}`);
705
+ upgrade(p).version = VERSION;
706
+ }
707
+ p.version ??= VERSION;
708
+ p.beings ??= {};
709
+ p.bind ??= {};
710
+ p.heirs ??= {};
711
+ p.gone ??= {};
712
+ p.public ??= null;
713
+ return read(p);
714
+ }
715
+ var at = (rec, key) => Object.hasOwn(rec, key) ? rec[key] : void 0;
716
+ var put = (rec, key, value) => {
717
+ Object.defineProperty(rec, key, { value, writable: true, enumerable: true, configurable: true });
718
+ return value;
719
+ };
720
+ var drop = (rec, key) => {
721
+ if (Object.hasOwn(rec, key))
722
+ delete rec[key];
723
+ };
724
+ var emptyCells = () => ({ standings: {}, occupants: {} });
725
+ var emptyBind = () => ({ standings: {}, occupants: {}, knocks: {}, answered: {}, minted: [] });
726
+ var HEAD = "";
727
+ var STANDING = /* @__PURE__ */ new Set(["beings", "bind", "heirs", "gone"]);
728
+ var rowOf = (p, row) => {
729
+ const all = p;
730
+ if (row === HEAD)
731
+ return Object.fromEntries(Object.entries(all).filter(([k]) => k !== "beings" && k !== "bind"));
732
+ const cells = at(p.beings ?? {}, row);
733
+ return cells === void 0 ? void 0 : { cells, bind: at(p.bind ?? {}, row) ?? emptyBind() };
734
+ };
735
+ var rowsIn = (p) => [HEAD, ...Object.keys(p.beings ?? {})];
736
+ var restoreRow = (p, row, value) => {
737
+ const all = p;
738
+ if (row === HEAD) {
739
+ for (const k of Object.keys(all))
740
+ if (!STANDING.has(k))
741
+ drop(all, k);
742
+ for (const [k, v] of Object.entries(value ?? {}))
743
+ if (!STANDING.has(k))
744
+ put(all, k, v);
745
+ return;
746
+ }
747
+ if (value === void 0) {
748
+ drop(p.beings ?? {}, row);
749
+ drop(p.bind ?? {}, row);
750
+ return;
751
+ }
752
+ const into = (rows, kept) => {
753
+ const guarded = at(rows, row);
754
+ if (guarded === void 0)
755
+ return void put(rows, row, kept);
756
+ const held2 = unguarded(guarded);
757
+ for (const k of Object.keys(held2))
758
+ drop(held2, k);
759
+ for (const [k, v] of Object.entries(kept))
760
+ put(held2, k, v);
761
+ };
762
+ into(p.beings ?? {}, value.cells ?? emptyCells());
763
+ };
764
+ var fromRows = (rows) => {
765
+ const out = { ...rows[HEAD] ?? {} };
766
+ const beings = {};
767
+ const bind = {};
768
+ let any = Object.keys(out).length > 0;
769
+ for (const [row, value] of Object.entries(rows)) {
770
+ if (row === HEAD)
771
+ continue;
772
+ put(beings, row, value.cells);
773
+ put(bind, row, value.bind);
774
+ any = true;
775
+ }
776
+ if (!any)
777
+ return {};
778
+ out.beings = beings;
779
+ out.bind = bind;
780
+ return out;
781
+ };
782
+
783
+ // ../quo/dist/ward/heirs.js
784
+ var SPAN = 64;
785
+ var Heirs = class {
786
+ #p;
787
+ #wrote;
788
+ constructor(p, wrote2 = () => {
789
+ }) {
790
+ this.#p = p;
791
+ this.#wrote = wrote2;
792
+ }
793
+ open(heir, being, id) {
794
+ this.#p.heirs[heir] = { being, id, current: heir, announced: null, fresh: true, mark: 0, spent: [] };
795
+ this.#wrote();
796
+ }
797
+ // The id was removed. The heir is forgotten, and its last keys are kept
798
+ // apart, bounded, so that whoever still holds them hears `removed` at the
799
+ // door, once they have signed as themselves, and nobody else hears a thing.
800
+ close(heir) {
801
+ const h = this.#p.heirs[heir];
802
+ if (!h)
803
+ return;
804
+ delete this.#p.heirs[heir];
805
+ this.#p.gone[heir] = { current: h.current, announced: h.announced };
806
+ const keys = Object.keys(this.#p.gone);
807
+ for (const old of keys.slice(0, Math.max(0, keys.length - GONE)))
808
+ delete this.#p.gone[old];
809
+ this.#wrote();
810
+ }
811
+ // May `by` speak for a relation she removed? True only for the keys the
812
+ // door held when the id went, which nobody but their holder has.
813
+ gone(heir, by) {
814
+ const g = this.#p.gone[heir];
815
+ return !!g && (by === g.current || by === g.announced);
816
+ }
817
+ // May `by` speak for this heir? Returns the record if so, null if not.
818
+ // Does not write: the caller verifies the signature first, then settles.
819
+ admits(heir, by) {
820
+ const h = this.#p.heirs[heir];
821
+ if (!h)
822
+ return null;
823
+ if (h.fresh)
824
+ return by === h.current ? h : null;
825
+ return by === h.current || by === h.announced ? h : null;
826
+ }
827
+ // Once only. A number above the mark is honoured and moves it; a number
828
+ // inside the span is honoured once and never again; a number at or below
829
+ // the span is refused, because a door that remembered every number ever
830
+ // seen would be a door with unbounded memory. Counting starts where
831
+ // strangers must agree: the first legal number is one.
832
+ //
833
+ // The number rides inside the signed body, so bytes caught on the road
834
+ // carry the number they were sent under and are refused as themselves. A
835
+ // caller who means to ask again asks again, under the next number, and is
836
+ // heard: retry and fire-and-forget stay hers to build. Only the accident
837
+ // and the interception are refused.
838
+ //
839
+ // Writes, so the caller settles it only once the signature has checked out.
840
+ // The same bytes twice are one honoured call and one silence.
841
+ spend(h, seq) {
842
+ if (!Number.isSafeInteger(seq) || seq < 1)
843
+ return false;
844
+ if (seq > h.mark) {
845
+ if (h.mark > 0)
846
+ h.spent.push(h.mark);
847
+ h.mark = seq;
848
+ h.spent = h.spent.filter((past) => past > seq - SPAN);
849
+ return true;
850
+ }
851
+ if (seq === h.mark || seq <= h.mark - SPAN || h.spent.includes(seq))
852
+ return false;
853
+ h.spent.push(seq);
854
+ return true;
855
+ }
856
+ // The signature checked out. Spend the number and settle the keys, or do
857
+ // neither: a call that binds nothing must not burn a number on its way to
858
+ // being refused, or a stranger who cannot be heard would still leave a mark
859
+ // behind her. Every write below this line is one that is going to hold.
860
+ // This is where a knock is judged and where `unannounced` is said. settle
861
+ // below is the writing alone, and it judges nothing: one owner for the
862
+ // question, one for the answer.
863
+ honour(h, by, next, seq) {
864
+ if (h.fresh && (next === null || next === h.current))
865
+ return "unannounced";
866
+ if (!this.spend(h, seq))
867
+ return "repeated";
868
+ this.settle(h, by, next);
869
+ this.#wrote();
870
+ return true;
871
+ }
872
+ // The signature checked out and honour has judged. Settle the keys: a fresh
873
+ // heir rotates at once to what it announced, which honour has already held
874
+ // to being something other than itself, so that the heir dies as it speaks;
875
+ // a current key replaces its announcement; an announced key becomes current.
876
+ settle(h, by, next) {
877
+ if (h.fresh) {
878
+ if (next === null)
879
+ return;
880
+ h.current = next;
881
+ h.announced = null;
882
+ h.fresh = false;
883
+ return;
884
+ }
885
+ if (by === h.announced)
886
+ h.current = by;
887
+ if (next !== null)
888
+ h.announced = next;
889
+ }
890
+ };
891
+
892
+ // ../quo/dist/ward/arithmetic.js
893
+ var arithmetic_exports = {};
894
+ __export(arithmetic_exports, {
895
+ KEY: () => KEY,
896
+ NONCE: () => NONCE,
897
+ SIGNATURE: () => SIGNATURE,
898
+ TAG: () => TAG,
899
+ agree: () => agree,
900
+ box: () => box,
901
+ concat: () => concat,
902
+ decrypt: () => decrypt,
903
+ derive: () => derive,
904
+ encrypt: () => encrypt,
905
+ heldKeys: () => heldKeys,
906
+ hex: () => hex2,
907
+ sameBytes: () => sameBytes,
908
+ sealingPair: () => sealingPair,
909
+ sha256: () => sha256,
910
+ sign: () => sign,
911
+ signingPair: () => signingPair,
912
+ smallOrder: () => smallOrder,
913
+ unbox: () => unbox,
914
+ unhex: () => unhex,
915
+ verify: () => verify
916
+ });
917
+ var subtle2 = () => {
918
+ const s = globalThis.crypto?.subtle;
919
+ if (!s)
920
+ throw new Error("this terrain has no crypto.subtle: a ward needs a secure context");
921
+ return s;
922
+ };
923
+ var KEY = 32;
924
+ var SIGNATURE = 64;
925
+ var NONCE = 12;
926
+ var TAG = 16;
927
+ var SEAL_INFO = new TextEncoder().encode("quo-seal");
928
+ var SALT = new Uint8Array(0);
929
+ var ED_SECRET = unhex("302e020100300506032b657004220420");
930
+ var X_SECRET = unhex("302e020100300506032b656e04220420");
931
+ var ED = { name: "Ed25519" };
932
+ var X = { name: "X25519" };
933
+ var HEX = Array.from({ length: 256 }, (_, at3) => at3.toString(16).padStart(2, "0"));
934
+ function hex2(bytes) {
935
+ let out = "";
936
+ for (const byte of new Uint8Array(bytes))
937
+ out += HEX[byte];
938
+ return out;
939
+ }
940
+ function unhex(text3) {
941
+ const out = new Uint8Array(text3.length / 2);
942
+ for (let at3 = 0; at3 < out.length; at3 += 1)
943
+ out[at3] = parseInt(text3.slice(at3 * 2, at3 * 2 + 2), 16);
944
+ return out;
945
+ }
946
+ function concat(parts) {
947
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
948
+ let at3 = 0;
949
+ for (const part of parts) {
950
+ out.set(part, at3);
951
+ at3 += part.length;
952
+ }
953
+ return out;
954
+ }
955
+ function sameBytes(a, b) {
956
+ if (a.length !== b.length)
957
+ return false;
958
+ let diff = 0;
959
+ for (let at3 = 0; at3 < a.length; at3 += 1)
960
+ diff |= a[at3] ^ b[at3];
961
+ return diff === 0;
962
+ }
963
+ var SMALL_ORDER = [
964
+ "0100000000000000000000000000000000000000000000000000000000000000",
965
+ "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
966
+ "0000000000000000000000000000000000000000000000000000000000000000",
967
+ "0000000000000000000000000000000000000000000000000000000000000080",
968
+ "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05",
969
+ "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a",
970
+ "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85",
971
+ "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa"
972
+ ].map(unhex);
973
+ var unreduced = (pk) => (pk[31] & 127) === 127 && pk[0] >= 237 && pk.subarray(1, 31).every((b) => b === 255);
974
+ var smallOrder = (pk) => unreduced(pk) || SMALL_ORDER.some((p) => sameBytes(p, pk));
975
+ var key32 = (value, what) => {
976
+ if (!(value instanceof Uint8Array) || value.length !== KEY)
977
+ throw new Error(`${what} is not a 32-byte key`);
978
+ return value;
979
+ };
980
+ var pkcs8 = (prefix, value, what) => concat([prefix, key32(value, what)]);
981
+ var KEYS = 512;
982
+ var imported = /* @__PURE__ */ new Map();
983
+ var keep = (id, make) => {
984
+ const had = imported.get(id);
985
+ if (had !== void 0) {
986
+ imported.delete(id);
987
+ imported.set(id, had);
988
+ return had;
989
+ }
990
+ const made = make();
991
+ made.catch(() => imported.delete(id));
992
+ imported.set(id, made);
993
+ if (imported.size > KEYS)
994
+ imported.delete(imported.keys().next().value);
995
+ return made;
996
+ };
997
+ var heldKeys = () => ({ held: imported.size, bound: KEYS });
998
+ var secretKey = (alg, prefix, value, what, uses) => {
999
+ const bytes = pkcs8(prefix, value, what);
1000
+ return keep(`${alg.name}|${uses.join("+")}|${hex2(bytes)}`, () => subtle2().importKey("pkcs8", bytes, alg, true, uses));
1001
+ };
1002
+ var publicKey = (alg, value, what, uses) => {
1003
+ const bytes = key32(value, what);
1004
+ return keep(`${alg.name}|${uses.join("+")}|pk|${hex2(bytes)}`, () => subtle2().importKey("raw", bytes, alg, true, uses));
1005
+ };
1006
+ var publics = /* @__PURE__ */ new WeakMap();
1007
+ function rawPublic(secret) {
1008
+ const had = publics.get(secret);
1009
+ if (had !== void 0)
1010
+ return had;
1011
+ const read2 = (async () => {
1012
+ const jwk = await subtle2().exportKey("jwk", secret);
1013
+ const binary = atob(jwk.x.replaceAll("-", "+").replaceAll("_", "/"));
1014
+ const out = new Uint8Array(binary.length);
1015
+ for (let at3 = 0; at3 < binary.length; at3 += 1)
1016
+ out[at3] = binary.charCodeAt(at3);
1017
+ return out;
1018
+ })();
1019
+ publics.set(secret, read2);
1020
+ return read2;
1021
+ }
1022
+ async function sha256(...parts) {
1023
+ return new Uint8Array(await subtle2().digest("SHA-256", concat(parts)));
1024
+ }
1025
+ async function signingPair(seed2) {
1026
+ const secret = await secretKey(ED, ED_SECRET, seed2, "seed", ["sign"]);
1027
+ return { secret: Uint8Array.from(seed2), pk: Uint8Array.from(await rawPublic(secret)) };
1028
+ }
1029
+ async function sealingPair(seed2) {
1030
+ const secret = await secretKey(X, X_SECRET, seed2, "seed", ["deriveBits"]);
1031
+ return { secret: Uint8Array.from(seed2), pk: Uint8Array.from(await rawPublic(secret)) };
1032
+ }
1033
+ async function sign(message, secret) {
1034
+ const key = await secretKey(ED, ED_SECRET, secret, "secret", ["sign"]);
1035
+ return new Uint8Array(await subtle2().sign(ED, key, message));
1036
+ }
1037
+ async function verify(message, signature, pk) {
1038
+ if (!(signature instanceof Uint8Array) || signature.length !== SIGNATURE)
1039
+ return false;
1040
+ if (!(pk instanceof Uint8Array) || pk.length !== KEY || smallOrder(pk))
1041
+ return false;
1042
+ try {
1043
+ return await subtle2().verify(ED, await publicKey(ED, pk, "pk", ["verify"]), signature, message);
1044
+ } catch {
1045
+ return false;
1046
+ }
1047
+ }
1048
+ async function agree(secret, peerPk) {
1049
+ const key = await secretKey(X, X_SECRET, secret, "secret", ["deriveBits"]);
1050
+ const peer = await publicKey(X, peerPk, "padlock", []);
1051
+ const shared = new Uint8Array(await subtle2().deriveBits({ name: X.name, public: peer }, key, KEY * 8));
1052
+ if (shared.every((b) => b === 0))
1053
+ throw new Error("dead agreement");
1054
+ return shared;
1055
+ }
1056
+ async function derive(secret, label, bytes) {
1057
+ const material = await subtle2().importKey("raw", secret, "HKDF", false, ["deriveBits"]);
1058
+ return new Uint8Array(await subtle2().deriveBits({ name: "HKDF", hash: "SHA-256", salt: SALT, info: label }, material, bytes * 8));
1059
+ }
1060
+ async function cipherKey(shared, use) {
1061
+ const out = await derive(shared, SEAL_INFO, KEY + NONCE);
1062
+ return { key: await subtle2().importKey("raw", out.subarray(0, KEY), "AES-GCM", false, [use]), nonce: out.subarray(KEY) };
1063
+ }
1064
+ async function encrypt(shared, plaintext, aad) {
1065
+ const { key, nonce } = await cipherKey(shared, "encrypt");
1066
+ return new Uint8Array(await subtle2().encrypt({ name: "AES-GCM", iv: nonce, additionalData: key32(aad, "aad"), tagLength: TAG * 8 }, key, plaintext));
1067
+ }
1068
+ async function decrypt(shared, ciphertext, aad) {
1069
+ if (ciphertext.length < TAG)
1070
+ throw new Error("short input");
1071
+ const { key, nonce } = await cipherKey(shared, "decrypt");
1072
+ return new Uint8Array(await subtle2().decrypt({ name: "AES-GCM", iv: nonce, additionalData: key32(aad, "aad"), tagLength: TAG * 8 }, key, ciphertext));
1073
+ }
1074
+ async function box(inside, padlock, seed2) {
1075
+ const ephemeral = await sealingPair(seed2);
1076
+ const shared = await agree(ephemeral.secret, padlock);
1077
+ return { bytes: concat([ephemeral.pk, await encrypt(shared, inside, ephemeral.pk)]), ephemeral };
1078
+ }
1079
+ async function unbox(bytes, padlockSecret) {
1080
+ if (bytes.length <= KEY)
1081
+ throw new Error("short input");
1082
+ const ephemeralPk = bytes.subarray(0, KEY);
1083
+ const shared = await agree(padlockSecret, ephemeralPk);
1084
+ return decrypt(shared, bytes.subarray(KEY), ephemeralPk);
1085
+ }
1086
+
1087
+ // ../quo/dist/ward/seal.js
1088
+ var utf8 = new TextEncoder();
1089
+ var text2 = new TextDecoder();
1090
+ var WARD_SIGN = new TextEncoder().encode("quo-ward-sign");
1091
+ var WARD_SEAL = new TextEncoder().encode("quo-ward-seal");
1092
+ var SIZE = 1024 * 1024;
1093
+ async function wardKey(seed2) {
1094
+ const seed32 = typeof seed2 === "string" ? await sha256(utf8.encode(seed2)) : seed2.length === KEY ? seed2 : await sha256(seed2);
1095
+ const s = await signingPair(await derive(seed32, WARD_SIGN, KEY));
1096
+ const p = await sealingPair(await derive(seed32, WARD_SEAL, KEY));
1097
+ return { sign: s.secret, padlock: p.secret, signPk: s.pk, padlockPk: p.pk, pk: hex2(s.pk) + hex2(p.pk) };
1098
+ }
1099
+ var wardSignPk = (pk) => unhex(pk.slice(0, KEY * 2));
1100
+ var wardPadlock = (pk) => unhex(pk.slice(KEY * 2));
1101
+ var isWardPk = (pk) => typeof pk === "string" && /^[0-9a-f]{128}$/.test(pk);
1102
+ var isHex = (v) => typeof v === "string" && /^[0-9a-f]{64}$/.test(v);
1103
+ async function beingKey(seed2) {
1104
+ return { seed: hex2(seed2), pk: hex2((await signingPair(seed2)).pk) };
1105
+ }
1106
+ async function sealAsk(to, payload, signer, padlock, seed2) {
1107
+ const body = utf8.encode(JSON.stringify({ to, ...payload }));
1108
+ const inside = concat([body, await sign(body, signer)]);
1109
+ const { bytes, ephemeral } = await box(inside, padlock, seed2);
1110
+ return { bytes, ephemeral: ephemeral.secret };
1111
+ }
1112
+ async function openAsk(bytes, padlockSecret) {
1113
+ try {
1114
+ if (!(bytes instanceof Uint8Array) || bytes.length < 1 || bytes.length > SIZE)
1115
+ return null;
1116
+ const inside = await unbox(bytes, padlockSecret);
1117
+ if (inside.length <= SIGNATURE)
1118
+ return null;
1119
+ const body = inside.subarray(0, inside.length - SIGNATURE);
1120
+ const signature = inside.subarray(inside.length - SIGNATURE);
1121
+ const payload = JSON.parse(text2.decode(body));
1122
+ const to = payload.to;
1123
+ if (to !== null && !isHex(to))
1124
+ return null;
1125
+ if (!isHex(payload.by))
1126
+ return null;
1127
+ if (payload.next !== null && !isHex(payload.next))
1128
+ return null;
1129
+ if (payload.method !== void 0 && typeof payload.method !== "string")
1130
+ return null;
1131
+ if (payload.args !== void 0 && (payload.args === null || typeof payload.args !== "object" || Array.isArray(payload.args)))
1132
+ return null;
1133
+ if (payload.args !== void 0 && cellFault(payload.args, "args") !== null)
1134
+ return null;
1135
+ if (!Number.isSafeInteger(payload.seq) || payload.seq < 1)
1136
+ return null;
1137
+ if (payload.hops !== void 0 && (!Number.isSafeInteger(payload.hops) || payload.hops < 0))
1138
+ return null;
1139
+ return { to, payload, body, signature, ephemeralPk: bytes.subarray(0, KEY) };
1140
+ } catch {
1141
+ return null;
1142
+ }
1143
+ }
1144
+ var verifyAsk = (a, pk) => verify(a.body, a.signature, unhex(pk));
1145
+ async function sealReply(reply, ephemeralPk, wardSign, seed2) {
1146
+ const body = utf8.encode(JSON.stringify(reply));
1147
+ return (await box(concat([body, await sign(body, wardSign)]), ephemeralPk, seed2)).bytes;
1148
+ }
1149
+ async function openReply(bytes, ephemeralSecret, signPk) {
1150
+ try {
1151
+ if (!(bytes instanceof Uint8Array) || bytes.length > SIZE)
1152
+ return null;
1153
+ const inside = await unbox(bytes, ephemeralSecret);
1154
+ if (inside.length <= SIGNATURE)
1155
+ return null;
1156
+ const body = inside.subarray(0, inside.length - SIGNATURE);
1157
+ if (!await verify(body, inside.subarray(inside.length - SIGNATURE), signPk))
1158
+ return null;
1159
+ const reply = JSON.parse(text2.decode(body));
1160
+ if (reply === null || typeof reply !== "object" || Array.isArray(reply))
1161
+ return null;
1162
+ const r = reply;
1163
+ if (r.silence === true)
1164
+ return { silence: true };
1165
+ if (Object.hasOwn(r, "quo"))
1166
+ return isDoorWord(r.quo) ? { quo: r.quo } : null;
1167
+ if (!Object.hasOwn(r, "object") || r.object === void 0)
1168
+ return null;
1169
+ if (r.seen !== null && !isHex(r.seen))
1170
+ return null;
1171
+ return { object: r.object, seen: r.seen };
1172
+ } catch {
1173
+ return null;
1174
+ }
1175
+ }
1176
+
1177
+ // ../quo/dist/ward/allowance.js
1178
+ var DEFAULT = { time: 3e4 };
1179
+ var CEILING = { time: 3e5 };
1180
+ function allow(wanted, ceiling = CEILING, base = DEFAULT) {
1181
+ const whole2 = (n, fallback) => {
1182
+ const w = typeof n === "number" ? Math.floor(n) : NaN;
1183
+ return Number.isFinite(w) && w > 0 ? w : fallback;
1184
+ };
1185
+ return { time: Math.min(whole2(wanted?.time, base.time), ceiling.time) };
1186
+ }
1187
+ var spent = (a) => !(Number.isSafeInteger(a.time) && a.time > 0);
1188
+ var LATE = /* @__PURE__ */ Symbol("late");
1189
+ async function within(ms, work, rang2) {
1190
+ let timer;
1191
+ const bell = new Promise((ring) => {
1192
+ timer = setTimeout(() => {
1193
+ rang2?.();
1194
+ ring(LATE);
1195
+ }, ms);
1196
+ });
1197
+ try {
1198
+ return await Promise.race([work, bell]);
1199
+ } finally {
1200
+ clearTimeout(timer);
1201
+ void work.then(() => {
1202
+ }, () => {
1203
+ });
1204
+ }
1205
+ }
1206
+
1207
+ // ../quo/dist/ward/door.js
1208
+ var SILENCE = { silence: true };
1209
+ var said = (quo) => ({ quo });
1210
+ async function answered2(door, asker, method, args, bound) {
1211
+ const threw = bound ? said("threw") : SILENCE;
1212
+ let out;
1213
+ try {
1214
+ out = await door.being.answer(asker, method, args);
1215
+ } catch {
1216
+ return threw;
1217
+ }
1218
+ if (out === void 0 || isSilence(out))
1219
+ return SILENCE;
1220
+ if (isWord(out))
1221
+ return threw;
1222
+ if (cellFault(out, "answer") !== null)
1223
+ return threw;
1224
+ return { object: out, seen: null };
1225
+ }
1226
+ async function arrive(door, asker, method, args, bound) {
1227
+ const reply = await answered2(door, asker, method, args, bound);
1228
+ if (method === void 0 || !("object" in reply))
1229
+ return reply;
1230
+ return { object: reply.object, seen: await seen(door, asker) };
1231
+ }
1232
+ async function seen(door, asker) {
1233
+ try {
1234
+ const bp = await door.being.answer(asker);
1235
+ if (isSilence(bp) || isWord(bp))
1236
+ return null;
1237
+ if (cellFault(bp, "blueprint") !== null)
1238
+ return null;
1239
+ return await digest(bp);
1240
+ } catch {
1241
+ return null;
1242
+ }
1243
+ }
1244
+ function makeDoor(key, heirs, doors, publicKey2, random, keep2) {
1245
+ const judge = async (bytes) => {
1246
+ const a = await openAsk(bytes, key.padlock);
1247
+ if (!a)
1248
+ return null;
1249
+ const { to, payload, ephemeralPk } = a;
1250
+ const refuse2 = () => ({ reply: SILENCE, ephemeralPk, heard: false });
1251
+ if (spent({ time: payload.time }) || payload.hops === 0)
1252
+ return refuse2();
1253
+ const args = payload.args ?? {};
1254
+ if (to === null) {
1255
+ const pk = publicKey2();
1256
+ const pub = pk !== null ? doors.get(pk) : void 0;
1257
+ if (!pub || !await verifyAsk(a, payload.by))
1258
+ return refuse2();
1259
+ return { reply: await arrive(pub, {}, payload.method, args, false), ephemeralPk, heard: false };
1260
+ }
1261
+ const h = heirs.admits(to, payload.by);
1262
+ if (!h) {
1263
+ if (!heirs.gone(to, payload.by) || !await verifyAsk(a, payload.by))
1264
+ return refuse2();
1265
+ return { reply: said("removed"), ephemeralPk, heard: true };
1266
+ }
1267
+ if (!await verifyAsk(a, payload.by))
1268
+ return refuse2();
1269
+ if (!heirs.admits(to, payload.by))
1270
+ return heirs.gone(to, payload.by) ? { reply: said("removed"), ephemeralPk, heard: true } : refuse2();
1271
+ const door = doors.get(h.being);
1272
+ if (!door)
1273
+ return { reply: said("absent"), ephemeralPk, heard: true };
1274
+ if (!at(door.cells.occupants, h.id))
1275
+ return { reply: said("removed"), ephemeralPk, heard: true };
1276
+ const honoured = heirs.honour(h, payload.by, payload.next, payload.seq);
1277
+ if (honoured !== true)
1278
+ return { reply: said(honoured), ephemeralPk, heard: true };
1279
+ return { reply: await arrive(door, { id: h.id }, payload.method, args, true), ephemeralPk, heard: true };
1280
+ };
1281
+ return async function door(bytes) {
1282
+ const out = await judge(bytes).catch(() => null);
1283
+ let reply = out?.reply ?? SILENCE;
1284
+ const heard = out?.heard ?? false;
1285
+ if (!await keep2().catch(() => false))
1286
+ reply = heard ? said("threw") : SILENCE;
1287
+ try {
1288
+ return { bytes: await sealReply(reply, out?.ephemeralPk ?? lid(bytes, random), key.sign, random(32)), heard };
1289
+ } catch {
1290
+ return { bytes: await sealReply(SILENCE, (await sealingPair(random(32))).pk, key.sign, random(32)), heard };
1291
+ }
1292
+ };
1293
+ }
1294
+ function lid(bytes, random) {
1295
+ if (bytes instanceof Uint8Array && bytes.length >= KEY)
1296
+ return bytes.subarray(0, KEY);
1297
+ return random(KEY);
1298
+ }
1299
+
1300
+ // ../quo/dist/ward/stance.js
1301
+ var CALLS = ["knock", "take", "remove"];
1302
+ function buildStance(inside, key, cells, bind) {
1303
+ const named = (method) => method === void 0 || typeof method === "string";
1304
+ const reserved = (id) => RESERVED_IDS.includes(id) || CALLS.includes(id) || unkeepable(id);
1305
+ const nameOf = (inv) => inv.heir === void 0 ? `public:${inv.ward}` : `${inv.ward}:${inv.heir}`;
1306
+ const valid = (inv) => {
1307
+ const i = inv;
1308
+ if (!i || typeof i !== "object" || !isWardPk(i.ward))
1309
+ return false;
1310
+ if (i.heir === void 0)
1311
+ return i.secret === void 0;
1312
+ return isHex(i.heir) && isHex(i.secret);
1313
+ };
1314
+ const lanes = /* @__PURE__ */ new Map();
1315
+ const lane = (name, run) => {
1316
+ const mine = (lanes.get(name) ?? Promise.resolve()).then(run, run);
1317
+ const quiet = mine.then(() => {
1318
+ }, () => {
1319
+ });
1320
+ lanes.set(name, quiet);
1321
+ void quiet.then(() => {
1322
+ if (lanes.get(name) === quiet)
1323
+ lanes.delete(name);
1324
+ });
1325
+ return mine;
1326
+ };
1327
+ const bounded = async (wanted, work) => {
1328
+ let late = false;
1329
+ const out = await within(allow(wanted).time, work(() => !late), () => {
1330
+ late = true;
1331
+ });
1332
+ return out === LATE ? word("late") : out;
1333
+ };
1334
+ const read2 = (r) => {
1335
+ if (isSilence(r) || isWord(r))
1336
+ return { answer: r, door: false, seen: null };
1337
+ if ("silence" in r)
1338
+ return { answer: silence, door: true, seen: null };
1339
+ if ("quo" in r)
1340
+ return { answer: word(r.quo), door: true, seen: null };
1341
+ return { answer: r.object, door: true, seen: r.seen };
1342
+ };
1343
+ const honoured = (r) => r.door && !(isWord(r.answer) && wordOf(r.answer) !== "threw");
1344
+ const answered4 = (r) => r.door && !isSilence(r.answer) && !isWord(r.answer);
1345
+ const send = async (keys, method, args, wanted, rec, live = () => true) => {
1346
+ const r = read2(await inside.send(bind, keys, method, args, wanted));
1347
+ if (rec && method !== void 0 && r.seen !== null && live())
1348
+ rec.seen = r.seen;
1349
+ return r.answer;
1350
+ };
1351
+ const knocking = async (inv, method, args, wanted, live = () => true) => {
1352
+ const name = nameOf(inv);
1353
+ const taken = takenAs(name);
1354
+ if (taken !== void 0) {
1355
+ const keys2 = at(bind.standings, taken);
1356
+ if (!keys2)
1357
+ return word("dropped");
1358
+ return lane(`ask:${taken}`, () => asking(taken, keys2, at(cells.standings, taken), method, args, wanted, live));
1359
+ }
1360
+ const k = bind.knocks[name] ??= { current: (await inside.mintKey(bind)).seed, next: null, spoke: false, sent: false, seq: 0 };
1361
+ prune(bind.knocks, bind.answered);
1362
+ if (inv.heir !== void 0 && !k.spoke && k.sent) {
1363
+ const own = { ward: inv.ward, heir: inv.heir, current: k.current, next: k.next, seq: k.seq };
1364
+ const r2 = read2(await inside.send(bind, own, method, args, wanted));
1365
+ k.seq = own.seq;
1366
+ k.current = own.current;
1367
+ k.next = own.next;
1368
+ inside.wrote();
1369
+ if (!r2.door) {
1370
+ if (!isSilence(r2.answer))
1371
+ return r2.answer;
1372
+ } else if (!isSilence(r2.answer)) {
1373
+ k.spoke = true;
1374
+ if (answered4(r2))
1375
+ bind.answered[name] = true;
1376
+ return r2.answer;
1377
+ }
1378
+ }
1379
+ const first = inv.heir !== void 0 && !k.spoke;
1380
+ const keys = first ? { ward: inv.ward, heir: inv.heir, current: inv.secret, next: k.current, seq: k.seq } : { ward: inv.ward, heir: inv.heir ?? null, current: k.current, next: k.next, seq: k.seq };
1381
+ const r = read2(await inside.send(bind, keys, method, args, wanted));
1382
+ k.sent = true;
1383
+ k.seq = keys.seq;
1384
+ if (!first) {
1385
+ k.current = keys.current;
1386
+ k.next = keys.next;
1387
+ }
1388
+ if (honoured(r))
1389
+ k.spoke = true;
1390
+ if (answered4(r))
1391
+ bind.answered[name] = true;
1392
+ inside.wrote();
1393
+ return r.answer;
1394
+ };
1395
+ const keyName = (k) => k.heir === null ? `public:${k.ward}` : `${k.ward}:${k.heir}`;
1396
+ const takenAs = (name) => Object.keys(bind.standings).find((id) => keyName(bind.standings[id]) === name);
1397
+ const gone = () => !inside.live();
1398
+ const calls = {
1399
+ knock: async (inv, method, args = {}, wanted) => {
1400
+ if (gone())
1401
+ return word("dropped");
1402
+ if (!named(method))
1403
+ return unreached();
1404
+ if (!valid(inv))
1405
+ return word("invitation");
1406
+ const id = takenAs(nameOf(inv));
1407
+ if (id !== void 0)
1408
+ return standingAt(id).ask(method, args, wanted);
1409
+ return bounded(wanted, (live) => lane(`knock:${nameOf(inv)}`, () => live() ? knocking(inv, method, args, wanted, live) : Promise.resolve(word("late"))));
1410
+ },
1411
+ // Take consumes. Until now the relation lived in the knock record, under
1412
+ // the ward and the heir; from now it lives in the standing, under her id.
1413
+ // It never lives in both: two records seeded from one relation are two
1414
+ // lanes speaking for one line of keys and one count, and whichever spoke
1415
+ // last holds it while the other is refused. She would read that refusal as
1416
+ // the far being falling quiet, which is a thing a sovereign being may do,
1417
+ // so the loss would look exactly like an answer she is owed nothing of.
1418
+ // One relation, one home, and the invitation is spent: knocking it again
1419
+ // is another consumed it, and taking it again births nothing.
1420
+ //
1421
+ // So it is spoken on the relation's lane, like every other word spoken
1422
+ // for it. A take reads the count and the keys a knock writes, and copies
1423
+ // them into the standing. Off the lane it reads them between a knock's
1424
+ // send and its answer: the standing is born holding a number the far door
1425
+ // is about to honour and a key it is about to rotate past, and her first
1426
+ // ask on it is refused. The relation she took correctly would be dead
1427
+ // because a second knock on the same invitation was careless.
1428
+ take: async (id, inv) => {
1429
+ if (gone() || !valid(inv))
1430
+ return null;
1431
+ const name = nameOf(inv);
1432
+ return lane(`knock:${name}`, () => {
1433
+ if (!bind.answered[name])
1434
+ return Promise.resolve(null);
1435
+ if (reserved(id) || at(cells.standings, id) || at(cells.occupants, id))
1436
+ return Promise.resolve(null);
1437
+ const k = bind.knocks[name];
1438
+ put(bind.standings, id, { ward: inv.ward, heir: inv.heir ?? null, current: k.current, next: k.next, seq: k.seq });
1439
+ put(cells.standings, id, { id, digest: null, blueprint: null, seen: null });
1440
+ delete bind.knocks[name];
1441
+ delete bind.answered[name];
1442
+ inside.wrote();
1443
+ return Promise.resolve(id);
1444
+ });
1445
+ },
1446
+ remove: (id) => {
1447
+ if (gone())
1448
+ return;
1449
+ drop(cells.standings, id);
1450
+ drop(bind.standings, id);
1451
+ inside.wrote();
1452
+ }
1453
+ };
1454
+ const standingAt = (id) => ({
1455
+ id,
1456
+ // The keys are read now, not when the lane reaches this ask: an ask
1457
+ // issued while the standing stood is answered even if she drops it in
1458
+ // the next line. Waiting for the lane is not a reason to lose it.
1459
+ ask: (method, args = {}, wanted) => {
1460
+ if (gone())
1461
+ return Promise.resolve(word("dropped"));
1462
+ if (!named(method))
1463
+ return Promise.resolve(unreached());
1464
+ const keys = at(bind.standings, id);
1465
+ if (!keys)
1466
+ return Promise.resolve(word("dropped"));
1467
+ const rec = at(cells.standings, id);
1468
+ return bounded(wanted, (live) => lane(`ask:${id}`, () => live() ? asking(id, keys, rec, method, args, wanted, live) : Promise.resolve(word("late"))));
1469
+ }
1470
+ });
1471
+ const asking = async (id, keys, rec, method, args, wanted, live = () => true) => {
1472
+ const out = await send(keys, method, args, wanted, rec, live);
1473
+ if (method === void 0 && !isSilence(out) && !isWord(out)) {
1474
+ if (cellFault(out, "blueprint") !== null)
1475
+ return silence;
1476
+ if (rec && live() && at(cells.standings, id) === rec) {
1477
+ rec.blueprint = isBlueprint(out) ? out : null;
1478
+ rec.digest = rec.seen = await digest(out);
1479
+ }
1480
+ }
1481
+ return out;
1482
+ };
1483
+ const standings = new Proxy(calls, {
1484
+ // Own keys only: `in` would also find every name on Object's prototype,
1485
+ // and a standing she took as `constructor` or `toString` would answer with
1486
+ // Object's, not hers.
1487
+ get: (t, id) => typeof id === "string" && Object.hasOwn(t, id) ? t[id] : typeof id === "string" && at(cells.standings, id) ? standingAt(id) : void 0
1488
+ });
1489
+ return {
1490
+ cells,
1491
+ // A being may make. The ward refuses what it refuses the owner: a key
1492
+ // already booted, a class the harbor does not hold, and a throw at birth
1493
+ // leaves the partition as it was. Reaching into another is still the
1494
+ // owner's alone: what a maker may have is a relation to what she made,
1495
+ // by naming an id, and it is made the way every relation is. The being
1496
+ // made mints an occupant for her maker under the maker's own key, so she
1497
+ // knows who made her by that name and by nothing else; her maker knocks,
1498
+ // takes it under the id she gave, and holds one standing like any other.
1499
+ // Nothing half-lives: a relation that could not be made unmakes the
1500
+ // being, who is a moment old and known to nobody.
1501
+ boot: async (className, made, id) => {
1502
+ if (gone())
1503
+ return null;
1504
+ if (id !== void 0 && (reserved(id) || at(cells.standings, id) || at(cells.occupants, id)))
1505
+ return null;
1506
+ const born = inside.instantiate(className, made);
1507
+ if (born === null || id === void 0)
1508
+ return born;
1509
+ const inv = await inside.relate(born, key);
1510
+ if (inv) {
1511
+ const out = await calls.knock(inv);
1512
+ if (!isSilence(out) && !isWord(out) && await calls.take(id, inv) === id)
1513
+ return born;
1514
+ }
1515
+ inside.unmake(born);
1516
+ return null;
1517
+ },
1518
+ occupants: {
1519
+ // rotation one: the ward mints the occupant's first key and gives the secret away.
1520
+ // it keeps the pk beside the id and nothing else. the invitation IS the key.
1521
+ invite: async (id, notes) => {
1522
+ if (gone() || reserved(id) || at(cells.occupants, id) || at(cells.standings, id))
1523
+ return null;
1524
+ if (notes !== void 0 && cellFault(notes, "notes") !== null)
1525
+ return null;
1526
+ const k = await inside.mintKey(bind);
1527
+ if (reserved(id) || at(cells.occupants, id) || at(cells.standings, id))
1528
+ return null;
1529
+ put(cells.occupants, id, { id, notes: notes ? JSON.parse(JSON.stringify(notes)) : {} });
1530
+ inside.openHeir(k.pk, key, id);
1531
+ put(bind.occupants, id, k.pk);
1532
+ inside.wrote();
1533
+ return { ward: inside.pk, heir: k.pk, secret: k.seed };
1534
+ },
1535
+ remove: (id) => {
1536
+ if (gone())
1537
+ return;
1538
+ drop(cells.occupants, id);
1539
+ const heir = at(bind.occupants, id);
1540
+ drop(bind.occupants, id);
1541
+ if (heir)
1542
+ inside.closeHeir(heir);
1543
+ inside.wrote();
1544
+ }
1545
+ },
1546
+ standings,
1547
+ // A standing at one of the things this device can do. The same three
1548
+ // moves her boot makes, with the harbor's root doing the inviting instead
1549
+ // of a being of this ward: ask the ground for the name, knock with what
1550
+ // comes back, take it under the id she gave. She is handed the id.
1551
+ //
1552
+ // The invitation never reaches her, and that is the difference between
1553
+ // this relation and every other she holds. Her own invitations are hers
1554
+ // to give away, because giving one away is giving away her own relation.
1555
+ // This one is the device's, minted for this ward alone, and a value she
1556
+ // could copy is a capability she could hand to anyone. A being who wants
1557
+ // to lend her device access to another does it in the open, by an ask of
1558
+ // her own that forwards to this standing, where her gate reads who is
1559
+ // asking and she can stop.
1560
+ lend: async (name, id) => {
1561
+ if (gone() || typeof name !== "string" || typeof id !== "string")
1562
+ return null;
1563
+ if (reserved(id) || at(cells.standings, id) || at(cells.occupants, id))
1564
+ return null;
1565
+ const took = await inside.lend(name, async (inv) => {
1566
+ if (gone() || reserved(id) || at(cells.standings, id) || at(cells.occupants, id))
1567
+ return false;
1568
+ const out = await calls.knock(inv);
1569
+ if (isSilence(out) || isWord(out))
1570
+ return false;
1571
+ return await calls.take(id, inv) === id;
1572
+ });
1573
+ return took ? id : null;
1574
+ }
1575
+ };
1576
+ }
1577
+
1578
+ // ../quo/dist/ward/owner.js
1579
+ var str2 = { type: "string" };
1580
+ var OWNER_ASKS = [
1581
+ { name: "boot", description: "boot a being by class name, under a key the owner chooses", input: { type: "object", properties: { key: str2, class: str2 }, required: ["key", "class"] } },
1582
+ { name: "public", description: "mark a booted being as the one public being of the ward, reached by anyone at the bare pk; null takes the mark off and leaves her booted", input: { type: "object", properties: { key: str2 }, required: ["key"] } },
1583
+ { name: "invite", description: "mint an invitation on a being of the ward, under the id she will know the occupant by, with the notes she will read on it; on the ward pk it mints an owner, and only the root may", input: { type: "object", properties: { being: str2, id: str2, notes: { type: "object" } }, required: ["being", "id"] } },
1584
+ {
1585
+ name: "knock",
1586
+ description: "knock for a being of the ward with an invitation, and take the standing under id if answered; being is a key booted, or { boot: class, key } to boot her first",
1587
+ input: { type: "object", properties: { being: { description: "a key, or { boot, key }" }, id: str2, invitation: { type: "object" }, method: str2, args: { type: "object" }, wanted: { type: "object", properties: { time: { type: "number" } } } }, required: ["being", "id", "invitation"] }
1588
+ },
1589
+ { name: "remove", description: "take a relation out of a being of the ward by id, occupant or standing; on the ward pk it unseats an owner, and only the root may", input: { type: "object", properties: { being: str2, id: str2 }, required: ["being", "id"] } },
1590
+ { name: "unboot", description: "take a being out of the ward, with every relation she holds; her occupants hear removed, and the ward itself is refused", input: { type: "object", properties: { being: str2 }, required: ["being"] } },
1591
+ {
1592
+ name: "ask",
1593
+ description: "ask a being of the ward, as the owner; with no being it is the public being asked as nobody, which is what a stranger would hear",
1594
+ input: { type: "object", properties: { being: str2, method: str2, args: { type: "object" }, wanted: { type: "object", properties: { time: { type: "number" } } } } }
1595
+ }
1596
+ ];
1597
+ async function ownerAnswer(w, asker, method, args) {
1598
+ const named = (v) => typeof v === "string" ? v : null;
1599
+ if (method === void 0)
1600
+ return describe(w);
1601
+ if (method === "boot") {
1602
+ const key = named(args.key), className = named(args.class);
1603
+ if (key === null || className === null)
1604
+ return { error: "no such class, or key taken" };
1605
+ const door = w.instantiate(className, key);
1606
+ if (door === "threw")
1607
+ return { error: "threw at birth" };
1608
+ return door ? { booted: door.key } : { error: "no such class, or key taken" };
1609
+ }
1610
+ if (method === "public") {
1611
+ if (args.key === null) {
1612
+ w.setPublic(null);
1613
+ return { public: null };
1614
+ }
1615
+ const key = named(args.key);
1616
+ if (key === null || key === w.pk || !w.doors.has(key))
1617
+ return { error: "no such being" };
1618
+ const standing = w.publicKey();
1619
+ if (standing !== null && standing !== key && w.doors.has(standing))
1620
+ return { error: "a ward has one public being" };
1621
+ w.setPublic(key);
1622
+ return { public: key };
1623
+ }
1624
+ if (method === "invite") {
1625
+ const being = named(args.being), id = named(args.id);
1626
+ if (being === null || id === null)
1627
+ return { error: "no such being" };
1628
+ if (being === w.pk && asker.id !== OWNER)
1629
+ return { error: "no such being" };
1630
+ const notes = args.notes;
1631
+ const door = w.doors.get(being);
1632
+ if (!door)
1633
+ return { error: "no such being" };
1634
+ return await door.stance.occupants.invite(id, notes !== null && typeof notes === "object" && !Array.isArray(notes) ? notes : void 0);
1635
+ }
1636
+ if (method === "knock") {
1637
+ const b = args.being;
1638
+ let door = typeof b === "string" ? w.doors.get(b) : void 0;
1639
+ if (!door && b && typeof b === "object") {
1640
+ const key = named(b.key), className = named(b.boot);
1641
+ if (key === null || className === null)
1642
+ return { error: "no such being" };
1643
+ const made = w.instantiate(className, key);
1644
+ if (made === "threw")
1645
+ return { error: "threw at birth" };
1646
+ door = w.doors.get(key);
1647
+ }
1648
+ const id = named(args.id);
1649
+ if (!door || id === null || door.key === w.pk)
1650
+ return { error: "no such being" };
1651
+ const inv = args.invitation;
1652
+ const out = await door.stance.standings.knock(inv, args.method, args.args ?? {}, args.wanted);
1653
+ if (isSilence(out))
1654
+ return { error: "silence" };
1655
+ if (isWord(out))
1656
+ return { error: wordOf(out) };
1657
+ return { taken: await door.stance.standings.take(id, inv), answer: out };
1658
+ }
1659
+ if (method === "remove") {
1660
+ const being = named(args.being), id = named(args.id);
1661
+ if (being === null || id === null)
1662
+ return { error: "no such being" };
1663
+ if (being === w.pk && asker.id !== OWNER)
1664
+ return { error: "no such being" };
1665
+ const door = w.doors.get(being);
1666
+ if (!door)
1667
+ return { error: "no such being" };
1668
+ if (Object.hasOwn(door.cells.occupants, id))
1669
+ door.stance.occupants.remove(id);
1670
+ else if (Object.hasOwn(door.cells.standings, id))
1671
+ door.stance.standings.remove(id);
1672
+ else
1673
+ return { error: "nothing to remove" };
1674
+ return { removed: id };
1675
+ }
1676
+ if (method === "unboot") {
1677
+ const being = named(args.being);
1678
+ if (being === null || being === w.pk)
1679
+ return { error: "no such being" };
1680
+ const removed = w.unboot(being);
1681
+ return removed === null ? { error: "no such being" } : { unbooted: being, removed };
1682
+ }
1683
+ if (method === "ask") {
1684
+ const being = args.being === void 0 ? null : named(args.being);
1685
+ if (args.being !== void 0 && being === null)
1686
+ return { error: "no such being" };
1687
+ if (args.method !== void 0 && typeof args.method !== "string")
1688
+ return { error: "an ask is named by a word" };
1689
+ const key = being ?? w.publicKey();
1690
+ const door = key === null ? void 0 : w.doors.get(key);
1691
+ if (!door || door.key === w.pk)
1692
+ return { error: "no such being" };
1693
+ const bound = being !== null;
1694
+ const out = await within(allow(args.wanted).time, answered2(door, bound ? { id: OWNER } : {}, args.method, args.args ?? {}, bound));
1695
+ if (out === LATE)
1696
+ return { error: "late" };
1697
+ if ("quo" in out)
1698
+ return { error: out.quo };
1699
+ if (!("object" in out))
1700
+ return { error: "silence" };
1701
+ return out.object;
1702
+ }
1703
+ return { error: "unknown ask" };
1704
+ }
1705
+ async function describe(w) {
1706
+ const beings = {};
1707
+ const asked = [...w.doors].filter(([key]) => key !== w.pk);
1708
+ const digests = await Promise.all(asked.map(async ([, door]) => await within(DEFAULT.time, seen(door, { id: OWNER }))));
1709
+ for (const [i, [key, door]] of asked.entries()) {
1710
+ const d = digests[i];
1711
+ put(beings, key, { class: door.cells.class ?? null, public: w.publicKey() === key, digest: d === LATE ? null : d });
1712
+ }
1713
+ for (const [key, cls] of Object.entries(w.absent()))
1714
+ put(beings, key, { class: cls, public: w.publicKey() === key, digest: null, absent: true });
1715
+ return {
1716
+ asks: OWNER_ASKS,
1717
+ notes: { pk: w.pk, beings }
1718
+ };
1719
+ }
1720
+
1721
+ // ../quo/dist/ward/ward.js
1722
+ async function Ward(ground) {
1723
+ const w = await Self.boot(ground);
1724
+ return { door: w.door, ask: w.ask };
1725
+ }
1726
+ var Self = class _Self {
1727
+ key;
1728
+ pk;
1729
+ g;
1730
+ p;
1731
+ heirs;
1732
+ doors = /* @__PURE__ */ new Map();
1733
+ #birthing = /* @__PURE__ */ new Set();
1734
+ // keys whose constructor is running: taken, though no door and no row say so yet
1735
+ door;
1736
+ ask;
1737
+ static async boot(ground) {
1738
+ return new _Self(ground, await wardKey(ground.seed));
1739
+ }
1740
+ constructor(ground, key) {
1741
+ this.g = ground;
1742
+ this.key = key;
1743
+ this.pk = key.pk;
1744
+ this.p = open(ground.memory);
1745
+ this.#wrote(HEAD);
1746
+ this.heirs = new Heirs(this.p, () => this.#wrote(HEAD));
1747
+ this.door = makeDoor(key, this.heirs, this.doors, () => this.p.public, (n) => ground.random(n), () => this.#keep());
1748
+ this.#boot(this.pk, () => this);
1749
+ for (const [k, c] of Object.entries(this.p.beings)) {
1750
+ const className = c.class;
1751
+ if (k === this.pk || !className)
1752
+ continue;
1753
+ try {
1754
+ this.#boot(k, (stance) => this.g.instantiate(className, stance));
1755
+ } catch {
1756
+ }
1757
+ }
1758
+ this.ask = async (method, args = {}) => {
1759
+ try {
1760
+ const out = await this.answer({ id: OWNER }, method, args);
1761
+ if (!await this.#keep().catch(() => false))
1762
+ return silence;
1763
+ return isSilence(out) || isWord(out) ? silence : out;
1764
+ } catch {
1765
+ return silence;
1766
+ }
1767
+ };
1768
+ }
1769
+ // ---- the ward as a being. asked only by its owners: the root, through
1770
+ // the ask pointer, and its occupants, through the door, whom only the root
1771
+ // can invite. A stranger cannot be a named asker here, because the door
1772
+ // names nobody the ward did not bind.
1773
+ async answer(asker, method, args = {}) {
1774
+ if (asker.id === void 0)
1775
+ return silence;
1776
+ return ownerAnswer({
1777
+ pk: this.pk,
1778
+ doors: this.doors,
1779
+ absent: () => this.#absent(),
1780
+ publicKey: () => this.p.public,
1781
+ instantiate: (className, key) => this.#instantiate(className, key),
1782
+ unboot: (key) => this.#unboot(key),
1783
+ setPublic: (key) => {
1784
+ this.p.public = key;
1785
+ this.#wrote(HEAD);
1786
+ }
1787
+ }, asker, method, args);
1788
+ }
1789
+ // ---- ward functions. on the object. no stance reaches them.
1790
+ // The partition was written, and which row of it: a being by her key, or
1791
+ // the head. The harbor is told, and nothing more: what it does with the
1792
+ // word is its own, and the ward never learns. Every write says its row,
1793
+ // because a harbor told only that something moved must keep all of it, and
1794
+ // a being's row is the one part of a partition that grows without limit.
1795
+ #wrote(row) {
1796
+ this.g.wrote?.(row);
1797
+ }
1798
+ // Whether what was written is kept, asked once at the end of an arrival
1799
+ // and never during one. A harbor that keeps nothing was never going to
1800
+ // refuse anything, so its silence here is yes: a ward driven with no store
1801
+ // under it answers exactly as it did before a harbor could say no.
1802
+ async #keep() {
1803
+ return this.g.keep ? await this.g.keep() : true;
1804
+ }
1805
+ // The rows no door holds this run: beings whose class threw at birth or
1806
+ // is not the harbor's to give. Key to class.
1807
+ #absent() {
1808
+ const out = {};
1809
+ for (const [k, c] of Object.entries(this.p.beings))
1810
+ if (k !== this.pk && !this.doors.has(k))
1811
+ put(out, k, c.class ?? null);
1812
+ return out;
1813
+ }
1814
+ // A new being under a key nobody holds. A key with a row in the partition
1815
+ // is booted, whether a door holds it this run or not: an absent being's
1816
+ // row is hers, with every relation in it, and no class is booted over it.
1817
+ // A throw at birth is that boot's refusal and nothing half-lives; the
1818
+ // ward says which, since the owner hears objects.
1819
+ #instantiate(className, key) {
1820
+ if (typeof key !== "string" || typeof className !== "string" || !key || this.doors.has(key) || at(this.p.beings, key) || this.#birthing.has(key))
1821
+ return null;
1822
+ let door;
1823
+ this.#birthing.add(key);
1824
+ try {
1825
+ door = this.#boot(key, (stance) => this.g.instantiate(className, stance));
1826
+ } catch {
1827
+ return "threw";
1828
+ } finally {
1829
+ this.#birthing.delete(key);
1830
+ }
1831
+ if (!door)
1832
+ return null;
1833
+ unguarded(door.cells).class = className;
1834
+ this.#wrote(key);
1835
+ return door;
1836
+ }
1837
+ // The inverse of boot, and the only way a being leaves a ward. Her
1838
+ // relations go first and by the same calls she would have used herself,
1839
+ // so every occupant hears `removed` at the door from the keys it kept,
1840
+ // rather than meeting a being who is simply not there any more. What is
1841
+ // left after that is her cells and her bind table, and they go with her:
1842
+ // a row naming a being no door holds would be read on the next restart
1843
+ // and boot her again. An absent being leaves the same way: she has no
1844
+ // stance to speak for her, so the ward closes her heirs itself.
1845
+ #unboot(key) {
1846
+ if (key === this.pk)
1847
+ return null;
1848
+ const door = this.doors.get(key);
1849
+ const cells = door?.cells ?? at(this.p.beings, key);
1850
+ if (!cells)
1851
+ return null;
1852
+ const ids = (r) => r !== null && typeof r === "object" ? Object.keys(r) : [];
1853
+ const occupants = ids(cells.occupants), standings = ids(cells.standings);
1854
+ if (door) {
1855
+ for (const id of occupants)
1856
+ door.stance.occupants.remove(id);
1857
+ for (const id of standings)
1858
+ door.stance.standings.remove(id);
1859
+ } else {
1860
+ const bind = at(this.p.bind, key);
1861
+ for (const heir of Object.values(bind?.occupants ?? {}))
1862
+ this.heirs.close(heir);
1863
+ }
1864
+ if (this.p.public === key)
1865
+ this.p.public = null;
1866
+ drop(this.p.beings, key);
1867
+ drop(this.p.bind, key);
1868
+ this.doors.delete(key);
1869
+ this.#wrote(key);
1870
+ this.#wrote(HEAD);
1871
+ return [...occupants, ...standings];
1872
+ }
1873
+ // Nothing is written until there is somebody to write it for: a class the
1874
+ // harbor does not know makes no being, and a boot that made nobody must
1875
+ // leave the partition as it found it.
1876
+ //
1877
+ // `make` is who to build with the stance this raises. Two callers and two
1878
+ // makers: the ground's `instantiate` for a being of the ward, and the ward
1879
+ // itself, which is the first being in its own map and is already made.
1880
+ #boot(key, make) {
1881
+ const cells = guardCells(at(this.p.beings, key) ?? emptyCells(), () => this.#wrote(key));
1882
+ const bind = at(this.p.bind, key) ?? emptyBind();
1883
+ const stance = buildStance({
1884
+ pk: this.pk,
1885
+ // This stance, not merely this key: unboot and boot again under the
1886
+ // same key makes a new being, and the old stance is not hers.
1887
+ live: () => this.doors.get(key)?.stance === stance,
1888
+ mintKey: (b) => this.#mintKey(b, key),
1889
+ openHeir: (heir, being2, id) => this.heirs.open(heir, being2, id),
1890
+ closeHeir: (heir) => this.heirs.close(heir),
1891
+ send: (b, keys, method, args, wanted) => this.#send(b, keys, method, args, wanted, key),
1892
+ // A throw at birth is null to her, not a throw in her method: she asked
1893
+ // for a being and got none, and her own answer is still hers to give.
1894
+ instantiate: (className, k) => {
1895
+ const made = this.#instantiate(className, k);
1896
+ return made && made !== "threw" ? made.key : null;
1897
+ },
1898
+ // The two halves of a maker's way back, each the ward's own call: the
1899
+ // being made mints an occupant for her maker, and a relation that
1900
+ // never came about takes her out again.
1901
+ relate: async (k, id) => await this.doors.get(k)?.stance.occupants.invite(id) ?? null,
1902
+ unmake: (k) => void this.#unboot(k),
1903
+ // What this device lends this ward's beings. The ward knocks and takes
1904
+ // inside the harbor's own call and reads nothing in the value: which
1905
+ // names there are, which ward may ask for one, and what becomes of one
1906
+ // she did not take, is the harbor's and no word of the ward.
1907
+ lend: async (name, take) => await this.g.lend?.(name, take) ?? false,
1908
+ wrote: () => this.#wrote(key)
1909
+ }, key, cells, bind);
1910
+ const being = make(stance);
1911
+ if (!being || typeof being.answer !== "function")
1912
+ return null;
1913
+ put(this.p.beings, key, cells);
1914
+ put(this.p.bind, key, bind);
1915
+ const door = { key, cells, bind, stance, being };
1916
+ this.doors.set(key, door);
1917
+ this.#wrote(key);
1918
+ return door;
1919
+ }
1920
+ async #mintKey(bind, row) {
1921
+ const k = await beingKey(this.g.random(32));
1922
+ bind.minted.push(k.pk);
1923
+ if (bind.minted.length > MINTED)
1924
+ bind.minted.splice(0, bind.minted.length - MINTED);
1925
+ this.#wrote(row);
1926
+ return k;
1927
+ }
1928
+ // One send for every door, the ward's own included. Mine: never leaves.
1929
+ // Not mine: carry. Signs as the standing's current key, announces its
1930
+ // next, seals to the far ward, opens the reply with the ephemeral secret,
1931
+ // and rotates to the announced key once the far door has answered under
1932
+ // the current one.
1933
+ async #send(bind, keys, method, args, wanted, row) {
1934
+ const lower = this.g.calling?.();
1935
+ try {
1936
+ return await this.#sent(bind, keys, method, args, wanted, row);
1937
+ } finally {
1938
+ lower?.();
1939
+ }
1940
+ }
1941
+ async #sent(bind, keys, method, args, wanted, row) {
1942
+ const budget = allow(wanted);
1943
+ const by = (await beingKey(unhex(keys.current))).pk;
1944
+ if (keys.next === null && keys.heir !== null)
1945
+ keys.next = (await this.#mintKey(bind, row)).seed;
1946
+ const next = keys.next === null ? null : (await beingKey(unhex(keys.next))).pk;
1947
+ const seq = keys.seq += 1;
1948
+ this.#wrote(row);
1949
+ if (args === null || typeof args !== "object" || Array.isArray(args) || cellFault(args, "args") !== null)
1950
+ return unreached();
1951
+ let sealed;
1952
+ try {
1953
+ sealed = await sealAsk(keys.heir, { by, next, seq, time: budget.time, method, args }, unhex(keys.current), wardPadlock(keys.ward), this.g.random(32));
1954
+ } catch {
1955
+ return unreached();
1956
+ }
1957
+ const { bytes, ephemeral } = sealed;
1958
+ const carried2 = (keys.ward === this.pk ? this.door(new Uint8Array(bytes)).then((r) => r.bytes) : this.g.carry(keys.ward, bytes)).then((b) => b, () => void 0);
1959
+ const out = await within(budget.time, carried2);
1960
+ if (out === LATE)
1961
+ return word("late");
1962
+ if (out === void 0)
1963
+ return unreached();
1964
+ const reply = await openReply(out, ephemeral, wardSignPk(keys.ward));
1965
+ if (!reply)
1966
+ return silence;
1967
+ if ("object" in reply && keys.next !== null) {
1968
+ keys.current = keys.next;
1969
+ keys.next = null;
1970
+ }
1971
+ this.#wrote(row);
1972
+ return reply;
1973
+ }
1974
+ };
1975
+
1976
+ // dock/notifications.ts
1977
+ var seed = () => globalThis.crypto.getRandomValues(new Uint8Array(32));
1978
+ var Notifications = class extends Faculty {
1979
+ // The banner key, as hex: the seed it was made from, kept so the pair can
1980
+ // be made again, and the public half a device hands out.
1981
+ static cells = { watched: {}, minted: 0, banner: null };
1982
+ static asks = {
1983
+ notify: { description: "show the human a line", input: { type: "object", properties: { title: { type: "string" }, body: { type: "string" } }, required: ["title"] } },
1984
+ banner: { description: "this device's banner key, the public half, which a world seals a notification to", input: { type: "object" } }
1985
+ };
1986
+ notify(_args) {
1987
+ return Promise.resolve({ error: "this box shows no notifications" });
1988
+ }
1989
+ // The pair, minted once and kept. Every call after the first is the same
1990
+ // key, because a device that minted a new one on every ask would be a
1991
+ // device whose worlds all seal to a key it has forgotten.
1992
+ async key() {
1993
+ if (typeof this.cells.banner !== "string") {
1994
+ this.cells.banner = arithmetic_exports.hex(seed());
1995
+ await this.plant(await arithmetic_exports.sealingPair(arithmetic_exports.unhex(this.cells.banner)));
1996
+ }
1997
+ return arithmetic_exports.sealingPair(arithmetic_exports.unhex(this.cells.banner));
1998
+ }
1999
+ async banner(_args) {
2000
+ return { banner: arithmetic_exports.hex((await this.key()).pk) };
2001
+ }
2002
+ // Where this terrain's notification process reads the secret half. The
2003
+ // base plants nothing, which is right for a terrain that draws its own
2004
+ // banners in the same process; a phone overrides it and writes into the
2005
+ // keychain group its extension shares with the app.
2006
+ plant(_pair) {
2007
+ return Promise.resolve();
2008
+ }
2009
+ };
2010
+
2011
+ // dock/webpush.ts
2012
+ var subtle3 = () => globalThis.crypto.subtle;
2013
+ var ES256 = { name: "ECDSA", namedCurve: "P-256" };
2014
+ var b64url = (bytes) => {
2015
+ let binary = "";
2016
+ for (const b of bytes) binary += String.fromCharCode(b);
2017
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
2018
+ };
2019
+ var unb64url = (v) => {
2020
+ const binary = atob(v.replaceAll("-", "+").replaceAll("_", "/"));
2021
+ const out = new Uint8Array(binary.length);
2022
+ for (let at3 = 0; at3 < binary.length; at3 += 1) out[at3] = binary.charCodeAt(at3);
2023
+ return out;
2024
+ };
2025
+ function publicOf(key) {
2026
+ if (typeof key.x !== "string" || typeof key.y !== "string") throw new Error("a vapid key is a P-256 JWK");
2027
+ const x = unb64url(key.x), y = unb64url(key.y);
2028
+ const raw = new Uint8Array(65);
2029
+ raw[0] = 4;
2030
+ raw.set(x, 1);
2031
+ raw.set(y, 33);
2032
+ return b64url(raw);
2033
+ }
2034
+ async function mintVapid() {
2035
+ const pair = await subtle3().generateKey(ES256, true, ["sign", "verify"]);
2036
+ const key = await subtle3().exportKey("jwk", pair.privateKey);
2037
+ return { key, publicKey: publicOf(key) };
2038
+ }
2039
+ var OVERHEAD = 16 + 4 + 1 + 65 + 1 + 16;
2040
+
2041
+ // dock/push.ts
2042
+ var isToken = (v) => v !== null && typeof v === "object" && !Array.isArray(v) && typeof v.kind === "string";
2043
+ var SEAL = 4096;
2044
+ function banner(o) {
2045
+ const text3 = (v) => typeof v === "string" && v.trim() ? v.trim() : "";
2046
+ const title = text3(o.title) || text3(o.from) || "quo";
2047
+ const body = text3(o.body) || text3(o.said) || Object.entries(o).filter(([k]) => k !== "title" && k !== "from").map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
2048
+ return { title, body };
2049
+ }
2050
+ async function sealBanner(line, key) {
2051
+ if (!/^[0-9a-f]{64}$/.test(key)) return null;
2052
+ try {
2053
+ const boxed = await arithmetic_exports.box(new TextEncoder().encode(JSON.stringify(line)), arithmetic_exports.unhex(key), globalThis.crypto.getRandomValues(new Uint8Array(32)));
2054
+ const wire = arithmetic_exports.hex(boxed.bytes);
2055
+ return wire.length > SEAL ? null : wire;
2056
+ } catch {
2057
+ return null;
2058
+ }
2059
+ }
2060
+ var root = (_occ, asker) => asker.id === OWNER;
2061
+ var Push = class extends Faculty {
2062
+ // What she keeps, beside every faculty's: the world's doorbell key, a P-256
2063
+ // JWK she mints herself the first time a terrain asks for one, so a Web
2064
+ // Push is signed under a key that lives with the wards and in no file; and
2065
+ // the bell service a phone's ring is handed to when its token names none,
2066
+ // a row the root keeps, since whose service it is belongs to an estate.
2067
+ static cells = { watched: {}, minted: 0, key: null, service: null };
2068
+ static asks = {
2069
+ ring: { description: "wake a device: its token, the banner sealed to it, or the line itself where the ground encrypts it", input: { type: "object", properties: { token: { type: "object" }, sealed: { type: "string" }, line: { type: "object", properties: { title: { type: "string" }, body: { type: "string" } }, required: ["title", "body"] } }, required: ["token"] } },
2070
+ kinds: { description: "which kinds this box can ring", input: { type: "object" } },
2071
+ keep: { description: "the bell service a phone is rung through when its token names none; null forgets it", input: { type: "object", properties: { service: { type: ["string", "null"] } }, required: ["service"] }, for: root }
2072
+ };
2073
+ // The doorbell key, minted once and kept. A terrain asks for it when it
2074
+ // serves a web route; a box that never does holds none and rings no tabs.
2075
+ async mint() {
2076
+ if (this.cells.key === null) this.cells.key = (await mintVapid()).key;
2077
+ return this.cells.key;
2078
+ }
2079
+ get key() {
2080
+ return this.cells.key;
2081
+ }
2082
+ get service() {
2083
+ return typeof this.cells.service === "string" ? this.cells.service : void 0;
2084
+ }
2085
+ keep(args) {
2086
+ const service = args.service;
2087
+ if (service !== null && (typeof service !== "string" || !/^https?:\/\/\S+$/.test(service))) return { error: "a bell service is a URL over http or https, or null" };
2088
+ this.cells.service = service === null ? null : service.replace(/\/$/, "");
2089
+ return { service: this.cells.service };
2090
+ }
2091
+ // What this box can ring. A terrain overrides it and builds its rings from
2092
+ // its environment and this being's cells; the base rings nothing.
2093
+ get rings() {
2094
+ return {};
2095
+ }
2096
+ kinds() {
2097
+ return { kinds: Object.keys(this.rings) };
2098
+ }
2099
+ async ring(args) {
2100
+ const token = args.token;
2101
+ if (!isToken(token)) return { error: "a token names its kind" };
2102
+ const sealed = args.sealed;
2103
+ if (sealed !== void 0 && typeof sealed !== "string") return { error: "a seal is text" };
2104
+ if (typeof sealed === "string" && sealed.length > SEAL) return { error: `a seal is at most ${SEAL} bytes` };
2105
+ const said2 = args.line;
2106
+ if (said2 !== void 0 && (said2 === null || typeof said2 !== "object" || Array.isArray(said2) || typeof said2.title !== "string" || typeof said2.body !== "string")) return { error: "a line is a title and a body" };
2107
+ const line = said2;
2108
+ const r = Object.hasOwn(this.rings, token.kind) ? this.rings[token.kind] : void 0;
2109
+ if (!r) return { error: `this box cannot ring a ${token.kind}` };
2110
+ try {
2111
+ await r.ring(token, sealed, line);
2112
+ return { rang: token.kind };
2113
+ } catch (e) {
2114
+ return { error: e instanceof Error ? e.message : "the ring threw" };
2115
+ }
2116
+ }
2117
+ };
2118
+
2119
+ // dock/timer.ts
2120
+ var MINUTE = 6e4;
2121
+ var DAY = 864e5;
2122
+ var Timer = class extends Faculty {
2123
+ static asks = {
2124
+ watch: { description: "wake me when I say I am next due", input: { type: "object", properties: { invitation: { type: "object" }, method: { type: "string" }, args: { type: "object" } }, required: ["invitation", "method"] } },
2125
+ due: { description: "bring my next round forward to this moment", input: { type: "object", properties: { at: { type: "number" } }, required: ["at"] } },
2126
+ now: { description: "this box\u2019s moment", input: { type: "object" } }
2127
+ };
2128
+ #timeout;
2129
+ #stopped = false;
2130
+ #armed = null;
2131
+ #round = null;
2132
+ // The moment she is armed for, or null while she is asleep. For the
2133
+ // device's own code and for a suite; it crosses no door.
2134
+ get armed() {
2135
+ return this.#armed;
2136
+ }
2137
+ // A watcher says she is due sooner than the box knew. It only ever brings
2138
+ // the round forward: what fires is decided at the tick, in the watcher's
2139
+ // own cells, so an arming that moved later on somebody's word would be a
2140
+ // schedule somebody else held silenced. The door is the whole gate here:
2141
+ // a standing at a faculty comes from `lend` alone, and this adds no second
2142
+ // one.
2143
+ due(args) {
2144
+ const at3 = args.at;
2145
+ if (typeof at3 !== "number" || !Number.isFinite(at3)) return { error: "a due carries the moment: { at }" };
2146
+ const when = moment(at3);
2147
+ if (this.#armed === null || when < this.#armed) this.#arm(when);
2148
+ return { armed: this.#armed };
2149
+ }
2150
+ // Whoever was just filed is ticked at once, and what she answers is what
2151
+ // the box arms for. One move covers a box booting, a home moving in and a
2152
+ // watcher born long after the box was up: she is never waiting for a round
2153
+ // nobody scheduled.
2154
+ async welcomed(id) {
2155
+ await this.round(Date.now(), id);
2156
+ }
2157
+ // The arming went off. A moment still well ahead is the day's cap this
2158
+ // class put on its own timeout, so it is armed again rather than fired: a
2159
+ // terrain calls this and never has to know how far out the arming was.
2160
+ async rang() {
2161
+ if (this.#stopped) return [];
2162
+ if (this.#armed !== null && this.#armed - Date.now() > 1e3) {
2163
+ this.arm(this.#armed);
2164
+ return [];
2165
+ }
2166
+ return this.fire(Date.now());
2167
+ }
2168
+ // The moment, read here and handed on. A device's own code may call it at a
2169
+ // moment it names, which is how a suite proves a wake without waiting for
2170
+ // one and how an operator makes a round happen now.
2171
+ fire(now) {
2172
+ return this.round(now);
2173
+ }
2174
+ // One round, and never two: a second while one is in flight is handed the
2175
+ // one already running. What every watcher answered decides the next
2176
+ // arming, and a `due` that landed while the round ran is not lost.
2177
+ round(now, only) {
2178
+ if (this.#round) return this.#round;
2179
+ const mine = this.#run(now, only);
2180
+ this.#round = mine;
2181
+ return mine.finally(() => {
2182
+ if (this.#round === mine) this.#round = null;
2183
+ });
2184
+ }
2185
+ async #run(now, only) {
2186
+ if (only === void 0) this.#arm(null);
2187
+ const woke = await this.woke((w) => ({ ...w.args, now }), only);
2188
+ if (woke === null) {
2189
+ this.stop();
2190
+ return [];
2191
+ }
2192
+ const named = soonest(woke);
2193
+ const said2 = named !== null && named > Date.now() ? named : null;
2194
+ const held2 = this.#armed;
2195
+ const at3 = said2 === null ? held2 : held2 === null ? moment(said2) : Math.min(held2, moment(said2));
2196
+ if (at3 !== null && at3 !== this.#armed) this.#arm(at3);
2197
+ else if (at3 === null && this.#armed !== null) this.#arm(null);
2198
+ return woke;
2199
+ }
2200
+ #arm(when) {
2201
+ if (this.#stopped) return;
2202
+ this.#armed = when;
2203
+ this.arm(when);
2204
+ }
2205
+ // The one thing a terrain says about itself: be woken at this moment, or at
2206
+ // no moment at all. A subclass replaces this and nothing else of her.
2207
+ //
2208
+ // Here it is a timeout, capped at a day and armed again when the cap goes
2209
+ // off. The box stays up for its work and never for this, so the handle is
2210
+ // unreferenced.
2211
+ arm(when) {
2212
+ clearTimeout(this.#timeout);
2213
+ this.#timeout = void 0;
2214
+ if (when === null) return;
2215
+ this.#timeout = setTimeout(() => void this.rang(), Math.min(Math.max(when - Date.now(), 0), DAY));
2216
+ this.#timeout.unref?.();
2217
+ }
2218
+ // A being her ward took out reaches nobody, so nothing is left to wake. The
2219
+ // device calls this when it puts the box down.
2220
+ stop() {
2221
+ this.#stopped = true;
2222
+ this.#armed = null;
2223
+ this.arm(null);
2224
+ }
2225
+ now() {
2226
+ return { now: Date.now() };
2227
+ }
2228
+ };
2229
+ var moment = (at3) => Math.max(at3, Math.floor(at3 / MINUTE) * MINUTE + 500);
2230
+ function soonest(woke) {
2231
+ let at3 = null;
2232
+ for (const w of woke) {
2233
+ if (w.out === null || typeof w.out !== "object") continue;
2234
+ const next = w.out.next;
2235
+ if (typeof next !== "number" || !Number.isFinite(next)) continue;
2236
+ if (at3 === null || next < at3) at3 = next;
2237
+ }
2238
+ return at3;
2239
+ }
2240
+
2241
+ // dock/apns.ts
2242
+ var LIFE = 45 * 60 * 1e3;
2243
+
2244
+ // dock/fcm.ts
2245
+ var LIFE2 = 59 * 60 * 1e3;
2246
+
2247
+ // dock/index.ts
2248
+ var LOCATION = "location";
2249
+ var NOTIFICATIONS = "notifications";
2250
+ var PUSH = "push";
2251
+ var TIMER = "timer";
2252
+ var FACULTIES = { [LOCATION]: "Location", [NOTIFICATIONS]: "Notifications", [PUSH]: "Push", [TIMER]: "Timer" };
2253
+ var FACULTY_CLASSES = { Console, Local, Location, Notifications, Push, Timer };
2254
+ var DOCK = "dock";
2255
+ async function stand(harbor, lent = FACULTIES) {
2256
+ const kept = await harbor.store.load(DOCK);
2257
+ const ward = harbor.wards.get(DOCK) ?? (kept ? await harbor.host(DOCK, kept) : await harbor.create(DOCK, CONSOLE));
2258
+ return standOn(ward, lent);
2259
+ }
2260
+ async function standOn(ward, lent = FACULTIES) {
2261
+ for (const [name, className] of Object.entries(lent)) await ward.ask("boot", { key: name, class: className });
2262
+ await ward.ask("boot", { key: CONSOLE, class: "Console" });
2263
+ for (const name of Object.keys(lent)) {
2264
+ const inv = await ward.ask("invite", { being: name, id: `${CONSOLE}:${name}` });
2265
+ if (!isInvitation(inv)) continue;
2266
+ await ward.ask("knock", { being: CONSOLE, id: name, invitation: inv });
2267
+ }
2268
+ await ward.save?.();
2269
+ return lending(ward);
2270
+ }
2271
+ function lending(ward) {
2272
+ return async (name, take) => {
2273
+ const id = `lent:${fresh()}`;
2274
+ const out = await ward.ask("invite", { being: name, id });
2275
+ if (!isInvitation(out)) return false;
2276
+ const took = await take(out);
2277
+ if (!took) await ward.ask("remove", { being: name, id });
2278
+ await ward.save?.();
2279
+ return took;
2280
+ };
2281
+ }
2282
+ var fresh = () => Array.from(globalThis.crypto.getRandomValues(new Uint8Array(8))).map((b) => b.toString(16).padStart(2, "0")).join("");
2283
+
2284
+ // beings/user.ts
2285
+ var DESK = "desk";
2286
+ var BELL = "bell";
2287
+ var HOME = "home";
2288
+ var isDesk = (occ) => occ?.id === DESK;
2289
+ var isDevice = (occ) => occ !== void 0 && occ.id !== DESK;
2290
+ var rootMinted = (occ) => isDevice(occ) && occ?.notes.client === void 0;
2291
+ var client = (occ) => typeof occ?.notes.client === "string" ? occ.notes.client : rootMinted(occ) ? occ.id : null;
2292
+ var MINTINGS = ["wake", "reach", "mint", "revoke"];
2293
+ var OUTBOX = 32;
2294
+ var rang = (out) => !isSilence(out) && !isWord(out) && out !== null && typeof out === "object" && !Array.isArray(out) && out.rang !== void 0 && out.rang !== false;
2295
+ var held = (occ) => isDesk(occ) ? { wake: true, reach: true, mint: true, revoke: true } : { wake: occ?.notes.wake === true || rootMinted(occ), reach: occ?.notes.reach === true || rootMinted(occ), mint: occ?.notes.mint === true, revoke: occ?.notes.revoke === true };
2296
+ var mayWake = (occ) => held(occ).wake;
2297
+ var mayMint = (occ) => isDesk(occ) || isDevice(occ) && held(occ).mint;
2298
+ var mayRevoke = (occ) => isDesk(occ) || isDevice(occ) && held(occ).revoke;
2299
+ var User = class extends Carrier {
2300
+ static carries(occ) {
2301
+ return isDevice(occ) && held(occ).reach;
2302
+ }
2303
+ // `ways` holds a device's way back that could not be taken when it was
2304
+ // handed over: the knock was unreached, a tab whose pk the far harbor had
2305
+ // not bound yet, a phone in a tunnel. Nothing delivered spends nothing,
2306
+ // so the invitation is tried again at the next push.
2307
+ // The bell standing is plumbing and carries nothing: a device that may
2308
+ // reach what she holds sees acme and the house, and never the envoy that
2309
+ // wakes it. Her ways back are plumbing too, and a way back to a presence
2310
+ // being carries that device's faculties, `to:phone-location-where`, which
2311
+ // is how a model at home learns where the human is.
2312
+ static hidden(id) {
2313
+ return super.hidden(id) || id === PUSH || id.startsWith(`${BELL}:`);
2314
+ }
2315
+ // Her own door is carried to a device that may mint, and to nobody else:
2316
+ // `mint` is the word for a device trusted to change who is in her world,
2317
+ // and owning the world from that device is that trust said in full. A
2318
+ // device that reaches sees acme and the house, and no `home-boot`.
2319
+ static shows(id, occ) {
2320
+ return id !== HOME || held(occ).mint;
2321
+ }
2322
+ static cells = { name: "", reports: [], ways: {}, outbox: {} };
2323
+ // Her standing at the box's push faculty, taken again at every birth: a
2324
+ // standing points at a being and not at a box, and a ward that moved is
2325
+ // handed the new harbor's ground. It is how she rings a browser endpoint,
2326
+ // which is the one kind of doorbell a world holds itself, since an
2327
+ // endpoint answers only to the VAPID secret this world minted. Birth is
2328
+ // synchronous and taking a standing is not, so the promise is kept and
2329
+ // the ring waits on it.
2330
+ //
2331
+ // A box that lends no push rings no tabs, which is what a world with no
2332
+ // web route behind it is.
2333
+ #push;
2334
+ constructor(stance) {
2335
+ super(stance);
2336
+ this.#push = join(this, PUSH);
2337
+ }
2338
+ // A world whose own being she is, is a home: a device reaching her holds
2339
+ // a standing at home and not a membership, and she says so in her notes,
2340
+ // since nothing else in a describe tells a shell which of its worlds is
2341
+ // the human's own. She knows it by the door she holds: the being setup
2342
+ // knocked on the ward's own door as its owner is home's, and a member of
2343
+ // the same class the desk made in an org's ward holds no door and says
2344
+ // nothing.
2345
+ describe(asker) {
2346
+ const bp = super.describe(asker);
2347
+ if (!this.standings[HOME]) return bp;
2348
+ const notes = bp.notes !== null && typeof bp.notes === "object" && !Array.isArray(bp.notes) ? bp.notes : {};
2349
+ return { ...bp, notes: { ...notes, home: true } };
2350
+ }
2351
+ static asks = {
2352
+ hello: { description: "say hello, and hand back an invitation so she can reach you", input: { type: "object", properties: { invitation: { type: "object" } } } },
2353
+ whoami: { description: "who she thinks you are", input: { type: "object" }, for: isDevice },
2354
+ device: { description: "mint an invitation for another device", input: { type: "object", properties: { client: { type: "string" }, wake: { type: "boolean" }, reach: { type: "boolean" }, mint: { type: "boolean" }, revoke: { type: "boolean" } }, required: ["client"] }, for: mayMint },
2355
+ push: { description: "push an object to a device: wake it with an event", input: { type: "object", properties: { client: { type: "string" }, object: { type: "object" } }, required: ["client", "object"] }, for: mayWake },
2356
+ chores: { description: "what the agent may run", input: { type: "object" }, for: (occ) => client(occ) === "agent" },
2357
+ look: { description: "how she is shown", input: { type: "object" }, for: isDevice },
2358
+ page: { description: "her page, as a tree of values", input: { type: "object" }, for: isDevice },
2359
+ name: { description: "what she is called", input: { type: "object", properties: { name: { type: "string" } }, required: ["name"] }, for: rootMinted },
2360
+ devices: { description: "the devices in this world, and which one you are", input: { type: "object" }, for: isDevice },
2361
+ join: { description: "join a world with an invitation: she knocks, hands back a way to reach her, and keeps the standing", input: { type: "object", properties: { invitation: { type: "object", description: "the invitation that world minted" }, name: { type: "string", description: "what to call it here" } }, required: ["invitation", "name"] }, for: mayMint },
2362
+ forget: { description: "revoke a device: drop its way in and her way back to it, in one act", input: { type: "object", properties: { client: { type: "string" } }, required: ["client"] }, for: mayRevoke },
2363
+ report: { description: "what a run of yours found", input: { type: "object", properties: { event: { type: "object" }, result: {} }, required: ["event", "result"] }, for: isDevice },
2364
+ bell: { description: "how to wake this device: a standing at its own doorbell and the banner key it seals to, or a browser push endpoint", input: { type: "object", properties: { invitation: { type: "object" }, banner: { type: "string" }, token: { type: "object" } } }, for: isDevice },
2365
+ missed: { description: "what this device missed while it was unreachable", input: { type: "object" }, for: isDevice }
2366
+ };
2367
+ // Anyone may say hello. A device that hands her an invitation in the args
2368
+ // is taken as a standing under its own id, so she can push to it later;
2369
+ // if it cannot be taken now, it is kept and tried at the next push. The
2370
+ // desk's first hello names her: a being does not know her own key, and the
2371
+ // root's setup knocks with the name it booted her under. Once named, she
2372
+ // keeps it.
2373
+ async hello(args, asker) {
2374
+ if (asker.id !== void 0 && args.invitation && typeof args.invitation === "object" && !Array.isArray(args.invitation)) {
2375
+ await this.wayBack(asker.id, args.invitation);
2376
+ }
2377
+ if (asker.id === DESK && !this.cells.name && typeof args.name === "string" && /^[\w.-]{1,80}$/.test(args.name)) this.cells.name = args.name;
2378
+ return { welcome: asker.id ?? null, name: this.cells.name };
2379
+ }
2380
+ // Take a device's way back, or keep it for later when the knock did not
2381
+ // reach. A refusal spends the heir and is final; the way is dropped.
2382
+ async wayBack(id, inv) {
2383
+ const ways = this.cells.ways;
2384
+ const back = await this.knock(inv);
2385
+ if (!isSilence(back) && !isWord(back)) {
2386
+ await this.take(`to:${id}`, inv);
2387
+ delete ways[id];
2388
+ return true;
2389
+ }
2390
+ if (isWord(back) && wordOf(back) !== "unreached") delete ways[id];
2391
+ else ways[id] = inv;
2392
+ return false;
2393
+ }
2394
+ whoami(_args, asker) {
2395
+ return { id: asker.id ?? null, client: client(this.occupant(asker)) };
2396
+ }
2397
+ // The front desk asks, or a device of hers that may mint; she mints. The
2398
+ // client identity goes into the occupant's notes, and that is what every
2399
+ // gate reads. The three words travel with it, each asked for and each
2400
+ // written only if the asker holds it: devices beget devices, and a device
2401
+ // hands on no more than it was given.
2402
+ async device(args, asker) {
2403
+ const c = typeof args.client === "string" ? args.client : null;
2404
+ if (c === null) return { error: "client is a string" };
2405
+ const mine = held(this.occupant(asker));
2406
+ const inv = await this.invite(c);
2407
+ if (inv === null) return { error: "id taken" };
2408
+ const rec = this.cells.occupants[c];
2409
+ if (rec) {
2410
+ rec.notes.client = c;
2411
+ for (const w of MINTINGS) if (args[w] === true && mine[w]) rec.notes[w] = true;
2412
+ }
2413
+ return inv;
2414
+ }
2415
+ // Her way out and the device's way in are two relations, and Quo keeps
2416
+ // them apart. She joins them herself, here, as her own rule: she does
2417
+ // not reach a device she no longer admits. Without it a revoked device
2418
+ // stops being able to ask her and keeps receiving everything she pushes.
2419
+ async push(args) {
2420
+ const c = typeof args.client === "string" ? args.client : null;
2421
+ if (c === null || !this.cells.occupants[c]) return { error: "no such device, or it gave no way back" };
2422
+ const ways = this.cells.ways;
2423
+ const object = args.object ?? {};
2424
+ if (!this.standings[`to:${c}`] && ways[c]) await this.wayBack(c, ways[c]);
2425
+ const st = this.standings[`to:${c}`];
2426
+ if (!st) return ways[c] ? this.unreached(c, object) : { error: "no such device, or it gave no way back" };
2427
+ const out = await st.ask("notify", object);
2428
+ if (isWord(out) && wordOf(out) === "unreached") return this.unreached(c, object);
2429
+ return isSilence(out) ? { error: "silence" } : isWord(out) ? { error: wordOf(out) } : { pushed: out };
2430
+ }
2431
+ // Nothing was delivered, so the device is asleep, closed or in a tunnel.
2432
+ // The object waits in her outbox under that device, bounded and oldest
2433
+ // out, since a device gone for a month must not grow without end; then
2434
+ // she rings, and the pusher hears both facts. What the ring carries is
2435
+ // the device's token and nothing of the object: the device wakes, dials,
2436
+ // and drains it over its own standing, sealed.
2437
+ async unreached(id, object) {
2438
+ const box2 = this.cells.outbox[id] ??= [];
2439
+ box2.push(object);
2440
+ while (box2.length > OUTBOX) box2.shift();
2441
+ return { unreached: true, rang: await this.ring(id, object) };
2442
+ }
2443
+ // A phone is rung on the standing it handed her, with the line sealed to
2444
+ // the banner key it handed her beside it. Who is ringing is the standing
2445
+ // the ring arrives on, so the ask carries no world, no pk and no token,
2446
+ // and there is nothing in it to forge. A browser is rung on its endpoint
2447
+ // through this box's own push faculty, because an endpoint answers only
2448
+ // to the key this world minted, and the line goes with it unsealed:
2449
+ // Web Push encrypts a body to the subscription's own keys, so a second
2450
+ // seal would hide it from nobody it is not already hidden from.
2451
+ async ring(id, object) {
2452
+ const rec = this.cells.occupants[id];
2453
+ const at3 = this.standings[`${BELL}:${id}`];
2454
+ const key = rec?.notes.banner;
2455
+ if (at3 && typeof key === "string") {
2456
+ const sealed = await sealBanner(banner(object), key);
2457
+ return sealed === null ? false : rang(await at3.ask("ring", { sealed }));
2458
+ }
2459
+ const token = rec?.notes.bell;
2460
+ if (!isToken(token)) return false;
2461
+ await this.#push;
2462
+ const st = this.standings[PUSH];
2463
+ if (!st) return false;
2464
+ return rang(await st.ask("ring", { token, line: banner(object) }));
2465
+ }
2466
+ // A device says how it is woken, and what it says is kept on its own
2467
+ // occupant, so forgetting the device forgets the way to wake it in the
2468
+ // same act.
2469
+ //
2470
+ // A phone or a desk hands a standing at a doorbell of its own, and the
2471
+ // banner key that doorbell never learns. This world holds a way to wake
2472
+ // that one device and nothing it could hand on: no other world rings on
2473
+ // this standing, the device cuts it by itself at the doorbell, and the
2474
+ // platform's token rotating is one write there that no world is told of.
2475
+ //
2476
+ // A browser hands its endpoint instead and needs no doorbell: an endpoint
2477
+ // is rung only by whoever holds the VAPID secret the subscription was
2478
+ // made under, and that secret is this world's own.
2479
+ //
2480
+ // A store's token never arrives here. The same token at every world is
2481
+ // the bearer credential the doorbell exists to take away, so one is
2482
+ // refused where a human can read why.
2483
+ async bell(args, asker) {
2484
+ const c = client(this.occupant(asker));
2485
+ const rec = c === null ? void 0 : this.cells.occupants[c];
2486
+ if (c === null || !rec) return { error: "no client" };
2487
+ if (args.invitation === void 0 && args.token === void 0) {
2488
+ const at3 = this.standings[`${BELL}:${c}`];
2489
+ return { kept: at3 && typeof rec.notes.banner === "string" ? "doorbell" : isToken(rec.notes.bell) ? "web" : null };
2490
+ }
2491
+ if (isInvitation(args.invitation)) {
2492
+ const key = args.banner;
2493
+ if (typeof key !== "string" || !/^[0-9a-f]{64}$/.test(key)) return { error: "a banner key is thirty two bytes as hex" };
2494
+ const id = `${BELL}:${c}`;
2495
+ this.standings.remove(id);
2496
+ const out = await this.knock(args.invitation);
2497
+ if (isSilence(out) || isWord(out)) return { error: isSilence(out) ? "silence" : wordOf(out) };
2498
+ if (await this.take(id, args.invitation) === null) return { error: "that invitation was refused" };
2499
+ rec.notes.banner = key;
2500
+ delete rec.notes.bell;
2501
+ return { kept: "doorbell" };
2502
+ }
2503
+ if (!isToken(args.token)) return { error: "a token names its kind" };
2504
+ if (args.token.kind !== "web") return { error: "a device hands a standing at its own doorbell, never a store token" };
2505
+ rec.notes.bell = args.token;
2506
+ return { kept: "web" };
2507
+ }
2508
+ // The device is back. It drains what it missed and composes what the
2509
+ // human sees from these objects, which crossed sealed and were never on
2510
+ // a push service. Emptied as it is handed over: an ask that answered is
2511
+ // an ask that arrived, and a device that lost the answer has lost one
2512
+ // notification, not its relation.
2513
+ missed(_args, asker) {
2514
+ const c = client(this.occupant(asker));
2515
+ if (c === null) return { error: "no client" };
2516
+ const box2 = this.cells.outbox;
2517
+ const out = box2[c] ?? [];
2518
+ delete box2[c];
2519
+ return { missed: out };
2520
+ }
2521
+ // She joins a world. This is the day a homeless org gains a home: an org
2522
+ // whose whole existence was standings in other orgs' worlds boots a home
2523
+ // on a box it trusts, and that home joins each of those from there.
2524
+ // Nothing is converted and no id changes, because from the far world's
2525
+ // side this home is one more thing of hers holding a standing, exactly as
2526
+ // her phone is: her device there minted the invitation with `device`, and
2527
+ // this is the other half, the knock.
2528
+ //
2529
+ // It is a device that may mint who asks it, and that needs no word of its
2530
+ // own: `mint` is what shows her own door to a device, so one that holds
2531
+ // it can boot and invite in her world already, and adding one standing to
2532
+ // what she carries is less than that. In practice such a device holds
2533
+ // `reach` as well, since a device that joined a world it could not then
2534
+ // see would be a device doing something for somebody else. She hands back a way for the far being
2535
+ // to reach her, as a device does at hello, and a far being who refuses it
2536
+ // is joined all the same.
2537
+ async join(args) {
2538
+ const inv = args.invitation;
2539
+ if (!isInvitation(inv)) return { error: "that is not an invitation" };
2540
+ const name = typeof args.name === "string" ? args.name.trim() : "";
2541
+ if (!/^[\w.-]{1,80}$/.test(name) || name === HOME || name === PUSH) return { error: "a name is a word, and not home or the push" };
2542
+ if (this.standings[name]) return { error: "name taken" };
2543
+ const back = `from:${name}`;
2544
+ const mine = await this.invite(back);
2545
+ if (mine === null) return { error: "name taken" };
2546
+ const out = await this.knock(inv, "hello", { invitation: mine });
2547
+ if (isSilence(out) || isWord(out)) {
2548
+ this.occupants.remove(back);
2549
+ return { error: isSilence(out) ? "silence" : wordOf(out) };
2550
+ }
2551
+ if (out !== null && typeof out === "object" && !Array.isArray(out) && "error" in out) this.occupants.remove(back);
2552
+ const took = await this.take(name, inv);
2553
+ if (took === null) {
2554
+ this.occupants.remove(back);
2555
+ return { error: "that invitation was refused" };
2556
+ }
2557
+ return { joined: name };
2558
+ }
2559
+ // Her devices, by the names they were given, with what each was granted
2560
+ // and which of them is asking. Without this a human cannot use `forget`
2561
+ // or `push` at all: both name a device, and nobody who has just lost a
2562
+ // phone remembers what it was called. It is read-only and says nothing
2563
+ // secret; a device is a name and three words, and the keys are in the
2564
+ // relations rather than here.
2565
+ // People-shaped rows, since a page says devices are people: a name, and
2566
+ // a line that says what each may do and which one is asking.
2567
+ devices(_args, asker) {
2568
+ const me = client(this.occupant(asker));
2569
+ return Object.entries(this.cells.occupants).filter(([id]) => id !== DESK).map(([id, rec]) => {
2570
+ const notes = rec.notes;
2571
+ const name = typeof notes.client === "string" ? notes.client : id;
2572
+ const has = MINTINGS.filter((w) => held(rec)[w]);
2573
+ const line = [...name === me ? ["this one"] : [], ...has.length ? [`may ${has.join(", ")}`] : []].join(" \xB7 ");
2574
+ return { name, line, client: name, ...name === me ? { you: true } : {}, ...has.length ? { may: has } : {} };
2575
+ });
2576
+ }
2577
+ // Revocation is one act at her, because only she knows both ids. A side
2578
+ // that had to remove two relations by hand could leave half of one
2579
+ // standing, and every side would have to remember which half.
2580
+ forget(args) {
2581
+ const c = typeof args.client === "string" ? args.client : null;
2582
+ if (c === null) return { error: "client is a string" };
2583
+ this.occupants.remove(c);
2584
+ this.standings.remove(`to:${c}`);
2585
+ this.standings.remove(`${BELL}:${c}`);
2586
+ delete this.cells.ways[c];
2587
+ delete this.cells.outbox[c];
2588
+ return { forgot: c };
2589
+ }
2590
+ chores() {
2591
+ return { chores: ["census", "report"] };
2592
+ }
2593
+ // How a device shows her: her name as the title, and what each ask is
2594
+ // called. Values only; the screen and the model side each read their part.
2595
+ look() {
2596
+ return {
2597
+ name: this.cells.name || "you",
2598
+ order: ["whoami", "devices", "device", "join", "push", "forget", "name"],
2599
+ asks: { whoami: { title: "who am I", readOnly: true }, devices: { title: "your devices", readOnly: true }, device: { title: "invite a device" }, join: { title: "join a world" }, push: { title: "wake a device" }, forget: { title: "take a device out of your world", destructive: true }, name: { title: "call her" } }
2600
+ };
2601
+ }
2602
+ // Her page for a device: her name, who this device is, the worlds she
2603
+ // holds as sections, and the three things a human does here by hand,
2604
+ // bringing her next device in, waking one and naming her. The invitation
2605
+ // `device` answers is a way in, and the side paints it as the link it is,
2606
+ // since a being does not know where her page lives. `hello` and `report` are wiring, a
2607
+ // device's first word and an agent's callback; they stay in her describe
2608
+ // under the gate and off her page, since a page is presentation and the
2609
+ // gate is permission. A device that may not do a thing sees no form for
2610
+ // it, however the page names it.
2611
+ page(_args, asker) {
2612
+ return {
2613
+ kind: "stack",
2614
+ of: [
2615
+ { kind: "text", text: this.cells.name || "you", role: "title" },
2616
+ { kind: "text", text: `on ${client(this.occupant(asker)) ?? "a device"}`, role: "quiet" },
2617
+ { kind: "text", text: "your devices", role: "label" },
2618
+ { kind: "answer", ask: "devices", as: "people" },
2619
+ // every standing she carries as its section, and the door of her
2620
+ // own world as a door: seven owner asks are a page of their own
2621
+ ...Object.keys(this.cells.standings).sort((a, b) => a === HOME ? 1 : b === HOME ? -1 : a.localeCompare(b)).map((id) => id === HOME ? { kind: "standing", id, as: "door" } : { kind: "standing", id }),
2622
+ { kind: "form", ask: "device" },
2623
+ { kind: "form", ask: "join" },
2624
+ { kind: "form", ask: "push" },
2625
+ { kind: "form", ask: "forget" },
2626
+ { kind: "form", ask: "name" }
2627
+ ]
2628
+ };
2629
+ }
2630
+ // A device the root minted may say what she is called: the root trusts
2631
+ // it with its id and its reach already, and a being does not know her own
2632
+ // key. A name is a word; she keeps the last one given.
2633
+ name(args) {
2634
+ const name = typeof args.name === "string" ? args.name.trim() : "";
2635
+ if (!/^[\w.-]{1,80}$/.test(name)) return { error: "a name is a word" };
2636
+ this.cells.name = name;
2637
+ return { named: name };
2638
+ }
2639
+ // A device that ran something for her says what it found. Kept, so that
2640
+ // whoever renders her can show it; the ask itself is the callback.
2641
+ report(args, asker) {
2642
+ this.cells.reports.push({ from: asker.id ?? null, event: args.event ?? null, result: args.result ?? null });
2643
+ return { reported: true };
2644
+ }
2645
+ };
2646
+
2647
+ // beings/desk.ts
2648
+ var Desk = class extends Being {
2649
+ static asks = {
2650
+ hello: { description: "how to get in", input: { type: "object" } },
2651
+ send: { description: "send a way in to an address of yours", input: { type: "object", properties: { kind: { type: "string" }, to: { type: "string" } }, required: ["kind", "to"] } },
2652
+ device: { description: "trade a proof for a device invitation", input: { type: "object", properties: { proof: { type: "object" } }, required: ["proof"] } }
2653
+ };
2654
+ // The box's way in, taken at every birth as any being takes what her box
2655
+ // lends. A box that lends none, a tab or the edge, leaves her holding
2656
+ // nothing under that id and the kind is not offered.
2657
+ constructor(stance) {
2658
+ super(stance);
2659
+ void join(this, LOCAL, void 0, WAY(LOCAL));
2660
+ }
2661
+ // The ids of the ways in she holds, in the order they were placed.
2662
+ get ways() {
2663
+ const out = [];
2664
+ for (const id of Object.keys(this.cells.standings)) {
2665
+ const kind = kindOf(id);
2666
+ if (kind !== null && this.standings[id]) out.push({ id, kind });
2667
+ }
2668
+ return out;
2669
+ }
2670
+ // Before she is described, she learns what her ways in can be asked. A
2671
+ // standing's blueprint is filled by an empty ask and by nothing else, and
2672
+ // a way in placed on her a moment ago has none yet; each is asked once,
2673
+ // and a way in that will not describe stays a kind she can only verify.
2674
+ async answer(asker, method, args = {}) {
2675
+ if (method === void 0) {
2676
+ for (const w of this.ways) if (this.cells.standings[w.id]?.blueprint === null) await this.standings[w.id]?.ask();
2677
+ }
2678
+ return await super.answer(asker, method, args);
2679
+ }
2680
+ // The kinds are the ways in she holds and change with them, so the form a
2681
+ // stranger sees is shaped now rather than declared once: `send` is there
2682
+ // only where a way in says it can be asked to send, and it names the kinds
2683
+ // that can. A world with none shows no such form, and a link is the only
2684
+ // way in, as it always was.
2685
+ describe(asker) {
2686
+ const bp = super.describe(asker);
2687
+ const kinds = this.ways.filter((w) => this.cells.standings[w.id]?.blueprint?.asks.some((a) => a.name === "send")).map((w) => w.kind);
2688
+ const asks = bp.asks.flatMap((a) => {
2689
+ if (a.name !== "send") return [a];
2690
+ if (kinds.length === 0) return [];
2691
+ return [{ ...a, input: { type: "object", properties: { kind: { type: "string", enum: kinds, default: kinds[0] }, to: { type: "string" } }, required: ["kind", "to"] } }];
2692
+ });
2693
+ return { ...bp, asks };
2694
+ }
2695
+ // A stranger learns the proof kinds and nothing else. Who lives on this
2696
+ // world is not the caller's to know: the way in names the user, so
2697
+ // nobody at the door ever has to, and a world with a thousand of them
2698
+ // would otherwise hand the whole list to anyone who knocked.
2699
+ hello(_args, asker) {
2700
+ return { desk: true, asker: asker.id ?? null, kinds: this.ways.map((w) => w.kind) };
2701
+ }
2702
+ // A stranger asks for a way in to an address that is hers. Nothing of this
2703
+ // world changes here and nothing is minted: the way in sends, and the
2704
+ // proof that comes back is what admits her.
2705
+ async send(args) {
2706
+ const { kind, to } = args;
2707
+ if (typeof kind !== "string" || typeof to !== "string") return { error: "kind and to are words" };
2708
+ const way = this.standings[WAY(kind)];
2709
+ if (!way) return { error: "no such way in" };
2710
+ const out = await way.ask("send", { to });
2711
+ if (isSilence(out) || isWord(out)) return { error: "no such way in" };
2712
+ return out;
2713
+ }
2714
+ async device(args) {
2715
+ const proof = args.proof;
2716
+ if (!proof || typeof proof !== "object" || Array.isArray(proof) || typeof proof.kind !== "string") return { error: "proof has a kind" };
2717
+ const way = this.standings[WAY(proof.kind)];
2718
+ const said2 = way ? await way.ask("verify", { proof }) : null;
2719
+ const who = said2 === null || isSilence(said2) || isWord(said2) ? null : verified(said2);
2720
+ if (!who) return { error: "refused" };
2721
+ const id = `user:${who.user}`;
2722
+ if (!this.standings[id] && (who.make === true || typeof who.make === "string")) {
2723
+ if (!/^[\w.-]{1,80}$/.test(who.user) || who.user === "desk") return { error: "a user is a word, and not desk" };
2724
+ if (await this.boot(typeof who.make === "string" ? who.make : "User", who.user, id) === null) return { error: "no such user" };
2725
+ await this.standings[id].ask("hello", { name: who.user });
2726
+ }
2727
+ const user = this.standings[id];
2728
+ if (!user) return { error: "no such user" };
2729
+ const inv = await user.ask("device", { client: who.client, ...who.wake === true ? { wake: true } : {}, ...who.reach === true ? { reach: true } : {}, ...who.mint === true ? { mint: true } : {}, ...who.revoke === true ? { revoke: true } : {} });
2730
+ if (isSilence(inv)) return { error: "silence" };
2731
+ if (isWord(inv)) return { error: wordOf(inv) };
2732
+ return inv;
2733
+ }
2734
+ };
2735
+
2736
+ // beings/avatar.ts
2737
+ var USER = "user";
2738
+ var PUSHER = "user:push";
2739
+ var Avatar = class extends Carrier {
2740
+ static cells = { inbox: [] };
2741
+ static asks = {
2742
+ notify: { description: "a push from the user being", input: { type: "object" }, for: (occ) => occ?.id === PUSHER }
2743
+ };
2744
+ // Her sides' ears. One identity is one avatar for life, and two sessions
2745
+ // of that identity are two sides on her at once, so a push goes to every
2746
+ // ear present; each side adds its own on open and removes it on close.
2747
+ // Dropped if there is none.
2748
+ ears = /* @__PURE__ */ new Set();
2749
+ // ---- what the user being may ask her
2750
+ notify(args) {
2751
+ this.cells.inbox.push(args);
2752
+ for (const ear of this.ears) ear(args);
2753
+ return { notified: true };
2754
+ }
2755
+ // ---- what her side calls. Not asks: nobody reaches these through a door.
2756
+ // The credential exchange's last step. `desk` is the desk's public
2757
+ // invitation, `proof` is what the route produced. She knocks the desk as a
2758
+ // stranger, gets an invitation the user being minted, and joins with it. A
2759
+ // knock on a public invitation births nothing, so she holds no standing at
2760
+ // the desk afterwards. If she already holds `user`, this is a reconnect and
2761
+ // nothing is minted.
2762
+ async enter(desk, proof) {
2763
+ if (this.standings[USER]) return this.tools();
2764
+ const inv = await this.knock(desk, "device", { proof });
2765
+ if (isSilence(inv)) return { error: "silence" };
2766
+ if (isWord(inv)) return { error: wordOf(inv) };
2767
+ if (!inv || typeof inv !== "object" || Array.isArray(inv) || "error" in inv) return inv ?? { error: "no invitation" };
2768
+ return this.join(inv);
2769
+ }
2770
+ // Knock with an invitation, hand the far being one back so she can push,
2771
+ // and take her as `user`. The only moment a standing is born here. A being
2772
+ // who answers the hello with an error object is a being who answered: she
2773
+ // is taken all the same, and the way back she did not take is dropped, so
2774
+ // any world's public being may admit a guest, hello or not.
2775
+ async join(invitation) {
2776
+ if (this.standings[USER]) {
2777
+ const had = await this.tools();
2778
+ if (!("error" in had) || had.error !== "removed") return had;
2779
+ this.standings.remove(USER);
2780
+ this.occupants.remove(PUSHER);
2781
+ }
2782
+ const mine = await this.invite(PUSHER);
2783
+ if (mine === null) return { error: "user is taken" };
2784
+ const out = await this.knock(invitation, "hello", { invitation: mine });
2785
+ if (isSilence(out) || isWord(out)) {
2786
+ this.occupants.remove(PUSHER);
2787
+ return { error: isSilence(out) ? "silence" : wordOf(out) };
2788
+ }
2789
+ if (out !== null && typeof out === "object" && !Array.isArray(out) && "error" in out) this.occupants.remove(PUSHER);
2790
+ await this.take(USER, invitation);
2791
+ return this.tools();
2792
+ }
2793
+ // Her describe, as the user being shows it to her. This is the tool list.
2794
+ async tools() {
2795
+ const st = this.standings[USER];
2796
+ if (!st) return { error: "not joined" };
2797
+ const bp = await st.ask();
2798
+ if (isSilence(bp) || isWord(bp)) return { error: isSilence(bp) ? "silence" : wordOf(bp) };
2799
+ return bp;
2800
+ }
2801
+ // One call from the side, one ask on the standing. The three words pass
2802
+ // through untouched; the side renders them.
2803
+ call(name, args = {}, wanted) {
2804
+ const st = this.standings[USER];
2805
+ return st ? st.ask(name, args, wanted) : Promise.resolve({ error: "not joined" });
2806
+ }
2807
+ };
2808
+
2809
+ // beings/presence.ts
2810
+ var Presence = class extends Avatar {
2811
+ // Her world's user being sees the box, and nobody else: the way back she
2812
+ // handed over at the join is the one occupant who may ask through her.
2813
+ static carries(occupant2, _asker) {
2814
+ return occupant2?.id === PUSHER;
2815
+ }
2816
+ // Her standing at the user being is hers to speak through and never to
2817
+ // carry: the user being carries her, so carrying it back would be a
2818
+ // loop, and it is the one standing that is not a faculty.
2819
+ static skips(id) {
2820
+ return id === USER;
2821
+ }
2822
+ // Which names the box lent her, by name, settled after birth.
2823
+ lent;
2824
+ constructor(stance) {
2825
+ super(stance);
2826
+ this.lent = Promise.all(Object.keys(FACULTIES).map(async (name) => [name, await join(this, name)])).then((all) => Object.fromEntries(all));
2827
+ }
2828
+ };
2829
+
2830
+ // beings/courier.ts
2831
+ var MOVER = "mover";
2832
+ var HEX2 = /^(?:[0-9a-f]{2})+$/;
2833
+ var PK = /^[0-9a-f]{128}$/;
2834
+ var invited = (occ) => occ !== void 0;
2835
+ function isMoving(v) {
2836
+ if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
2837
+ const m = v;
2838
+ const object = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
2839
+ return typeof m.world === "string" && PK.test(m.world) && typeof m.at === "string" && typeof m.name === "string" && typeof m.seed === "string" && HEX2.test(m.seed) && object(m.partition) && object(m.record);
2840
+ }
2841
+ var answered3 = (out) => {
2842
+ if (isSilence(out)) return { error: "silence" };
2843
+ if (isWord(out)) return { error: wordOf(out) };
2844
+ if (out === null || typeof out !== "object" || Array.isArray(out)) return { error: "the other device answered nothing" };
2845
+ return out;
2846
+ };
2847
+ var Courier = class extends Being {
2848
+ static asks = {
2849
+ hand: { description: "the ward this device is giving away: its world, where that world lives, and the ward itself", input: { type: "object" }, for: invited },
2850
+ done: { description: "the ward arrived on the other device: delete the copy here", input: { type: "object" }, for: invited }
2851
+ };
2852
+ // The shell holding the device that gives: what to hand over, and what to
2853
+ // do when the other end says it landed. Set while a move is offered and
2854
+ // gone after, so a courier nobody is holding gives nothing.
2855
+ giving;
2856
+ gone;
2857
+ // ---- what the device taking asks
2858
+ async hand() {
2859
+ if (!this.giving) return { error: "this device is giving nothing away" };
2860
+ return await this.giving();
2861
+ }
2862
+ async done() {
2863
+ if (!this.gone) return { error: "this device is giving nothing away" };
2864
+ return await this.gone();
2865
+ }
2866
+ // ---- what the shell taking calls. Not asks: nobody reaches these through a door.
2867
+ // Knock the far courier, take the ward she answers with, and hold her:
2868
+ // the standing is what the second ask goes through, and a knock is spent
2869
+ // the moment it is answered.
2870
+ async fetch(invitation) {
2871
+ const out = answered3(await this.knock(invitation, "hand", {}));
2872
+ if ("error" in out && typeof out.error === "string") return { error: out.error };
2873
+ if (!isMoving(out)) return { error: "the other device answered with something that is not a ward" };
2874
+ if (await this.take(MOVER, invitation) === null) return { error: "this device could not hold the other one" };
2875
+ return out;
2876
+ }
2877
+ // It is here: the other device may delete its copy.
2878
+ async landed() {
2879
+ const standing = this.standings[MOVER];
2880
+ if (!standing) return { error: "this device is not holding the other one" };
2881
+ const out = answered3(await standing.ask("done", {}));
2882
+ if (typeof out.deleted === "string") return { deleted: out.deleted };
2883
+ return { error: typeof out.error === "string" ? out.error : "the other device would not let go" };
2884
+ }
2885
+ };
2886
+
2887
+ // beings/envoy.ts
2888
+ var FRESH = 5 * 6e4;
2889
+
2890
+ // beings/clock.ts
2891
+ var WOKE = `${TIMER}:wake`;
2892
+ var MINUTE2 = 6e4;
2893
+ var HOUR = 36e5;
2894
+ var SPAN2 = 23 * HOUR + 59 * MINUTE2;
2895
+
2896
+ // ../quo/dist/ward/ground.js
2897
+ var maker = (objects, ...registries) => (className, stance) => {
2898
+ const r = registries.find((reg) => reg !== void 0 && Object.hasOwn(reg, className));
2899
+ const C = r?.[className];
2900
+ if (!C)
2901
+ return null;
2902
+ const obj = new C(stance);
2903
+ objects.set(stance.cells, obj);
2904
+ return obj;
2905
+ };
2906
+ var entropy = (n) => globalThis.crypto.getRandomValues(new Uint8Array(n));
2907
+ async function learnPk(w) {
2908
+ const notes = await w.ask();
2909
+ const pk = notes?.notes?.pk;
2910
+ if (typeof pk !== "string")
2911
+ throw new Error("the ward did not say its pk");
2912
+ return pk;
2913
+ }
2914
+
2915
+ // ../quo/dist/harbor/reach.js
2916
+ var never = () => new Promise(() => {
2917
+ });
2918
+ function request(url) {
2919
+ const base = url.replace(/\/$/, "");
2920
+ return {
2921
+ close: () => {
2922
+ },
2923
+ carry: async (pk, bytes) => {
2924
+ let res;
2925
+ const body = new Uint8Array(bytes.length);
2926
+ body.set(bytes);
2927
+ try {
2928
+ res = await fetch(`${base}/${pk}`, { method: "POST", headers: { "content-type": "application/octet-stream", "quo-suite": String(SUITE) }, body });
2929
+ } catch {
2930
+ return void 0;
2931
+ }
2932
+ if (res.status === 404 || res.status >= 400 && res.status < 500 || res.status === 502 || res.status === 503 || res.status === 504)
2933
+ return void 0;
2934
+ if (!res.ok)
2935
+ return never();
2936
+ return new Uint8Array(await res.arrayBuffer());
2937
+ }
2938
+ };
2939
+ }
2940
+ var ASK = 0;
2941
+ var REPLY = 1;
2942
+ var NONE = 2;
2943
+ var SUITE = 1;
2944
+ var REFUSED = 4001;
2945
+ var OPEN = 1;
2946
+ var PENDING = 4096;
2947
+ var Socket = class {
2948
+ line;
2949
+ pending = /* @__PURE__ */ new Map();
2950
+ far = [];
2951
+ // the pks the far side announced
2952
+ #id = 1;
2953
+ #open;
2954
+ constructor(line, deliver, onAnnounce, onClose) {
2955
+ this.line = line;
2956
+ line.binaryType = "arraybuffer";
2957
+ this.#open = line.readyState === OPEN ? Promise.resolve() : new Promise((ok, no) => {
2958
+ line.addEventListener("open", ok);
2959
+ line.addEventListener("error", no);
2960
+ line.addEventListener("close", () => no(new Error("closed before it opened")));
2961
+ });
2962
+ this.#open.catch(() => {
2963
+ });
2964
+ line.addEventListener("message", (e) => {
2965
+ if (typeof e.data === "string") {
2966
+ try {
2967
+ const m = JSON.parse(e.data);
2968
+ if (m.suite !== void 0 && m.suite !== SUITE) {
2969
+ this.far = [];
2970
+ line.close(REFUSED, "suite");
2971
+ return;
2972
+ }
2973
+ if (Array.isArray(m.announce)) {
2974
+ this.far = m.announce.filter((x) => typeof x === "string");
2975
+ onAnnounce(this.far);
2976
+ }
2977
+ } catch {
2978
+ }
2979
+ return;
2980
+ }
2981
+ const buf = e.data instanceof ArrayBuffer ? new Uint8Array(e.data) : ArrayBuffer.isView(e.data) ? new Uint8Array(e.data.buffer, e.data.byteOffset, e.data.byteLength) : void 0;
2982
+ if (!buf || buf.length < 5)
2983
+ return;
2984
+ const kind = buf[0], id = new DataView(buf.buffer, buf.byteOffset, buf.byteLength).getUint32(1);
2985
+ if (kind === ASK) {
2986
+ const pk = hex2(buf.subarray(5, 69));
2987
+ void deliver(pk, new Uint8Array(buf.subarray(69))).then((back) => back, () => void 0).then((back) => {
2988
+ const head = new Uint8Array(5);
2989
+ head[0] = back === void 0 ? NONE : REPLY;
2990
+ new DataView(head.buffer).setUint32(1, id);
2991
+ if (line.readyState === OPEN)
2992
+ line.send(back === void 0 ? head : concat([head, back]));
2993
+ });
2994
+ return;
2995
+ }
2996
+ const waiting = this.pending.get(id);
2997
+ if (!waiting)
2998
+ return;
2999
+ this.pending.delete(id);
3000
+ waiting(kind === REPLY ? new Uint8Array(buf.subarray(5)) : void 0);
3001
+ });
3002
+ line.addEventListener("error", () => {
3003
+ });
3004
+ line.addEventListener("close", (e) => {
3005
+ this.pending.clear();
3006
+ onClose({ code: e?.code, reason: e?.reason });
3007
+ });
3008
+ }
3009
+ // Say which ward pks this side holds.
3010
+ announce(pks) {
3011
+ if (this.line.readyState === OPEN)
3012
+ this.line.send(JSON.stringify({ announce: pks, suite: SUITE }));
3013
+ }
3014
+ async carry(pk, bytes) {
3015
+ try {
3016
+ await this.#open;
3017
+ } catch {
3018
+ return void 0;
3019
+ }
3020
+ if (this.line.readyState !== OPEN)
3021
+ return void 0;
3022
+ const id = this.#id++;
3023
+ const head = new Uint8Array(69);
3024
+ head[0] = ASK;
3025
+ new DataView(head.buffer).setUint32(1, id);
3026
+ head.set(unhex(pk), 5);
3027
+ return new Promise((ok) => {
3028
+ if (this.pending.size >= PENDING)
3029
+ this.pending.delete(this.pending.keys().next().value);
3030
+ this.pending.set(id, ok);
3031
+ this.line.send(concat([head, bytes]));
3032
+ });
3033
+ }
3034
+ close() {
3035
+ this.line.close();
3036
+ }
3037
+ };
3038
+
3039
+ // ../quo/dist/harbor/store.js
3040
+ var values = (p) => JSON.parse(JSON.stringify(p));
3041
+ var rowsOf = (partition, rows) => rows.map((row) => {
3042
+ const value = rowOf(partition, row);
3043
+ return [row, value === void 0 ? void 0 : values(value)];
3044
+ });
3045
+
3046
+ // ../quo/dist/harbor/core.js
3047
+ var DEFAULT_CODE = "classes/index.ts";
3048
+ var PROBE = 1e4;
3049
+ var ANNOUNCE = 64;
3050
+ var PROVING = 8;
3051
+ var announces = /* @__PURE__ */ new WeakMap();
3052
+ var Harbor = class {
3053
+ store;
3054
+ loader;
3055
+ wards = /* @__PURE__ */ new Map();
3056
+ // name -> pointers
3057
+ doors = /* @__PURE__ */ new Map();
3058
+ // ward pk -> door
3059
+ reaches = /* @__PURE__ */ new Map();
3060
+ // the directory: foreign ward pk -> reach
3061
+ down = /* @__PURE__ */ new Set();
3062
+ // pks this harbor will not reach right now: weather, for a test
3063
+ refused = /* @__PURE__ */ new Map();
3064
+ // own ward pk -> arrivals its door would not admit. what a terrain does with it is its own
3065
+ faults = /* @__PURE__ */ new Map();
3066
+ // ward name -> saves the store refused. what a terrain does with it is its own
3067
+ // Told every time a save is refused, with the ward's name and how many of
3068
+ // its saves the store has now refused. The refusal is already the answer of
3069
+ // the ask that caused it, so nobody is waiting on this; it is for the box's
3070
+ // own record, a journal line or a counter, and a terrain that keeps none
3071
+ // leaves it alone.
3072
+ onFault = () => {
3073
+ };
3074
+ unbooted = [];
3075
+ // the wards the store keeps that would not host this run, by name
3076
+ classes = {};
3077
+ // classes handed in-process, beside the loader's
3078
+ #saving = /* @__PURE__ */ new Map();
3079
+ // The dialers' sockets, in the order they were dialed: where a pk nobody
3080
+ // here knows is sent, because the listener a harbor dialed is a rendezvous
3081
+ // and may hold that pk for someone else. It is a list because a harbor may
3082
+ // dial more than one, and a rendezvous is a listener and nothing more, so
3083
+ // there is never only one of them. A pk is tried down the list until one
3084
+ // carries it: with a single slot, a harbor holding two lines answered
3085
+ // unreached for a pk the other line could have reached.
3086
+ fallbacks = [];
3087
+ // How a dialer says what this harbor holds now. An announce is a claim and
3088
+ // a claim is worth nothing until the door behind it answers a probe, so
3089
+ // saying it again costs a proof and buys nothing to an impostor. What is
3090
+ // not free is never saying it again: a ward booted, adopted or dropped
3091
+ // after a line opened would be announced only by the next line, and a line
3092
+ // that does not drop never opens again, so it would be unreachable through
3093
+ // that listener for as long as the socket stayed healthy.
3094
+ announcers = /* @__PURE__ */ new Set();
3095
+ #announce() {
3096
+ for (const say of this.announcers)
3097
+ say();
3098
+ }
3099
+ // What this device lends the beings of the wards it hosts, by name. One
3100
+ // for the harbor, and `lendFor` says which ward may ask it: every ward,
3101
+ // unless a terrain says a stranger's ward is lent nothing.
3102
+ // Set at birth or once the harbor's own ward is standing, which is the
3103
+ // usual order: a terrain puts that ward up first, then boots the rest on a
3104
+ // ground that can reach it.
3105
+ lend;
3106
+ constructor(store, loader = async () => ({}), lend) {
3107
+ this.store = store;
3108
+ this.loader = loader;
3109
+ this.lend = lend;
3110
+ }
3111
+ lendFor(_name, _record) {
3112
+ return this.lend;
3113
+ }
3114
+ // Boot every ward the store keeps, and learn the hints. One ward that will
3115
+ // not host, a partition this kit cannot read or a loader that throws, is
3116
+ // named in `unbooted` and takes nobody else down with it.
3117
+ async boot() {
3118
+ for (const name of await this.store.list()) {
3119
+ if (this.wards.has(name))
3120
+ continue;
3121
+ try {
3122
+ const kept = await this.store.load(name);
3123
+ if (kept)
3124
+ await this.host(name, kept);
3125
+ } catch {
3126
+ this.unbooted.push(name);
3127
+ }
3128
+ }
3129
+ for (const [pk, url] of Object.entries(await this.store.hints()))
3130
+ this.hint(pk, url);
3131
+ }
3132
+ // The object a ward writes into, by the name it is kept under: the
3133
+ // partition the harbor was handed at boot and keeps so it can save it.
3134
+ // Nothing in the harbor reads it, and it is not on `Hosted`, because a
3135
+ // side that holds a ward would then hold every seed in it and reach any
3136
+ // being past every door. It is here for one reader, the conformance
3137
+ // suite, whose census is ward state read straight and whose forgeries are
3138
+ // ward state written straight, the way `MemoryHarbor.partitions` is. A
3139
+ // side asks the ward.
3140
+ partitionOf(name) {
3141
+ return this.#saving.get(name)?.memory;
3142
+ }
3143
+ // ---- saving
3144
+ // The ward wrote one row. Its partition is saved once the line is free, and
3145
+ // once for every burst of writes, with every row named in that burst.
3146
+ #wrote(name, row) {
3147
+ const s = this.#saving.get(name);
3148
+ if (!s || s.gone)
3149
+ return;
3150
+ s.dirty.add(row);
3151
+ if (s.timer === void 0)
3152
+ s.timer = setTimeout(() => void this.#flush(name), 0);
3153
+ }
3154
+ // The rows a store refused, put back in the ward's memory to what the
3155
+ // store holds for them. A being whose row was refused would otherwise go
3156
+ // on answering out of a memory that runs ahead of the device: her next
3157
+ // write is a row past the same ceiling, refused again, and everything she
3158
+ // was told between now and the reload is gone with it. Standing her at
3159
+ // what is kept costs her the writes that were never kept, which is what
3160
+ // the asker was told, and leaves a small write after it one the store
3161
+ // takes.
3162
+ //
3163
+ // The shape is read where the shape lives. The harbor hands each row and
3164
+ // the copy it kept to the partition, which knows which part of itself a row
3165
+ // is; nothing here reads a being, a bind table or a head. And nothing here
3166
+ // asks the store: what it took is what this harbor handed it, which is
3167
+ // `kept`, and a row nobody has handed it since this ward was hosted is a
3168
+ // row the ward has not moved, so there is nothing to put back.
3169
+ // Every row together, and never the refused ones alone. A relation's state
3170
+ // is spread over rows, the head for the door's heirs and a being's own row
3171
+ // for the keys she speaks under: put one back and the door stands ahead of
3172
+ // the knocker and the relation never speaks again. So the ward is put back
3173
+ // to the whole point, which is what the store held at the last save taken
3174
+ // while no call was in flight. A row that point never knew is left where it
3175
+ // is and stays named: no save has ever taken it, so there is nothing to put
3176
+ // it back to. The rows that moved come back, so the store is put back with
3177
+ // them and a reload finds no half-done row.
3178
+ #standAtKept(s) {
3179
+ if (s.gone)
3180
+ return [];
3181
+ const moved = [];
3182
+ for (const row of s.kept.keys()) {
3183
+ const was = s.kept.get(row);
3184
+ const now = rowsOf(s.memory, [row])[0][1];
3185
+ if (JSON.stringify(was) === JSON.stringify(now))
3186
+ continue;
3187
+ restoreRow(s.memory, row, was);
3188
+ moved.push(row);
3189
+ }
3190
+ for (const row of moved)
3191
+ s.dirty.delete(row);
3192
+ return moved;
3193
+ }
3194
+ // The ward back to the point, and the store with it. The store is written
3195
+ // because a reload reads it and not the memory: a refusal that put only the
3196
+ // memory back would leave the rows the store did take standing ahead of the
3197
+ // ward until the next restart, and one half of a relation ahead of the
3198
+ // other. What goes out is what the store already took once, so it is a
3199
+ // write the store has no reason to refuse; one that refuses it anyway is
3200
+ // counted like any other, and there is nothing further to put back to.
3201
+ async #putBack(name, s) {
3202
+ const under2 = [...s.under.keys()];
3203
+ s.under.clear();
3204
+ const moved = this.#standAtKept(s);
3205
+ const rows = [.../* @__PURE__ */ new Set([...moved, ...under2])];
3206
+ if (rows.length === 0 || s.gone)
3207
+ return;
3208
+ try {
3209
+ await this.store.save(name, s.memory, rows);
3210
+ for (const row of rows)
3211
+ s.dirty.delete(row);
3212
+ } catch {
3213
+ this.#fault(name);
3214
+ }
3215
+ }
3216
+ // The call ended and the ward is whole, so the point catches up with it. A
3217
+ // row the store took under the call and nobody has touched since is where
3218
+ // the ward stands, so it joins the point as it is; a row that moved after
3219
+ // that save is named for one more, and that save is quiet and moves the
3220
+ // point itself. Nothing is written twice for a ward whose call left its
3221
+ // rows where the store already has them.
3222
+ #settle(name, s) {
3223
+ for (const [row, value] of s.under) {
3224
+ const now = rowsOf(s.memory, [row])[0][1];
3225
+ if (JSON.stringify(value) === JSON.stringify(now))
3226
+ s.kept.set(row, value);
3227
+ else
3228
+ s.dirty.add(row);
3229
+ }
3230
+ s.under.clear();
3231
+ if (s.dirty.size > 0)
3232
+ void this.#flush(name);
3233
+ }
3234
+ // One refusal, counted and told. What a terrain does with the count is its
3235
+ // own; the arrival that caused it is told by the answer, not by this.
3236
+ #fault(name) {
3237
+ const faults = (this.faults.get(name) ?? 0) + 1;
3238
+ this.faults.set(name, faults);
3239
+ try {
3240
+ this.onFault(name, faults);
3241
+ } catch {
3242
+ }
3243
+ }
3244
+ // Answers whether everything it wrote was kept. False is a store that
3245
+ // refused, and it is the ward's answer to the arrival that caused it, not
3246
+ // a number in a map nobody reads. A flush that had nothing to write kept
3247
+ // everything it was given, which is true and is why a read is never
3248
+ // refused.
3249
+ async #flush(name) {
3250
+ const s = this.#saving.get(name);
3251
+ if (!s)
3252
+ return true;
3253
+ if (s.timer !== void 0) {
3254
+ clearTimeout(s.timer);
3255
+ s.timer = void 0;
3256
+ }
3257
+ if (s.running)
3258
+ return s.running;
3259
+ s.running = (async () => {
3260
+ if (s.back) {
3261
+ if (s.calls > 0)
3262
+ return false;
3263
+ s.back = false;
3264
+ await this.#putBack(name, s);
3265
+ return false;
3266
+ }
3267
+ while (s.dirty.size > 0 && !s.gone) {
3268
+ const rows = [...s.dirty];
3269
+ s.dirty.clear();
3270
+ const writing = rowsOf(s.memory, rows);
3271
+ try {
3272
+ const quiet = s.calls === 0;
3273
+ await this.store.save(name, s.memory, rows);
3274
+ for (const [row, value] of writing)
3275
+ (quiet ? s.kept : s.under).set(row, value);
3276
+ } catch {
3277
+ this.#fault(name);
3278
+ for (const row of rows)
3279
+ s.dirty.add(row);
3280
+ s.fault = true;
3281
+ if (s.calls > 0) {
3282
+ s.back = true;
3283
+ return false;
3284
+ }
3285
+ await this.#putBack(name, s);
3286
+ return false;
3287
+ }
3288
+ }
3289
+ return true;
3290
+ })();
3291
+ try {
3292
+ return await s.running;
3293
+ } finally {
3294
+ s.running = null;
3295
+ }
3296
+ }
3297
+ // ---- the directory
3298
+ // Bytes to one of this harbor's own doors, and the one bit the door says
3299
+ // beside its reply, counted.
3300
+ async #own(pk, door, bytes) {
3301
+ const r = await door(bytes);
3302
+ if (!r.heard)
3303
+ this.refused.set(pk, (this.refused.get(pk) ?? 0) + 1);
3304
+ return new Uint8Array(r.bytes);
3305
+ }
3306
+ async carry(pk, bytes) {
3307
+ if (this.down.has(pk))
3308
+ return void 0;
3309
+ const door = this.doors.get(pk);
3310
+ if (door)
3311
+ return this.#own(pk, door, bytes);
3312
+ const bound = this.reaches.get(pk);
3313
+ if (bound)
3314
+ return bound.reach.carry(pk, bytes);
3315
+ for (const reach of this.fallbacks) {
3316
+ const back = await reach.carry(pk, bytes);
3317
+ if (back !== void 0)
3318
+ return back;
3319
+ }
3320
+ return void 0;
3321
+ }
3322
+ async deliver(pk, bytes) {
3323
+ if (this.down.has(pk))
3324
+ return void 0;
3325
+ const door = this.doors.get(pk);
3326
+ if (door)
3327
+ return this.#own(pk, door, bytes);
3328
+ const bound = this.reaches.get(pk);
3329
+ return bound?.held ? bound.reach.carry(pk, bytes) : void 0;
3330
+ }
3331
+ hint(pk, url) {
3332
+ if (this.reaches.has(pk))
3333
+ return;
3334
+ this.reaches.set(pk, { reach: request(url), held: false });
3335
+ }
3336
+ async remember(pk, url) {
3337
+ this.hint(pk, url);
3338
+ await this.store.hint(pk, url);
3339
+ }
3340
+ // An announce is a claim, and a claim binds nothing until proven: anyone
3341
+ // who can reach a listener could otherwise name a pk that is not theirs and
3342
+ // take its reachability. The proof is the door itself, as it already is. A
3343
+ // lid this harbor minted and noise after it is a box that does not open,
3344
+ // and the door answers one such box with silence sealed to the lid and
3345
+ // signed by the ward key. Only the holder of that ward's seed can write
3346
+ // that reply, the lid is fresh so nothing replays, and a box that does not
3347
+ // open writes nothing at the ward. This is the one box a harbor ever opens,
3348
+ // the one it sealed itself, and it reads nothing from it but that it is
3349
+ // the silence a door owes such a box, signed by the claimed key.
3350
+ async prove(pk, reach) {
3351
+ if (!isWardPk(pk))
3352
+ return false;
3353
+ try {
3354
+ const lid2 = await sealingPair(entropy(KEY));
3355
+ const back = await within(PROBE, reach.carry(pk, concat([lid2.pk, entropy(KEY)])));
3356
+ if (back === LATE || back === void 0)
3357
+ return false;
3358
+ const reply = await openReply(back, lid2.secret, wardSignPk(pk));
3359
+ return reply !== null && "silence" in reply;
3360
+ } catch {
3361
+ return false;
3362
+ }
3363
+ }
3364
+ // Bind what the far side claims, each pk proven at its door first. Both
3365
+ // sides bind this way: the listener the dialer's claims, the dialer the
3366
+ // listener's, since a claim is a claim whichever end made it. An announce
3367
+ // names at most this many pks, and they are proven a few at a time: a
3368
+ // side that announced a thousand would otherwise cost a thousand keys, a
3369
+ // thousand frames and a thousand waits at once, on its word alone.
3370
+ async bind(pks, reach, held2) {
3371
+ const said2 = (announces.get(reach) ?? 0) + 1;
3372
+ announces.set(reach, said2);
3373
+ for (const [pk, b] of this.reaches)
3374
+ if (b.reach === reach && !pks.includes(pk))
3375
+ this.reaches.delete(pk);
3376
+ const claimed = [...new Set(pks)].slice(0, ANNOUNCE);
3377
+ const prove = async (pk) => {
3378
+ if (this.doors.has(pk))
3379
+ return;
3380
+ const standing = this.reaches.get(pk);
3381
+ if (standing?.held && standing.reach !== reach)
3382
+ return;
3383
+ if (!await this.prove(pk, reach))
3384
+ return;
3385
+ if (announces.get(reach) !== said2)
3386
+ return;
3387
+ if (this.reaches.get(pk)?.held && this.reaches.get(pk).reach !== reach)
3388
+ return;
3389
+ this.reaches.set(pk, { reach, held: held2 });
3390
+ };
3391
+ for (let i = 0; i < claimed.length; i += PROVING)
3392
+ await Promise.all(claimed.slice(i, i + PROVING).map(prove));
3393
+ }
3394
+ unbind(reach) {
3395
+ for (const [pk, b] of this.reaches)
3396
+ if (b.reach === reach)
3397
+ this.reaches.delete(pk);
3398
+ }
3399
+ // ---- wards
3400
+ // Mint a seed and boot an empty ward under a name: a world born here.
3401
+ async create(name, user = "", code = DEFAULT_CODE) {
3402
+ const kept = { seed: entropy(KEY), partition: {}, record: { pk: "", code, user } };
3403
+ await this.store.put(name, kept);
3404
+ const hosted = await this.host(name, kept);
3405
+ await this.store.record(name, hosted.record);
3406
+ return hosted;
3407
+ }
3408
+ // Stop serving a ward and take it out of the store: what was kept comes
3409
+ // back, for another harbor to put. The partition is written first, and
3410
+ // never again under this name: whoever still holds the old pointers holds
3411
+ // a ward that saves nothing and answers its owner silence.
3412
+ async drop(name) {
3413
+ const h = this.wards.get(name);
3414
+ if (h) {
3415
+ await h.save();
3416
+ const s = this.#saving.get(name);
3417
+ if (s)
3418
+ s.gone = true;
3419
+ this.#saving.delete(name);
3420
+ this.wards.delete(name);
3421
+ this.doors.delete(h.pk);
3422
+ this.#announce();
3423
+ }
3424
+ return this.store.take(name);
3425
+ }
3426
+ // Put what another harbor dropped, and boot it: same seed, same pk. A ward
3427
+ // that will not boot is not adopted. This is the one path where a partition
3428
+ // written somewhere else arrives, so it is the one place the ward's own
3429
+ // shape check is met, and the name is refused for as long as it is kept:
3430
+ // a partition put and left would hold the name against every later try,
3431
+ // with a copy of it in a store that can never serve it. It goes back out,
3432
+ // and the only copy is the one the caller is still holding.
3433
+ async adopt(name, kept) {
3434
+ await this.store.put(name, kept);
3435
+ try {
3436
+ return await this.host(name, kept);
3437
+ } catch (e) {
3438
+ await this.store.take(name);
3439
+ throw e;
3440
+ }
3441
+ }
3442
+ // A ward from what the store keeps: the ground, once, and two pointers.
3443
+ async host(name, kept) {
3444
+ const { seed: seed2, partition: memory } = kept;
3445
+ const classes = await this.loader(kept.record);
3446
+ const objects = /* @__PURE__ */ new WeakMap();
3447
+ const ground = {
3448
+ seed: seed2,
3449
+ memory,
3450
+ // This ward's own classes first, then the harbor's: a ward that names a
3451
+ // class of its own is answered with hers.
3452
+ instantiate: maker(objects, classes, this.classes),
3453
+ carry: (pk2, bytes) => this.carry(pk2, new Uint8Array(bytes)),
3454
+ random: entropy,
3455
+ wrote: (row) => this.#wrote(name, row),
3456
+ // What the ward wrote is in the store when this resolves, and it says
3457
+ // whether the store took it. The ward calls it at the end of every
3458
+ // arrival, before it seals, so a call through a pointer has already
3459
+ // waited for the store when it comes back: a restart right after one
3460
+ // finds everything, and a refusal is that arrival's own answer rather
3461
+ // than a number counted behind it. A ward that wrote nothing has
3462
+ // nothing to save, so asking it what it holds is free, which is what
3463
+ // makes the ask pointer the way to find out rather than an expensive
3464
+ // one.
3465
+ //
3466
+ // A ward on its way out keeps nothing and refuses nothing: an arrival
3467
+ // racing an unhost is not a store saying no, and saying so would be a
3468
+ // lie in the one direction that matters.
3469
+ keep: async () => {
3470
+ const s = this.#saving.get(name);
3471
+ if (!s || s.gone)
3472
+ return true;
3473
+ const ok = await this.#flush(name);
3474
+ const no = !ok || s.fault;
3475
+ s.fault = false;
3476
+ return !no;
3477
+ },
3478
+ // The ward says a call is in flight, and says when it ends. The harbor
3479
+ // goes on writing under it; what it does not do is take a save made
3480
+ // there as the point a refused ward is put back to, and a refusal that
3481
+ // lands under a call waits here for the call to end before it puts the
3482
+ // ward back. The ward is the one that knows, because a being reaching
3483
+ // another being of her own ward goes to that ward's door directly and
3484
+ // the harbor never sees that call at all.
3485
+ calling: () => {
3486
+ const s = this.#saving.get(name);
3487
+ if (!s)
3488
+ return () => {
3489
+ };
3490
+ s.calls += 1;
3491
+ let lowered = false;
3492
+ return () => {
3493
+ if (lowered)
3494
+ return;
3495
+ lowered = true;
3496
+ s.calls -= 1;
3497
+ if (s.calls > 0 || s.gone)
3498
+ return;
3499
+ if (s.back)
3500
+ void this.#flush(name);
3501
+ else
3502
+ this.#settle(name, s);
3503
+ };
3504
+ }
3505
+ };
3506
+ const lend = this.lendFor(name, kept.record);
3507
+ if (lend)
3508
+ ground.lend = lend;
3509
+ const saving = { memory, dirty: /* @__PURE__ */ new Set(), kept: new Map(rowsOf(memory, rowsIn(memory))), running: null, timer: void 0, gone: false, calls: 0, fault: false, back: false, under: /* @__PURE__ */ new Map() };
3510
+ this.#saving.set(name, saving);
3511
+ let w, pk;
3512
+ try {
3513
+ w = await Ward(ground);
3514
+ pk = await learnPk(w);
3515
+ } catch (e) {
3516
+ saving.gone = true;
3517
+ this.#saving.delete(name);
3518
+ throw e;
3519
+ }
3520
+ const save = async () => void await ground.keep?.();
3521
+ const door = (bytes) => w.door(bytes);
3522
+ const ask = async (method, args) => {
3523
+ if (saving.gone)
3524
+ return silence;
3525
+ return w.ask(method, args);
3526
+ };
3527
+ const beings = memory.beings;
3528
+ const being = (key) => {
3529
+ const cells = beings && Object.hasOwn(beings, key) ? beings[key] : void 0;
3530
+ return cells ? objects.get(cells) : void 0;
3531
+ };
3532
+ const keys = () => Object.keys(beings ?? {}).filter((k) => being(k) !== void 0);
3533
+ const hosted = { door, ask, pk, name, record: { ...kept.record, pk }, being, keys, save };
3534
+ this.wards.set(name, hosted);
3535
+ this.doors.set(hosted.pk, door);
3536
+ await save();
3537
+ this.#announce();
3538
+ return hosted;
3539
+ }
3540
+ };
3541
+
3542
+ // ../quo/dist/harbor/dial.js
3543
+ function dial(harbor, url) {
3544
+ const d = { url, socket: null, wake: () => {
3545
+ }, close: () => {
3546
+ } };
3547
+ let stopped = false, wait = 1e3, timer, line, held2;
3548
+ const connect = () => {
3549
+ timer = void 0;
3550
+ if (stopped)
3551
+ return;
3552
+ line = new WebSocket(url.replace(/\/$/, "").replace(/^http/, "ws"));
3553
+ const s = new Socket(line, (pk, bytes) => harbor.deliver(pk, bytes), (far) => {
3554
+ wait = 1e3;
3555
+ void harbor.bind(far, s, false);
3556
+ }, (why) => {
3557
+ shed();
3558
+ if (held2?.socket !== s)
3559
+ return;
3560
+ held2 = void 0;
3561
+ if (why.code === REFUSED)
3562
+ wait = 3e4;
3563
+ if (!stopped)
3564
+ timer = setTimeout(connect, wait);
3565
+ wait = Math.min(wait * 2, 3e4);
3566
+ });
3567
+ const shed = () => {
3568
+ harbor.announcers.delete(say);
3569
+ harbor.unbind(s);
3570
+ if (d.socket === s)
3571
+ d.socket = null;
3572
+ const at3 = harbor.fallbacks.indexOf(s);
3573
+ if (at3 !== -1)
3574
+ harbor.fallbacks.splice(at3, 1);
3575
+ };
3576
+ held2 = { socket: s, shed };
3577
+ harbor.fallbacks.push(s);
3578
+ const say = () => s.announce([...harbor.doors.keys()]);
3579
+ line.addEventListener("open", () => {
3580
+ if (stopped)
3581
+ return s.close();
3582
+ d.socket = s;
3583
+ harbor.announcers.add(say);
3584
+ say();
3585
+ });
3586
+ };
3587
+ d.wake = () => {
3588
+ if (stopped)
3589
+ return;
3590
+ clearTimeout(timer);
3591
+ timer = void 0;
3592
+ wait = 1e3;
3593
+ const old = held2;
3594
+ held2 = void 0;
3595
+ old?.shed();
3596
+ old?.socket.close();
3597
+ connect();
3598
+ };
3599
+ d.close = () => {
3600
+ stopped = true;
3601
+ clearTimeout(timer);
3602
+ line?.close();
3603
+ };
3604
+ connect();
3605
+ return d;
3606
+ }
3607
+
3608
+ // harbor/idb.ts
3609
+ var WARDS2 = "wards";
3610
+ var ROWS = "rows";
3611
+ var HINTS = "hints";
3612
+ var at2 = (name, row) => `${name}\0${row}`;
3613
+ var under = (name) => IDBKeyRange.bound(at2(name, ""), at2(name, "\uFFFF"));
3614
+ function open2(name) {
3615
+ return new Promise((ok, no) => {
3616
+ const req = indexedDB.open(name, 2);
3617
+ req.onupgradeneeded = () => {
3618
+ const db = req.result;
3619
+ if (!db.objectStoreNames.contains(WARDS2)) db.createObjectStore(WARDS2);
3620
+ if (!db.objectStoreNames.contains(ROWS)) db.createObjectStore(ROWS);
3621
+ if (!db.objectStoreNames.contains(HINTS)) db.createObjectStore(HINTS);
3622
+ };
3623
+ req.onsuccess = () => ok(req.result);
3624
+ req.onerror = () => no(req.error ?? new Error("indexedDB would not open"));
3625
+ });
3626
+ }
3627
+ function done(req) {
3628
+ return new Promise((ok, no) => {
3629
+ req.onsuccess = () => ok(req.result);
3630
+ req.onerror = () => no(req.error ?? new Error("indexedDB request failed"));
3631
+ });
3632
+ }
3633
+ var Idb = class {
3634
+ name;
3635
+ #db;
3636
+ constructor(name = "quo") {
3637
+ this.name = name;
3638
+ }
3639
+ #open() {
3640
+ return this.#db ??= open2(this.name);
3641
+ }
3642
+ async #tx(store, mode) {
3643
+ return (await this.#open()).transaction(store, mode).objectStore(store);
3644
+ }
3645
+ async list() {
3646
+ return (await done((await this.#tx(WARDS2, "readonly")).getAllKeys())).map(String);
3647
+ }
3648
+ async load(name) {
3649
+ const row = await done((await this.#tx(WARDS2, "readonly")).get(name));
3650
+ if (!row) return void 0;
3651
+ const st = await this.#tx(ROWS, "readonly");
3652
+ const keys = (await done(st.getAllKeys(under(name)))).map(String);
3653
+ const vals = await done(st.getAll(under(name)));
3654
+ return { seed: new Uint8Array(row.seed), partition: fromRows(Object.fromEntries(keys.map((k, i) => [k.slice(name.length + 1), vals[i]]))), record: row.record };
3655
+ }
3656
+ async put(name, kept) {
3657
+ const st = await this.#tx(WARDS2, "readwrite");
3658
+ if (await done(st.getKey(name)) !== void 0) throw new Error(`ward ${name} already exists in ${this.name}`);
3659
+ await done(st.put({ seed: kept.seed, record: kept.record }, name));
3660
+ await this.#keep(name, kept.partition, rowsIn(kept.partition));
3661
+ }
3662
+ async save(name, partition, rows) {
3663
+ if (await done((await this.#tx(WARDS2, "readonly")).getKey(name)) === void 0) return;
3664
+ await this.#keep(name, partition, rows);
3665
+ }
3666
+ // The rows named, written or deleted. A row whose being has left comes back
3667
+ // undefined and her record goes with her.
3668
+ async #keep(name, partition, rows) {
3669
+ const st = await this.#tx(ROWS, "readwrite");
3670
+ for (const [row, value] of rowsOf(partition, rows)) {
3671
+ if (value === void 0) await done(st.delete(at2(name, row)));
3672
+ else await done(st.put(value, at2(name, row)));
3673
+ }
3674
+ }
3675
+ async record(name, record) {
3676
+ const st = await this.#tx(WARDS2, "readwrite");
3677
+ const row = await done(st.get(name));
3678
+ if (!row) return;
3679
+ await done(st.put({ ...row, record }, name));
3680
+ }
3681
+ async take(name) {
3682
+ const kept = await this.load(name);
3683
+ if (!kept) return void 0;
3684
+ await done((await this.#tx(ROWS, "readwrite")).delete(under(name)));
3685
+ await done((await this.#tx(WARDS2, "readwrite")).delete(name));
3686
+ return kept;
3687
+ }
3688
+ async hints() {
3689
+ const st = await this.#tx(HINTS, "readonly");
3690
+ const keys = (await done(st.getAllKeys())).map(String);
3691
+ const vals = await done(st.getAll());
3692
+ return Object.fromEntries(keys.map((k, i) => [k, vals[i]]));
3693
+ }
3694
+ async hint(pk, url) {
3695
+ await done((await this.#tx(HINTS, "readwrite")).put(url, pk));
3696
+ }
3697
+ close() {
3698
+ void this.#db?.then((db) => db.close());
3699
+ this.#db = void 0;
3700
+ }
3701
+ };
3702
+
3703
+ // harbor/browser.ts
3704
+ var BUILT_IN = { User, Desk, Avatar, Presence, Courier, ...FACULTY_CLASSES };
3705
+ var BrowserHarbor = class extends Harbor {
3706
+ name;
3707
+ dialers = [];
3708
+ #locked;
3709
+ #release;
3710
+ // `lend` is what this box lends the beings of the wards it hosts: a name
3711
+ // to an invitation on a being of its own ward. `stand` builds one; a bare
3712
+ // tab hands none and every name answers null.
3713
+ constructor(store = "quo", classes = {}, lend) {
3714
+ const s = typeof store === "string" ? new Idb(store) : store;
3715
+ super(s, async () => ({ ...BUILT_IN, ...classes }), lend);
3716
+ this.name = typeof store === "string" ? store : "quo";
3717
+ this.#locked = typeof store === "string";
3718
+ }
3719
+ // What this box lends, by name. A bare tab says nothing, stands no ward of
3720
+ // its own and lends nothing.
3721
+ lent;
3722
+ // What this box lends is said before it boots, and standing the ward is
3723
+ // part of booting: a being born in any other ward lends from her first
3724
+ // line, and a ground that could not reach it yet would leave her holding
3725
+ // nothing until her next birth.
3726
+ async stand(lent) {
3727
+ this.lent = lent;
3728
+ if (this.wards.size > 0) this.lend = await stand(this, lent);
3729
+ }
3730
+ // Take the lock and boot every ward kept here. A lock already held is
3731
+ // another tab's harbor over this seed, and this one refuses to boot.
3732
+ async boot() {
3733
+ if (this.#locked) await this.#lease();
3734
+ if (this.lent) this.lend = await stand(this, this.lent);
3735
+ await super.boot();
3736
+ await settled();
3737
+ }
3738
+ // Hold one socket to a world's quo. route.
3739
+ dial(url) {
3740
+ const d = dial(this, url);
3741
+ this.dialers.push(d);
3742
+ return d;
3743
+ }
3744
+ // The device is back: a phone out of a pocket, an app to the foreground.
3745
+ // Every socket is told, and one that died silently is dialed again.
3746
+ wake() {
3747
+ for (const d of this.dialers) d.wake();
3748
+ }
3749
+ async close() {
3750
+ for (const d of this.dialers.splice(0)) d.close();
3751
+ this.store.close?.();
3752
+ this.#release?.();
3753
+ this.#release = void 0;
3754
+ }
3755
+ async #lease() {
3756
+ const locks = globalThis.navigator?.locks;
3757
+ if (!locks) return;
3758
+ const granted = await new Promise((ok) => {
3759
+ void locks.request(`quo:${this.name}`, { ifAvailable: true }, (lock) => {
3760
+ if (!lock) {
3761
+ ok(false);
3762
+ return;
3763
+ }
3764
+ ok(true);
3765
+ return new Promise((release) => this.#release = release);
3766
+ });
3767
+ });
3768
+ if (!granted) throw new Error(`harbor ${this.name} is held by another tab`);
3769
+ }
3770
+ };
3771
+
3772
+ // human/worlds.ts
3773
+ var localName = (pk) => `w-${pk.slice(0, 16)}`;
3774
+ function relations(ward) {
3775
+ const out = [];
3776
+ for (const key of ward.keys()) {
3777
+ const a = ward.being(key);
3778
+ if (a instanceof Avatar && a.standings[USER]) out.push({ key, avatar: a });
3779
+ }
3780
+ return out;
3781
+ }
3782
+
3783
+ // human/wake.ts
3784
+ async function drained(harbor, pks) {
3785
+ const out = [];
3786
+ for (const pk of pks) {
3787
+ const ward = harbor.wards.get(localName(pk));
3788
+ if (!ward) continue;
3789
+ for (const rel of relations(ward)) {
3790
+ const got = await rel.avatar.call("missed");
3791
+ const objects = got.missed;
3792
+ if (!Array.isArray(objects)) continue;
3793
+ for (const o of objects) {
3794
+ if (o === null || typeof o !== "object" || Array.isArray(o)) continue;
3795
+ rel.avatar.notify(o);
3796
+ out.push(o);
3797
+ }
3798
+ }
3799
+ await ward.save();
3800
+ }
3801
+ return out;
3802
+ }
3803
+ function notice(objects) {
3804
+ const line = banner(objects[objects.length - 1] ?? {});
3805
+ return { ...line, body: objects.length > 1 ? `${line.body} (and ${objects.length - 1} more)` : line.body };
3806
+ }
3807
+
3808
+ // human/worker.ts
3809
+ var carried = (event) => {
3810
+ try {
3811
+ const said2 = event.data?.json();
3812
+ if (!said2 || typeof said2.title !== "string") return null;
3813
+ return { title: said2.title, body: typeof said2.body === "string" ? said2.body : "" };
3814
+ } catch {
3815
+ return null;
3816
+ }
3817
+ };
3818
+ var sw = globalThis;
3819
+ var within2 = (ms, work, floor) => Promise.race([work, new Promise((ok) => setTimeout(() => ok(floor), ms))]);
3820
+ async function fromPage() {
3821
+ const pages = await sw.clients.matchAll({ type: "window", includeUncontrolled: true });
3822
+ if (pages.length === 0) return null;
3823
+ return within2(
3824
+ 5e3,
3825
+ new Promise((ok) => {
3826
+ for (const page of pages) {
3827
+ const channel = new MessageChannel();
3828
+ channel.port1.onmessage = (e) => {
3829
+ const said2 = e.data ?? {};
3830
+ if (Array.isArray(said2.missed)) ok(said2.missed);
3831
+ };
3832
+ page.postMessage({ quo: "wake" }, [channel.port2]);
3833
+ }
3834
+ }),
3835
+ null
3836
+ );
3837
+ }
3838
+ async function here(cfg2) {
3839
+ const harbor = new BrowserHarbor("quo");
3840
+ try {
3841
+ await harbor.boot();
3842
+ } catch {
3843
+ return [];
3844
+ }
3845
+ try {
3846
+ harbor.dial(cfg2.quo);
3847
+ return await within2(
3848
+ 2e4,
3849
+ drained(
3850
+ harbor,
3851
+ Object.values(cfg2.wards).map((w) => w.pk)
3852
+ ),
3853
+ []
3854
+ );
3855
+ } finally {
3856
+ await harbor.close();
3857
+ }
3858
+ }
3859
+ async function woken(cfg2, said2) {
3860
+ const objects = await fromPage() ?? await here(cfg2);
3861
+ if (!said2 && objects.length === 0) return;
3862
+ const { title, body } = said2 ?? notice(objects);
3863
+ await within2(5e3, sw.registration.showNotification(title, { body, tag: "quo" }), void 0);
3864
+ }
3865
+ var cfg = sw.quo;
3866
+ sw.addEventListener("install", () => void sw.skipWaiting());
3867
+ sw.addEventListener("activate", (e) => e.waitUntil(sw.clients.claim()));
3868
+ if (cfg) sw.addEventListener("push", (e) => e.waitUntil(woken(cfg, carried(e))));