@quo-systems/quo 0.2.14 → 0.2.16

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 (62) hide show
  1. package/README.md +37 -20
  2. package/dist/being/being.d.ts +1 -0
  3. package/dist/being/being.js +8 -3
  4. package/dist/being/index.d.ts +1 -1
  5. package/dist/being/index.js +1 -1
  6. package/dist/being/types.d.ts +2 -0
  7. package/dist/being/types.js +17 -0
  8. package/dist/conformance/beings.d.ts +41 -0
  9. package/dist/conformance/beings.js +28 -2
  10. package/dist/conformance/index.d.ts +10 -1
  11. package/dist/conformance/index.js +162 -6
  12. package/dist/harbor/core.d.ts +4 -2
  13. package/dist/harbor/core.js +25 -4
  14. package/dist/harbor/index.d.ts +1 -1
  15. package/dist/harbor/memory.d.ts +4 -3
  16. package/dist/harbor/memory.js +13 -4
  17. package/dist/harbor/reach.js +1 -1
  18. package/dist/ward/arithmetic.d.ts +4 -0
  19. package/dist/ward/arithmetic.js +82 -12
  20. package/dist/ward/cells.d.ts +2 -0
  21. package/dist/ward/cells.js +60 -11
  22. package/dist/ward/door.d.ts +1 -0
  23. package/dist/ward/door.js +29 -10
  24. package/dist/ward/ground.d.ts +3 -1
  25. package/dist/ward/ground.js +1 -1
  26. package/dist/ward/heirs.js +8 -1
  27. package/dist/ward/index.d.ts +2 -2
  28. package/dist/ward/index.js +3 -3
  29. package/dist/ward/owner.js +49 -8
  30. package/dist/ward/seal.d.ts +1 -0
  31. package/dist/ward/seal.js +11 -3
  32. package/dist/ward/stance.d.ts +1 -0
  33. package/dist/ward/stance.js +62 -4
  34. package/dist/ward/ward.js +13 -4
  35. package/package.json +7 -6
  36. package/{SPEC.md → protocol/SPEC.md} +367 -537
  37. package/protocol/vectors/door.json +345 -0
  38. package/quo-kit.md +595 -0
  39. package/src/being/being.ts +8 -3
  40. package/src/being/index.ts +1 -1
  41. package/src/being/types.ts +34 -0
  42. package/src/conformance/beings.ts +25 -2
  43. package/src/conformance/estate.ts +9 -9
  44. package/src/conformance/index.ts +204 -7
  45. package/src/conformance/reach.ts +1 -1
  46. package/src/harbor/core.ts +26 -5
  47. package/src/harbor/index.ts +1 -1
  48. package/src/harbor/memory.ts +14 -5
  49. package/src/harbor/reach.ts +1 -1
  50. package/src/ward/arithmetic.ts +83 -14
  51. package/src/ward/cells.ts +59 -10
  52. package/src/ward/door.ts +27 -9
  53. package/src/ward/ground.ts +39 -11
  54. package/src/ward/heirs.ts +7 -1
  55. package/src/ward/index.ts +4 -4
  56. package/src/ward/owner.ts +45 -10
  57. package/src/ward/seal.ts +12 -3
  58. package/src/ward/stance.ts +60 -4
  59. package/src/ward/ward.ts +14 -5
  60. /package/{vectors → protocol/vectors}/arithmetic.json +0 -0
  61. /package/{vectors → protocol/vectors}/framing.json +0 -0
  62. /package/{vectors → protocol/vectors}/wire.json +0 -0
