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