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,388 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // The estate. Every other chapter is a scene: two or three beings, one move,
3
- // one answer. This one is a graph under churn.
4
- //
5
- // An organisation does not run one relation. It runs a few hundred, scattered
6
- // over wards on scattered harbors, and they are not born together: partners
7
- // are invited, employees knock, standings are taken, people are kicked, wards
8
- // move to harbors of their own. Nothing is wrong at the second move. What
9
- // goes wrong goes wrong at the hundredth, when a kick and a take and a
10
- // migration have landed on top of each other, and no scene written by hand
11
- // will ever be that sequence.
12
- //
13
- // So this chapter does not write the sequence. It keeps a model of the graph,
14
- // plays legal moves against it from three fixed seeds, and after every move
15
- // asks two things:
16
- //
17
- // the ledger is a graph every arc the wards hold closes on both ends
18
- // the ledger is what it can do every arc in the model is asked, and answers
19
- // exactly what being that arc means it answers
20
- //
21
- // The second is asked under one ward, under a ward per being, and under two
22
- // harbors, and that is where the promise of Quo is held: a being never learns
23
- // where the other one is, so every answer, after every move, under every
24
- // topology, is the answer the model wrote without knowing the topology
25
- // either. Held after every one of sixty moves, it is the promise at a scale a scene
26
- // cannot reach, and it is what makes a migration honest: if no answer can
27
- // tell the topology apart, moving a ward between harbors cannot be felt.
28
- // The model itself is the script's own bookkeeping and takes nothing from a
29
- // ward, so it is never compared with itself across topologies: that would
30
- // hold by construction and prove nothing.
31
- //
32
- // A relation is two arcs and never one edge. The occupant arc is the host's,
33
- // born at invite; the standing arc is the guest's, born at take, and only
34
- // after an answer. `SPEC.md`: take binds B's side only, the knock bound hers.
35
- // A model that kept one symmetric edge would be a wrong model, and would
36
- // agree happily with a ward that had drifted.
37
- import { assert } from './assert.js';
38
- import { isSilence, isUnreached, told } from '../being/silence.js';
39
- import { Member } from './beings.js';
40
- import { HEX64 } from './index.js';
41
- const said = (x, w) => told(x) === w;
42
- // A pseudo-random source in the language alone: the estate must replay, on
43
- // every terrain, from a number a failure can print.
44
- const rolls = (seed) => () => {
45
- seed = (seed + 0x6d2b79f5) | 0;
46
- let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
47
- t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
48
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
49
- };
50
- // ---- the ledger is a graph
51
- // Held after every move. Each line is a way the bookkeeping could stop being
52
- // a graph: an arc with one end, an arc with two owners, a relation filed in
53
- // two places at once, a count that went backwards, a secret in the cells.
54
- function ledger(c, before, where, g) {
55
- const at = (why) => `${where}: ${why}`;
56
- const heirsSeen = new Set();
57
- // What the model knows, which the ward's own bookkeeping cannot tell it.
58
- // Without this the ledger only checks the ward against itself, and a drift
59
- // is caught late, if the sweep happens to ask the affected arc, or never.
60
- if (g) {
61
- for (const s of g.seats) {
62
- const heir = c.wards[c.binds[s.host].ward].heirs[s.inv.heir];
63
- if (!s.live) {
64
- assert.ok(!heir, at(`${s.host} kicked ${s.id} and the heir is still held`));
65
- assert.ok(!(s.id in c.binds[s.host].occupants), at(`${s.host} kicked ${s.id} and still holds the occupant`));
66
- continue;
67
- }
68
- assert.ok(heir, at(`${s.host}'s live seat ${s.id} has no heir`));
69
- assert.equal(heir.id, s.id, at(`the heir for ${s.host}/${s.id} is filed under ${heir.id}`));
70
- // A seat nobody has knocked is a heir that has never spoken: fresh, and
71
- // at zero. One that answered has spoken, so it is neither.
72
- assert.equal(heir.fresh, !s.answered, at(`${s.host}/${s.id} is ${s.answered ? 'answered' : 'unknocked'} and its heir says fresh=${heir.fresh}`));
73
- assert.equal(heir.mark === 0, !s.answered, at(`${s.host}/${s.id} is ${s.answered ? 'answered' : 'unknocked'} and its heir is at ${heir.mark}`));
74
- }
75
- for (const h of g.holds) {
76
- const standing = c.binds[h.owner].standings[h.id];
77
- if (!h.live) {
78
- assert.ok(!standing, at(`${h.owner} dropped ${h.id} and the standing is still there`));
79
- continue;
80
- }
81
- assert.ok(standing, at(`${h.owner}'s live standing ${h.id} is gone from the bind table`));
82
- assert.equal(standing.heir, h.seat.inv.heir, at(`${h.owner}/${h.id} names a heir that is not the seat it was born on`));
83
- // The count she has spent, against the number the far door honoured.
84
- // Her side is never behind: she takes a number, then it crosses.
85
- const far = c.wards[c.binds[h.seat.host].ward].heirs[h.seat.inv.heir];
86
- if (far)
87
- assert.ok(standing.seq >= far.mark, at(`${h.owner}/${h.id} counted ${standing.seq} and the far door has honoured ${far.mark}`));
88
- }
89
- // Every seat and hold the model knows about, and no relation the ward
90
- // holds that the model never made.
91
- const mine = new Set(g.seats.filter((s) => s.live).map((s) => `${s.host}/${s.id}`));
92
- for (const [key, b] of Object.entries(c.binds))
93
- for (const id of Object.keys(b.occupants))
94
- assert.ok(mine.has(`${key}/${id}`), at(`${key} holds an occupant ${id} the model never made`));
95
- }
96
- for (const [pk, w] of Object.entries(c.wards)) {
97
- for (const [heir, r] of Object.entries(w.heirs)) {
98
- assert.ok(!heirsSeen.has(heir), at(`heir ${heir} is held by two wards`));
99
- heirsSeen.add(heir);
100
- assert.match(heir, HEX64, at(`heir ${heir} is not a key`));
101
- // the far end of the arc: the being the ward says minted it holds it back, under the same id
102
- const b = c.binds[r.being];
103
- assert.ok(b, at(`heir ${heir} names ${r.being}, who lives in no ward`));
104
- assert.equal(b.ward, pk, at(`heir ${heir} is kept by ${pk}, but ${r.being} lives behind ${b.ward}`));
105
- assert.equal(b.occupants[r.id], heir, at(`heir ${heir} says it is ${r.being}'s ${r.id}, who does not hold it`));
106
- assert.ok(r.spent.every((n) => n <= r.mark), at(`heir ${heir} spent a number above its mark`));
107
- }
108
- }
109
- for (const [key, b] of Object.entries(c.binds)) {
110
- // every occupant id the being holds is a heir her own ward keeps
111
- for (const [id, heir] of Object.entries(b.occupants)) {
112
- const r = c.wards[b.ward].heirs[heir];
113
- assert.ok(r, at(`${key}'s occupant ${id} names heir ${heir}, which her ward has forgotten`));
114
- assert.equal(r.being, key, at(`${key}'s occupant ${id} names a heir of ${r.being}'s`));
115
- assert.equal(r.id, id, at(`${key}'s occupant ${id} names a heir filed under ${r.id}`));
116
- }
117
- // a taken relation lives in the standing and never also in a knock record
118
- for (const [id, s] of Object.entries(b.standings)) {
119
- const name = s.heir === null ? `public:${s.ward}` : `${s.ward}:${s.heir}`;
120
- assert.ok(!b.knocks.includes(name), at(`${key}'s standing ${id} still has a knock record`));
121
- assert.ok(!b.answered.includes(name), at(`${key}'s standing ${id} is still marked answered`));
122
- }
123
- // no key her side holds is anywhere in anyone's cells
124
- const cells = JSON.stringify(c.cells);
125
- for (const word of b.secrets)
126
- assert.ok(!cells.includes(word), at(`a key of ${key}'s is in the cells`));
127
- // counts climb and never fall. a door that forgot what it honoured honours it again
128
- const was = before?.binds[key];
129
- if (was)
130
- for (const [id, s] of Object.entries(b.standings))
131
- assert.ok(!was.standings[id] || s.seq >= was.standings[id].seq, at(`${key}'s standing ${id} counted backwards`));
132
- }
133
- if (before)
134
- for (const [pk, w] of Object.entries(c.wards))
135
- for (const [heir, r] of Object.entries(w.heirs)) {
136
- const was = before.wards[pk]?.heirs[heir];
137
- if (was)
138
- assert.ok(r.mark >= was.mark, at(`heir ${heir} marked backwards`));
139
- }
140
- }
141
- // ---- the chapter
142
- export function estate(label, make, { canDown, test }) {
143
- const t = (name, fn) => test(`[${label}] ${name}`, {}, fn);
144
- // The story, told once, by hand, so that the next person can read what the
145
- // model is for. A partner is invited into a harbor she is borrowing, works,
146
- // moves to a harbor of her own, is kicked, and is invited back as somebody
147
- // else. Nothing here is random and nothing here is generated.
148
- t('estate: a partner is invited into a borrowed harbor, works, moves to her own, is kicked, and comes back as somebody else', async () => {
149
- const w = await make();
150
- const acme = await w.boot('ACME', Member);
151
- const partner = await w.boot('PARTNER', Member);
152
- // 1. acme mints a name for her and asks her ward for an invitation. the
153
- // occupant exists from that moment, and the partner knows nothing yet.
154
- const first = (await acme.being.invite('partner-1'));
155
- assert.deepEqual(Object.keys(acme.cells.occupants), ['partner-1']);
156
- assert.deepEqual(partner.cells.standings, {});
157
- // 2. she knocks. from now on every ask of hers arrives as that name.
158
- assert.deepEqual(await partner.being.knock(first, 'ping'), { pong: 'partner-1' });
159
- // 3. and only then does she keep acme, under a name of her own minting.
160
- assert.equal(await partner.being.take('the-client', first), 'the-client');
161
- assert.deepEqual(await partner.being.standings['the-client'].ask('ping'), { pong: 'partner-1' });
162
- // 4. she gets her own harbor. the same seed, the same pk, so the arc acme
163
- // holds still points at her and the arc she holds still points at acme.
164
- await w.migrate('PARTNER');
165
- await w.migrate('ACME');
166
- assert.deepEqual(await partner.being.standings['the-client'].ask('ping'), { pong: 'partner-1' });
167
- const back = (await partner.being.invite('acme'));
168
- assert.deepEqual(await acme.being.knock(back, 'ping'), { pong: 'acme' });
169
- assert.equal(await acme.being.take('them', back), 'them');
170
- // 5. acme kicks her. her standing is still hers to hold, and she is not
171
- // told until she asks: then the door, under the key it bound for her,
172
- // says removed, and only to her.
173
- acme.being.occupants.remove('partner-1');
174
- assert.ok(said(await partner.being.standings['the-client'].ask('ping'), 'removed'));
175
- assert.deepEqual(Object.keys(partner.cells.standings), ['the-client']); // she never learns
176
- assert.deepEqual(await acme.being.standings.them.ask('ping'), { pong: 'acme' }); // the other arc never moved
177
- // 6. and back, as somebody else: a second invitation is a second relation,
178
- // sharing nothing with the first. the old one stays dead.
179
- const second = (await acme.being.invite('partner-2'));
180
- assert.notEqual(second.heir, first.heir);
181
- assert.deepEqual(await partner.being.knock(second, 'ping'), { pong: 'partner-2' });
182
- assert.equal(await partner.being.take('the-client-again', second), 'the-client-again');
183
- assert.ok(said(await partner.being.standings['the-client'].ask('ping'), 'removed'));
184
- ledger(w.census(), undefined, 'the story');
185
- });
186
- for (const seed of [1, 2, 3]) {
187
- t(`estate: sixty moves from seed ${seed}, the ledger a graph and every arc asked after each one`, async () => {
188
- const w = await make();
189
- const roll = rolls(seed);
190
- const pick = (xs) => xs[Math.floor(roll() * xs.length)];
191
- const hands = {};
192
- const g = { seats: [], holds: [] };
193
- // What this run actually got to. A generated walk protects only the
194
- // states it reaches, and a list of assertions nobody ran is a list of
195
- // assertions nobody has. Every case named here is checked off as it
196
- // fires and the run fails at the end if one never did.
197
- const reached = new Set();
198
- const MUST = [
199
- 'a kicked seat knocked by the guest it bound, who hears removed',
200
- 'a standing dropped by its owner, whose host is never told',
201
- 'a seat kicked by its host, whose guest is never told',
202
- 'a relation taken, so it lives in the standing and not the knock',
203
- 'a ward moved to another harbor under everyone standing on it',
204
- ];
205
- let minted = 0;
206
- const born = async (key) => (hands[key] = await w.boot(key, Member));
207
- for (const key of ['M1', 'M2', 'M3'])
208
- await born(key);
209
- let before;
210
- // Every move that is legal right now, as a thunk. A move that would not
211
- // be legal is not written down, so the chapter never asserts on a
212
- // question the spec does not answer.
213
- const moves = () => {
214
- const keys = Object.keys(hands);
215
- const out = [];
216
- // invite: a fresh occupant arc, and nothing of the guest's in it
217
- out.push(async () => {
218
- const host = pick(keys);
219
- const id = `o${++minted}`;
220
- const inv = (await hands[host].being.invite(id));
221
- assert.equal(inv.ward, hands[host].pk);
222
- assert.match(inv.heir, HEX64);
223
- g.seats.push({ host, id, inv, guest: null, answered: false, live: true, taken: false });
224
- return `${host} invites ${id}`;
225
- });
226
- // knock: the guest an unspent seat gets is anyone but the host; a
227
- // spent one only ever answers the one who spent it. Once she has
228
- // taken it the invitation is not a knock any more -- it joins the
229
- // standing's lane, and is exactly as alive as the standing is.
230
- for (const s of g.seats) {
231
- const knock = async () => {
232
- const guest = s.guest ?? pick(keys.filter((k) => k !== s.host));
233
- const hold = g.holds.find((h) => h.seat === s);
234
- const back = await hands[guest].being.knock(s.inv, 'ping');
235
- // Whether this knock is heard at all: the seat has no guest yet,
236
- // or its guest is this one, and nothing of hers has dropped the
237
- // key the door would bind her by. A hold exists only where a
238
- // guest does, so the two tests are one condition and not two.
239
- // Then the seat says which answer: alive is the pong, kicked is
240
- // `removed`, and unheard is silence whichever it is.
241
- const speaks = (s.guest === null || s.guest === guest) && (hold === undefined || hold.live);
242
- if (!speaks)
243
- assert.ok(isSilence(back), `${guest} knocking ${s.host}/${s.id} should be silence`);
244
- else if (s.live) {
245
- assert.deepEqual(back, { pong: s.id }, `${guest} knocking ${s.host}/${s.id}`);
246
- s.guest = guest;
247
- s.answered = true;
248
- }
249
- else {
250
- assert.ok(said(back, 'removed'), `${guest} knocking kicked ${s.host}/${s.id} should hear removed`);
251
- reached.add('a kicked seat knocked by the guest it bound, who hears removed');
252
- }
253
- return `${guest} knocks ${s.host}/${s.id}`;
254
- };
255
- out.push(knock);
256
- // Weighted, not written twice: the case this move exists to protect
257
- // is a seat kicked after it answered and before it was taken, and a
258
- // uniform walk over a list that grows with every invitation reaches
259
- // it by luck if at all. Three entries make it likely by
260
- // construction, and the count at the end of the run makes it
261
- // certain: a walk that never got there fails rather than passing
262
- // quietly on an assertion that never ran.
263
- if (!s.live && s.answered && !s.taken)
264
- out.push(knock, knock);
265
- // a stranger on a spent seat: silence, and nothing rebinds
266
- if (s.guest !== null)
267
- out.push(async () => {
268
- const other = keys.filter((k) => k !== s.host && k !== s.guest);
269
- if (!other.length)
270
- return 'no stranger to try';
271
- const who = pick(other);
272
- assert.ok(isSilence(await hands[who].being.knock(s.inv, 'ping')), `${who} should not get in on ${s.host}/${s.id}`);
273
- return `${who} is refused at ${s.host}/${s.id}`;
274
- });
275
- }
276
- // take: only after an answer, only once. the far side may have kicked
277
- // her in between -- take reads the knock record, and still births one.
278
- for (const s of g.seats.filter((s) => s.answered && !s.taken))
279
- out.push(async () => {
280
- const id = `s${++minted}`;
281
- assert.equal(await hands[s.guest].being.take(id, s.inv), id);
282
- s.taken = true;
283
- g.holds.push({ owner: s.guest, id, seat: s, live: true });
284
- reached.add('a relation taken, so it lives in the standing and not the knock');
285
- assert.equal(await hands[s.guest].being.take(`${id}-again`, s.inv), null); // spent for her
286
- return `${s.guest} takes ${s.host}/${s.id} as ${id}`;
287
- });
288
- // drop: hers to drop, and the host is never told
289
- for (const h of g.holds.filter((h) => h.live))
290
- out.push(async () => {
291
- hands[h.owner].being.standings.remove(h.id);
292
- h.live = false;
293
- reached.add('a standing dropped by its owner, whose host is never told');
294
- assert.equal(hands[h.owner].being.standings[h.id], undefined);
295
- return `${h.owner} drops ${h.id}`;
296
- });
297
- // kick: the host's, and the guest is never told either
298
- for (const s of g.seats.filter((s) => s.live))
299
- out.push(async () => {
300
- hands[s.host].being.occupants.remove(s.id);
301
- s.live = false;
302
- reached.add('a seat kicked by its host, whose guest is never told');
303
- return `${s.host} kicks ${s.id}`;
304
- });
305
- // a new being: an estate grows
306
- if (keys.length < 6)
307
- out.push(async () => (await born(`M${keys.length + 1}`), `M${keys.length + 1} is booted`));
308
- // a ward moves harbor, under everyone standing on it
309
- out.push(async () => {
310
- const who = pick(keys);
311
- await w.migrate(who);
312
- reached.add('a ward moved to another harbor under everyone standing on it');
313
- return `${who}'s ward moves harbor`;
314
- });
315
- // weather: the host's door is gone. unreached, never silence, and the
316
- // relation is exactly where it was when it comes back. The move is on
317
- // the list under every topology even where there is nothing to cut,
318
- // because the list is the script: a move missing in one world would
319
- // make it a different script, and the three could not be compared.
320
- for (const h of g.holds.filter((h) => h.live && h.seat.live))
321
- out.push(async () => {
322
- if (!canDown)
323
- return 'nothing to cut inside one ward';
324
- hands[h.seat.host].down = true;
325
- assert.ok(isUnreached(await hands[h.owner].being.standings[h.id].ask('ping')), `${h.owner}/${h.id} should be unreached`);
326
- hands[h.seat.host].down = false;
327
- return `${h.seat.host} is cut and comes back`;
328
- });
329
- return out;
330
- };
331
- // Every arc in the model, asked. This is where the ledger stops being
332
- // bookkeeping and becomes what the door actually does.
333
- const sweep = async (step) => {
334
- for (const h of g.holds) {
335
- const standing = hands[h.owner].being.standings[h.id];
336
- if (!h.live) {
337
- assert.equal(standing, undefined, `step ${step}: ${h.owner}'s dropped ${h.id} is still there`);
338
- continue;
339
- }
340
- const out = await standing.ask('ping');
341
- if (h.seat.live)
342
- assert.deepEqual(out, { pong: h.seat.id }, `step ${step}: ${h.owner}/${h.id} -> ${h.seat.host}/${h.seat.id}`);
343
- else
344
- assert.ok(said(out, 'removed'), `step ${step}: ${h.owner}/${h.id} is kicked and should hear removed`);
345
- }
346
- };
347
- // The trace is the seed's own account of itself: a red run prints the
348
- // moves that got there, which is the only thing that makes a generated
349
- // sequence debuggable at all.
350
- const trace = [];
351
- const at = (step) => `seed ${seed} step ${step}\n ${trace.join('\n ')}`;
352
- // Whatever fails inside a step carries the trace that got there. One
353
- // wrapper, so the move and the sweep are decorated the same way and a
354
- // third thing added to a step cannot be decorated a fourth.
355
- const inStep = async (step, work) => {
356
- try {
357
- return await work();
358
- }
359
- catch (e) {
360
- throw new Error(`${at(step)}\n -> ${e.message}`, { cause: e });
361
- }
362
- };
363
- for (let step = 1; step <= 60; step++) {
364
- const move = pick(moves());
365
- // The move that failed goes into the trace before it runs, not after
366
- // it returns: a move names itself as it finishes, so a throw left the
367
- // trace ending at the last move that worked and saying nothing about
368
- // the one that did not. It is written in twice, provisionally and
369
- // then as it named itself, and the provisional line goes when the
370
- // real one lands.
371
- trace.push(`step ${step}: a move that has not said what it did`);
372
- const did = await inStep(step, move);
373
- trace[trace.length - 1] = did;
374
- const c = w.census();
375
- ledger(c, before, at(step), g);
376
- before = c;
377
- await inStep(step, () => sweep(step));
378
- }
379
- // And the walk is held to having gone somewhere. Sixty moves under one
380
- // seed is a sample, not a proof, so the states the chapter is written
381
- // to protect are named and counted: a run that never reached one has
382
- // not tested it, and says so here rather than passing on an assertion
383
- // that never ran.
384
- const missed = MUST.filter((what) => !reached.has(what));
385
- assert.deepEqual(missed, [], `seed ${seed} never reached: ${missed.join('; ')}\n ${trace.join('\n ')}`);
386
- });
387
- }
388
- }
@@ -1,80 +0,0 @@
1
- import type { BeingClass, Cells, Invitation, JsonObject } from '../being/types.ts';
2
- import { Printer, Shop, Customer, Echo, Maker, Member } from './beings.ts';
3
- export type Handle<B = unknown> = {
4
- pk: string;
5
- being: B;
6
- cells: Cells;
7
- down: boolean;
8
- minted: {
9
- has(pk: string): boolean;
10
- };
11
- forgeKnock(inv: Invitation): void;
12
- };
13
- export type HeirView = {
14
- current: string;
15
- announced: string | null;
16
- fresh: boolean;
17
- };
18
- export type Census = {
19
- wards: Record<string, {
20
- beings: string[];
21
- public: string | null;
22
- heirs: Record<string, {
23
- being: string;
24
- id: string;
25
- current: string;
26
- announced: string | null;
27
- fresh: boolean;
28
- mark: number;
29
- spent: number[];
30
- }>;
31
- }>;
32
- binds: Record<string, {
33
- ward: string;
34
- standings: Record<string, {
35
- ward: string;
36
- heir: string | null;
37
- seq: number;
38
- }>;
39
- occupants: Record<string, string>;
40
- knocks: string[];
41
- answered: string[];
42
- minted: string[];
43
- secrets: string[];
44
- }>;
45
- cells: Record<string, Cells>;
46
- };
47
- export type World = {
48
- oneWard?: boolean;
49
- boot<B>(key: string, Class: BeingClass, cells?: JsonObject, opts?: {
50
- isPublic?: boolean;
51
- }): Promise<Handle<B>>;
52
- heir(inv: Invitation): HeirView | undefined;
53
- census(): Census;
54
- migrate(key: string): Promise<void>;
55
- owner(key: string): (method?: string, args?: JsonObject) => Promise<unknown>;
56
- } & Partial<Standing> & Partial<Refusing>;
57
- export type Standing = {
58
- partition(key: string): JsonObject;
59
- restart(key: string, partition?: JsonObject): Promise<void>;
60
- };
61
- export type Refusing = {
62
- refuse(key: string, on: boolean): void;
63
- };
64
- export { Printer, Shop, Customer, Echo, Maker, Member };
65
- export declare const HEX64: RegExp;
66
- export type Runner = (name: string, opts: {
67
- skip?: string | false;
68
- }, fn: () => Promise<void> | void) => void;
69
- export type Made = {
70
- lends?: Record<string, string>;
71
- };
72
- export declare function conform(label: string, make: (made?: Made) => Promise<World>, { canDown, canLend, canRestart, canRefuse, test }: {
73
- canDown?: boolean;
74
- canLend?: boolean;
75
- canRestart?: boolean;
76
- canRefuse?: boolean;
77
- test: Runner;
78
- }): void;
79
- export { conformStore } from './store.ts';
80
- export { conformReach, type FarSide } from './reach.ts';