package/quo-kit.md ADDED
@@ -0,0 +1,595 @@
1
+ # quo-kit
2
+
3
+ This is the kit: one TypeScript implementation of Quo, run directly on Node
4
+ with no dependencies, published as `@quo-systems/quo`. It assumes
5
+ `papers/SPEC.md`, which is the protocol and assumes nothing, and it adds
6
+ nothing to it. Everything here is a choice this kit made and another kit may
7
+ refuse: a base class, a spelling for silence, a file layout, a store, a
8
+ bundle, a gate. Where this paper and the protocol disagree the protocol wins,
9
+ because a sentence that a kit in another language needs to interoperate is
10
+ not this paper's to hold.
11
+
12
+ It carries no version and keeps no history, and it is rewritten in place to
13
+ say what the tree says.
14
+
15
+ ## The tree
16
+
17
+ The repository is one TypeScript implementation of the protocol, run
18
+ directly on Node's type stripping, no dependencies. Node 22.18
19
+ or later. The package is `@quo-systems/quo`, published from 0.1.0, the
20
+ first version under this name, and it starts over: nothing before it under
21
+ any other name is this protocol. Before 1.0.0 every version may change the
22
+ words, and nothing is kept for a holder of an earlier one.
23
+
24
+ The tree runs as TypeScript and the package ships as JavaScript. Node
25
+ strips types nowhere under `node_modules`, so a consumer cannot load the
26
+ source. `npm run build` emits `dist/` from `src`
27
+ alone, one JavaScript file and one declaration file per source file with
28
+ every relative import rewritten, and the exports map points there, types
29
+ beside default. The
30
+ build runs before every gate and every publish and is never tracked;
31
+ nothing in this repository imports it except through the package name,
32
+ which is how a consumer does. `test/package.test.ts` is the one
33
+ suite that meets the package as a stranger: it packs the tarball,
34
+ installs it into an empty folder, and imports every entry point under
35
+ plain Node. `npm pack --dry-run` shows what ships: `dist/`, the four
36
+ source folders, the protocol shelf, this paper, the README, the licence and
37
+ the notice, and nothing else. Two papers ship and the build copies both in,
38
+ each to where it belongs: the spec into `protocol/`, beside the vectors it
39
+ is read against and away from every line of TypeScript, because that folder
40
+ is what a kit in another language is written against and nothing else; this
41
+ paper to the package root, where it says which of what a reader sees here
42
+ was a choice. Publishing runs both
43
+ gates first, `npm run check` and `npm run check:terrain`, and refuses on a
44
+ failure.
45
+
46
+ ```
47
+ protocol/ the shelf a second kit reads. SPEC.md, copied in by the build, and the vectors beside it.
48
+ protocol/vectors/ fixed inputs and outputs: the arithmetic, Quo's own framing, the frames on the wire, the door's thirteen cases.
49
+ src/being/ the Being side. types, the base class, silence, the digest.
50
+ src/ward/ the ward. the Ground contract, door, seal, arithmetic, heirs, stance, owner, partition, cells, allowance.
51
+ src/harbor/ the memory harbor, the store, the reach, the dialer and the harbor core.
52
+ src/conformance/ the behaviours any ward must show, and the fixed beings they are shown with.
53
+ test/ the suites.
54
+ ```
55
+
56
+ ## The three shelves
57
+
58
+ The package holds three kinds of thing and they are not interchangeable.
59
+ Knowing which shelf a file is on decides whether a kit in another language
60
+ may read it, whether this paper or the spec governs it, and whether it
61
+ travels the day a second kit exists.
62
+
63
+ | shelf | what stands there | language |
64
+ | -------- | ---------------------------------------------------- | ----------------------- |
65
+ | protocol | `protocol/`: `SPEC.md` and the vectors | none, prose and bytes |
66
+ | kit | `src/being/`, `src/ward/`, `src/harbor/`, this paper | TypeScript, chosen here |
67
+ | harness | `src/conformance/`, `test/` | TypeScript, ours |
68
+
69
+ The protocol shelf is the only one a kit in Rust or Go reads. It is a
70
+ document that assumes nothing and four files of fixed bytes, and it is
71
+ written so that folder alone, lifted into an empty repository, is the whole
72
+ protocol. Nothing on it imports anything, because nothing on it is code.
73
+
74
+ The kit shelf is this interpretation and another kit may refuse every line
75
+ of it: a base class, a spelling for silence, a store interface, a reach, a
76
+ dialer, a file layout.
77
+
78
+ The harness shelf proves the kit shelf, and it is not the protocol however
79
+ much it reads like it. `src/conformance/` imports `Being`, `silence.ts`,
80
+ `digest.ts` and the kit's own `Store` and `Reach`, so a kit in another
81
+ language cannot run one line of it. It is exported all the same, as
82
+ `@quo-systems/quo/conformance`, because every harbor the dock stands on is
83
+ a TypeScript harbor and the suite is how one is accepted. The day a second
84
+ kit exists, the conformance suite does not run there. It is ported: a kit
85
+ writes the six obligations of the probe, carries the suite and its beings
86
+ into its own language, and reads them beside its own tests.
87
+
88
+ The hand to that kit is two things of two kinds, as the spec has it. The
89
+ vectors are the byte-level hand and the ported checklist is the
90
+ behavioural one. The suite does not become data, because the beings it is
91
+ shown with run only in the ward its kit wrote. The four areas are the
92
+ whole byte-level hand: `arithmetic.json`, `framing.json`, `wire.json` and
93
+ `door.json`, the last being the door's thirteen cases, each an arrival with
94
+ the bytes in, the bytes out and the partition's digest on both sides of the
95
+ judgement. The shelf owes nothing beyond them.
96
+
97
+ Entry points: `@quo-systems/quo` is the Being side, `@quo-systems/quo/ward`
98
+ is `Ward`, `@quo-systems/quo/harbor` is `MemoryHarbor`, `Harbor`, the
99
+ store, the reach and the dialer, and `@quo-systems/quo/conformance` is
100
+ `conform` with the store and reach suites beside it.
101
+
102
+ ## The terrain
103
+
104
+ Under `src/being/`, `src/ward/`, `src/harbor/` and `src/conformance/` no
105
+ runtime is named. The whole
106
+ platform surface is the language plus ten globals. Six are the ward's own:
107
+ `crypto`, `TextEncoder`, `TextDecoder`, `structuredClone`, `setTimeout` and
108
+ `atob`. Four more came with the harbor, and are the whole of what a reach
109
+ costs: `fetch` and `WebSocket`, the two kinds of reach; `DataView`, the frame
110
+ id on a socket; and `clearTimeout`, the dialer's reconnect called off. That
111
+ list is a promise, not an accident: `test/terrain.test.ts` fails the build
112
+ both when a file names a platform and when it reaches for a global outside
113
+ the list, and when the list names one the tree has stopped using.
114
+
115
+ The package exports the emitted `dist/`, JavaScript with a declaration
116
+ beside it, on every specifier, so plain Node imports it; the tree runs the
117
+ source directly, and every other terrain reaches a ward through a
118
+ bundler. `test/bundle.test.ts` bundles the three words and runs them, and
119
+ asserts the artefact carries nothing a terrain cannot provide.
120
+
121
+ The five algorithms are read out of WebCrypto, which is why the ward names no
122
+ package, on any terrain that carries them. `crypto.subtle` is read at every
123
+ use and never captured at load, so a page without one, a plain http:// origin
124
+ or a sandboxed frame, fails at the first ask with one sentence rather than
125
+ deep inside a key import. The floor is probed in `test/floor.test.ts`, the
126
+ arithmetic is `src/ward/arithmetic.ts` and the seal is `src/ward/seal.ts`.
127
+
128
+ ## The being side
129
+
130
+ Silence is the symbol `quo.silence` and a word is a frozen object under the
131
+ symbol key `quo.word` holding its name, both from `src/being/silence.ts`;
132
+ `isWord` and `wordOf` read them, and `isUnreached` is the one word a being
133
+ asks about most. `told` names a word and leaves everything else exactly as it
134
+ came, so asking which word arrived is one comparison rather than two joined by
135
+ an and: `told(out) === 'late'`. A kit in another language spells it however
136
+ that language spells one value standing for either, and owes nothing here.
137
+
138
+ The digest is `src/being/digest.ts`.
139
+
140
+ ### The base class
141
+
142
+ The kit offers a base class, `Being` in `src/being/being.ts`, which is a
143
+ convenience and not Quo:
144
+
145
+ - `static cells` are her defaults, merged at birth only where a key is
146
+ missing, so a restart keeps what she wrote.
147
+ - `static asks` declares what she can be asked, name to
148
+ `{ description?, input?, output?, for? }`, in blueprint order.
149
+ `for(occupant, asker)` decides whether this asker sees the ask, and so
150
+ whether this asker may call it: what she shows is what she can be asked,
151
+ one gate for describe and for dispatch.
152
+ - Both statics are read off the class the object was made from, and a
153
+ subclass that declares either **replaces** its parent's rather than adding
154
+ to it. That is the rule and not an oversight: a being's blueprint is
155
+ exactly what the class in front of you declares, in the order she chose,
156
+ and a merge would hand her asks she may mean to drop and an order she did
157
+ not write. A subclass that means to extend says so, in one spelling,
158
+ `static override asks = { ...Parent.asks, mine: {} }`, and the same for
159
+ cells. A parent that means to be subclassed at all annotates rather than
160
+ infers, `static override cells: JsonObject = { ... }`, since this language
161
+ holds a subclass's static side to its parent's and would otherwise refuse a
162
+ subclass declaring fewer keys than the parent happened to write.
163
+ - `answer` is written for her. The empty ask is `describe(asker)`, which she
164
+ may override by hand. A named ask calls the method of that name with
165
+ `(args, asker)`. Anything not declared, hidden from this asker, or
166
+ inherited from Object's prototype is `{ error: 'unknown ask' }` and the
167
+ method is never entered.
168
+ - A declared ask with no method, or one named after the base's own members,
169
+ fails at birth, loudly, so a boot fails and nothing half-lives. The
170
+ method is one on her prototype chain below Object's: a class field
171
+ holding a function is not there yet when the base checks, and the
172
+ refusal says so.
173
+ - `occupant(asker)` is the occupant record for whoever is at the door, and
174
+ undefined at a public being. `invite`, `knock`, `take`, `boot`, `cells`,
175
+ `standings` and `occupants` reach the stance and nothing else.
176
+
177
+ ### Examples
178
+
179
+ The smallest being. Answers whoever her ward names, describes one ask.
180
+
181
+ ```js
182
+ class Echo {
183
+ constructor(stance) {
184
+ this.s = stance;
185
+ }
186
+ answer(asker, method, args) {
187
+ if (method === undefined) return { asks: [{ name: 'echo', input: {} }], notes: {} };
188
+ return { from: asker.id ?? null, ...args };
189
+ }
190
+ }
191
+ ```
192
+
193
+ A shop. Invites in her own time, hands the invitation out by any channel,
194
+ and takes a guest back only if the guest offers a way.
195
+
196
+ ```js
197
+ class Shop {
198
+ constructor(stance) {
199
+ this.s = stance;
200
+ }
201
+ async invite(name) {
202
+ const id = `g-${name}`;
203
+ const inv = await this.s.occupants.invite(id);
204
+ this.s.cells.occupants[id].notes.expireAt = 2028; // hers, not Quo's
205
+ return inv; // goes by mail
206
+ }
207
+ async answer(asker, method, args) {
208
+ if (method === undefined) return { asks: [{ name: 'hello', input: {} }], notes: {} };
209
+ if (asker.id === undefined) return { welcome: false }; // a stranger at the public door has no record
210
+ const rec = this.s.cells.occupants[asker.id];
211
+ if (method === 'hello' && args.invitation) {
212
+ const back = await this.s.standings.knock(args.invitation, 'hi');
213
+ if (back !== silence && !isUnreached(back))
214
+ await this.s.standings.take(`back-${asker.id}`, args.invitation);
215
+ }
216
+ return { welcome: rec.notes.expireAt > 2026 };
217
+ }
218
+ }
219
+ ```
220
+
221
+ A guest. Consumes an invitation, and only then decides to keep the shop.
222
+
223
+ ```js
224
+ class Guest {
225
+ constructor(stance) {
226
+ this.s = stance;
227
+ }
228
+ async join(invitation) {
229
+ const mine = await this.s.occupants.invite('shop'); // so the shop can reach me
230
+ const out = await this.s.standings.knock(invitation, 'hello', { invitation: mine });
231
+ if (out === silence || isUnreached(out)) return out; // nothing was born
232
+ await this.s.standings.take('shop', invitation); // now, and only now
233
+ return out;
234
+ }
235
+ answer(asker, method) {
236
+ if (method === undefined) return { asks: [{ name: 'hi', input: {} }], notes: {} };
237
+ return { heard: method };
238
+ }
239
+ }
240
+ ```
241
+
242
+ A relay. Forwards every ask to one standing and never looks inside, and asks
243
+ for a short wait because she is one door of several. Silence passes through
244
+ her; a word does not, since a word out of a being is `threw` at the door,
245
+ D13, so she says it as an error of her own.
246
+
247
+ ```js
248
+ class Relay {
249
+ constructor(stance) {
250
+ this.s = stance;
251
+ }
252
+ async answer(asker, method, args) {
253
+ const out = await this.s.standings.next.ask(method, args, { time: 2000 });
254
+ return isWord(out) ? { error: wordOf(out) } : out; // unreached and late reach her asker as an error, not as a throw
255
+ }
256
+ }
257
+ ```
258
+
259
+ A watcher. Notices a standing changed shape, then decides.
260
+
261
+ ```js
262
+ class Watcher {
263
+ constructor(stance) {
264
+ this.s = stance;
265
+ }
266
+ async answer(asker, method, args) {
267
+ if (method === undefined) return { asks: [{ name: 'poke', input: {} }], notes: {} };
268
+ const out = await this.s.standings.src.ask('read', args);
269
+ const rec = this.s.cells.standings.src;
270
+ if (rec.seen !== rec.digest) {
271
+ const bp = await this.s.standings.src.ask(); // refresh, or
272
+ if (!bp?.asks.some((a) => a.name === 'read')) this.s.standings.remove('src'); // walk away
273
+ }
274
+ return out;
275
+ }
276
+ }
277
+ ```
278
+
279
+ ## The ward side
280
+
281
+ The protocol names the ground as seven capabilities and leaves how they are
282
+ gathered to a kit. This kit gathers them into one object, `Ground` in
283
+ `src/ward/ground.ts`, with one name each:
284
+
285
+ ```
286
+ ground
287
+ seed bytes, or a string. 32 bytes are the seed; anything else is SHA-256'd to 32 bytes first.
288
+ memory the partition. an object.
289
+ instantiate (class name, stance) -> object | null
290
+ carry (ward pk, bytes) -> bytes | undefined undefined: no door was reached. a throw is read the same.
291
+ random (n) -> n bytes of entropy
292
+ wrote () -> nothing. said after every write to the partition. left out by a harbor that keeps nothing.
293
+ lend lend(name, take) -> taken. left out by a harbor that lends nothing.
294
+ ```
295
+
296
+ Beside that contract are the three pieces every harbor in this kit builds one
297
+ out of: `maker`, the code half, which finds a class by own key in the first
298
+ registry that holds it and remembers the object by the cells; `entropy`, the
299
+ one line every terrain has; and `learnPk`, the empty ask, since a harbor
300
+ learns its ward's pk the way anyone learns anything and has no second
301
+ derivation to keep in step. They are convenience and never contract: a kit
302
+ writes its own harbor, and may write these again.
303
+
304
+ The cells guard is `src/ward/cells.ts`. Beside the nesting bound the protocol
305
+ sets, it refuses a hole in a list, an accessor and a key named `__proto__`,
306
+ because this runtime reads each one back as something JSON never wrote. The
307
+ same guard reads args at the seal and her answer at the door.
308
+
309
+ It also bounds breadth, at `BREADTH`, a million values, which is this kit's
310
+ number and no word of the protocol: a bound on what a being holds is never
311
+ the protocol's business, and what crosses is bounded in bytes at the seal.
312
+ Depth alone does not bound what a value costs to keep. One subvalue may sit
313
+ under two keys, and nesting that forty levels deep is inside the nesting
314
+ bound and is a trillion values once written down. So the walk counts the tree
315
+ while it proves the graph, and a value already counted is a value already
316
+ proven: a graph that shares is walked once per node and never once per path,
317
+ which is the difference between a guard and a ward stopped by one write.
318
+
319
+ The names this kit reserves beyond `OWNER` and `PUBLIC` are `__proto__`,
320
+ `knock`, `take` and `remove`. The last three are the calls on `standings`,
321
+ which is an object here whose methods are named on the same face as her ids,
322
+ so a standing under one of them would be unreachable. A kit with free
323
+ functions has no such collision, and no far ward can tell either way, because
324
+ an id never crosses a door.
325
+
326
+ This ward's allowance is thirty seconds by default and five minutes at the
327
+ ceiling, in `src/ward/allowance.ts`. Those two numbers are a ward's own
328
+ policy, never negotiated and never on the wire.
329
+
330
+ The partition shape is `src/ward/partition.ts`, and the three bounded lists
331
+ the protocol leaves to a ward are numbers here: eight pks under `minted`,
332
+ and `gone` and `knocks` bounded the same way, oldest out. `last` on the heir
333
+ is the cut slot for a reply a later version might keep; nothing writes it and
334
+ nothing reads it.
335
+
336
+ The seal is `src/ward/seal.ts` and the arithmetic is `src/ward/arithmetic.ts`.
337
+ `SIZE` there is the protocol's one mebibyte, read before an ask is opened and
338
+ before a reply is. `test/ward.test.ts` reads every byte string that crossed,
339
+ so that nothing inner is readable in the bytes.
340
+
341
+ An ask over two wards on two harbors spends thirty-four calls into
342
+ `crypto.subtle`, fourteen of them key imports, and `test/cost.test.ts` holds
343
+ it to forty and sixteen. Importing a key is the most expensive thing on that
344
+ path and most of the imports are the same key again: a ward signs every reply
345
+ with one key, opens every ask with one padlock, and verifies a relation under
346
+ the key it verified it under last time. So the arithmetic keeps an imported
347
+ key by the bytes it was imported from, and the public half beside the key it
348
+ was read from. The cache is bounded at `KEYS`, five hundred and twelve, least
349
+ recently used out, because a relation mints a fresh key on every ask and one
350
+ that only grew would hold a key for every ask a ward ever made. None of it is
351
+ a decision a peer can see: a kit that caches nothing speaks the same bytes,
352
+ which is why this is the kit's paper and not the protocol's.
353
+
354
+ ## The harbor side
355
+
356
+ The **store** is `src/harbor/store.ts`, the **reach** is
357
+ `src/harbor/reach.ts`, the **harbor core** is `src/harbor/core.ts` and the
358
+ **dialer** is `src/harbor/dial.ts`. Both kinds of reach are written on the
359
+ standard surface every terrain carries, fetch and WebSocket. The memory store
360
+ beside the store interface is the kit's own; a disk, a tab and an edge object
361
+ each have theirs, outside this tree, and every one of them passes
362
+ `src/conformance/store.ts` untouched, as every reach passes
363
+ `src/conformance/reach.ts`.
364
+
365
+ The dialer's wait doubles from a second to thirty when a line drops, and
366
+ returns to that first second where the far side announced and was taken.
367
+ Those are this harbor's numbers: what a far side sees is only that a dialer
368
+ refused for a suite waits.
369
+
370
+ ## The memory harbor
371
+
372
+ The memory harbor, `src/harbor/memory.ts`, is one process and no wire. It
373
+ keeps partitions by seed, routes its own doors, and may be linked to peers,
374
+ which stands in for a wire, and cut, which stands in for weather. It routes
375
+ one hop: a pk that is not its own and not a direct peer's is nothing. It
376
+ keeps no lease: two of them may hold one seed and one partition, which is
377
+ the broken vouch every real harbor refuses, kept here so that the tests can
378
+ show what divergence looks like. Every ward and being assertion in the tree
379
+ is made on it first, so that the network never hides a fault in the words;
380
+ the harbor core then passes the same suite over memory stores.
381
+
382
+ ## The suites
383
+
384
+ The beings are fixed and the ward is what is tested. A ward kit in any
385
+ language offers the ground, the door, and the owner's ask as written, and
386
+ passes the same suite. Those tests are the checklist, not the mock.
387
+
388
+ - `test/being.test.ts`: the base class against a stance stub. No ward, no
389
+ harbor. Defaults, describe, dispatch, the gate, the reserved names, the
390
+ digest.
391
+ - `src/conformance/index.ts`: one suite of behaviours, written against the
392
+ stance and one probe. `World` is this kit's spelling of the six obligations
393
+ the protocol names for a probe, and `conform` takes a call that makes one.
394
+ A kit hands the suite its ward's heir table and
395
+ bind table as values, and a forged knock, so the suite can see that a
396
+ refusal wrote nothing and a knock bound what it should. It reads the
397
+ tables and never writes them; the forged knock is the one thing it sends
398
+ through a door. With the beings in `src/conformance/beings.ts`: a
399
+ printer, a shop, two customers, one raw being with no base class, and a
400
+ member of an estate. The probe is asked to build a world, and what it is
401
+ asked for includes what the box lends: a name and the class of the being
402
+ it lends under it. The harness stands one of each in a ward its harbor
403
+ roots and grounds every ward it boots to lend them, which is how the
404
+ seventh member is reached from inside the suite. A harness with no such
405
+ ward to stand says `canLend: false` at the call and that chapter skips,
406
+ named, the way `canDown` already works. Any ward must pass it, under any
407
+ topology.
408
+ - `src/conformance/estate.ts`: the estate, and the last chapter of that
409
+ suite. Every other chapter is a scene: three beings, one move, one answer.
410
+ This one is a graph under churn, which is the shape an organisation running
411
+ on Quo actually has -- scattered wards on scattered harbors, partners and
412
+ employees invited, knocking, taken, kicked, and moving to harbors of their
413
+ own. It keeps a model of the graph, plays legal moves against it from three
414
+ fixed seeds, and after every move holds two things: that the ledger every
415
+ ward keeps closes on both ends, and that every arc in it answers when asked
416
+ exactly what being that arc means. The second is held under all three
417
+ topologies, and that is the promise of Quo at a scale a scene cannot
418
+ reach: the model never learns the topology, so neither may any answer. The
419
+ model is the script's own bookkeeping and is never compared with itself
420
+ across topologies, which would hold by construction and prove nothing. A
421
+ relation is two arcs and never one edge:
422
+ the occupant is the host's, the standing is the guest's, and the model
423
+ keeps them apart because the ward does. A red run prints its seed and the
424
+ moves that got there. It is read beside one hand-written story that says in
425
+ prose what the model is for.
426
+ - `test/ward.test.ts`: the conformance suite against the real ward under
427
+ three topologies over the memory harbor, one ward, one harbor with a ward
428
+ per being, two harbors; and what only a real ward can be asked: the
429
+ owner's asks, the door judged byte by byte, rotation and a lost reply,
430
+ replay, a restart, and every byte on the wire inspected for anything inner.
431
+ - `test/public.test.ts`: the public being, what the door does and does not
432
+ do for her.
433
+ - `test/silence.test.ts`: the protocol's chapter "Silence, the words, error"
434
+ by its numbers: the five cases and two unreacheds of her ward, the thirteen
435
+ cases of the door with a partition snapshot under each, seven strangers
436
+ met with one silence and six bound keys hearing their word, hops refused
437
+ at zero, and the six lines of the law of one silence. The world it runs in
438
+ and the thirteen cases as records are `test/door.ts`, which draws no
439
+ entropy from the device: every byte comes from one SplitMix64 stream, so
440
+ the cases are the same bytes twice. The suite reads
441
+ `protocol/vectors/door.json` and holds the tree to it, and the two laws it
442
+ states over the whole list, one length for every stranger and one
443
+ plaintext under the box, run over the corpus rather than a table beside
444
+ it. `test/write-door-vectors.ts` writes that corpus, by a hand and never
445
+ by the gate: a diff in it is a protocol change.
446
+ - `test/blueprint.test.ts`: blueprints and instantiation through a real
447
+ ward. What a boot leaves behind, what a restart brings back when the code
448
+ moved under the cells, the gate and the door, the reserved ids.
449
+ - `test/allowance.test.ts`: the allowance. Default, ceiling, nonsense, the
450
+ bound, the signed body, the door's refusal, and one ask through a ward.
451
+ - `test/lend.test.ts`: the seventh member of the ground, over two real wards
452
+ on one memory harbor. The ground answers an invitation and the ward knocks
453
+ and takes it, so a being is handed an id and never the value; null is every
454
+ kind of no; a being of the harbor's own ward wakes her by holding a
455
+ standing she minted, and her door names it as the occupant she chose;
456
+ removing that occupant is the whole of unsubscribe; the relation is born
457
+ again under the same id; a taker that will not take leaves the faculty no
458
+ occupant and no key, and the next lend stands; and a being taken out of her
459
+ ward lends nothing.
460
+ - `test/seal.test.ts`: the arithmetic against `protocol/vectors/arithmetic.json`,
461
+ then the seal, round trip, and what it refuses; then the framing against
462
+ `protocol/vectors/framing.json`. The arithmetic is standard and any language has
463
+ it. The framing is Quo's own, and a kit that reproduces the hashes and
464
+ not the ward pk, the digest, the signed ask body, the two sealed shapes,
465
+ the invitation or the knock is not this protocol. Every seed in that file
466
+ is fixed, so every output is fixed. The ask body is the payload as JSON
467
+ and is not canonical: the vectors pin the order the type declares, and a
468
+ kit that emits another order interoperates, because a door verifies the
469
+ bytes it received, and will not reproduce the vectors. `hops` is in no
470
+ vector, because nothing sets it. The invitation is JSON too, and the
471
+ knock is pinned as the ask it is, by the heir, to the heir, announcing
472
+ the knocker's own key, and then proven at a real door: a ward booted on
473
+ the vector's seed, drawing the heir secret as its first entropy, mints
474
+ the invitation vector, binds the knock vector at its door, answers it,
475
+ spends the heir, and refuses the same bytes again.
476
+ - `test/break.test.ts`: adversarial probes. Each states what the protocol
477
+ promises, then tries to break it.
478
+ - `test/gaps.test.ts` and `test/gaps2.test.ts`: cases the memory harbor
479
+ cannot express because it is honest, in-order and lossless: a lost reply,
480
+ a cycle, a partition written down and read back, a long relation, one
481
+ seed booted twice. Each test is a claim that the ward is wrong; one that
482
+ will not go red is not a bug.
483
+ - `src/conformance/store.ts`: the store suite, written against the store
484
+ interface alone and handed a maker of fresh stores. A fresh store keeps
485
+ nothing; what was put comes back as it went in, as values and not as the
486
+ object; a name already kept is refused; save and record touch only their
487
+ part and are nothing on a name not kept; take hands a ward out and frees
488
+ the name; hints are kept by pk and the last one wins.
489
+ - `src/conformance/reach.ts`: the reach suite, written against the reach
490
+ interface alone and handed a far side it controls: a door held behind a
491
+ pk over there, and the far side dropped. The door's bytes come back;
492
+ nothing for a pk nobody holds; asks in flight at once each get their own
493
+ answer; what crosses is a copy both ways; and once the far side is gone,
494
+ nothing. It cannot assert that a reach which sent and then lost the line
495
+ answers nothing at all, since a suite cannot wait forever.
496
+ - `test/harbor.test.ts`: the harbor pieces on the library's own ground,
497
+ no device and no wire. The store suite against the memory store; the
498
+ reach suite against the socket framing over two lines in one process, so
499
+ the frames are asserted with no network under them; the conformance
500
+ suite against two harbor cores over memory stores reaching each other
501
+ in-process, drop and adopt as the migration; a ward that moves harbor
502
+ keeping its pk and every relation, the rendezvous told on the line in
503
+ hand; a ward booted or dropped after a line opened, announced on that
504
+ line, and a claim on a pk nobody holds binding nothing; a restart from
505
+ the store with the hints; the dialer over a stubbed line, announce, a claim proven
506
+ at a real door before it binds and a claim nobody there holds left
507
+ unbound, fallback, unbind and the wait before it dials again; and the frames against
508
+ `protocol/vectors/wire.json`, the ask, the reply, nothing delivered and the
509
+ announce, so a kit reproduces the bytes on a socket; and the request
510
+ reach against the request record in the same file, over a fetch that
511
+ sees what a listener would, one POST with the suite in its header, the
512
+ reply as a 200 and nothing delivered as a 404. A socket to a real
513
+ listener and every real store pass the same suites outside this tree.
514
+ - `test/terrain.test.ts`: the terrain census. Nothing under `src/being/`,
515
+ `src/ward/`, `src/harbor/` or `src/conformance/` names a runtime, and
516
+ nothing reaches for a global outside the ones it names; nor does the
517
+ census name one the tree has stopped using. It reads the source as text
518
+ and not as a parse: it is there to catch drift, and does not pretend to
519
+ stop someone determined to get around it.
520
+ - `test/assert.test.ts`: `src/conformance/assert.ts` against
521
+ `node:assert/strict`, pair by pair. The suite means the same thing on
522
+ every terrain only if those two agree, so they are compared and not
523
+ trusted.
524
+ - `test/floor.test.ts`: the five algorithms this terrain must carry, probed
525
+ one by one; that the arithmetic spends every one of them; and the two ways
526
+ a terrain can be short -- no `crypto.subtle` at all, and a subtle without
527
+ the curves -- each failing at the first call, in one sentence.
528
+ - `test/cost.test.ts`: what an ask costs. Every other suite is blind to it:
529
+ a change that doubles the work per ask leaves all of them green. The
530
+ assertion is the count of calls into `crypto.subtle`, which is the same
531
+ number on every terrain and does not move because another lane of the gate
532
+ is running; a regression in it is somebody deriving again what the ward
533
+ already holds. Beside it, that the kept keys stay under their bound however
534
+ long a ward talks, and one wall clock set an order of magnitude wide, which
535
+ is a smoke alarm and not a measurement.
536
+ - `test/package.test.ts`: the package as a stranger meets it. The tarball
537
+ is packed, installed into an empty folder with nothing but Node, and
538
+ every entry point the exports map names is imported. Every other suite
539
+ reaches the source through a path or a workspace link, which resolves
540
+ outside `node_modules`, where Node strips types; this is the one that
541
+ cannot.
542
+ - `test/bundle.test.ts`: the three words bundled as a consumer must bundle
543
+ them, the artefact read for anything a terrain cannot provide, and then the
544
+ whole conformance suite run out of the bundle.
545
+ - `test/terrain/browser.test.ts`, `test/terrain/edge.test.ts`,
546
+ `test/terrain/deno.test.ts` and `test/terrain/bun.test.ts`: that same
547
+ bundle and that same suite in a real Chromium, in workerd, in Deno and in
548
+ Bun, plus each terrain's floor, and, for the browser, a page whose
549
+ `crypto` has no `subtle`. Deno runs it a second time with every permission
550
+ denied but read -- no net, no environment, no write, no subprocess -- which
551
+ is where a ward that had quietly come to need one of them would fail, and
552
+ nowhere else in this repository. A terrain whose binary is absent skips and
553
+ names the command that installs it; it never passes quietly.
554
+ `test/terrain/engine.ts` runs the bundle in another engine on this machine.
555
+ Behind `npm run check:terrain`. `test/terrain/bundle.ts` is the
556
+ bundler and the reference run, `test/terrain/exercise.ts` is what every
557
+ terrain runs, `test/terrain/floor.ts` is the floor -- each written once, so
558
+ no terrain can be probed for less than another.
559
+ - `test/world.ts`: the memory-harbor probe and the three topologies, held
560
+ apart from any one chapter because every terrain drives the same one. It
561
+ also answers the two asks the estate needs: the census, which is every
562
+ partition in the world as values, and a migration, which lifts a partition
563
+ out of one harbor and boots it from the same seed in the other -- same
564
+ seed, same pk, so every standing anyone holds still points at her.
565
+ - `test/repo.test.ts`: the papers, the README and the project instructions
566
+ against the repository: every path they name exists, every
567
+ source file carries its licence, the package names no host and no user,
568
+ the awaitable calls are marked and
569
+ awaited in the examples above, the allowance is decided in the protocol and
570
+ off its Open list, and the allowance is time alone on both sides of the
571
+ door.
572
+
573
+ The conformance suite is checkable wherever a ward runs. Its runner is handed
574
+ in, and the five assertions it makes are `src/conformance/assert.ts`, held to
575
+ `node:assert/strict`'s own behaviour by `test/assert.test.ts`.
576
+ `test/terrain/exercise.ts` is what every terrain runs -- the whole suite,
577
+ under all three topologies, and the floor probe. Node is the reference: no
578
+ terrain carries a count of its own, so a test added to the suite is demanded
579
+ of every terrain at once. Five run it: Node, a browser, workerd, Deno and
580
+ Bun. Bun is the one that is not V8, so "the language alone" is checked
581
+ against two implementations of the language and not one.
582
+
583
+ ## The gate
584
+
585
+ The gate is `npm run check`: typecheck under TypeScript 7, oxlint with
586
+ type-aware rules, markdownlint, then every suite under `node --test`. There
587
+ is no CI; the gate runs in seconds and is run before every commit. Nothing
588
+ red is committed except a test marked todo, which is a claim the tree does
589
+ not yet meet and says so.
590
+
591
+ `npm run check` is Node alone, and stays fast. `npm run check:terrain` is
592
+ every other terrain, the browser, workerd, Deno and Bun, kept apart because
593
+ each is a binary of its own and the browser is a download and not a
594
+ package: a fresh clone needs `npx playwright install chromium` first, and is
595
+ told so in one sentence rather than a stack trace.
@@ -16,14 +16,14 @@ export type AskSpec = {
16
16
  };
