nervur 0.19.2 → 0.20.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 (205) hide show
  1. package/README.md +66 -101
  2. package/dist/being/being.d.ts +7 -15
  3. package/dist/being/being.js +16 -67
  4. package/dist/being/digest.d.ts +1 -1
  5. package/dist/being/digest.js +7 -27
  6. package/dist/being/faculty.d.ts +9 -0
  7. package/dist/being/faculty.js +68 -0
  8. package/dist/being/index.d.ts +4 -5
  9. package/dist/being/index.js +5 -5
  10. package/dist/being/types.d.ts +35 -62
  11. package/dist/being/types.js +3 -57
  12. package/dist/being/words.d.ts +13 -0
  13. package/dist/being/words.js +14 -0
  14. package/dist/contract/index.d.ts +26 -0
  15. package/dist/contract/index.js +19 -0
  16. package/dist/crypto/aes.d.ts +3 -0
  17. package/dist/crypto/aes.js +24 -0
  18. package/dist/crypto/bytes.d.ts +6 -0
  19. package/dist/crypto/bytes.js +33 -0
  20. package/dist/crypto/ed25519.d.ts +3 -0
  21. package/dist/crypto/ed25519.js +85 -0
  22. package/dist/crypto/hash.d.ts +2 -0
  23. package/dist/crypto/hash.js +11 -0
  24. package/dist/crypto/index.d.ts +7 -0
  25. package/dist/crypto/index.js +10 -0
  26. package/dist/crypto/json.d.ts +8 -0
  27. package/dist/crypto/json.js +250 -0
  28. package/dist/crypto/mlkem.d.ts +12 -0
  29. package/dist/crypto/mlkem.js +36 -0
  30. package/dist/crypto/subtle.d.ts +10 -0
  31. package/dist/crypto/subtle.js +29 -0
  32. package/dist/crypto/x25519.d.ts +2 -0
  33. package/dist/crypto/x25519.js +21 -0
  34. package/dist/folder/index.d.ts +13 -0
  35. package/dist/folder/index.js +125 -0
  36. package/dist/harbor/dock.d.ts +19 -0
  37. package/dist/harbor/dock.js +92 -0
  38. package/dist/harbor/harbor.d.ts +22 -0
  39. package/dist/harbor/harbor.js +84 -0
  40. package/dist/harbor/index.d.ts +4 -10
  41. package/dist/harbor/index.js +5 -18
  42. package/dist/harbor/registry.d.ts +7 -0
  43. package/dist/harbor/registry.js +13 -0
  44. package/dist/harbor/terrain.d.ts +8 -0
  45. package/dist/harbor/terrain.js +2 -0
  46. package/dist/index.d.ts +7 -0
  47. package/dist/index.js +14 -0
  48. package/dist/pointer/bodies.d.ts +20 -0
  49. package/dist/pointer/bodies.js +45 -0
  50. package/dist/pointer/index.d.ts +2 -0
  51. package/dist/pointer/index.js +4 -0
  52. package/dist/pointer/world.d.ts +34 -0
  53. package/dist/pointer/world.js +76 -0
  54. package/dist/quo/door.d.ts +34 -0
  55. package/dist/quo/door.js +172 -0
  56. package/dist/quo/index.d.ts +8 -0
  57. package/dist/quo/index.js +11 -0
  58. package/dist/quo/invitation.d.ts +7 -0
  59. package/dist/quo/invitation.js +15 -0
  60. package/dist/quo/keys.d.ts +40 -0
  61. package/dist/quo/keys.js +79 -0
  62. package/dist/quo/payload.d.ts +15 -0
  63. package/dist/quo/payload.js +55 -0
  64. package/dist/quo/relations.d.ts +40 -0
  65. package/dist/quo/relations.js +33 -0
  66. package/dist/quo/reply.d.ts +13 -0
  67. package/dist/quo/reply.js +35 -0
  68. package/dist/quo/seal.d.ts +42 -0
  69. package/dist/quo/seal.js +78 -0
  70. package/dist/quo/standing.d.ts +39 -0
  71. package/dist/quo/standing.js +90 -0
  72. package/dist/ward/allowance.d.ts +13 -9
  73. package/dist/ward/allowance.js +28 -67
  74. package/dist/ward/cells.d.ts +6 -6
  75. package/dist/ward/cells.js +67 -179
  76. package/dist/ward/index.d.ts +5 -10
  77. package/dist/ward/index.js +6 -13
  78. package/dist/ward/partition.d.ts +44 -60
  79. package/dist/ward/partition.js +56 -285
  80. package/dist/ward/stance.d.ts +29 -20
  81. package/dist/ward/stance.js +173 -406
  82. package/dist/ward/ward-being.d.ts +33 -0
  83. package/dist/ward/ward-being.js +76 -0
  84. package/dist/ward/ward.d.ts +43 -11
  85. package/dist/ward/ward.js +215 -363
  86. package/package.json +19 -33
  87. package/src/being/being.ts +31 -78
  88. package/src/being/digest.ts +7 -35
  89. package/src/being/faculty.ts +66 -0
  90. package/src/being/index.ts +5 -6
  91. package/src/being/types.ts +47 -145
  92. package/src/being/words.ts +31 -0
  93. package/src/contract/index.ts +44 -0
  94. package/src/crypto/aes.ts +26 -0
  95. package/src/crypto/bytes.ts +37 -0
  96. package/src/crypto/ed25519.ts +84 -0
  97. package/src/crypto/hash.ts +14 -0
  98. package/src/crypto/index.ts +10 -0
  99. package/src/crypto/json.ts +241 -0
  100. package/src/crypto/mlkem.ts +38 -0
  101. package/src/crypto/subtle.ts +33 -0
  102. package/src/crypto/x25519.ts +21 -0
  103. package/src/folder/index.ts +134 -0
  104. package/src/harbor/dock.ts +101 -0
  105. package/src/harbor/harbor.ts +105 -0
  106. package/src/harbor/index.ts +5 -19
  107. package/src/harbor/registry.ts +20 -0
  108. package/src/harbor/terrain.ts +13 -0
  109. package/src/index.ts +20 -0
  110. package/src/pointer/bodies.ts +47 -0
  111. package/src/pointer/index.ts +4 -0
  112. package/src/pointer/world.ts +91 -0
  113. package/src/quo/door.ts +178 -0
  114. package/src/quo/index.ts +11 -0
  115. package/src/quo/invitation.ts +15 -0
  116. package/src/quo/keys.ts +91 -0
  117. package/src/quo/payload.ts +61 -0
  118. package/src/quo/relations.ts +62 -0
  119. package/src/quo/reply.ts +38 -0
  120. package/src/quo/seal.ts +101 -0
  121. package/src/quo/standing.ts +111 -0
  122. package/src/stand/main.ts +19 -0
  123. package/src/stand/stand.ts +178 -0
  124. package/src/ward/allowance.ts +37 -75
  125. package/src/ward/cells.ts +63 -176
  126. package/src/ward/index.ts +6 -17
  127. package/src/ward/partition.ts +86 -326
  128. package/src/ward/stance.ts +185 -420
  129. package/src/ward/ward-being.ts +97 -0
  130. package/src/ward/ward.ts +229 -363
  131. package/dist/being/lent.d.ts +0 -32
  132. package/dist/being/lent.js +0 -72
  133. package/dist/being/silence.d.ts +0 -12
  134. package/dist/being/silence.js +0 -41
  135. package/dist/conformance/assert.d.ts +0 -11
  136. package/dist/conformance/assert.js +0 -106
  137. package/dist/conformance/beings.d.ts +0 -199
  138. package/dist/conformance/beings.js +0 -188
  139. package/dist/conformance/estate.d.ts +0 -5
  140. package/dist/conformance/estate.js +0 -388
  141. package/dist/conformance/index.d.ts +0 -80
  142. package/dist/conformance/index.js +0 -819
  143. package/dist/conformance/reach.d.ts +0 -10
  144. package/dist/conformance/reach.js +0 -72
  145. package/dist/conformance/store.d.ts +0 -5
  146. package/dist/conformance/store.js +0 -113
  147. package/dist/harbor/box.d.ts +0 -121
  148. package/dist/harbor/box.js +0 -121
  149. package/dist/harbor/core.d.ts +0 -55
  150. package/dist/harbor/core.js +0 -662
  151. package/dist/harbor/dial.d.ts +0 -9
  152. package/dist/harbor/dial.js +0 -81
  153. package/dist/harbor/memory.d.ts +0 -29
  154. package/dist/harbor/memory.js +0 -104
  155. package/dist/harbor/reach.d.ts +0 -36
  156. package/dist/harbor/reach.js +0 -199
  157. package/dist/harbor/store.d.ts +0 -35
  158. package/dist/harbor/store.js +0 -62
  159. package/dist/vector/cases.d.ts +0 -42
  160. package/dist/vector/cases.js +0 -223
  161. package/dist/vector/index.d.ts +0 -6
  162. package/dist/vector/index.js +0 -8
  163. package/dist/vector/stand.d.ts +0 -9
  164. package/dist/vector/stand.js +0 -77
  165. package/dist/vector/world.d.ts +0 -144
  166. package/dist/vector/world.js +0 -209
  167. package/dist/ward/arithmetic.d.ts +0 -31
  168. package/dist/ward/arithmetic.js +0 -249
  169. package/dist/ward/door.d.ts +0 -18
  170. package/dist/ward/door.js +0 -186
  171. package/dist/ward/ground.d.ts +0 -29
  172. package/dist/ward/ground.js +0 -56
  173. package/dist/ward/heirs.d.ts +0 -13
  174. package/dist/ward/heirs.js +0 -117
  175. package/dist/ward/json.d.ts +0 -2
  176. package/dist/ward/json.js +0 -163
  177. package/dist/ward/owner.d.ts +0 -13
  178. package/dist/ward/owner.js +0 -220
  179. package/dist/ward/seal.d.ts +0 -52
  180. package/dist/ward/seal.js +0 -150
  181. package/src/being/lent.ts +0 -72
  182. package/src/being/silence.ts +0 -46
  183. package/src/conformance/assert.ts +0 -100
  184. package/src/conformance/beings.ts +0 -188
  185. package/src/conformance/estate.ts +0 -412
  186. package/src/conformance/index.ts +0 -965
  187. package/src/conformance/reach.ts +0 -83
  188. package/src/conformance/store.ts +0 -125
  189. package/src/harbor/box.ts +0 -131
  190. package/src/harbor/core.ts +0 -699
  191. package/src/harbor/dial.ts +0 -112
  192. package/src/harbor/memory.ts +0 -123
  193. package/src/harbor/reach.ts +0 -221
  194. package/src/harbor/store.ts +0 -91
  195. package/src/vector/cases.ts +0 -257
  196. package/src/vector/index.ts +0 -11
  197. package/src/vector/stand.ts +0 -76
  198. package/src/vector/world.ts +0 -232
  199. package/src/ward/arithmetic.ts +0 -251
  200. package/src/ward/door.ts +0 -186
  201. package/src/ward/ground.ts +0 -142
  202. package/src/ward/heirs.ts +0 -116
  203. package/src/ward/json.ts +0 -144
  204. package/src/ward/owner.ts +0 -214
  205. package/src/ward/seal.ts +0 -178
