@quo-systems/quo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +91 -0
- package/SPEC.md +1595 -0
- package/package.json +64 -0
- package/src/being/being.ts +92 -0
- package/src/being/digest.ts +31 -0
- package/src/being/index.ts +7 -0
- package/src/being/silence.ts +14 -0
- package/src/being/types.ts +88 -0
- package/src/conformance/assert.ts +67 -0
- package/src/conformance/beings.ts +129 -0
- package/src/conformance/estate.ts +339 -0
- package/src/conformance/index.ts +516 -0
- package/src/conformance/reach.ts +83 -0
- package/src/conformance/store.ts +109 -0
- package/src/harbor/core.ts +178 -0
- package/src/harbor/dial.ts +61 -0
- package/src/harbor/index.ts +11 -0
- package/src/harbor/memory.ts +74 -0
- package/src/harbor/reach.ts +189 -0
- package/src/harbor/store.ts +69 -0
- package/src/ward/allowance.ts +74 -0
- package/src/ward/arithmetic.ts +161 -0
- package/src/ward/cells.ts +90 -0
- package/src/ward/door.ts +102 -0
- package/src/ward/ground.ts +35 -0
- package/src/ward/heirs.ts +87 -0
- package/src/ward/index.ts +14 -0
- package/src/ward/owner.ts +122 -0
- package/src/ward/partition.ts +123 -0
- package/src/ward/seal.ts +133 -0
- package/src/ward/stance.ts +264 -0
- package/src/ward/ward.ts +209 -0
- package/vectors/arithmetic.json +110 -0
- package/vectors/framing.json +71 -0
- package/vectors/wire.json +48 -0
|
@@ -0,0 +1,339 @@
|
|
|
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 three 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
|
+
// the graph is not the topology the same seed, played under one ward, under
|
|
21
|
+
// a ward per being, and under two harbors,
|
|
22
|
+
// ends in the same graph
|
|
23
|
+
//
|
|
24
|
+
// The third is the one that matters most here. A being never learns where the
|
|
25
|
+
// other one is, so a script that is topology-invariant at forty moves is the
|
|
26
|
+
// promise of Quo held at a scale a scene cannot reach -- and it is what makes
|
|
27
|
+
// a migration honest: if nobody can tell the topology apart, moving a ward
|
|
28
|
+
// between harbors cannot be felt.
|
|
29
|
+
//
|
|
30
|
+
// A relation is two arcs and never one edge. The occupant arc is the host's,
|
|
31
|
+
// born at invite; the standing arc is the guest's, born at take, and only
|
|
32
|
+
// after an answer. `SPEC.md`: take binds B's side only, the knock bound hers.
|
|
33
|
+
// A model that kept one symmetric edge would be a wrong model, and would
|
|
34
|
+
// agree happily with a ward that had drifted.
|
|
35
|
+
import { assert } from './assert.ts';
|
|
36
|
+
import { isSilence, isUnreached } from '../being/silence.ts';
|
|
37
|
+
import type { Invitation } from '../being/types.ts';
|
|
38
|
+
import { Member } from './beings.ts';
|
|
39
|
+
import type { Census, Handle, Runner, World } from './index.ts';
|
|
40
|
+
|
|
41
|
+
const HEX64 = /^[0-9a-f]{64}$/;
|
|
42
|
+
|
|
43
|
+
// One occupant arc, as the host holds it. `guest` is null until somebody
|
|
44
|
+
// knocks; `live` goes false when she is kicked. Both are the host's business
|
|
45
|
+
// alone -- the guest is never told either one.
|
|
46
|
+
type Seat = { host: string; id: string; inv: Invitation; guest: string | null; answered: boolean; live: boolean; taken: boolean };
|
|
47
|
+
// One standing arc, as the guest holds it. It carries the seat it was born
|
|
48
|
+
// on, because what an ask on it should come back with is the host's name for
|
|
49
|
+
// her, and whether the host has since kicked her.
|
|
50
|
+
type Hold = { owner: string; id: string; seat: Seat; live: boolean };
|
|
51
|
+
type Graph = { seats: Seat[]; holds: Hold[] };
|
|
52
|
+
|
|
53
|
+
// A pseudo-random source in the language alone: the estate must replay, on
|
|
54
|
+
// every terrain, from a number a failure can print.
|
|
55
|
+
const rolls = (seed: number) => () => {
|
|
56
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
57
|
+
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
58
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
59
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// The graph, with nothing of the world in it: no key, no ward pk, no harbor.
|
|
63
|
+
// Two runs under two topologies are the same estate when these agree.
|
|
64
|
+
const abc = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0);
|
|
65
|
+
const fingerprint = (g: Graph): string =>
|
|
66
|
+
JSON.stringify({
|
|
67
|
+
seats: g.seats.map((s) => JSON.stringify([s.host, s.id, s.guest, s.answered, s.live, s.taken])).sort(abc),
|
|
68
|
+
holds: g.holds.map((h) => JSON.stringify([h.owner, h.id, h.seat.host, h.seat.id, h.live])).sort(abc),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Every run of one seed, under every topology, must land on the same graph.
|
|
72
|
+
// The topologies run in one process, so the first to finish records and the
|
|
73
|
+
// rest are held to it.
|
|
74
|
+
const landed = new Map<string, { by: string; print: string }>();
|
|
75
|
+
|
|
76
|
+
// ---- the ledger is a graph
|
|
77
|
+
|
|
78
|
+
// Held after every move. Each line is a way the bookkeeping could stop being
|
|
79
|
+
// a graph: an arc with one end, an arc with two owners, a relation filed in
|
|
80
|
+
// two places at once, a count that went backwards, a secret in the cells.
|
|
81
|
+
function ledger(c: Census, before: Census | undefined, where: string) {
|
|
82
|
+
const at = (why: string) => `${where}: ${why}`;
|
|
83
|
+
const heirsSeen = new Set<string>();
|
|
84
|
+
|
|
85
|
+
for (const [pk, w] of Object.entries(c.wards)) {
|
|
86
|
+
for (const [heir, r] of Object.entries(w.heirs)) {
|
|
87
|
+
assert.ok(!heirsSeen.has(heir), at(`heir ${heir} is held by two wards`));
|
|
88
|
+
heirsSeen.add(heir);
|
|
89
|
+
assert.match(heir, HEX64, at(`heir ${heir} is not a key`));
|
|
90
|
+
// the far end of the arc: the being the ward says minted it holds it back, under the same id
|
|
91
|
+
const b = c.binds[r.being];
|
|
92
|
+
assert.ok(b, at(`heir ${heir} names ${r.being}, who lives in no ward`));
|
|
93
|
+
assert.equal(b.ward, pk, at(`heir ${heir} is kept by ${pk}, but ${r.being} lives behind ${b.ward}`));
|
|
94
|
+
assert.equal(b.occupants[r.id], heir, at(`heir ${heir} says it is ${r.being}'s ${r.id}, who does not hold it`));
|
|
95
|
+
assert.ok(r.spent.every((n) => n <= r.mark), at(`heir ${heir} spent a number above its mark`));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const [key, b] of Object.entries(c.binds)) {
|
|
100
|
+
// every occupant id the being holds is a heir her own ward keeps
|
|
101
|
+
for (const [id, heir] of Object.entries(b.occupants)) {
|
|
102
|
+
const r = c.wards[b.ward].heirs[heir];
|
|
103
|
+
assert.ok(r, at(`${key}'s occupant ${id} names heir ${heir}, which her ward has forgotten`));
|
|
104
|
+
assert.equal(r.being, key, at(`${key}'s occupant ${id} names a heir of ${r.being}'s`));
|
|
105
|
+
assert.equal(r.id, id, at(`${key}'s occupant ${id} names a heir filed under ${r.id}`));
|
|
106
|
+
}
|
|
107
|
+
// a taken relation lives in the standing and never also in a knock record
|
|
108
|
+
for (const [id, s] of Object.entries(b.standings)) {
|
|
109
|
+
const name = s.heir === null ? `public:${s.ward}` : `${s.ward}:${s.heir}`;
|
|
110
|
+
assert.ok(!b.knocks.includes(name), at(`${key}'s standing ${id} still has a knock record`));
|
|
111
|
+
assert.ok(!b.answered.includes(name), at(`${key}'s standing ${id} is still marked answered`));
|
|
112
|
+
}
|
|
113
|
+
// no key her side holds is anywhere in anyone's cells
|
|
114
|
+
const cells = JSON.stringify(c.cells);
|
|
115
|
+
for (const word of b.secrets) assert.ok(!cells.includes(word), at(`a key of ${key}'s is in the cells`));
|
|
116
|
+
// counts climb and never fall. a door that forgot what it honoured honours it again
|
|
117
|
+
const was = before?.binds[key];
|
|
118
|
+
if (was)
|
|
119
|
+
for (const [id, s] of Object.entries(b.standings)) assert.ok(!was.standings[id] || s.seq >= was.standings[id].seq, at(`${key}'s standing ${id} counted backwards`));
|
|
120
|
+
}
|
|
121
|
+
if (before)
|
|
122
|
+
for (const [pk, w] of Object.entries(c.wards))
|
|
123
|
+
for (const [heir, r] of Object.entries(w.heirs)) {
|
|
124
|
+
const was = before.wards[pk]?.heirs[heir];
|
|
125
|
+
if (was) assert.ok(r.mark >= was.mark, at(`heir ${heir} marked backwards`));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---- the chapter
|
|
130
|
+
|
|
131
|
+
export function estate(label: string, make: () => Promise<World>, { canDown, test }: { canDown: boolean; test: Runner }) {
|
|
132
|
+
const t = (name: string, fn: () => Promise<void> | void) => test(`[${label}] ${name}`, {}, fn);
|
|
133
|
+
|
|
134
|
+
// The story, told once, by hand, so that the next person can read what the
|
|
135
|
+
// model is for. A partner is invited into a harbor she is borrowing, works,
|
|
136
|
+
// moves to a harbor of her own, is kicked, and is invited back as somebody
|
|
137
|
+
// else. Nothing here is random and nothing here is generated.
|
|
138
|
+
t('estate: a partner is invited into a borrowed harbor, works, moves to her own, is kicked, and comes back as somebody else', async () => {
|
|
139
|
+
const w = await make();
|
|
140
|
+
const acme = await w.boot<Member>('ACME', Member);
|
|
141
|
+
const partner = await w.boot<Member>('PARTNER', Member);
|
|
142
|
+
|
|
143
|
+
// 1. acme mints a name for her and asks her ward for an invitation. the
|
|
144
|
+
// occupant exists from that moment, and the partner knows nothing yet.
|
|
145
|
+
const first = (await acme.being.invite('partner-1'))!;
|
|
146
|
+
assert.deepEqual(Object.keys(acme.cells.occupants), ['partner-1']);
|
|
147
|
+
assert.deepEqual(partner.cells.standings, {});
|
|
148
|
+
|
|
149
|
+
// 2. she knocks. from now on every ask of hers arrives as that name.
|
|
150
|
+
assert.deepEqual(await partner.being.knock(first, 'ping'), { pong: 'partner-1' });
|
|
151
|
+
// 3. and only then does she keep acme, under a name of her own minting.
|
|
152
|
+
assert.equal(await partner.being.take('the-client', first), 'the-client');
|
|
153
|
+
assert.deepEqual(await partner.being.standings['the-client']!.ask('ping'), { pong: 'partner-1' });
|
|
154
|
+
|
|
155
|
+
// 4. she gets her own harbor. the same seed, the same pk, so the arc acme
|
|
156
|
+
// holds still points at her and the arc she holds still points at acme.
|
|
157
|
+
await w.migrate('PARTNER');
|
|
158
|
+
await w.migrate('ACME');
|
|
159
|
+
assert.deepEqual(await partner.being.standings['the-client']!.ask('ping'), { pong: 'partner-1' });
|
|
160
|
+
const back = (await partner.being.invite('acme'))!;
|
|
161
|
+
assert.deepEqual(await acme.being.knock(back, 'ping'), { pong: 'acme' });
|
|
162
|
+
assert.equal(await acme.being.take('them', back), 'them');
|
|
163
|
+
|
|
164
|
+
// 5. acme kicks her. her standing is still hers to hold -- she is never
|
|
165
|
+
// told -- and it is silence from here on.
|
|
166
|
+
acme.being.occupants.remove('partner-1');
|
|
167
|
+
assert.ok(isSilence(await partner.being.standings['the-client']!.ask('ping')));
|
|
168
|
+
assert.deepEqual(Object.keys(partner.cells.standings), ['the-client']); // she never learns
|
|
169
|
+
assert.deepEqual(await acme.being.standings.them!.ask('ping'), { pong: 'acme' }); // the other arc never moved
|
|
170
|
+
|
|
171
|
+
// 6. and back, as somebody else: a second invitation is a second relation,
|
|
172
|
+
// sharing nothing with the first. the old one stays dead.
|
|
173
|
+
const second = (await acme.being.invite('partner-2'))!;
|
|
174
|
+
assert.notEqual(second.heir, first.heir);
|
|
175
|
+
assert.deepEqual(await partner.being.knock(second, 'ping'), { pong: 'partner-2' });
|
|
176
|
+
assert.equal(await partner.being.take('the-client-again', second), 'the-client-again');
|
|
177
|
+
assert.ok(isSilence(await partner.being.standings['the-client']!.ask('ping')));
|
|
178
|
+
ledger(w.census(), undefined, 'the story');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
for (const seed of [1, 2, 3]) {
|
|
182
|
+
t(`estate: sixty moves from seed ${seed}, the ledger a graph and every arc asked after each one`, async () => {
|
|
183
|
+
const w = await make();
|
|
184
|
+
const roll = rolls(seed);
|
|
185
|
+
const pick = <T>(xs: T[]): T => xs[Math.floor(roll() * xs.length)];
|
|
186
|
+
const hands: Record<string, Handle<Member>> = {};
|
|
187
|
+
const g: Graph = { seats: [], holds: [] };
|
|
188
|
+
let minted = 0;
|
|
189
|
+
const born = async (key: string) => (hands[key] = await w.boot<Member>(key, Member));
|
|
190
|
+
for (const key of ['M1', 'M2', 'M3']) await born(key);
|
|
191
|
+
let before: Census | undefined;
|
|
192
|
+
|
|
193
|
+
// Every move that is legal right now, as a thunk. A move that would not
|
|
194
|
+
// be legal is not written down, so the chapter never asserts on a
|
|
195
|
+
// question the spec does not answer.
|
|
196
|
+
const moves = (): (() => Promise<string>)[] => {
|
|
197
|
+
const keys = Object.keys(hands);
|
|
198
|
+
const out: (() => Promise<string>)[] = [];
|
|
199
|
+
|
|
200
|
+
// invite: a fresh occupant arc, and nothing of the guest's in it
|
|
201
|
+
out.push(async () => {
|
|
202
|
+
const host = pick(keys);
|
|
203
|
+
const id = `o${++minted}`;
|
|
204
|
+
const inv = (await hands[host].being.invite(id))!;
|
|
205
|
+
assert.equal(inv.ward, hands[host].pk);
|
|
206
|
+
assert.match(inv.heir!, HEX64);
|
|
207
|
+
g.seats.push({ host, id, inv, guest: null, answered: false, live: true, taken: false });
|
|
208
|
+
return `${host} invites ${id}`;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// knock: the guest an unspent seat gets is anyone but the host; a
|
|
212
|
+
// spent one only ever answers the one who spent it. Once she has
|
|
213
|
+
// taken it the invitation is not a knock any more -- it joins the
|
|
214
|
+
// standing's lane, and is exactly as alive as the standing is.
|
|
215
|
+
for (const s of g.seats) {
|
|
216
|
+
out.push(async () => {
|
|
217
|
+
const guest = s.guest ?? pick(keys.filter((k) => k !== s.host));
|
|
218
|
+
const hold = g.holds.find((h) => h.seat === s);
|
|
219
|
+
const back = await hands[guest].being.knock(s.inv, 'ping');
|
|
220
|
+
if (s.live && (s.guest === null || s.guest === guest) && (hold === undefined || hold.live)) {
|
|
221
|
+
assert.deepEqual(back, { pong: s.id }, `${guest} knocking ${s.host}/${s.id}`);
|
|
222
|
+
s.guest = guest;
|
|
223
|
+
s.answered = true;
|
|
224
|
+
} else assert.ok(isSilence(back), `${guest} knocking ${s.host}/${s.id} should be silence`);
|
|
225
|
+
return `${guest} knocks ${s.host}/${s.id}`;
|
|
226
|
+
});
|
|
227
|
+
// a stranger on a spent seat: silence, and nothing rebinds
|
|
228
|
+
if (s.guest !== null)
|
|
229
|
+
out.push(async () => {
|
|
230
|
+
const other = keys.filter((k) => k !== s.host && k !== s.guest);
|
|
231
|
+
if (!other.length) return 'no stranger to try';
|
|
232
|
+
const who = pick(other);
|
|
233
|
+
assert.ok(isSilence(await hands[who].being.knock(s.inv, 'ping')), `${who} should not get in on ${s.host}/${s.id}`);
|
|
234
|
+
return `${who} is refused at ${s.host}/${s.id}`;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// take: only after an answer, only once. the far side may have kicked
|
|
239
|
+
// her in between -- take reads the knock record, and still births one.
|
|
240
|
+
for (const s of g.seats.filter((s) => s.answered && !s.taken))
|
|
241
|
+
out.push(async () => {
|
|
242
|
+
const id = `s${++minted}`;
|
|
243
|
+
assert.equal(await hands[s.guest!].being.take(id, s.inv), id);
|
|
244
|
+
s.taken = true;
|
|
245
|
+
g.holds.push({ owner: s.guest!, id, seat: s, live: true });
|
|
246
|
+
assert.equal(await hands[s.guest!].being.take(`${id}-again`, s.inv), null); // spent for her
|
|
247
|
+
return `${s.guest} takes ${s.host}/${s.id} as ${id}`;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// drop: hers to drop, and the host is never told
|
|
251
|
+
for (const h of g.holds.filter((h) => h.live))
|
|
252
|
+
out.push(async () => {
|
|
253
|
+
hands[h.owner].being.standings.remove(h.id);
|
|
254
|
+
h.live = false;
|
|
255
|
+
assert.equal(hands[h.owner].being.standings[h.id], undefined);
|
|
256
|
+
return `${h.owner} drops ${h.id}`;
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// kick: the host's, and the guest is never told either
|
|
260
|
+
for (const s of g.seats.filter((s) => s.live))
|
|
261
|
+
out.push(async () => {
|
|
262
|
+
hands[s.host].being.occupants.remove(s.id);
|
|
263
|
+
s.live = false;
|
|
264
|
+
return `${s.host} kicks ${s.id}`;
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// a new being: an estate grows
|
|
268
|
+
if (keys.length < 6) out.push(async () => (await born(`M${keys.length + 1}`), `M${keys.length + 1} is booted`));
|
|
269
|
+
|
|
270
|
+
// a ward moves harbor, under everyone standing on it
|
|
271
|
+
out.push(async () => {
|
|
272
|
+
const who = pick(keys);
|
|
273
|
+
await w.migrate(who);
|
|
274
|
+
return `${who}'s ward moves harbor`;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// weather: the host's door is gone. unreached, never silence, and the
|
|
278
|
+
// relation is exactly where it was when it comes back.
|
|
279
|
+
// weather: the host's door is gone. unreached, never silence, and the
|
|
280
|
+
// relation is exactly where it was when it comes back. The move is on
|
|
281
|
+
// the list under every topology even where there is nothing to cut,
|
|
282
|
+
// because the list is the script: a move missing in one world would
|
|
283
|
+
// make it a different script, and the three could not be compared.
|
|
284
|
+
for (const h of g.holds.filter((h) => h.live && h.seat.live))
|
|
285
|
+
out.push(async () => {
|
|
286
|
+
if (!canDown) return 'nothing to cut inside one ward';
|
|
287
|
+
hands[h.seat.host].down = true;
|
|
288
|
+
assert.ok(isUnreached(await hands[h.owner].being.standings[h.id]!.ask('ping')), `${h.owner}/${h.id} should be unreached`);
|
|
289
|
+
hands[h.seat.host].down = false;
|
|
290
|
+
return `${h.seat.host} is cut and comes back`;
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return out;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// Every arc in the model, asked. This is where the ledger stops being
|
|
297
|
+
// bookkeeping and becomes what the door actually does.
|
|
298
|
+
const sweep = async (step: number) => {
|
|
299
|
+
for (const h of g.holds) {
|
|
300
|
+
const standing = hands[h.owner].being.standings[h.id];
|
|
301
|
+
if (!h.live) {
|
|
302
|
+
assert.equal(standing, undefined, `step ${step}: ${h.owner}'s dropped ${h.id} is still there`);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const out = await standing!.ask('ping');
|
|
306
|
+
if (h.seat.live) assert.deepEqual(out, { pong: h.seat.id }, `step ${step}: ${h.owner}/${h.id} -> ${h.seat.host}/${h.seat.id}`);
|
|
307
|
+
else assert.ok(isSilence(out), `step ${step}: ${h.owner}/${h.id} is kicked and should be silence`);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// The trace is the seed's own account of itself: a red run prints the
|
|
312
|
+
// moves that got there, which is the only thing that makes a generated
|
|
313
|
+
// sequence debuggable at all.
|
|
314
|
+
const trace: string[] = [];
|
|
315
|
+
const at = (step: number) => `seed ${seed} step ${step}\n ${trace.join('\n ')}`;
|
|
316
|
+
for (let step = 1; step <= 60; step++) {
|
|
317
|
+
const move = pick(moves());
|
|
318
|
+
try {
|
|
319
|
+
trace.push(await move());
|
|
320
|
+
} catch (e) {
|
|
321
|
+
throw new Error(`${at(step)}\n -> ${(e as Error).message}`, { cause: e });
|
|
322
|
+
}
|
|
323
|
+
const c = w.census();
|
|
324
|
+
ledger(c, before, at(step));
|
|
325
|
+
before = c;
|
|
326
|
+
await sweep(step).catch((e: Error) => {
|
|
327
|
+
throw new Error(`${at(step)}\n -> ${e.message}`, { cause: e });
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// and the estate that came out is the estate every topology comes out with
|
|
332
|
+
const print = fingerprint(g);
|
|
333
|
+
const key = `seed ${seed}`;
|
|
334
|
+
const first = landed.get(key);
|
|
335
|
+
if (!first) landed.set(key, { by: label, print });
|
|
336
|
+
else assert.equal(print, first.print, `the estate under ${label} is not the estate under ${first.by}: a being learned the topology`);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|