17
17
 
18
18
  // Names a subclass may not use for an ask, because they are the base's own.
19
- const RESERVED = new Set(['answer', 'describe', 'stance', 'cells', 'standings', 'occupants', 'occupant', 'invite', 'knock', 'take', 'boot', 'constructor']);
19
+ const RESERVED = new Set(['answer', 'describe', 'stance', 'cells', 'standings', 'occupants', 'occupant', 'invite', 'knock', 'take', 'boot', 'lend', 'constructor']);
20
20
 
21
21
  // Whether she has a method of that name, written on her own prototype chain
22
22
  // below Object's. A name Object lends every object, `hasOwnProperty` or
23
23
  // `toString`, is not a method she wrote; and a field she assigns in her own
24
24
  // constructor is not there yet when the base checks, so an ask is a method
25
25
  // on the prototype and nothing else.
26
- const method = (self: object, name: string): boolean => {
26
+ const wrote = (self: object, name: string): boolean => {
27
27
  for (let p = Object.getPrototypeOf(self); p !== null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
28
28
  if (Object.hasOwn(p, name)) return typeof (p as Record<string, unknown>)[name] === 'function';
29
29
  }
@@ -51,7 +51,7 @@ export class Being {
51
51
  const C = this.constructor as typeof Being;
52
52
  for (const name of Object.keys(C.asks)) {
53
53
  if (RESERVED.has(name)) throw new Error(`ask '${name}' is a reserved name`);
54
- if (!method(this, name)) throw new Error(`ask '${name}' has no method on the prototype`);
54
+ if (!wrote(this, name)) throw new Error(`ask '${name}' has no method on the prototype`);
55
55
  }
56
56
  // Own keys only: a default named after a member of Object's prototype is
57
57
  // still hers, and still missing until she writes it.
@@ -67,6 +67,11 @@ export class Being {
67
67
  get occupants(): Occupants {
68
68
  return this.stance.occupants;
69
69
  }
70
+ // A standing at one of the things this device can do, under an id of hers.
71
+ // The ward knocks and takes it for her; the invitation never reaches her.
72
+ lend(name: string, id: string): Promise<string | null> {
73
+ return this.stance.lend(name, id);
74
+ }
70
75
  invite(id: string, notes?: JsonObject): Promise<Invitation | null> {
71
76
  return this.stance.occupants.invite(id, notes);
72
77
  }
@@ -3,5 +3,5 @@
3
3
  export { Being, type AskSpec } from './being.ts';
4
4
  export { silence, isSilence, answered, unreached, isUnreached, word, isWord, wordOf, told, DOOR_WORDS, isDoorWord } from './silence.ts';
5
5
  export { digest, canonical } from './digest.ts';
6
- export { OWNER, PUBLIC, RESERVED_IDS, isBlueprint, invitationArgs } from './types.ts';
6
+ export { OWNER, PUBLIC, RESERVED_IDS, isBlueprint, invitationArgs, isInvitation } from './types.ts';
7
7
  export type * from './types.ts';