@quo-systems/quo 0.1.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.
@@ -0,0 +1,516 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The ward conformance suite. The beings are fixed; the ward is under test.
3
+ //
4
+ // A ward is handed in as a probe:
5
+ //
6
+ // make() -> world
7
+ // world.boot(key, Class, cells?, { isPublic }) -> handle
8
+ // world.heir(invitation) -> { current, announced, fresh } | undefined
9
+ // what the inviting ward holds for that heir
10
+ // world.oneWard true when every being shares one ward
11
+ // handle.pk the ward pk she lives behind
12
+ // handle.being the object, as the test drives it
13
+ // handle.cells her cells, as the ward keeps them
14
+ // handle.down = true | false her door is unreachable (needs canDown)
15
+ // handle.minted.has(pk) every pk her side ever minted
16
+ // handle.forgeKnock(invitation) make her side knock with a key of its own, unannounced
17
+ // world.census() every partition in the world, as values
18
+ // world.migrate(key) her ward moves to another harbor, same seed, same pk
19
+ //
20
+ // Every assertion here is the truth's. Nothing here knows how a ward is built.
21
+ import { assert } from './assert.ts';
22
+ import { silence, isSilence, isUnreached } from '../being/silence.ts';
23
+ import { digest } from '../being/digest.ts';
24
+ import type { Asker, BeingClass, Cells, Invitation, Json, JsonObject } from '../being/types.ts';
25
+ import { Printer, Shop, Customer, Echo, Member } from './beings.ts';
26
+ import { estate } from './estate.ts';
27
+
28
+ export type Handle<B = unknown> = {
29
+ pk: string;
30
+ being: B;
31
+ cells: Cells;
32
+ down: boolean;
33
+ minted: { has(pk: string): boolean };
34
+ forgeKnock(inv: Invitation): void;
35
+ };
36
+ export type HeirView = { current: string; announced: string | null; fresh: boolean };
37
+
38
+ // The whole world's bookkeeping, as values. What a ward keeps, read the way
39
+ // an auditor reads it: the estate chapter holds it to being a graph, and the
40
+ // secrets are here so that it can prove they are nowhere else.
41
+ export type Census = {
42
+ wards: Record<string, { beings: string[]; public: string | null; heirs: Record<string, { being: string; id: string; current: string; announced: string | null; fresh: boolean; mark: number; spent: number[] }> }>;
43
+ binds: Record<string, { ward: string; standings: Record<string, { ward: string; heir: string | null; seq: number }>; occupants: Record<string, string>; knocks: string[]; answered: string[]; minted: string[]; secrets: string[] }>;
44
+ cells: Record<string, Cells>;
45
+ };
46
+ export type World = {
47
+ oneWard?: boolean;
48
+ boot<B>(key: string, Class: BeingClass, cells?: JsonObject, opts?: { isPublic?: boolean }): Promise<Handle<B>>;
49
+ heir(inv: Invitation): HeirView | undefined;
50
+ census(): Census;
51
+ migrate(key: string): Promise<void>;
52
+ };
53
+
54
+ export { Printer, Shop, Customer, Echo, Member };
55
+
56
+ const HEX64 = /^[0-9a-f]{64}$/;
57
+
58
+ // The runner is handed in. Node passes `node:test`; a browser and an edge
59
+ // worker pass a shim of their own. The suite is the ward's truth, and the
60
+ // ward's truth must be checkable wherever a ward runs, not only here.
61
+ export type Runner = (name: string, opts: { skip?: string | false }, fn: () => Promise<void> | void) => void;
62
+
63
+ export function conform(label: string, make: () => Promise<World>, { canDown = true, test }: { canDown?: boolean; test: Runner }) {
64
+ const t = (name: string, fn: () => Promise<void> | void) => test(`[${label}] ${name}`, {}, fn);
65
+ const down = (name: string, fn: () => Promise<void> | void) => test(`[${label}] ${name}`, { skip: !canDown && 'nothing to cut inside one ward' }, fn);
66
+
67
+ async function world({ printerInvitesShop = true } = {}) {
68
+ const w = await make();
69
+ const printer = await w.boot<Printer>('P', Printer);
70
+ const shop = await w.boot<Shop>('S', Shop);
71
+ const alice = await w.boot<Customer>('A', Customer);
72
+ const bob = await w.boot<Customer>('B', Customer);
73
+ if (printerInvitesShop) await shop.being.keepPrinter((await printer.being.invite('shop'))!);
74
+ return { w, printer, shop, alice, bob };
75
+ }
76
+ const bp = (x: unknown) => x as { asks: { name: string }[] };
77
+
78
+ // ---- invite
79
+
80
+ t('invite mints the occupant and a heir: the invitation is the far ward pk, the heir pk, and the heir secret. nothing of her.', async () => {
81
+ const { w, shop } = await world();
82
+ const inv = await shop.being.invite('gold');
83
+ assert.deepEqual(Object.keys(shop.cells.occupants), ['cust1']);
84
+ assert.deepEqual(Object.keys(inv).sort(), ['heir', 'secret', 'ward']);
85
+ assert.match(inv.ward, /^[0-9a-f]{128}$/);
86
+ assert.match(inv.heir!, HEX64);
87
+ assert.match(inv.secret!, HEX64);
88
+ assert.equal(inv.ward, shop.pk);
89
+ assert.ok(shop.minted.has(inv.heir!)); // rotation one: her ward minted the occupant's first key
90
+ assert.deepEqual(w.heir(inv), { current: inv.heir, announced: null, fresh: true }); // and keeps only its pk
91
+ for (const word of [inv.heir!, inv.secret!]) assert.ok(!JSON.stringify(shop.cells).includes(word)); // never in her cells
92
+ });
93
+
94
+ t('two invites, two heirs, nothing shared', async () => {
95
+ const { shop } = await world();
96
+ const i1 = await shop.being.invite();
97
+ const i2 = await shop.being.invite();
98
+ assert.notEqual(i1.heir, i2.heir);
99
+ assert.notEqual(i1.secret, i2.secret);
100
+ assert.equal(i1.ward, i2.ward);
101
+ });
102
+
103
+ t('remove before any knock: the heir is forgotten, a knock on it is silence', async () => {
104
+ const { w, shop, alice } = await world();
105
+ const inv = await shop.being.invite();
106
+ shop.being.occupants.remove('cust1');
107
+ assert.equal(w.heir(inv), undefined);
108
+ assert.ok(isSilence(await alice.being.join(inv)));
109
+ assert.deepEqual(alice.cells.standings, {});
110
+ });
111
+
112
+ // ---- consume, the knock
113
+
114
+ t('a valid knock binds the id to her own key and spends the heir; the asker is { id }', async () => {
115
+ const { w, shop, alice } = await world();
116
+ const inv = await shop.being.invite('gold');
117
+ assert.deepEqual(await alice.being.join(inv), { welcome: true });
118
+ const h = w.heir(inv)!;
119
+ assert.equal(h.fresh, false);
120
+ assert.notEqual(h.current, inv.heir); // rotation two: the heir is spent
121
+ assert.ok(alice.minted.has(h.current)); // her own key speaks now
122
+ assert.ok(!shop.minted.has(h.current)); // which the inviter never held
123
+ assert.equal(shop.cells.occupants.cust1.notes.asks, 1);
124
+ });
125
+
126
+ t('a second knock with the same invitation, from another: silence, nothing rebinds', async () => {
127
+ const { w, shop, alice, bob } = await world();
128
+ const inv = await shop.being.invite();
129
+ await alice.being.join(inv);
130
+ const bound = w.heir(inv)!.current;
131
+ assert.ok(isSilence(await bob.being.join(inv)));
132
+ assert.equal(w.heir(inv)!.current, bound); // still alice's
133
+ assert.deepEqual(bob.cells.standings, {});
134
+ });
135
+
136
+ t('a knock with the wrong secret, or a forged heir, is silence and never reaches her', async () => {
137
+ const { w, shop, alice } = await world();
138
+ let reached = 0;
139
+ const real = shop.being.answer.bind(shop.being);
140
+ shop.being.answer = (...a) => (reached++, real(...a));
141
+ const inv = await shop.being.invite();
142
+ const other = await shop.being.invite();
143
+ assert.ok(isSilence(await alice.being.knock({ ward: inv.ward, heir: 'f'.repeat(64), secret: inv.secret }, 'hello')));
144
+ assert.ok(isSilence(await alice.being.knock({ ward: inv.ward, heir: inv.heir, secret: other.secret }, 'hello'))); // a real secret, the wrong heir
145
+ assert.ok(isSilence(await alice.being.knock({ ward: inv.ward, heir: inv.heir, secret: '0'.repeat(64) }, 'hello')));
146
+ assert.equal(reached, 0);
147
+ assert.equal(w.heir(inv)!.fresh, true); // still unbound
148
+ });
149
+
150
+ t('the same knocker may knock again: her key is bound, no heir needed, it is an ask, and it announces her next key', async () => {
151
+ const { w, shop, alice } = await world();
152
+ const inv = await shop.being.invite();
153
+ assert.deepEqual(await alice.being.join(inv), { welcome: true });
154
+ const k1 = w.heir(inv)!.current;
155
+ assert.equal(w.heir(inv)!.announced, null); // a knock announces her key and dies; nothing further yet
156
+ assert.deepEqual(await alice.being.knock(inv, 'hello'), { welcome: true });
157
+ assert.equal(shop.cells.occupants.cust1.notes.asks, 2);
158
+ const h = w.heir(inv)!;
159
+ assert.equal(h.current, k1); // k1 spoke
160
+ assert.ok(h.announced && alice.minted.has(h.announced) && h.announced !== k1); // and vouched for the next
161
+ });
162
+
163
+ t('a knocker who presents a key of her own, unannounced, on a bound heir: silence', async () => {
164
+ const { w, shop, alice, bob } = await world();
165
+ const inv = await shop.being.invite();
166
+ await alice.being.join(inv);
167
+ const bound = w.heir(inv)!.current;
168
+ bob.forgeKnock(inv);
169
+ assert.ok(isSilence(await bob.being.knock(inv, 'hello')));
170
+ assert.equal(w.heir(inv)!.current, bound);
171
+ assert.equal(shop.cells.occupants.cust1.notes.asks, 1);
172
+ });
173
+
174
+ t('ids never cross: hers for me and mine for her are unrelated', async () => {
175
+ const { shop, alice } = await world();
176
+ await alice.being.join(await shop.being.invite());
177
+ assert.deepEqual(Object.keys(alice.cells.standings), ['shop']);
178
+ assert.deepEqual(Object.keys(shop.cells.occupants), ['cust1']);
179
+ });
180
+
181
+ down('a standing is born only after an answer; silence or unreached births nothing; an unreached knock does not spend the heir', async () => {
182
+ const { w, shop, alice } = await world();
183
+ const inv = await shop.being.invite();
184
+ shop.down = true;
185
+ assert.ok(isUnreached(await alice.being.join(inv)));
186
+ assert.deepEqual(alice.cells.standings, {});
187
+ assert.equal(w.heir(inv)!.fresh, true);
188
+ shop.down = false;
189
+ assert.deepEqual(await alice.being.join(inv), { welcome: true }); // the same invitation still works
190
+ const inv2 = await shop.being.invite();
191
+ shop.being.occupants.remove('cust2'); // she changed her mind before the knock
192
+ assert.ok(isSilence(await alice.being.join(inv2)));
193
+ assert.deepEqual(Object.keys(alice.cells.standings), ['shop']);
194
+ });
195
+
196
+ t('take without a knock, or after a silent knock, births nothing', async () => {
197
+ const { shop, alice } = await world();
198
+ const inv = await shop.being.invite();
199
+ assert.equal(await alice.being.take('shop', inv), null);
200
+ shop.being.occupants.remove('cust1');
201
+ assert.ok(isSilence(await alice.being.knock(inv, 'hello')));
202
+ assert.equal(await alice.being.take('shop', inv), null);
203
+ assert.deepEqual(alice.cells.standings, {});
204
+ });
205
+
206
+ // ---- asks
207
+
208
+ t('an occupant asks, a standing is asked, an object comes back', async () => {
209
+ const { alice, printer, shop } = await world();
210
+ await alice.being.join(await shop.being.invite());
211
+ assert.deepEqual(await alice.being.buy('hat'), { ok: true, receipt: 'receipt for hat' });
212
+ assert.equal((printer.cells.jobs as unknown[]).length, 1);
213
+ assert.equal((shop.cells.sales as JsonObject[])[0].by, 'cust1');
214
+ });
215
+
216
+ t('every answered ask rotates: each call speaks as the key the last one announced, and announces a fresh one, all minted by her side', async () => {
217
+ const { w, shop, alice } = await world();
218
+ const inv = await shop.being.invite();
219
+ await alice.being.join(inv);
220
+ const seen = new Set<string>([w.heir(inv)!.current]);
221
+ let expectNext = w.heir(inv)!.current; // after the knock, her key is current and nothing is announced
222
+ for (let i = 0; i < 3; i++) {
223
+ await alice.being.buy('hat');
224
+ const h = w.heir(inv)!;
225
+ assert.equal(h.current, expectNext); // this call spoke as what was announced before
226
+ assert.ok(h.announced && alice.minted.has(h.announced) && !seen.has(h.announced)); // and announced a fresh key
227
+ seen.add(h.announced);
228
+ expectNext = h.announced!;
229
+ }
230
+ assert.equal((shop.cells.sales as unknown[]).length, 3);
231
+ assert.equal(seen.size, 4);
232
+ });
233
+
234
+ t('asks in flight together on one standing: every one is answered, the rotation stays a single line, and the relation lives', async () => {
235
+ const { w, shop, alice } = await world();
236
+ const inv = await shop.being.invite();
237
+ await alice.being.join(inv);
238
+ const bound = w.heir(inv)!.current; // the key her side bound at the knock
239
+
240
+ const out = await Promise.all(Array.from({ length: 5 }, (_, i) => alice.being.buy(`hat${i}`)));
241
+ for (const [i, o] of out.entries()) assert.deepEqual(o, { ok: true, receipt: `receipt for hat${i}` }, `ask ${i} was answered`);
242
+ assert.equal((shop.cells.sales as unknown[]).length, 5); // each arrived once. none was lost, none was doubled
243
+
244
+ // five asks, five rotations, one line: the door's key is the fifth her
245
+ // side minted, and the side that did not mint it never held it.
246
+ const h = w.heir(inv)!;
247
+ assert.notEqual(h.current, bound);
248
+ assert.ok(alice.minted.has(h.current));
249
+ assert.ok(!shop.minted.has(h.current));
250
+
251
+ // and the two sides still agree, which is the whole of it: a relation
252
+ // that survived the crowd is a relation that still answers.
253
+ assert.deepEqual(await alice.being.buy('after'), { ok: true, receipt: 'receipt for after' });
254
+ });
255
+
256
+ t('two knocks in flight on one invitation: one heir, one binding, and she may still ask', async () => {
257
+ const { w, shop, alice } = await world();
258
+ const inv = await shop.being.invite();
259
+ const [a, b] = await Promise.all([alice.being.join(inv), alice.being.join(inv)]);
260
+ // the same knocker knocking twice at once is not two occupants: the heir
261
+ // is spent once, and both calls speak for the one relation she is building.
262
+ assert.deepEqual(Object.keys(shop.cells.occupants), ['cust1']);
263
+ for (const r of [a, b]) assert.ok(!isSilence(r) && !isUnreached(r), 'both knocks were answered');
264
+ const h = w.heir(inv)!;
265
+ assert.equal(h.fresh, false);
266
+ assert.ok(alice.minted.has(h.current)); // her own key speaks, exactly as after one knock
267
+ // What she does with the standing afterwards is not this test's. A take
268
+ // reads the relation where the knock left it, and a knock still in flight
269
+ // has not left it anywhere yet: the standing is born a step behind the
270
+ // door, and its first ask speaks under a number already honoured.
271
+ });
272
+
273
+ t('an inherited name is not an ask through a ward either: her cells do not come back for the asking', async () => {
274
+ const { shop, alice } = await world();
275
+ await alice.being.join(await shop.being.invite()); // plain: refund is hidden from her
276
+ shop.cells.ledger = { swiss: 'CH-99' };
277
+ for (const name of ['valueOf', 'toString', 'constructor', '__proto__', 'hasOwnProperty']) {
278
+ const out = await alice.being.standings.shop!.ask(name);
279
+ assert.deepEqual(out, { error: 'unknown ask' }, name);
280
+ assert.ok(!JSON.stringify(out).includes('CH-99'), `${name} carried her cells out`);
281
+ }
282
+ // the blueprint never named one of them, and the gate is the same gate
283
+ const blueprint = bp(await alice.being.learn());
284
+ for (const ask of blueprint.asks) assert.ok(!['valueOf', 'toString', 'constructor'].includes(ask.name));
285
+ assert.deepEqual(await alice.being.standings.shop!.ask('refund'), { error: 'unknown ask' }); // still hidden, still the same answer
286
+ });
287
+
288
+ t('symmetry: the customer offers a way back inside args; the shop takes a gold one', async () => {
289
+ const { shop, alice, bob } = await world();
290
+ await alice.being.join(await shop.being.invite('gold'));
291
+ await bob.being.join(await shop.being.invite('plain'));
292
+ assert.deepEqual(Object.keys(shop.cells.standings), ['prn', 'vip-cust1']);
293
+ assert.deepEqual(alice.cells.heard, [{ from: 'shop-back', method: 'hi' }]);
294
+ assert.deepEqual(bob.cells.heard, []);
295
+ assert.deepEqual(await shop.being.standings['vip-cust1']!.ask('hi'), { heard: 'hi' });
296
+ });
297
+
298
+ t('the printer never invited the shop: no standing, an error object, no throw', async () => {
299
+ const { alice, shop } = await world({ printerInvitesShop: false });
300
+ await alice.being.join(await shop.being.invite());
301
+ assert.deepEqual(await alice.being.buy('hat'), { error: 'no printer' });
302
+ });
303
+
304
+ t('the empty ask is describe; shaped per asker', async () => {
305
+ const { shop, alice, bob } = await world();
306
+ await alice.being.join(await shop.being.invite('gold'));
307
+ await bob.being.join(await shop.being.invite());
308
+ const names = (x: unknown) => bp(x).asks.map((a) => a.name);
309
+ assert.deepEqual(names(await alice.being.learn()), ['hello', 'buy', 'refund']);
310
+ assert.deepEqual(names(await bob.being.learn()), ['hello', 'buy']);
311
+ });
312
+
313
+ t('the digest is per relation: two occupants, two digests of one being, both true', async () => {
314
+ const { shop, alice, bob } = await world();
315
+ await alice.being.join(await shop.being.invite('gold'));
316
+ await bob.being.join(await shop.being.invite());
317
+ await alice.being.learn();
318
+ await bob.being.learn();
319
+ assert.notEqual(alice.cells.standings.shop.digest, bob.cells.standings.shop.digest);
320
+ });
321
+
322
+ t('describe caches blueprint and digest in the asker cells', async () => {
323
+ const { shop, alice } = await world();
324
+ await alice.being.join(await shop.being.invite());
325
+ const out = await alice.being.learn();
326
+ assert.deepEqual(alice.cells.standings.shop.blueprint, out);
327
+ assert.equal(alice.cells.standings.shop.digest, await digest(out as Json));
328
+ });
329
+
330
+ t('a changed standing is noticed by digest, without carrying the blueprint', async () => {
331
+ const { alice, printer, shop } = await world();
332
+ await alice.being.join(await shop.being.invite());
333
+ await shop.being.standings.prn!.ask();
334
+ (printer.cells.asks as JsonObject[]).push({ name: 'scan', input: {} }); // she changed
335
+ await alice.being.buy('hat');
336
+ const st = shop.cells.standings.prn;
337
+ assert.notEqual(st.seen, st.digest);
338
+ assert.equal(shop.cells.occupants.cust1.notes.printerChanged, true);
339
+ });
340
+
341
+ t('the digest rides on every answer: seen updates without a describe from her side', async () => {
342
+ const { alice, printer, shop } = await world();
343
+ await alice.being.join(await shop.being.invite());
344
+ let describes = 0;
345
+ const real = printer.being.answer.bind(printer.being);
346
+ printer.being.answer = (a, m, x) => (m === undefined && describes++, real(a, m, x));
347
+ await alice.being.buy('hat'); // shop asks printer once
348
+ const st = shop.cells.standings.prn;
349
+ assert.equal(st.seen, await digest((await real({ id: 'shop' })) as Json));
350
+ assert.equal(describes, 1); // the far ward ran her describe, in process, once per answer
351
+ assert.equal(st.digest, null); // she never fetched it herself
352
+ });
353
+
354
+ t('a silent refresh leaves seen untouched', async () => {
355
+ const { alice, printer, shop } = await world();
356
+ await alice.being.join(await shop.being.invite());
357
+ await shop.being.standings.prn!.ask();
358
+ const before = shop.cells.standings.prn.seen;
359
+ const real = printer.being.answer.bind(printer.being);
360
+ printer.being.answer = async (a, m, x) => (m === undefined ? silence : real(a, m, x));
361
+ await alice.being.buy('hat');
362
+ assert.equal(shop.cells.standings.prn.seen, before);
363
+ });
364
+
365
+ down('unreached is the ward word, distinct from silence, carries nothing, safe to retry', async () => {
366
+ const { alice, printer, shop } = await world();
367
+ await alice.being.join(await shop.being.invite());
368
+ printer.down = true;
369
+ assert.deepEqual(await alice.being.buy('hat'), { error: 'printer unreachable', retry: true });
370
+ const out = await shop.being.standings.prn!.ask('print', { doc: 'x' });
371
+ assert.ok(isUnreached(out) && !isSilence(out));
372
+ assert.deepEqual(Object.keys(out), []);
373
+ printer.down = false;
374
+ assert.deepEqual(await alice.being.buy('hat'), { ok: true, receipt: 'receipt for hat' });
375
+ });
376
+
377
+ t('a removed occupant is silence to the far side and her heir is forgotten; a dropped standing is gone', async () => {
378
+ const { w, alice, shop } = await world();
379
+ const inv = await shop.being.invite();
380
+ await alice.being.join(inv);
381
+ shop.being.occupants.remove('cust1');
382
+ assert.equal(w.heir(inv), undefined);
383
+ assert.ok(isSilence(await alice.being.buy('hat')));
384
+ alice.being.standings.remove('shop');
385
+ assert.equal(alice.being.standings.shop, undefined);
386
+ assert.ok(isSilence(await alice.being.knock(inv, 'hello'))); // and a re-knock finds no heir
387
+ const again = await shop.being.invite(); // cust2. a new heir
388
+ assert.notEqual(again.heir, inv.heir);
389
+ assert.deepEqual(await alice.being.knock(again, 'hello'), { welcome: true });
390
+ });
391
+
392
+ t('dropping a standing while an ask on it is in flight does not crash', async () => {
393
+ const { alice, shop } = await world();
394
+ await alice.being.join(await shop.being.invite());
395
+ const p = alice.being.buy('hat');
396
+ alice.being.standings.remove('shop');
397
+ assert.deepEqual(await p, { ok: true, receipt: 'receipt for hat' });
398
+ assert.equal((shop.cells.sales as unknown[]).length, 1);
399
+ });
400
+
401
+ t('a being that throws is silence outside; the ward stays up', async () => {
402
+ const w = await make();
403
+ class Bomb {
404
+ s: unknown;
405
+ constructor(s: unknown) {
406
+ this.s = s;
407
+ }
408
+ answer(_: Asker, m?: string) {
409
+ if (m === undefined) return { asks: [], notes: {} };
410
+ throw new Error('boom');
411
+ }
412
+ }
413
+ const x = await w.boot<Bomb>('X', Bomb);
414
+ const a = await w.boot<Customer>('A', Customer);
415
+ const inv = (await (x.being.s as Customer['stance']).occupants.invite('a'))!;
416
+ assert.ok(isSilence(await a.being.knock(inv, 'any')));
417
+ assert.deepEqual(await a.being.knock(inv), { asks: [], notes: {} });
418
+ });
419
+
420
+ t('null is an answer, silence is not', async () => {
421
+ const w = await make();
422
+ class Nul {
423
+ s: unknown;
424
+ constructor(s: unknown) {
425
+ this.s = s;
426
+ }
427
+ answer(_: Asker, m?: string) {
428
+ return m === undefined ? { asks: [], notes: {} } : null;
429
+ }
430
+ }
431
+ const n = await w.boot<Nul>('N', Nul);
432
+ const a = await w.boot<Customer>('A', Customer);
433
+ const out = await a.being.knock((await (n.being.s as Customer['stance']).occupants.invite('a'))!, 'x');
434
+ assert.equal(out, null);
435
+ assert.ok(!isSilence(out));
436
+ });
437
+
438
+ t('the raw shape is a being through a ward, exactly like one on the base class', async () => {
439
+ const w = await make();
440
+ const e = await w.boot<Echo>('E', Echo);
441
+ const a = await w.boot<Customer>('A', Customer);
442
+ const inv = (await e.being.s.occupants.invite('a'))!;
443
+ assert.deepEqual(await a.being.knock(inv, 'echo', { x: 1 }), { from: 'a', x: 1 });
444
+ assert.equal(await a.being.take('echo', inv), 'echo');
445
+ assert.deepEqual(await a.being.standings.echo!.ask('echo', { y: 2 }), { from: 'a', y: 2 });
446
+ assert.equal(a.cells.standings.echo.seen, await digest({ asks: [{ name: 'echo', input: {} }], notes: {} }));
447
+ });
448
+
449
+ // ---- public
450
+
451
+ t('a public being is asked by anyone as {}; a knock on her needs no heir and may be taken; a ward with none is silence; an unknown pk is unreached', async () => {
452
+ const w = await make();
453
+ class Pub {
454
+ s: { cells: Cells };
455
+ constructor(s: { cells: Cells }) {
456
+ this.s = s;
457
+ this.s.cells.askers ??= [];
458
+ }
459
+ answer(asker: Asker, m?: string, args?: JsonObject) {
460
+ if (m === undefined) return { asks: [{ name: 'time', input: {} }], notes: {} };
461
+ (this.s.cells.askers as Json[]).push(asker as Json);
462
+ return { time: 12, got: args ?? {} };
463
+ }
464
+ }
465
+ const pub = await w.boot<Pub>('PUB', Pub as never, {}, { isPublic: true });
466
+ const priv = await w.boot<Pub>('PRIV', Pub as never); // same class, not public
467
+ const a = await w.boot<Customer>('A', Customer);
468
+ const b = await w.boot<Customer>('B', Customer);
469
+ const at = (h: { pk: string }) => ({ ward: h.pk });
470
+ assert.deepEqual(await a.being.knock(at(pub), 'time', { x: 1 }), { time: 12, got: { x: 1 } });
471
+ assert.deepEqual(await b.being.knock(at(pub), 'time'), { time: 12, got: {} });
472
+ assert.deepEqual(pub.cells.askers, [{}, {}]);
473
+ assert.deepEqual(pub.cells.occupants, {}); // she never invited anyone
474
+ if (!w.oneWard) assert.ok(isSilence(await a.being.knock(at(priv), 'time'))); // a ward with no public being: silence
475
+ assert.ok(isUnreached(await a.being.knock({ ward: 'a'.repeat(128) }, 'time'))); // no door anywhere: unreached
476
+ assert.ok(isSilence(await a.being.knock({ ward: 'not a pk' }, 'time'))); // not even an address
477
+ assert.equal(await a.being.take('clock', at(pub)), 'clock');
478
+ assert.deepEqual(await a.being.standings.clock!.ask('time'), { time: 12, got: {} });
479
+ assert.equal(a.cells.standings.clock.seen, await digest((await a.being.standings.clock!.ask()) as Json));
480
+ });
481
+
482
+ // ---- keys stay where they belong
483
+
484
+ t("two rotations: the heir is the inviter's, the bound key is the knocker's, and neither side minted the other's", async () => {
485
+ const { w, shop, alice, bob } = await world();
486
+ const i1 = await shop.being.invite('gold');
487
+ const i2 = await shop.being.invite();
488
+ assert.ok(shop.minted.has(i1.heir!) && shop.minted.has(i2.heir!));
489
+ await alice.being.join(i1);
490
+ await bob.being.join(i2);
491
+ const h1 = w.heir(i1)!, h2 = w.heir(i2)!;
492
+ assert.ok(alice.minted.has(h1.current) && !shop.minted.has(h1.current) && !bob.minted.has(h1.current));
493
+ assert.ok(bob.minted.has(h2.current) && !shop.minted.has(h2.current) && !alice.minted.has(h2.current));
494
+ assert.notEqual(h1.current, h2.current); // no key serves two relations
495
+ assert.ok(!alice.minted.has(i1.heir!)); // alice never minted anything of the shop's
496
+ });
497
+
498
+ t('her cells carry no key: heir, secret, and bound key live in the bind table only', async () => {
499
+ const { w, shop, alice } = await world();
500
+ const inv = await shop.being.invite();
501
+ await alice.being.join(inv);
502
+ await alice.being.learn();
503
+ const words = [inv.heir!, inv.secret!, w.heir(inv)!.current];
504
+ for (const cells of [shop.cells, alice.cells]) for (const word of words) assert.ok(!JSON.stringify(cells).includes(word), `${word} in cells`);
505
+ });
506
+
507
+ // ---- the estate
508
+
509
+ estate(label, make, { canDown, test });
510
+ }
511
+
512
+ // The store and reach suites, beside the ward's: every store a harbor keeps
513
+ // wards in and every reach it carries bytes by passes its own, on the
514
+ // terrain it is written for.
515
+ export { conformStore } from './store.ts';
516
+ export { conformReach, type FarSide } from './reach.ts';
@@ -0,0 +1,83 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The reach conformance suite: what every reach must show, written against
3
+ // the interface alone. A reach is handed in as a maker of a fresh line to a
4
+ // far side the suite controls: `hold` puts a door behind a pk over there,
5
+ // `drop` takes the far side away, the line down or the listener gone. The
6
+ // suite asks for bytes and expects the door's bytes back, nothing for a pk
7
+ // nobody holds, and nothing once the far side is gone. It cannot assert
8
+ // that a reach which sent and then lost the line answers nothing at all,
9
+ // since a suite cannot wait forever; that promise is read in the code.
10
+ import { assert } from './assert.ts';
11
+ import type { Reach } from '../harbor/reach.ts';
12
+ import type { Runner } from './index.ts';
13
+
14
+ export type FarSide = {
15
+ reach: Reach;
16
+ hold(pk: string, door: (bytes: Uint8Array) => Promise<Uint8Array>): Promise<void>;
17
+ drop(): Promise<void>;
18
+ };
19
+
20
+ const pk = (b: string) => b.repeat(64);
21
+ const bytes = (...xs: number[]) => Uint8Array.from(xs);
22
+ const wait = (ms: number) => new Promise<void>((ok) => setTimeout(ok, ms));
23
+
24
+ export function conformReach(label: string, make: () => Promise<FarSide>, { test }: { test: Runner }) {
25
+ const t = (name: string, fn: () => Promise<void>) => test(`[${label}] ${name}`, {}, fn);
26
+
27
+ t('bytes to a pk the far side holds come back as the door answered', async () => {
28
+ const far = await make();
29
+ try {
30
+ await far.hold(pk('aa'), async (b) => bytes(...b, 9));
31
+ assert.deepEqual(await far.reach.carry(pk('aa'), bytes(1, 2, 3)), bytes(1, 2, 3, 9));
32
+ } finally {
33
+ await far.drop();
34
+ }
35
+ });
36
+
37
+ t('bytes to a pk nobody holds are nothing', async () => {
38
+ const far = await make();
39
+ try {
40
+ await far.hold(pk('aa'), async (b) => b);
41
+ assert.equal(await far.reach.carry(pk('bb'), bytes(1)), undefined);
42
+ } finally {
43
+ await far.drop();
44
+ }
45
+ });
46
+
47
+ t('asks in flight at once each get their own answer', async () => {
48
+ const far = await make();
49
+ try {
50
+ await far.hold(pk('aa'), async (b) => (await wait(b[0]! * 10), bytes(b[0]!)));
51
+ const out = await Promise.all([far.reach.carry(pk('aa'), bytes(3)), far.reach.carry(pk('aa'), bytes(1)), far.reach.carry(pk('aa'), bytes(2))]);
52
+ assert.deepEqual(out, [bytes(3), bytes(1), bytes(2)]);
53
+ } finally {
54
+ await far.drop();
55
+ }
56
+ });
57
+
58
+ t('what comes back is a copy, and what was sent was one', async () => {
59
+ const far = await make();
60
+ try {
61
+ let seen: Uint8Array | undefined;
62
+ const reply = bytes(7, 7);
63
+ await far.hold(pk('aa'), async (b) => ((seen = b), reply));
64
+ const sent = bytes(4, 5);
65
+ const back = (await far.reach.carry(pk('aa'), sent))!;
66
+ assert.deepEqual(back, bytes(7, 7));
67
+ back[0] = 0;
68
+ assert.deepEqual(reply, bytes(7, 7), 'the caller wrote through to the door');
69
+ seen![0] = 0;
70
+ assert.deepEqual(sent, bytes(4, 5), 'the door wrote through to the caller');
71
+ } finally {
72
+ await far.drop();
73
+ }
74
+ });
75
+
76
+ t('once the far side is gone, bytes are nothing', async () => {
77
+ const far = await make();
78
+ await far.hold(pk('aa'), async (b) => b);
79
+ assert.deepEqual(await far.reach.carry(pk('aa'), bytes(1)), bytes(1));
80
+ await far.drop();
81
+ assert.equal(await far.reach.carry(pk('aa'), bytes(1)), undefined);
82
+ });
83
+ }