@@ -1,819 +0,0 @@
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({ lends }) -> world. `lends` maps a name the box lends to the
7
- // class of the being it lends under it; the probe
8
- // stands one of each in a ward its harbor roots,
9
- // and every ward it boots is grounded to lend them
10
- // world.boot(key, Class, cells?, { isPublic }) -> handle
11
- // world.heir(invitation) -> { current, announced, fresh } | undefined
12
- // what the inviting ward holds for that heir
13
- // world.oneWard true when every being shares one ward
14
- // handle.pk the ward pk she lives behind
15
- // handle.being the object, as the test drives it
16
- // handle.cells her cells, as the ward keeps them
17
- // handle.down = true | false her door is unreachable (needs canDown)
18
- // handle.minted.has(pk) the last eight pks her side minted
19
- // handle.forgeKnock(invitation) make her side knock with a key of its own, unannounced
20
- // world.census() every partition in the world, as values
21
- // world.migrate(key) her ward moves to another harbor, same seed, same pk
22
- //
23
- // Every assertion here is the truth's. Nothing here knows how a ward is built.
24
- import { assert } from './assert.js';
25
- import { silence, answered, isSilence, isUnreached, told } from '../being/silence.js';
26
- import { digest } from '../being/digest.js';
27
- import { Printer, Shop, Customer, Echo, Maker, Member } from './beings.js';
28
- import { estate } from './estate.js';
29
- export { Printer, Shop, Customer, Echo, Maker, Member };
30
- // The two shapes the suite asserts, spelled here for the whole folder. They
31
- // are spelled again in `src/ward/seal.ts`, and that is the point: this suite
32
- // is what a second kit runs against its own ward, so it states the shapes it
33
- // expects instead of importing ours, and nothing under `src/conformance`
34
- // reaches into `src/ward` for anything.
35
- export const HEX64 = /^[0-9a-f]{64}$/; // one key, one secret, one digest
36
- const HEX128 = /^[0-9a-f]{128}$/; // a ward pk: the signing pk then the padlock
37
- // Her ward's word, as she receives it.
38
- const said = (x, w) => told(x) === w;
39
- // A world whose harness stands a ward up again. The two members are optional
40
- // on the probe and required of a harness that says it can, so a chapter that
41
- // needs them says so here rather than reaching past a maybe at every line.
42
- const able = (w) => w;
43
- // The same, for a world whose store can be made to refuse one row.
44
- const picky = (w) => w;
45
- // `canRefuse` is the one flag that is off unless a harness says otherwise. A
46
- // harness whose store cannot be made to say no is the common case, and a
47
- // harbor that leaves `keep` out of its ground is a harbor for which no store
48
- // can refuse: both skip the chapter rather than fail it, and both say so at
49
- // the call.
50
- export function conform(label, make, { canDown = true, canLend = true, canRestart = true, canRefuse = false, test }) {
51
- const t = (name, fn) => test(`[${label}] ${name}`, {}, fn);
52
- const down = (name, fn) => test(`[${label}] ${name}`, { skip: !canDown && 'nothing to cut inside one ward' }, fn);
53
- const lends = (name, fn) => test(`[${label}] ${name}`, { skip: !canLend && 'this harness stands no ward of its harbor to lend from' }, fn);
54
- const kept = (name, fn) => test(`[${label}] ${name}`, { skip: !canRestart && 'this harness cannot hand a ward a partition and stand it up again' }, fn);
55
- const refused = (name, fn) => test(`[${label}] ${name}`, { skip: !canRefuse && 'this harness has no store that can be made to refuse a row' }, fn);
56
- async function world({ printerInvitesShop = true } = {}) {
57
- const w = await make();
58
- const printer = await w.boot('P', Printer);
59
- const shop = await w.boot('S', Shop);
60
- const alice = await w.boot('A', Customer);
61
- const bob = await w.boot('B', Customer);
62
- if (printerInvitesShop)
63
- await shop.being.keepPrinter((await printer.being.invite('shop')));
64
- return { w, printer, shop, alice, bob };
65
- }
66
- const bp = (x) => x;
67
- // ---- invite
68
- 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 () => {
69
- const { w, shop } = await world();
70
- const inv = await shop.being.invite('gold');
71
- assert.deepEqual(Object.keys(shop.cells.occupants), ['cust1']);
72
- assert.deepEqual(Object.keys(inv).sort(), ['heir', 'secret', 'ward']);
73
- assert.match(inv.ward, HEX128);
74
- assert.match(inv.heir, HEX64);
75
- assert.match(inv.secret, HEX64);
76
- assert.equal(inv.ward, shop.pk);
77
- assert.ok(shop.minted.has(inv.heir)); // rotation one: her ward minted the occupant's first key
78
- assert.deepEqual(w.heir(inv), { current: inv.heir, announced: null, fresh: true }); // and keeps only its pk
79
- for (const word of [inv.heir, inv.secret])
80
- assert.ok(!JSON.stringify(shop.cells).includes(word)); // never in her cells
81
- });
82
- t('two invites, two heirs, nothing shared', async () => {
83
- const { shop } = await world();
84
- const i1 = await shop.being.invite();
85
- const i2 = await shop.being.invite();
86
- assert.notEqual(i1.heir, i2.heir);
87
- assert.notEqual(i1.secret, i2.secret);
88
- assert.equal(i1.ward, i2.ward);
89
- });
90
- t('remove before any knock: the heir is forgotten, and a knock on it by whoever holds it hears removed', async () => {
91
- const { w, shop, alice } = await world();
92
- const inv = await shop.being.invite();
93
- shop.being.occupants.remove('cust1');
94
- assert.equal(w.heir(inv), undefined);
95
- assert.ok(said(await alice.being.join(inv), 'removed'));
96
- assert.deepEqual(alice.cells.standings, {});
97
- });
98
- // ---- consume, the knock
99
- t('a valid knock binds the id to her own key and spends the heir; the asker is { id }', async () => {
100
- const { w, shop, alice } = await world();
101
- const inv = await shop.being.invite('gold');
102
- assert.deepEqual(await alice.being.join(inv), { welcome: true });
103
- const h = w.heir(inv);
104
- assert.equal(h.fresh, false);
105
- assert.notEqual(h.current, inv.heir); // rotation two: the heir is spent
106
- assert.ok(alice.minted.has(h.current)); // her own key speaks now
107
- assert.ok(!shop.minted.has(h.current)); // which the inviter never held
108
- assert.equal(shop.cells.occupants.cust1.notes.asks, 1);
109
- });
110
- t('a second knock with the same invitation, from another: silence, nothing rebinds', async () => {
111
- const { w, shop, alice, bob } = await world();
112
- const inv = await shop.being.invite();
113
- await alice.being.join(inv);
114
- const bound = w.heir(inv).current;
115
- assert.ok(isSilence(await bob.being.join(inv)));
116
- assert.equal(w.heir(inv).current, bound); // still alice's
117
- assert.deepEqual(bob.cells.standings, {});
118
- });
119
- t('a knock with the wrong secret, or a forged heir, is silence and never reaches her', async () => {
120
- const { w, shop, alice } = await world();
121
- let reached = 0;
122
- const real = shop.being.answer.bind(shop.being);
123
- shop.being.answer = (...a) => (reached++, real(...a));
124
- const inv = await shop.being.invite();
125
- const other = await shop.being.invite();
126
- assert.ok(isSilence(await alice.being.knock({ ward: inv.ward, heir: 'f'.repeat(64), secret: inv.secret }, 'hello')));
127
- assert.ok(isSilence(await alice.being.knock({ ward: inv.ward, heir: inv.heir, secret: other.secret }, 'hello'))); // a real secret, the wrong heir
128
- assert.ok(isSilence(await alice.being.knock({ ward: inv.ward, heir: inv.heir, secret: '0'.repeat(64) }, 'hello')));
129
- assert.equal(reached, 0);
130
- assert.equal(w.heir(inv).fresh, true); // still unbound
131
- });
132
- 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 () => {
133
- const { w, shop, alice } = await world();
134
- const inv = await shop.being.invite();
135
- assert.deepEqual(await alice.being.join(inv), { welcome: true });
136
- const k1 = w.heir(inv).current;
137
- assert.equal(w.heir(inv).announced, null); // a knock announces her key and dies; nothing further yet
138
- assert.deepEqual(await alice.being.knock(inv, 'hello'), { welcome: true });
139
- assert.equal(shop.cells.occupants.cust1.notes.asks, 2);
140
- const h = w.heir(inv);
141
- assert.equal(h.current, k1); // k1 spoke
142
- assert.ok(h.announced && alice.minted.has(h.announced) && h.announced !== k1); // and vouched for the next
143
- });
144
- t('a knocker who presents a key of her own, unannounced, on a bound heir: silence', async () => {
145
- const { w, shop, alice, bob } = await world();
146
- const inv = await shop.being.invite();
147
- await alice.being.join(inv);
148
- const bound = w.heir(inv).current;
149
- bob.forgeKnock(inv);
150
- assert.ok(isSilence(await bob.being.knock(inv, 'hello')));
151
- assert.equal(w.heir(inv).current, bound);
152
- assert.equal(shop.cells.occupants.cust1.notes.asks, 1);
153
- });
154
- t('ids never cross: hers for me and mine for her are unrelated', async () => {
155
- const { shop, alice } = await world();
156
- await alice.being.join(await shop.being.invite());
157
- assert.deepEqual(Object.keys(alice.cells.standings), ['shop']);
158
- assert.deepEqual(Object.keys(shop.cells.occupants), ['cust1']);
159
- });
160
- down('a standing is born only after an answer; silence or unreached births nothing; an unreached knock does not spend the heir', async () => {
161
- const { w, shop, alice } = await world();
162
- const inv = await shop.being.invite();
163
- shop.down = true;
164
- assert.ok(isUnreached(await alice.being.join(inv)));
165
- assert.deepEqual(alice.cells.standings, {});
166
- assert.equal(w.heir(inv).fresh, true);
167
- shop.down = false;
168
- assert.deepEqual(await alice.being.join(inv), { welcome: true }); // the same invitation still works
169
- const inv2 = await shop.being.invite();
170
- shop.being.occupants.remove('cust2'); // she changed her mind before the knock
171
- assert.ok(said(await alice.being.join(inv2), 'removed'));
172
- assert.deepEqual(Object.keys(alice.cells.standings), ['shop']);
173
- });
174
- // ---- the names that are nobody's to take
175
- //
176
- // Two lists, refused at both ends of the same namespace. The ward speaks
177
- // under its own words, so a being who could mint one would be a being whose
178
- // ward is indistinguishable from an occupant of hers. And standings are
179
- // reached by name, so a relation taken as one of the calls on them would be
180
- // a relation nothing can reach. A kit that ported invite without either
181
- // list passes every other scene in this file.
182
- t('the reserved ids and the calls on standings are refused at invite and at take, and nothing is written for them', async () => {
183
- // alice hosts here rather than the shop, because the shop's own `invite`
184
- // takes a tier and mints the id itself. This one is the stance's, which
185
- // is where the rule lives.
186
- const { w, alice, bob } = await world();
187
- // `__proto__` is in the list because a partition cannot hold it. A kit
188
- // that refuses it only where it writes her cells writes the ward's half
189
- // first and then throws, which leaves one relation in two standings and a
190
- // throw where this table promises a null.
191
- for (const id of ['OWNER', 'PUBLIC', 'knock', 'take', 'remove', '__proto__']) {
192
- assert.equal(await alice.being.invite(id), null, `${id} is not hers to mint`);
193
- assert.equal(Object.hasOwn(alice.cells.occupants, id), false, `and nothing was written for ${id}`);
194
- // The other end: a guest is answered and then refused the name.
195
- const inv = (await alice.being.invite(`for-${id}`));
196
- assert.deepEqual(await bob.being.knock(inv, 'hi'), { heard: 'hi' }, `the knock for ${id} was answered`);
197
- assert.equal(await bob.being.take(id, inv), null, `take refuses ${id} for the same reason`);
198
- assert.equal(Object.hasOwn(bob.cells.standings, id), false, `and nothing was written for ${id}`);
199
- // The ward's half too, and not only hers: a standing filed here with no
200
- // record of it in her cells is a lane nobody can see and nobody removes.
201
- assert.equal(Object.hasOwn(w.census().binds.B.standings, id), false, `and the ward wrote no keys for ${id}`);
202
- assert.equal(await bob.being.take(`as-${id}`, inv), `as-${id}`, 'and an ordinary id takes the same invitation');
203
- }
204
- // And the three calls are still the calls, not records that shadowed them.
205
- for (const call of ['knock', 'take', 'remove'])
206
- assert.equal(typeof bob.being.standings[call], 'function', `standings.${call} is the call`);
207
- });
208
- // ---- the owner
209
- //
210
- // The second pointer a ward hands back. Every ask below answers an object,
211
- // because the owner hears objects and never a word: a refusal is `{ error }`
212
- // with its reason, which is what a side renders.
213
- t('the owner boots, marks one public being, invites, knocks, removes and unboots, and refuses each in an object', async () => {
214
- const { w, shop, alice } = await world();
215
- const ask = w.owner('S');
216
- const bp = (await ask());
217
- assert.deepEqual(bp.asks.map((a) => a.name), ['boot', 'public', 'invite', 'knock', 'remove', 'unboot', 'ask'], 'seven asks, in this order');
218
- for (const a of bp.asks) {
219
- assert.equal(typeof a.description, 'string', `${a.name} says what it is for`);
220
- assert.equal(a.input.type, 'object', `${a.name} takes one object`);
221
- }
222
- assert.match(bp.notes.pk, HEX128);
223
- assert.equal(typeof bp.notes.beings, 'object');
224
- assert.deepEqual(await ask('nosuch'), { error: 'unknown ask' });
225
- // boot: a key already taken is refused, and so is a class nobody holds.
226
- assert.deepEqual(await ask('boot', { key: 'S', class: 'Shop' }), { error: 'no such class, or key taken' });
227
- assert.deepEqual(await ask('boot', { key: 'fresh', class: 'NoSuchClass' }), { error: 'no such class, or key taken' });
228
- assert.deepEqual(await ask('boot', { key: 42, class: 'Shop' }), { error: 'no such class, or key taken' }, 'a name is a string or it is nothing');
229
- // public: one per ward, null takes the mark off, and the ward is nobody's
230
- // public being.
231
- assert.deepEqual(await ask('public', { key: 'nobody' }), { error: 'no such being' });
232
- assert.deepEqual(await ask('public', { key: bp.notes.pk }), { error: 'no such being' }, 'the ward is a being to nobody outside');
233
- // invite and remove, on a being of the ward, by id.
234
- assert.deepEqual(await ask('invite', { being: 'nobody', id: 'x' }), { error: 'no such being' });
235
- const inv = (await ask('invite', { being: 'S', id: 'by-owner', notes: { tier: 'gold' } }));
236
- assert.match(inv.heir, HEX64);
237
- assert.deepEqual(shop.cells.occupants['by-owner'].notes, { tier: 'gold' }, 'the notes she minted under are hers to read');
238
- assert.deepEqual(await ask('remove', { being: 'S', id: 'nothing-of-hers' }), { error: 'nothing to remove' });
239
- assert.deepEqual(await ask('remove', { being: 'S', id: 'by-owner' }), { removed: 'by-owner' });
240
- assert.equal(Object.hasOwn(shop.cells.occupants, 'by-owner'), false);
241
- // knock: the owner knocks for a being of hers and takes the standing.
242
- const forA = (await ask('invite', { being: 'S', id: 'for-alice' }));
243
- const knocked = (await w.owner('A'))('knock', { being: 'A', id: 'via-owner', invitation: forA, method: 'hello' });
244
- assert.deepEqual(await knocked, { taken: 'via-owner', answer: { welcome: true } });
245
- assert.deepEqual(Object.keys(alice.cells.standings), ['via-owner']);
246
- assert.deepEqual(await alice.being.standings['via-owner'].ask('hello'), { welcome: true }, 'and it is an ordinary standing');
247
- // unboot: the inverse of boot, and the ward refuses itself.
248
- assert.deepEqual(await ask('unboot', { being: bp.notes.pk }), { error: 'no such being' });
249
- assert.deepEqual(await ask('unboot', { being: 'nobody' }), { error: 'no such being' });
250
- const gone = (await ask('unboot', { being: 'S' }));
251
- assert.equal(gone.unbooted, 'S');
252
- assert.ok(gone.removed.includes('for-alice'), 'her relations went with her, and the owner is told which');
253
- assert.ok(said(await alice.being.standings['via-owner'].ask('hello'), 'removed'), 'and her occupant hears removed at the door');
254
- });
255
- // The owner's seventh ask: reaching into a being and asking her. It is the
256
- // third asker of the ward-to-being edge, and the ward is the only one who
257
- // fills it in: nothing outside a ward names OWNER, and no side holds a
258
- // being's answer to judge it again. Strictly less than the unboot beside
259
- // it, which takes her out with every relation she holds.
260
- t('the owner asks a being of the ward, as owner or, with no being named, as nobody at the public being', async () => {
261
- const { w } = await world();
262
- const ask = w.owner('S');
263
- const echo = w.owner('E');
264
- await w.boot('E', Echo);
265
- // Named: she is asked as the owner, and hears the owner's name.
266
- assert.deepEqual(await echo('ask', { being: 'E', method: 'echo', args: { x: 1 } }), { from: 'OWNER', x: 1 }, 'the ward names the asker, and the name is the owner');
267
- assert.deepEqual(bp(await ask('ask', { being: 'S' })).asks.map((a) => a.name), ['hello', 'buy'], 'no method is her describe, and a gate that reads an occupant record shows the owner nothing: refund is hers to nobody here');
268
- assert.deepEqual(await ask('ask', { being: 'S', method: 'hello' }), { welcome: true });
269
- assert.deepEqual(await ask('ask', { being: 'S', method: 'nosuch' }), { error: 'unknown ask' }, 'her own answer, as any asker gets it');
270
- // Nobody named, and nobody home: one refusal, and never a fourth word.
271
- assert.deepEqual(await echo('ask', {}), { error: 'no such being' }, 'a ward with no public being has nobody to ask as nobody');
272
- assert.deepEqual(await echo('public', { key: 'E' }), { public: 'E' });
273
- assert.deepEqual(await echo('ask', { method: 'echo', args: { x: 2 } }), { from: null, x: 2 }, 'with no being named it is the public being, asked as nobody');
274
- // What it refuses, each in an object, because the owner hears objects.
275
- const pk = bp(await ask()).notes.pk;
276
- assert.deepEqual(await ask('ask', { being: 'nobody' }), { error: 'no such being' });
277
- assert.deepEqual(await ask('ask', { being: pk }), { error: 'no such being' }, 'the ward is a being to nobody outside, here as at invite and unboot');
278
- assert.deepEqual(await ask('ask', { being: 42 }), { error: 'no such being' }, 'a name is a string or it is nothing');
279
- assert.deepEqual(await ask('ask', { being: 'S', method: 42 }), { error: 'an ask is named by a word' }, 'and so is an ask');
280
- });
281
- // The one public being, marked and unmarked through the owner. A second
282
- // one would leave the first holding every relation she had, reachable by
283
- // nobody at the bare pk and told by nobody that she had been replaced.
284
- t('a ward has one public being: a second is refused, null takes the mark off, and she keeps everything she held', async () => {
285
- const { w, alice } = await world();
286
- const ask = w.owner('A');
287
- assert.deepEqual(await ask('public', { key: 'A' }), { public: 'A' });
288
- assert.deepEqual(await ask('public', { key: 'A' }), { public: 'A' }, 'marking the same one again is not a second');
289
- const bp = (await ask());
290
- assert.equal(bp.notes.beings.A.public, true, 'and the owner sees which one she is');
291
- const inv = (await alice.being.invite('kept'));
292
- assert.deepEqual(await ask('public', { key: null }), { public: null }, 'null takes the mark off');
293
- const off = (await ask());
294
- assert.equal(off.notes.beings.A.public, false);
295
- assert.ok(Object.hasOwn(alice.cells.occupants, 'kept'), 'retiring her is not destroying her: every relation she held is still hers');
296
- assert.equal(inv.ward, alice.pk);
297
- });
298
- // A relation she dropped is nobody's. The word is her own ward's, said to
299
- // her and never crossing a door, because there is no door left to ask.
300
- t('a standing she dropped is dropped: the word is hers, nothing crosses, and the far side is never told', async () => {
301
- const { shop, alice } = await world();
302
- await alice.being.join(await shop.being.invite());
303
- const standing = alice.being.standings.shop;
304
- alice.being.standings.remove('shop');
305
- assert.deepEqual(alice.cells.standings, {}, 'gone from her cells');
306
- assert.equal(alice.being.standings.shop, undefined, 'and gone from her stance');
307
- assert.ok(said(await standing.ask('hello'), 'dropped'), 'a handle she kept over the drop reaches nothing, and says so');
308
- assert.ok(Object.hasOwn(shop.cells.occupants, 'cust1'), 'the far side holds her seat still: dropping is hers alone and she tells nobody');
309
- });
310
- // ---- a being may make
311
- //
312
- // `boot` is on every stance, so a kit that never wired it hands its beings a
313
- // call that answers null for ever and nothing else here would notice.
314
- t('a being boots one of her ward s classes from inside her own answer, and may ask for a way back to what she made', async () => {
315
- const { w } = await world();
316
- const maker = await w.boot('MK', Maker);
317
- // A harbor holds the classes its device gave it, so one Member is booted
318
- // to put the class there. What the maker does after that is the ward's.
319
- await w.boot('MEM', Member);
320
- assert.deepEqual(await maker.being.open({ class: 'Member', key: 'made-1' }), { made: 'made-1' });
321
- assert.deepEqual(await maker.being.open({ class: 'Member', key: 'made-1' }), { made: null }, 'a key already taken makes nobody');
322
- assert.deepEqual(await maker.being.open({ class: 'NoSuchClass', key: 'made-2' }), { made: null }, 'a class the harbor does not hold makes nobody');
323
- assert.deepEqual(maker.cells.made, ['made-1']);
324
- // With an id she holds a standing to what she made, and the being made
325
- // knows her by her own key and by nothing else.
326
- assert.deepEqual(await maker.being.open({ class: 'Member', key: 'made-3', id: 'mine' }), { made: 'made-3' });
327
- assert.deepEqual(await maker.being.say({ id: 'mine' }), { said: { pong: 'MK' } }, 'she asks through the relation, and arrives under her own key');
328
- assert.deepEqual(await maker.being.say({ id: 'nothing' }), { said: null });
329
- });
330
- // The count, reached the one way a suite that owns no bytes can reach it:
331
- // a caller's numbers are in her own partition, so winding them back and
332
- // standing her ward up again makes her next ask carry a number the far
333
- // door has already honoured. A kit with no once-only delivery answers it
334
- // and passes every other chapter here clean.
335
- kept('a number already honoured is repeated: the far being is not asked twice, and nothing is written for it', async () => {
336
- const { w, shop, alice } = await world();
337
- const inv = await shop.being.invite();
338
- assert.deepEqual(await alice.being.join(inv), { welcome: true });
339
- assert.deepEqual(await alice.being.buy('a hat'), { ok: true, receipt: 'receipt for a hat' });
340
- const sold = shop.cells.sales.length;
341
- // Her keys for the shop, wound back to the number she has just spent.
342
- const kept = able(w);
343
- const p = kept.partition('A');
344
- const keys = p.bind.A.standings;
345
- const mine = keys.shop;
346
- const back = mine.seq - 1;
347
- mine.seq = back;
348
- await kept.restart('A', p);
349
- const again = await alice.being.buy('a second hat');
350
- assert.ok(said(again, 'repeated'), `a number already honoured is repeated, not ${JSON.stringify(again)}`);
351
- assert.equal(shop.cells.sales.length, sold, 'and she was never asked');
352
- // The count carries on from where it was: the next number is fresh.
353
- assert.deepEqual(await alice.being.buy('a third hat'), { ok: true, receipt: 'receipt for a third hat' });
354
- });
355
- // A restart is silent by construction: the ward comes back on the same
356
- // seed with the same pk, and every relation it holds still points at it.
357
- // What must survive with it is what the door has honoured, or a restart is
358
- // a stretch of time in which every number can be replayed.
359
- kept('a restart is silent: same pk, every relation still stands, and the far door still refuses what it spent', async () => {
360
- const { w, shop, alice } = await world();
361
- const inv = await shop.being.invite();
362
- assert.deepEqual(await alice.being.join(inv), { welcome: true });
363
- assert.deepEqual(await alice.being.buy('a hat'), { ok: true, receipt: 'receipt for a hat' });
364
- const pk = shop.pk;
365
- const able2 = able(w);
366
- const mark = JSON.stringify(able2.partition('S').heirs);
367
- await able2.restart('S');
368
- assert.equal(shop.pk, pk, 'a ward that came back changed its pk');
369
- assert.equal(JSON.stringify(able2.partition('S').heirs), mark, 'and it came back holding what it had honoured');
370
- assert.deepEqual(await alice.being.buy('a second hat'), { ok: true, receipt: 'receipt for a second hat' }, 'the relation goes on, and she was never told');
371
- assert.deepEqual(alice.cells.standings.shop.id, 'shop', 'her side was not told either');
372
- });
373
- // Notes are the one thing on a record that a being seeds and a ward keeps
374
- // without ever reading. A kit that files what it was handed rather than a
375
- // copy of it leaves the seeder a hand inside her cells: it writes without
376
- // the ward being told, so what a restart brings back is not what she read.
377
- t('notes are values and are kept as a copy: what the seeder still holds is not what the record holds', async () => {
378
- const { alice } = await world();
379
- const seeded = { tier: { deep: 'gold' } };
380
- assert.ok((await alice.being.invite('noted', seeded)) !== null, 'she minted with notes');
381
- seeded.tier.deep = 'moved';
382
- assert.deepEqual(alice.cells.occupants.noted.notes, { tier: { deep: 'gold' } }, 'the record is the copy, all the way down');
383
- // And notes no harbor could write back are no invitation, for the reason
384
- // every other write is refused: a record it could not keep.
385
- assert.equal(await alice.being.invite('bad-notes', { n: 0 / 0 }), null);
386
- assert.equal(Object.hasOwn(alice.cells.occupants, 'bad-notes'), false, 'and nothing was written for it');
387
- });
388
- // The wait, and the one thing a kit can get wrong that nothing else here
389
- // would notice. A ward bounds every ask itself, so a relation cycle ends:
390
- // A asks B, B asking back holds A's lane, and the third ask can never be
391
- // answered. A kit with no bound waits for ever and hangs, and every other
392
- // chapter in this suite would still pass it. The word is `late`, which
393
- // promises nothing either way, and the relation lives afterwards.
394
- t('the wait is bounded, so a cycle ends: the ask that cannot be answered is late, and the relation lives', async () => {
395
- const { w } = await world();
396
- const a = await w.boot('MA', Member);
397
- const b = await w.boot('MB', Member);
398
- const toB = (await b.being.invite('from-a'));
399
- const toA = (await a.being.invite('from-b'));
400
- assert.ok(answered(await a.being.knock(toB, 'ping')), 'a knocks b');
401
- assert.equal(await a.being.take('b', toB), 'b');
402
- assert.ok(answered(await b.being.knock(toA, 'ping')), 'b knocks a');
403
- assert.equal(await b.being.take('a', toA), 'a');
404
- // A asks B; B, answering, asks A back; A, answering that, asks B on the
405
- // lane the first ask still holds. Every link is a legal ask.
406
- const out = (await a.being.relay({ id: 'b', method: 'relay', args: { id: 'a', method: 'relay', args: { id: 'b', method: 'ping' }, time: 200 } }));
407
- assert.equal(out.said, null, 'the outer ask was answered');
408
- assert.equal(out.out?.said, 'late', 'and what it carries is the ask that could not be, said as late');
409
- // The relation is not the worse for it: the lane is free again and the
410
- // count moved on, which is what a bound is for.
411
- const after = (await a.being.relay({ id: 'b', method: 'ping' }));
412
- assert.equal(after.said, null, 'and she asks again on the same standing');
413
- });
414
- // Lend is boot's other half: what the device already has, reached the one
415
- // way anything is reached. The device is one standing the ward holds at
416
- // the box's being, and a lend is an ask on it; the being lent stands in
417
- // a ward the harbor roots, so nothing here can look at her from the
418
- // outside, and what the ward owes is what is asserted. She is handed an
419
- // id, the standing is an ordinary one, and the invitation is nowhere she
420
- // can read.
421
- lends('what the device lends is a being: she is handed an id, asks on an ordinary standing, and never sees the invitation', async () => {
422
- const w = await make({ lends: { pen: 'Member' } });
423
- const maker = await w.boot('MK', Maker);
424
- assert.deepEqual(await maker.being.borrow({ name: 'pen', id: 'pen' }), { lent: 'pen' }, 'the id she gave is the id she gets');
425
- // An ordinary standing from there on: she asks on it, and arrives at that
426
- // being's door as somebody the far side named.
427
- const said = (await maker.being.say({ id: 'pen' }));
428
- assert.equal(typeof said.said?.pong, 'string', 'she arrived under an id the far being minted');
429
- // The keys are the ward's. Nothing of the relation is in her cells, which
430
- // is the whole difference between this standing and one she gave away.
431
- const c = w.census();
432
- const heir = c.binds.MK.standings.pen.heir;
433
- assert.equal(typeof heir, 'string', 'the ward bound a heir for it');
434
- assert.equal(JSON.stringify(c.cells.MK).includes(heir), false, 'and the invitation reached her nowhere');
435
- // Null is every kind of no, and she cannot tell them apart.
436
- assert.deepEqual(await maker.being.borrow({ name: 'gps', id: 'x' }), { lent: null }, 'a name this box does not lend');
437
- assert.deepEqual(await maker.being.borrow({ name: 'pen', id: 'pen' }), { lent: null }, 'an id she already holds');
438
- assert.deepEqual(await maker.being.borrow({ name: 'pen', id: 'OWNER' }), { lent: null }, 'a word the ward reserves');
439
- });
440
- lends('a harbor with nothing to lend lends nothing, and says so the same way', async () => {
441
- const w = await make();
442
- const maker = await w.boot('MK', Maker);
443
- assert.deepEqual(await maker.being.borrow({ name: 'pen', id: 'pen' }), { lent: null });
444
- assert.equal(w.census().binds.MK.standings.pen, undefined, 'and nothing was written for it');
445
- });
446
- // The case the estate's walk reaches by weight and this reaches by hand: a
447
- // seat kicked after it answered and before it was taken. She holds a key
448
- // the door bound for her, so she is the one person who hears why, and she
449
- // hears it every time. A kit that forgot to keep the last keys of a
450
- // forgotten heir answers her silence, which is what it says to a stranger,
451
- // and she cannot tell being kicked from never having been let in.
452
- t('a relation kicked between the knock and the take: she hears removed, every time, and the take births nothing', async () => {
453
- const { shop, alice, bob } = await world();
454
- const inv = await shop.being.invite();
455
- assert.deepEqual(await alice.being.knock(inv, 'hello'), { welcome: true }, 'she knocked and was answered');
456
- shop.being.occupants.remove('cust1'); // kicked, and she is not told
457
- for (let i = 0; i < 3; i++)
458
- assert.ok(said(await alice.being.knock(inv, 'hello'), 'removed'), `knock ${i}: a spent heir stays spent, and she hears why every time`);
459
- // And the take still births one: it reads the knock record, which says
460
- // she was answered, and the far side kicking her afterwards is not
461
- // something her side can know or should guess at. The standing is hers,
462
- // and it is the standing that hears removed from then on. A kit that
463
- // refused the take here would leave her with no record to read the
464
- // refusal through, and nothing to drop.
465
- assert.equal(await alice.being.take('shop', inv), 'shop', 'the take reads the knock record, and the knock was answered');
466
- assert.ok(said(await alice.being.standings.shop.ask('hello'), 'removed'), 'and the standing is where she hears it now');
467
- alice.being.standings.remove('shop');
468
- assert.deepEqual(alice.cells.standings, {}, 'hers to drop, like any other');
469
- // And only to her: the same invitation in anyone else's hand is silence,
470
- // because the keys the door kept are hers and nobody else's.
471
- assert.ok(isSilence(await bob.being.knock(inv, 'hello')), 'a stranger holding the same invitation learns nothing at all');
472
- });
473
- t('take without a knock, or after a knock that was not answered, births nothing', async () => {
474
- const { shop, alice } = await world();
475
- const inv = await shop.being.invite();
476
- assert.equal(await alice.being.take('shop', inv), null);
477
- shop.being.occupants.remove('cust1');
478
- assert.ok(said(await alice.being.knock(inv, 'hello'), 'removed'));
479
- assert.equal(await alice.being.take('shop', inv), null);
480
- assert.deepEqual(alice.cells.standings, {});
481
- });
482
- // ---- asks
483
- t('an occupant asks, a standing is asked, an object comes back', async () => {
484
- const { alice, printer, shop } = await world();
485
- await alice.being.join(await shop.being.invite());
486
- assert.deepEqual(await alice.being.buy('hat'), { ok: true, receipt: 'receipt for hat' });
487
- assert.equal(printer.cells.jobs.length, 1);
488
- assert.equal(shop.cells.sales[0].by, 'cust1');
489
- });
490
- 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 () => {
491
- const { w, shop, alice } = await world();
492
- const inv = await shop.being.invite();
493
- await alice.being.join(inv);
494
- const seen = new Set([w.heir(inv).current]);
495
- let expectNext = w.heir(inv).current; // after the knock, her key is current and nothing is announced
496
- for (let i = 0; i < 3; i++) {
497
- await alice.being.buy('hat');
498
- const h = w.heir(inv);
499
- assert.equal(h.current, expectNext); // this call spoke as what was announced before
500
- assert.ok(h.announced && alice.minted.has(h.announced) && !seen.has(h.announced)); // and announced a fresh key
501
- seen.add(h.announced);
502
- expectNext = h.announced;
503
- }
504
- assert.equal(shop.cells.sales.length, 3);
505
- assert.equal(seen.size, 4);
506
- });
507
- t('asks in flight together on one standing: every one is answered, the rotation stays a single line, and the relation lives', async () => {
508
- const { w, shop, alice } = await world();
509
- const inv = await shop.being.invite();
510
- await alice.being.join(inv);
511
- const bound = w.heir(inv).current; // the key her side bound at the knock
512
- const out = await Promise.all(Array.from({ length: 5 }, (_, i) => alice.being.buy(`hat${i}`)));
513
- for (const [i, o] of out.entries())
514
- assert.deepEqual(o, { ok: true, receipt: `receipt for hat${i}` }, `ask ${i} was answered`);
515
- assert.equal(shop.cells.sales.length, 5); // each arrived once. none was lost, none was doubled
516
- // five asks, five rotations, one line: the door's key is the fifth her
517
- // side minted, and the side that did not mint it never held it.
518
- const h = w.heir(inv);
519
- assert.notEqual(h.current, bound);
520
- assert.ok(alice.minted.has(h.current));
521
- assert.ok(!shop.minted.has(h.current));
522
- // and the two sides still agree, which is the whole of it: a relation
523
- // that survived the crowd is a relation that still answers.
524
- assert.deepEqual(await alice.being.buy('after'), { ok: true, receipt: 'receipt for after' });
525
- });
526
- t('two knocks in flight on one invitation: one heir, one binding, and she may still ask', async () => {
527
- const { w, shop, alice } = await world();
528
- const inv = await shop.being.invite();
529
- const [a, b] = await Promise.all([alice.being.join(inv), alice.being.join(inv)]);
530
- // the same knocker knocking twice at once is not two occupants: the heir
531
- // is spent once, and both calls speak for the one relation she is building.
532
- assert.deepEqual(Object.keys(shop.cells.occupants), ['cust1']);
533
- for (const r of [a, b])
534
- assert.ok(!isSilence(r) && !isUnreached(r), 'both knocks were answered');
535
- const h = w.heir(inv);
536
- assert.equal(h.fresh, false);
537
- assert.ok(alice.minted.has(h.current)); // her own key speaks, exactly as after one knock
538
- // What she does with the standing afterwards is not this test's. A take
539
- // reads the relation where the knock left it, and a knock still in flight
540
- // has not left it anywhere yet: the standing is born a step behind the
541
- // door, and its first ask speaks under a number already honoured.
542
- });
543
- t('an inherited name is not an ask through a ward either: her cells do not come back for the asking', async () => {
544
- const { shop, alice } = await world();
545
- await alice.being.join(await shop.being.invite()); // plain: refund is hidden from her
546
- shop.cells.ledger = { swiss: 'CH-99' };
547
- for (const name of ['valueOf', 'toString', 'constructor', '__proto__', 'hasOwnProperty']) {
548
- const out = await alice.being.standings.shop.ask(name);
549
- assert.deepEqual(out, { error: 'unknown ask' }, name);
550
- assert.ok(!JSON.stringify(out).includes('CH-99'), `${name} carried her cells out`);
551
- }
552
- // the blueprint never named one of them, and the gate is the same gate
553
- const blueprint = bp(await alice.being.learn());
554
- for (const ask of blueprint.asks)
555
- assert.ok(!['valueOf', 'toString', 'constructor'].includes(ask.name));
556
- assert.deepEqual(await alice.being.standings.shop.ask('refund'), { error: 'unknown ask' }); // still hidden, still the same answer
557
- });
558
- t('symmetry: the customer offers a way back inside args; the shop takes a gold one', async () => {
559
- const { shop, alice, bob } = await world();
560
- await alice.being.join(await shop.being.invite('gold'));
561
- await bob.being.join(await shop.being.invite('plain'));
562
- assert.deepEqual(Object.keys(shop.cells.standings), ['prn', 'vip-cust1']);
563
- assert.deepEqual(alice.cells.heard, [{ from: 'shop-back', method: 'hi' }]);
564
- assert.deepEqual(bob.cells.heard, []);
565
- assert.deepEqual(await shop.being.standings['vip-cust1'].ask('hi'), { heard: 'hi' });
566
- });
567
- t('the printer never invited the shop: no standing, an error object, no throw', async () => {
568
- const { alice, shop } = await world({ printerInvitesShop: false });
569
- await alice.being.join(await shop.being.invite());
570
- assert.deepEqual(await alice.being.buy('hat'), { error: 'no printer' });
571
- });
572
- t('the empty ask is describe; shaped per asker', async () => {
573
- const { shop, alice, bob } = await world();
574
- await alice.being.join(await shop.being.invite('gold'));
575
- await bob.being.join(await shop.being.invite());
576
- const names = (x) => bp(x).asks.map((a) => a.name);
577
- assert.deepEqual(names(await alice.being.learn()), ['hello', 'buy', 'refund']);
578
- assert.deepEqual(names(await bob.being.learn()), ['hello', 'buy']);
579
- assert.deepEqual(await alice.being.standings.shop.ask('refund'), { ok: true }, 'and what she is shown she can ask');
580
- });
581
- t('the digest is per relation: two occupants, two digests of one being, both true', async () => {
582
- const { shop, alice, bob } = await world();
583
- await alice.being.join(await shop.being.invite('gold'));
584
- await bob.being.join(await shop.being.invite());
585
- await alice.being.learn();
586
- await bob.being.learn();
587
- assert.notEqual(alice.cells.standings.shop.digest, bob.cells.standings.shop.digest);
588
- });
589
- t('describe caches blueprint and digest in the asker cells', async () => {
590
- const { shop, alice } = await world();
591
- await alice.being.join(await shop.being.invite());
592
- const out = await alice.being.learn();
593
- assert.deepEqual(alice.cells.standings.shop.blueprint, out);
594
- assert.equal(alice.cells.standings.shop.digest, await digest(out));
595
- });
596
- t('a changed standing is noticed by digest, without carrying the blueprint', async () => {
597
- const { alice, printer, shop } = await world();
598
- await alice.being.join(await shop.being.invite());
599
- await shop.being.standings.prn.ask();
600
- printer.cells.asks.push({ name: 'scan', input: {} }); // she changed
601
- await alice.being.buy('hat');
602
- const st = shop.cells.standings.prn;
603
- assert.notEqual(st.seen, st.digest);
604
- assert.equal(shop.cells.occupants.cust1.notes.printerChanged, true);
605
- });
606
- t('the digest rides on every answer: seen updates without a describe from her side', async () => {
607
- const { alice, printer, shop } = await world();
608
- await alice.being.join(await shop.being.invite());
609
- let describes = 0;
610
- const real = printer.being.answer.bind(printer.being);
611
- printer.being.answer = (a, m, x) => (m === undefined && describes++, real(a, m, x));
612
- await alice.being.buy('hat'); // shop asks printer once
613
- const st = shop.cells.standings.prn;
614
- assert.equal(st.seen, await digest((await real({ id: 'shop' }))));
615
- assert.equal(describes, 1); // the far ward ran her describe, in process, once per answer
616
- assert.equal(st.digest, null); // she never fetched it herself
617
- });
618
- t('a silent refresh leaves seen untouched', async () => {
619
- const { alice, printer, shop } = await world();
620
- await alice.being.join(await shop.being.invite());
621
- await shop.being.standings.prn.ask();
622
- const before = shop.cells.standings.prn.seen;
623
- const real = printer.being.answer.bind(printer.being);
624
- printer.being.answer = async (a, m, x) => (m === undefined ? silence : real(a, m, x));
625
- await alice.being.buy('hat');
626
- assert.equal(shop.cells.standings.prn.seen, before);
627
- });
628
- down('unreached is the ward word, distinct from silence, carries nothing, safe to retry', async () => {
629
- const { alice, printer, shop } = await world();
630
- await alice.being.join(await shop.being.invite());
631
- printer.down = true;
632
- assert.deepEqual(await alice.being.buy('hat'), { error: 'printer unreachable', retry: true });
633
- const out = await shop.being.standings.prn.ask('print', { doc: 'x' });
634
- assert.ok(isUnreached(out) && !isSilence(out));
635
- assert.deepEqual(Object.keys(out), []);
636
- printer.down = false;
637
- assert.deepEqual(await alice.being.buy('hat'), { ok: true, receipt: 'receipt for hat' });
638
- });
639
- t('a removed occupant hears removed under the key the door bound, her heir is forgotten; a dropped standing is gone', async () => {
640
- const { w, alice, shop } = await world();
641
- const inv = await shop.being.invite();
642
- await alice.being.join(inv);
643
- shop.being.occupants.remove('cust1');
644
- assert.equal(w.heir(inv), undefined);
645
- assert.ok(said(await alice.being.buy('hat'), 'removed'));
646
- alice.being.standings.remove('shop');
647
- assert.equal(alice.being.standings.shop, undefined);
648
- 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
649
- const again = await shop.being.invite(); // cust2. a new heir
650
- assert.notEqual(again.heir, inv.heir);
651
- assert.deepEqual(await alice.being.knock(again, 'hello'), { welcome: true });
652
- });
653
- t('dropping a standing while an ask on it is in flight does not crash', async () => {
654
- const { alice, shop } = await world();
655
- await alice.being.join(await shop.being.invite());
656
- const p = alice.being.buy('hat');
657
- alice.being.standings.remove('shop');
658
- assert.deepEqual(await p, { ok: true, receipt: 'receipt for hat' });
659
- assert.equal(shop.cells.sales.length, 1);
660
- });
661
- t('a being that throws is the word threw to whom the door has bound; the ward stays up', async () => {
662
- const w = await make();
663
- class Bomb {
664
- s;
665
- constructor(s) {
666
- this.s = s;
667
- }
668
- answer(_, m) {
669
- if (m === undefined)
670
- return { asks: [], notes: {} };
671
- throw new Error('boom');
672
- }
673
- }
674
- const x = await w.boot('X', Bomb);
675
- const a = await w.boot('A', Customer);
676
- const inv = (await x.being.s.occupants.invite('a'));
677
- assert.ok(said(await a.being.knock(inv, 'any'), 'threw'));
678
- assert.deepEqual(await a.being.knock(inv), { asks: [], notes: {} });
679
- });
680
- t('null is an answer, silence is not', async () => {
681
- const w = await make();
682
- class Nul {
683
- s;
684
- constructor(s) {
685
- this.s = s;
686
- }
687
- answer(_, m) {
688
- return m === undefined ? { asks: [], notes: {} } : null;
689
- }
690
- }
691
- const n = await w.boot('N', Nul);
692
- const a = await w.boot('A', Customer);
693
- const out = await a.being.knock((await n.being.s.occupants.invite('a')), 'x');
694
- assert.equal(out, null);
695
- assert.ok(!isSilence(out));
696
- });
697
- t('the raw shape is a being through a ward, exactly like one on the base class', async () => {
698
- const w = await make();
699
- const e = await w.boot('E', Echo);
700
- const a = await w.boot('A', Customer);
701
- const inv = (await e.being.s.occupants.invite('a'));
702
- assert.deepEqual(await a.being.knock(inv, 'echo', { x: 1 }), { from: 'a', x: 1 });
703
- assert.equal(await a.being.take('echo', inv), 'echo');
704
- assert.deepEqual(await a.being.standings.echo.ask('echo', { y: 2 }), { from: 'a', y: 2 });
705
- assert.equal(a.cells.standings.echo.seen, await digest({ asks: [{ name: 'echo', input: {} }], notes: {} }));
706
- });
707
- // ---- a save the store refuses
708
- // The door has already said yes by the time a store says no, so the answer
709
- // is the last thing that can still carry the refusal. A being whose row was
710
- // not kept has a memory running ahead of what is on the device, and every
711
- // door that kept answering success would be telling an asker about writes
712
- // that are gone at the next reload.
713
- refused('a save the store refuses is the answer of the arrival that wrote it: threw at the door, silence at the ask pointer', async () => {
714
- const w = await make();
715
- const no = picky(w);
716
- const printer = await w.boot('P', Printer);
717
- const alice = await w.boot('A', Customer);
718
- const inv = (await printer.being.invite('a'));
719
- assert.deepEqual(await alice.being.knock(inv, 'print', { doc: 'one' }), { printed: 'one' });
720
- no.refuse('P', true);
721
- const out = await alice.being.knock(inv, 'print', { doc: 'two' });
722
- assert.ok(said(out, 'threw'), `a refused save is threw to a key the door holds, not ${JSON.stringify(out)}`);
723
- const owned = await no.owner('P')('invite', { being: 'P', id: 'later' });
724
- assert.ok(isSilence(owned), `a refused save is silence at the ask pointer, not ${JSON.stringify(owned)}`);
725
- // The ward is up, and a stranger hears nothing about the store either.
726
- assert.ok(isSilence(await alice.being.knock({ ward: printer.pk, heir: 'f'.repeat(64), secret: 'f'.repeat(64) }, 'print')));
727
- no.refuse('P', false);
728
- assert.deepEqual(await alice.being.knock(inv, 'print', { doc: 'three' }), { printed: 'three' }, 'the store took it, so the arrival is answered');
729
- });
730
- // The caller's own row, refused while she is in the middle of a call. A
731
- // knock is one call and two halves: she writes her side before the bytes go
732
- // out and again when they come back, and the far door writes the heir it
733
- // spent. What a refusal puts back is what she wrote and never what the
734
- // relation stands on, because her keys and the door's rotate at different
735
- // moments and neither holds the seed of a key it has moved past: a kit that
736
- // rolls back either speaks under a key the other will not admit, and that
737
- // relation never speaks again. The proof is that she is still heard
738
- // afterwards, and it holds wherever the far half lives, because a kit that
739
- // put the relation back would fail here with both beings of one ward and
740
- // with the far one on another device alike.
741
- refused('a refusal under a call puts back the whole call, and the relation speaks again after it', async () => {
742
- const w = await make();
743
- const no = picky(w);
744
- const printer = await w.boot('P', Printer);
745
- const alice = await w.boot('A', Customer);
746
- const inv = (await printer.being.invite('a'));
747
- assert.deepEqual(await alice.being.knock(inv, 'print', { doc: 'one' }), { printed: 'one' });
748
- no.refuse('A', true);
749
- await alice.being.knock(inv, 'print', { doc: 'two' });
750
- no.refuse('A', false);
751
- assert.deepEqual(await alice.being.knock(inv, 'print', { doc: 'three' }), { printed: 'three' }, 'the relation is whole: a half put back would never be heard again');
752
- });
753
- // ---- public
754
- 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 () => {
755
- const w = await make();
756
- // A being written by hand: no base class, one answer, and the stance is
757
- // all she is handed. She is a being to the ward on those terms alone.
758
- class Pub {
759
- s;
760
- constructor(s) {
761
- this.s = s;
762
- this.s.cells.askers ??= [];
763
- }
764
- answer(asker, m, args) {
765
- if (m === undefined)
766
- return { asks: [{ name: 'time', input: {} }], notes: {} };
767
- this.s.cells.askers.push(asker);
768
- return { time: 12, got: args ?? {} };
769
- }
770
- }
771
- const pub = await w.boot('PUB', Pub, {}, { isPublic: true });
772
- const priv = await w.boot('PRIV', Pub); // same class, not public
773
- const a = await w.boot('A', Customer);
774
- const b = await w.boot('B', Customer);
775
- const at = (h) => ({ ward: h.pk });
776
- assert.deepEqual(await a.being.knock(at(pub), 'time', { x: 1 }), { time: 12, got: { x: 1 } });
777
- assert.deepEqual(await b.being.knock(at(pub), 'time'), { time: 12, got: {} });
778
- assert.deepEqual(pub.cells.askers, [{}, {}]);
779
- assert.deepEqual(pub.cells.occupants, {}); // she never invited anyone
780
- if (!w.oneWard)
781
- assert.ok(isSilence(await a.being.knock(at(priv), 'time'))); // a ward with no public being: silence
782
- assert.ok(isUnreached(await a.being.knock({ ward: 'a'.repeat(128) }, 'time'))); // no door anywhere: unreached
783
- assert.ok(said(await a.being.knock({ ward: 'not a pk' }, 'time'), 'invitation')); // not even an address
784
- assert.equal(await a.being.take('clock', at(pub)), 'clock');
785
- assert.deepEqual(await a.being.standings.clock.ask('time'), { time: 12, got: {} });
786
- assert.equal(a.cells.standings.clock.seen, await digest((await a.being.standings.clock.ask())));
787
- });
788
- // ---- keys stay where they belong
789
- t("two rotations: the heir is the inviter's, the bound key is the knocker's, and neither side minted the other's", async () => {
790
- const { w, shop, alice, bob } = await world();
791
- const i1 = await shop.being.invite('gold');
792
- const i2 = await shop.being.invite();
793
- assert.ok(shop.minted.has(i1.heir) && shop.minted.has(i2.heir));
794
- await alice.being.join(i1);
795
- await bob.being.join(i2);
796
- const h1 = w.heir(i1), h2 = w.heir(i2);
797
- assert.ok(alice.minted.has(h1.current) && !shop.minted.has(h1.current) && !bob.minted.has(h1.current));
798
- assert.ok(bob.minted.has(h2.current) && !shop.minted.has(h2.current) && !alice.minted.has(h2.current));
799
- assert.notEqual(h1.current, h2.current); // no key serves two relations
800
- assert.ok(!alice.minted.has(i1.heir)); // alice never minted anything of the shop's
801
- });
802
- t('her cells carry no key: heir, secret, and bound key live in the bind table only', async () => {
803
- const { w, shop, alice } = await world();
804
- const inv = await shop.being.invite();
805
- await alice.being.join(inv);
806
- await alice.being.learn();
807
- const words = [inv.heir, inv.secret, w.heir(inv).current];
808
- for (const cells of [shop.cells, alice.cells])
809
- for (const word of words)
810
- assert.ok(!JSON.stringify(cells).includes(word), `${word} in cells`);
811
- });
812
- // ---- the estate
813
- estate(label, make, { canDown, test });
814
- }
815
- // The store and reach suites, beside the ward's: every store a harbor keeps
816
- // wards in and every reach it carries bytes by passes its own, on the
817
- // terrain it is written for.
818
- export { conformStore } from './store.js';
819
- export { conformReach } from './reach.js';