nervur 0.23.0 → 0.24.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/CONTRIBUTING.md +13 -0
- package/LICENSE +55 -202
- package/NOTICE +3 -8
- package/README.md +14 -156
- package/RELEASING.md +15 -0
- package/SECURITY.md +9 -0
- package/bin/nervur.js +6 -0
- package/package.json +26 -81
- package/AUTHORING.md +0 -935
- package/COMMAND.md +0 -179
- package/FACES.md +0 -280
- package/FACULTIES.md +0 -578
- package/GROUNDS.md +0 -315
- package/KIT-SPEC.md +0 -304
- package/WORLD.md +0 -195
- package/dist/app/app-ground.d.ts +0 -18
- package/dist/app/app-ground.js +0 -23
- package/dist/app/index.d.ts +0 -2
- package/dist/app/index.js +0 -5
- package/dist/app/native.d.ts +0 -11
- package/dist/app/native.js +0 -13
- package/dist/being/being.d.ts +0 -306
- package/dist/being/being.js +0 -20
- package/dist/being/covers.d.ts +0 -9
- package/dist/being/covers.js +0 -21
- package/dist/being/index.d.ts +0 -4
- package/dist/being/index.js +0 -6
- package/dist/being/need.d.ts +0 -49
- package/dist/being/need.js +0 -33
- package/dist/being/schema.d.ts +0 -100
- package/dist/being/schema.js +0 -132
- package/dist/being/table.d.ts +0 -48
- package/dist/being/table.js +0 -344
- package/dist/bench/bench-ground.d.ts +0 -73
- package/dist/bench/bench-ground.js +0 -153
- package/dist/bench/bench.d.ts +0 -101
- package/dist/bench/bench.js +0 -368
- package/dist/bench/fake-clock.d.ts +0 -17
- package/dist/bench/fake-clock.js +0 -34
- package/dist/bench/fake-memory.d.ts +0 -16
- package/dist/bench/fake-memory.js +0 -46
- package/dist/bench/fake-network.d.ts +0 -66
- package/dist/bench/fake-network.js +0 -222
- package/dist/bench/fake-unlock.d.ts +0 -6
- package/dist/bench/fake-unlock.js +0 -10
- package/dist/bench/index.d.ts +0 -3
- package/dist/bench/index.js +0 -5
- package/dist/bench/seeded.d.ts +0 -11
- package/dist/bench/seeded.js +0 -37
- package/dist/bench/settle.d.ts +0 -2
- package/dist/bench/settle.js +0 -49
- package/dist/bench/stewards.d.ts +0 -169
- package/dist/bench/stewards.js +0 -128
- package/dist/bodies/class-list.d.ts +0 -14
- package/dist/bodies/class-list.js +0 -33
- package/dist/bodies/joined-carry.d.ts +0 -29
- package/dist/bodies/joined-carry.js +0 -48
- package/dist/bodies/kept.d.ts +0 -44
- package/dist/bodies/kept.js +0 -91
- package/dist/bodies/noble-crypto.d.ts +0 -22
- package/dist/bodies/noble-crypto.js +0 -88
- package/dist/bodies/seed-keys.d.ts +0 -10
- package/dist/bodies/seed-keys.js +0 -42
- package/dist/bodies/strict-tools.d.ts +0 -10
- package/dist/bodies/strict-tools.js +0 -306
- package/dist/bodies/vouch.d.ts +0 -20
- package/dist/bodies/vouch.js +0 -71
- package/dist/bodies/web-carry.d.ts +0 -72
- package/dist/bodies/web-carry.js +0 -245
- package/dist/bodies/web-clock.d.ts +0 -12
- package/dist/bodies/web-clock.js +0 -33
- package/dist/browser/browser-ground.d.ts +0 -76
- package/dist/browser/browser-ground.js +0 -294
- package/dist/browser/index.d.ts +0 -4
- package/dist/browser/index.js +0 -7
- package/dist/browser/indexeddb-memory.d.ts +0 -17
- package/dist/browser/indexeddb-memory.js +0 -97
- package/dist/browser/locked-unlock.d.ts +0 -24
- package/dist/browser/locked-unlock.js +0 -85
- package/dist/browser/origin-classes.d.ts +0 -7
- package/dist/browser/origin-classes.js +0 -31
- package/dist/edge/durable-clock.d.ts +0 -19
- package/dist/edge/durable-clock.js +0 -61
- package/dist/edge/durable-memory.d.ts +0 -5
- package/dist/edge/durable-memory.js +0 -11
- package/dist/edge/durable.d.ts +0 -18
- package/dist/edge/durable.js +0 -24
- package/dist/edge/edge-ground.d.ts +0 -49
- package/dist/edge/edge-ground.js +0 -223
- package/dist/edge/index.d.ts +0 -7
- package/dist/edge/index.js +0 -9
- package/dist/edge/native-crypto.d.ts +0 -8
- package/dist/edge/native-crypto.js +0 -102
- package/dist/edge/secret-unlock.d.ts +0 -7
- package/dist/edge/secret-unlock.js +0 -13
- package/dist/edge/socket-carry.d.ts +0 -39
- package/dist/edge/socket-carry.js +0 -111
- package/dist/foundation.d.ts +0 -162
- package/dist/foundation.js +0 -4
- package/dist/ground/dock.d.ts +0 -1460
- package/dist/ground/dock.js +0 -1159
- package/dist/ground/ground.d.ts +0 -239
- package/dist/ground/ground.js +0 -787
- package/dist/ground/views.d.ts +0 -76
- package/dist/ground/views.js +0 -208
- package/dist/house/crossing.d.ts +0 -50
- package/dist/house/crossing.js +0 -153
- package/dist/house/house.d.ts +0 -88
- package/dist/house/house.js +0 -2069
- package/dist/house/rows-shape.d.ts +0 -110
- package/dist/house/rows-shape.js +0 -12
- package/dist/house/rows.d.ts +0 -27
- package/dist/house/rows.js +0 -185
- package/dist/index.d.ts +0 -33
- package/dist/index.js +0 -29
- package/dist/node/bridge.d.ts +0 -20
- package/dist/node/bridge.js +0 -284
- package/dist/node/cli.d.ts +0 -2
- package/dist/node/cli.js +0 -225
- package/dist/node/file-memory.d.ts +0 -13
- package/dist/node/file-memory.js +0 -86
- package/dist/node/folder-classes.d.ts +0 -5
- package/dist/node/folder-classes.js +0 -40
- package/dist/node/gone.d.ts +0 -2
- package/dist/node/gone.js +0 -7
- package/dist/node/hand.d.ts +0 -39
- package/dist/node/hand.js +0 -107
- package/dist/node/http.d.ts +0 -11
- package/dist/node/http.js +0 -90
- package/dist/node/index.d.ts +0 -11
- package/dist/node/index.js +0 -13
- package/dist/node/ledger-memory.d.ts +0 -17
- package/dist/node/ledger-memory.js +0 -152
- package/dist/node/lock.d.ts +0 -10
- package/dist/node/lock.js +0 -78
- package/dist/node/node-ground.d.ts +0 -26
- package/dist/node/node-ground.js +0 -228
- package/dist/node/notify.d.ts +0 -1
- package/dist/node/notify.js +0 -18
- package/dist/node/shell.d.ts +0 -11
- package/dist/node/shell.js +0 -35
- package/dist/node/sync-folder.d.ts +0 -6
- package/dist/node/sync-folder.js +0 -18
- package/dist/node/tcp-carry.d.ts +0 -42
- package/dist/node/tcp-carry.js +0 -193
- package/dist/node/unlock.d.ts +0 -17
- package/dist/node/unlock.js +0 -86
- package/dist/node/websocket.d.ts +0 -12
- package/dist/node/websocket.js +0 -134
- package/dist/quo/frame.d.ts +0 -33
- package/dist/quo/frame.js +0 -92
- package/dist/quo/index.d.ts +0 -1
- package/dist/quo/index.js +0 -5
- package/dist/quo/room.d.ts +0 -154
- package/dist/quo/room.js +0 -362
- package/dist/serve/index.d.ts +0 -45
- package/dist/serve/index.js +0 -71
package/dist/bench/bench.js
DELETED
|
@@ -1,368 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// The bench: two houses on one BenchGround, reached as any owner reaches
|
|
3
|
-
// hers. The author's house holds her classes and the fakes she hands it,
|
|
4
|
-
// as faculties granted to it alone. The bench's own house holds a caller,
|
|
5
|
-
// who asks her through a standing or as a stranger, so every ask crosses a
|
|
6
|
-
// door as it would in production. Nothing here reaches inside a house: a
|
|
7
|
-
// role is played by whoever an owner could have play it.
|
|
8
|
-
import { tableOf } from '../being/index.js';
|
|
9
|
-
import { BenchGround, Machine } from './bench-ground.js';
|
|
10
|
-
import { FakeNetwork } from './fake-network.js';
|
|
11
|
-
import { BenchCaller, BenchSteward } from './stewards.js';
|
|
12
|
-
const STEWARD = 'steward';
|
|
13
|
-
const PUBLIC = 'public';
|
|
14
|
-
const AUTHOR = 'author';
|
|
15
|
-
const TESTER = 'bench';
|
|
16
|
-
const HOST = 'bench';
|
|
17
|
-
/** The being of the author's house that plays a being introduced to her. */
|
|
18
|
-
const NEIGHBOUR = 'bench-neighbour';
|
|
19
|
-
/** Who an asker the house makes is to her roles. */
|
|
20
|
-
const ROOT = Object.freeze({ id: 'root', notes: {}, steward: {} });
|
|
21
|
-
const STRANGER = Object.freeze({ id: 'stranger', notes: {}, steward: {} });
|
|
22
|
-
/** A being the bench placed, asked and described as any role the bench can play. */
|
|
23
|
-
export class Placed {
|
|
24
|
-
id;
|
|
25
|
-
position;
|
|
26
|
-
#bench;
|
|
27
|
-
#table;
|
|
28
|
-
constructor(bench, id, position, table) {
|
|
29
|
-
this.#bench = bench;
|
|
30
|
-
this.id = id;
|
|
31
|
-
this.position = position;
|
|
32
|
-
this.#table = table;
|
|
33
|
-
}
|
|
34
|
-
/** The role an ask's entry names first; `root` where it names every occupant. */
|
|
35
|
-
roleFor(method) {
|
|
36
|
-
return this.#table.asks[method]?.for?.[0] ?? 'root';
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* One ask, as whoever plays `role`. An effect's answer is the reply it
|
|
40
|
-
* brings once it lands, and `null` is silence.
|
|
41
|
-
*/
|
|
42
|
-
ask(method, args = {}, { role = this.roleFor(method) } = {}) {
|
|
43
|
-
return this.#bench.asked(this, this.#table, role, method, args);
|
|
44
|
-
}
|
|
45
|
-
/** Her describe as `role` sees it: her state and the asks it shows. */
|
|
46
|
-
describe({ role = 'root' } = {}) {
|
|
47
|
-
return this.#bench.described(this, this.#table, role);
|
|
48
|
-
}
|
|
49
|
-
/** Her cells as last landed, read through the hand, as her owner reads them. */
|
|
50
|
-
cells() {
|
|
51
|
-
return this.#bench.cellsOf(this);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
export class Bench {
|
|
55
|
-
#network;
|
|
56
|
-
#ground;
|
|
57
|
-
/** The author's house's ward, which a stranger's card names. */
|
|
58
|
-
#ward;
|
|
59
|
-
/** The author's house is kept by the bench's own steward. */
|
|
60
|
-
#own;
|
|
61
|
-
#steward;
|
|
62
|
-
#public;
|
|
63
|
-
/** The caller's standing on each being, by the role it plays. */
|
|
64
|
-
#doors = new Map();
|
|
65
|
-
/** The beings the neighbour stands on. */
|
|
66
|
-
#introduced = new Set();
|
|
67
|
-
#count = 0;
|
|
68
|
-
constructor(network, ground, ward, placed) {
|
|
69
|
-
this.#network = network;
|
|
70
|
-
this.#ground = ground;
|
|
71
|
-
this.#ward = ward;
|
|
72
|
-
this.#own = placed.steward === undefined;
|
|
73
|
-
this.#steward = placed.steward;
|
|
74
|
-
this.#public = placed.public;
|
|
75
|
-
}
|
|
76
|
-
static async open({ classes, offers = [], seed = 'bench', steward, public: open }) {
|
|
77
|
-
const network = new FakeNetwork({ seed });
|
|
78
|
-
// Her fakes, each a faculty of the ground granted to her house alone.
|
|
79
|
-
const faculties = Object.fromEntries(offers.map((offer, index) => [`offer-${index}`, offer]));
|
|
80
|
-
const ground = await BenchGround.open({
|
|
81
|
-
network,
|
|
82
|
-
host: HOST,
|
|
83
|
-
names: [HOST],
|
|
84
|
-
machine: new Machine(seed),
|
|
85
|
-
faculties,
|
|
86
|
-
modules: {
|
|
87
|
-
[AUTHOR]: { steward: steward ?? BenchSteward, ...(open === undefined ? {} : { public: open }), beings: [...classes, BenchCaller] },
|
|
88
|
-
[TESTER]: { steward: BenchCaller },
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
const author = await ground.add(AUTHOR, AUTHOR, { faculties: Object.keys(faculties) });
|
|
92
|
-
const tester = await ground.add(TESTER, TESTER);
|
|
93
|
-
for (const standing of [author, tester])
|
|
94
|
-
if (standing.ward === undefined)
|
|
95
|
-
throw new Error(`the bench's house did not open: ${standing.why}`);
|
|
96
|
-
return new Bench(network, ground, author.ward, { steward, public: open });
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Her class here: the steward or the public being where the bench opened
|
|
100
|
-
* with her there, and otherwise borne by the bench's own steward, from
|
|
101
|
-
* her `born`.
|
|
102
|
-
*/
|
|
103
|
-
async place(Class, { id, born } = {}) {
|
|
104
|
-
const table = tableOf(Class);
|
|
105
|
-
if (Class === this.#steward)
|
|
106
|
-
return new Placed(this, STEWARD, 'steward', table);
|
|
107
|
-
if (Class === this.#public)
|
|
108
|
-
return new Placed(this, PUBLIC, 'public', table);
|
|
109
|
-
if (!this.#own)
|
|
110
|
-
throw new Error('the bench bears a being beside its own steward alone');
|
|
111
|
-
const name = id ?? `placed-${++this.#count}`;
|
|
112
|
-
await this.#rooted(AUTHOR, STEWARD, 'bear', { kind: table.kind, id: name, ...(born === undefined ? {} : { args: born }) });
|
|
113
|
-
await this.settle();
|
|
114
|
-
return new Placed(this, name, 'normal', table);
|
|
115
|
-
}
|
|
116
|
-
/** Lets every ask, effect and reply the houses started run to its end. */
|
|
117
|
-
settle() {
|
|
118
|
-
return this.#network.settle();
|
|
119
|
-
}
|
|
120
|
-
/** The clock moved on, and what came due run. */
|
|
121
|
-
advance(ms) {
|
|
122
|
-
return this.#network.advance(ms);
|
|
123
|
-
}
|
|
124
|
-
/** A placed being's cells, read through the hand. */
|
|
125
|
-
async cellsOf(placed) {
|
|
126
|
-
const read = await this.#ground.ask({ house: AUTHOR, id: placed.id, cells: true });
|
|
127
|
-
if (!('result' in read))
|
|
128
|
-
throw new Error(`the bench could not read the cells of ${placed.id}: ${JSON.stringify(read)}`);
|
|
129
|
-
return read.result;
|
|
130
|
-
}
|
|
131
|
-
// Who plays a role on her, judged on her cells as they stand. `root` and
|
|
132
|
-
// a role root holds are the hand's, on any being. On the public being,
|
|
133
|
-
// `stranger` and a role a stranger holds are a stranger's. The rest need
|
|
134
|
-
// the bench's own steward: hers for `steward`, a neighbour it introduces
|
|
135
|
-
// for `being`, and an occupant it invites, the role true in its steward
|
|
136
|
-
// notes, for any other her table names and that occupant holds.
|
|
137
|
-
async #player(placed, table, role) {
|
|
138
|
-
const me = { id: placed.id, position: placed.position, cells: await this.cellsOf(placed) };
|
|
139
|
-
const holds = (asker) => {
|
|
140
|
-
const test = table.roles[role];
|
|
141
|
-
try {
|
|
142
|
-
return test?.(asker, me) === true;
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
if (role === 'root' || holds(ROOT))
|
|
149
|
-
return 'hand';
|
|
150
|
-
if (placed.position === 'public' && (role === 'stranger' || holds(STRANGER)))
|
|
151
|
-
return 'stranger';
|
|
152
|
-
if (role === 'handle')
|
|
153
|
-
return { unplayed: 'a handle is minted by her own ask' };
|
|
154
|
-
if (!this.#own)
|
|
155
|
-
return { unplayed: `the bench plays ${role} beside its own steward alone` };
|
|
156
|
-
if (role === STEWARD)
|
|
157
|
-
return placed.position === 'steward' ? { unplayed: 'the steward has no steward' } : 'steward';
|
|
158
|
-
if (role === 'being')
|
|
159
|
-
return 'neighbour';
|
|
160
|
-
if (role in table.roles && !holds({ id: `bench-${role}`, notes: {}, steward: { [role]: true } }))
|
|
161
|
-
return { unplayed: `no one it makes holds ${role} here` };
|
|
162
|
-
return { notes: role };
|
|
163
|
-
}
|
|
164
|
-
/** One ask of a placed being, as whoever plays the role. */
|
|
165
|
-
async asked(placed, table, role, method, args) {
|
|
166
|
-
const player = await this.#player(placed, table, role);
|
|
167
|
-
if (typeof player === 'object' && 'unplayed' in player)
|
|
168
|
-
throw new Error(`the bench cannot play ${role} on ${placed.id}: ${player.unplayed}`);
|
|
169
|
-
if (player === 'hand') {
|
|
170
|
-
const answered = await this.#ground.ask({ house: AUTHOR, id: placed.id, method, args });
|
|
171
|
-
await this.settle();
|
|
172
|
-
return 'describe' in answered ? null : answered;
|
|
173
|
-
}
|
|
174
|
-
if (player === 'stranger')
|
|
175
|
-
return this.#outcome(TESTER, STEWARD, 'visit', { ward: this.#ward, at: [`bench://${HOST}`], method, args });
|
|
176
|
-
if (player === 'steward')
|
|
177
|
-
return this.#outcome(AUTHOR, STEWARD, 'ask', { id: placed.id, method, args });
|
|
178
|
-
if (player === 'neighbour')
|
|
179
|
-
return this.#outcome(AUTHOR, NEIGHBOUR, 'ask', { standing: await this.#neighbour(placed.id), method, args });
|
|
180
|
-
return this.#outcome(TESTER, STEWARD, 'ask', { standing: await this.#door(placed.id, player.notes), method, args });
|
|
181
|
-
}
|
|
182
|
-
/** What a placed being shows whoever plays the role. */
|
|
183
|
-
async described(placed, table, role) {
|
|
184
|
-
const player = await this.#player(placed, table, role);
|
|
185
|
-
if (typeof player === 'object' && 'unplayed' in player)
|
|
186
|
-
throw new Error(`the bench cannot play ${role} on ${placed.id}: ${player.unplayed}`);
|
|
187
|
-
let read;
|
|
188
|
-
if (player === 'hand') {
|
|
189
|
-
const answered = await this.#ground.ask({ house: AUTHOR, id: placed.id });
|
|
190
|
-
if (!('describe' in answered))
|
|
191
|
-
return null;
|
|
192
|
-
read = answered.describe;
|
|
193
|
-
}
|
|
194
|
-
else if (player === 'stranger')
|
|
195
|
-
read = await this.#rooted(TESTER, STEWARD, 'looks', { ward: this.#ward, at: [`bench://${HOST}`] });
|
|
196
|
-
else if (player === 'steward')
|
|
197
|
-
read = await this.#rooted(AUTHOR, STEWARD, 'shows', { id: placed.id });
|
|
198
|
-
else if (player === 'neighbour')
|
|
199
|
-
read = await this.#rooted(AUTHOR, NEIGHBOUR, 'describe', { standing: await this.#neighbour(placed.id) });
|
|
200
|
-
else
|
|
201
|
-
read = await this.#rooted(TESTER, STEWARD, 'describe', { standing: await this.#door(placed.id, player.notes) });
|
|
202
|
-
const described = read;
|
|
203
|
-
return typeof described?.state === 'string' && Array.isArray(described.asks) ? described : null;
|
|
204
|
-
}
|
|
205
|
-
// One of the bench's own beings asked through the hand, which fails the test where she answers an error.
|
|
206
|
-
async #rooted(house, id, method, args) {
|
|
207
|
-
const answered = await this.#ground.ask({ house, id, method, args });
|
|
208
|
-
await this.settle();
|
|
209
|
-
if (!('result' in answered))
|
|
210
|
-
throw new Error(`the bench's ${id} could not ${method}: ${JSON.stringify(answered)}`);
|
|
211
|
-
return answered.result;
|
|
212
|
-
}
|
|
213
|
-
// One ask through one of the bench's beings: its answer, or the reply its effect brought once it landed, or silence.
|
|
214
|
-
async #outcome(house, id, method, args) {
|
|
215
|
-
const replies = async () => (await this.#rooted(house, id, 'replies', {}));
|
|
216
|
-
const before = (await replies()).length;
|
|
217
|
-
const read = (await this.#rooted(house, id, method, args));
|
|
218
|
-
if (read.answer !== undefined)
|
|
219
|
-
return read.answer;
|
|
220
|
-
return (await replies())[before] ?? null;
|
|
221
|
-
}
|
|
222
|
-
// The caller's standing on her as an occupant whose steward notes hold the role.
|
|
223
|
-
async #door(being, role) {
|
|
224
|
-
const key = `${being}\n${role}`;
|
|
225
|
-
const held = this.#doors.get(key);
|
|
226
|
-
if (held !== undefined)
|
|
227
|
-
return held;
|
|
228
|
-
const { door } = (await this.#rooted(AUTHOR, STEWARD, 'door', { id: being, occupant: `bench-${role}`, role }));
|
|
229
|
-
const standing = (await this.#rooted(TESTER, STEWARD, 'adopt', { invitation: door }));
|
|
230
|
-
this.#doors.set(key, standing);
|
|
231
|
-
return standing;
|
|
232
|
-
}
|
|
233
|
-
// The neighbour, borne once and introduced to her; its standing on her is named for her.
|
|
234
|
-
async #neighbour(being) {
|
|
235
|
-
if (this.#introduced.size === 0)
|
|
236
|
-
await this.#rooted(AUTHOR, STEWARD, 'bear', { kind: 'org.nervur.bench.caller', id: NEIGHBOUR });
|
|
237
|
-
if (being !== STEWARD && !this.#introduced.has(being))
|
|
238
|
-
await this.#rooted(AUTHOR, STEWARD, 'introduce', { from: NEIGHBOUR, to: being });
|
|
239
|
-
this.#introduced.add(being);
|
|
240
|
-
return being;
|
|
241
|
-
}
|
|
242
|
-
/** Every example of her class run, twice, on two fresh benches. */
|
|
243
|
-
static async examples(Class, options = {}) {
|
|
244
|
-
const table = tableOf(Class);
|
|
245
|
-
const where = { ...options, seed: options.seed ?? 'examples' };
|
|
246
|
-
const findings = [];
|
|
247
|
-
for (const entry of Object.values(table.asks)) {
|
|
248
|
-
for (const [index, raw] of entry.examples.entries()) {
|
|
249
|
-
const example = raw;
|
|
250
|
-
const what = `${entry.method}, example ${index + 1}${example.description === undefined ? '' : `: ${example.description}`}`;
|
|
251
|
-
try {
|
|
252
|
-
const first = await Bench.#run(Class, table, entry, example, where);
|
|
253
|
-
const second = await Bench.#run(Class, table, entry, example, where);
|
|
254
|
-
if (example.gives !== undefined && canonical(first.answer) !== canonical(example.gives)) {
|
|
255
|
-
findings.push({ ok: false, what, why: `it gives ${JSON.stringify(first.answer)}, and the example says ${JSON.stringify(example.gives)}` });
|
|
256
|
-
}
|
|
257
|
-
else if (canonical(first.answer) !== canonical(second.answer)) {
|
|
258
|
-
findings.push({ ok: false, what, why: 'a re-run of the same history answered differently' });
|
|
259
|
-
}
|
|
260
|
-
else if (canonical(first.cells) !== canonical(second.cells)) {
|
|
261
|
-
findings.push({ ok: false, what, why: 'a re-run of the same history left her cells differently' });
|
|
262
|
-
}
|
|
263
|
-
else
|
|
264
|
-
findings.push({ ok: true, what });
|
|
265
|
-
}
|
|
266
|
-
catch (error) {
|
|
267
|
-
findings.push({ ok: false, what, why: error instanceof Error ? error.message : String(error) });
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return findings;
|
|
272
|
-
}
|
|
273
|
-
// A fresh bench with her in the position the check names, and the steward beside her where one is given.
|
|
274
|
-
static #opening(Class, table, example, { classes = [], seed, position = 'normal', steward }) {
|
|
275
|
-
return Bench.open({
|
|
276
|
-
classes: position === 'normal' ? [Class, ...classes] : classes,
|
|
277
|
-
offers: fakes(table, example),
|
|
278
|
-
...(seed === undefined ? {} : { seed }),
|
|
279
|
-
...(position === 'steward' ? { steward: Class } : steward === undefined ? {} : { steward }),
|
|
280
|
-
...(position === 'public' ? { public: Class } : {}),
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
// Her history asked in its order: each ask of it must answer, or the example names where it stopped.
|
|
284
|
-
static async #replay(placed, given) {
|
|
285
|
-
for (const [index, step] of given.entries()) {
|
|
286
|
-
const answered = await placed.ask(step.ask, step.args ?? {}, step.role === undefined ? {} : { role: step.role });
|
|
287
|
-
if (answered === null || 'error' in answered)
|
|
288
|
-
throw new Error(`given ${index + 1}, ${step.ask}, answered ${JSON.stringify(answered)}`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
static async #run(Class, table, entry, example, options) {
|
|
292
|
-
const bench = await Bench.#opening(Class, table, example, options);
|
|
293
|
-
const placed = await bench.place(Class);
|
|
294
|
-
await Bench.#replay(placed, example.given ?? []);
|
|
295
|
-
const answer = await placed.ask(entry.method, example.args ?? {}, example.role === undefined ? {} : { role: example.role });
|
|
296
|
-
return { answer: answer ?? { nothing: true }, cells: await placed.cells() };
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Every state her `born` and her examples' histories reach, described to
|
|
300
|
-
* every role the bench can play: what each is shown is what her table owes.
|
|
301
|
-
*/
|
|
302
|
-
static async walk(Class, options = {}) {
|
|
303
|
-
const table = tableOf(Class);
|
|
304
|
-
const where = { ...options, seed: options.seed ?? 'walk' };
|
|
305
|
-
const position = options.position ?? 'normal';
|
|
306
|
-
const findings = [];
|
|
307
|
-
const histories = [[], ...Object.values(table.asks).flatMap((entry) => entry.examples.map((example) => example.given ?? []))];
|
|
308
|
-
const seen = new Set();
|
|
309
|
-
// Each role her position lets someone hold: the steward has no steward, and only the public being has strangers.
|
|
310
|
-
const roles = [...new Set([STEWARD, ...Object.keys(table.roles), ...Object.values(table.asks).flatMap((entry) => entry.for ?? [])])].filter((role) => role !== 'handle' && (role !== 'stranger' || position === 'public') && (role !== STEWARD || position !== 'steward'));
|
|
311
|
-
for (const given of histories) {
|
|
312
|
-
const bench = await Bench.#opening(Class, table, {}, where);
|
|
313
|
-
const placed = await bench.place(Class);
|
|
314
|
-
try {
|
|
315
|
-
await Bench.#replay(placed, given);
|
|
316
|
-
}
|
|
317
|
-
catch {
|
|
318
|
-
// A history that stops is its example's finding, and reaches no state here.
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
// One visit a state, from the first history that reaches it.
|
|
322
|
-
const state = (await placed.describe())?.state;
|
|
323
|
-
if (state === undefined || seen.has(state))
|
|
324
|
-
continue;
|
|
325
|
-
seen.add(state);
|
|
326
|
-
for (const role of roles) {
|
|
327
|
-
const player = await bench.#player(placed, table, role);
|
|
328
|
-
if (typeof player === 'object' && 'unplayed' in player)
|
|
329
|
-
continue;
|
|
330
|
-
const what = `${state}, as ${role}`;
|
|
331
|
-
const described = await placed.describe({ role });
|
|
332
|
-
const shown = (described?.asks ?? []).map((entry) => entry.method).sort();
|
|
333
|
-
// A stranger is never shown an effect.
|
|
334
|
-
const owed = Object.values(table.asks)
|
|
335
|
-
.filter((entry) => !(entry.effect && player === 'stranger'))
|
|
336
|
-
.filter((entry) => (entry.for === null ? true : entry.for.includes(role)))
|
|
337
|
-
.map((entry) => entry.method)
|
|
338
|
-
.sort();
|
|
339
|
-
if (described === null)
|
|
340
|
-
findings.push({ ok: false, what, why: 'she answered nothing' });
|
|
341
|
-
else if (described.state !== state)
|
|
342
|
-
findings.push({ ok: false, what, why: `she says ${described.state}` });
|
|
343
|
-
else if (shown.join() !== owed.join())
|
|
344
|
-
findings.push({ ok: false, what, why: `she shows ${shown.join(', ') || 'nothing'}, and her table owes ${owed.join(', ') || 'nothing'}` });
|
|
345
|
-
else
|
|
346
|
-
findings.push({ ok: true, what });
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return findings;
|
|
350
|
-
}
|
|
351
|
-
/** Her examples and her walk, and an error naming every finding that failed. */
|
|
352
|
-
static async check(Class, options = {}) {
|
|
353
|
-
const findings = [...(await Bench.examples(Class, options)), ...(await Bench.walk(Class, options))];
|
|
354
|
-
const failed = findings.filter((finding) => !finding.ok);
|
|
355
|
-
if (failed.length > 0)
|
|
356
|
-
throw new Error(`the bench found ${failed.length}:\n${failed.map((finding) => `- ${finding.what}: ${finding.why}`).join('\n')}`);
|
|
357
|
-
return findings;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
// One JSON value as text with its keys in order, so two answers compare by what they hold.
|
|
361
|
-
const canonical = (value) => JSON.stringify(value, (_key, held) => typeof held === 'object' && held !== null && !Array.isArray(held) ? Object.fromEntries(Object.entries(held).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) : held);
|
|
362
|
-
// A fake of each faculty her needs name, answering what an example says,
|
|
363
|
-
// and `{ result: null }` where it says nothing.
|
|
364
|
-
const fakes = (table, example) => Object.entries(table.needs).map(([member, blueprint]) => {
|
|
365
|
-
const said = example.fakes?.[member] ?? {};
|
|
366
|
-
const object = Object.fromEntries(Object.keys(blueprint.methods).map((method) => [method, async () => said[method] ?? { result: null }]));
|
|
367
|
-
return { blueprint, object, kinds: [table.kind] };
|
|
368
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { Clock } from '../index.ts';
|
|
2
|
-
export declare class FakeClock implements Clock {
|
|
3
|
-
#private;
|
|
4
|
-
constructor(start?: number);
|
|
5
|
-
now(): number;
|
|
6
|
-
wait({ id, ms }: {
|
|
7
|
-
id: string;
|
|
8
|
-
ms: number;
|
|
9
|
-
}): Promise<boolean>;
|
|
10
|
-
cancel({ id }: {
|
|
11
|
-
id: string;
|
|
12
|
-
}): void;
|
|
13
|
-
/** The time moved on, and every wait it passes fired, earliest first. */
|
|
14
|
-
advance(ms: number): void;
|
|
15
|
-
/** The time of the earliest wait, or `null`. */
|
|
16
|
-
next(): number | null;
|
|
17
|
-
}
|
package/dist/bench/fake-clock.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export class FakeClock {
|
|
2
|
-
#now;
|
|
3
|
-
#waits = new Map();
|
|
4
|
-
constructor(start = Date.UTC(2026, 0, 1)) {
|
|
5
|
-
this.#now = start;
|
|
6
|
-
}
|
|
7
|
-
now() {
|
|
8
|
-
return this.#now;
|
|
9
|
-
}
|
|
10
|
-
wait({ id, ms }) {
|
|
11
|
-
this.#waits.get(id)?.resolve(false);
|
|
12
|
-
return new Promise((resolve) => {
|
|
13
|
-
this.#waits.set(id, { at: this.#now + Math.max(0, ms), resolve });
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
cancel({ id }) {
|
|
17
|
-
this.#waits.get(id)?.resolve(false);
|
|
18
|
-
this.#waits.delete(id);
|
|
19
|
-
}
|
|
20
|
-
/** The time moved on, and every wait it passes fired, earliest first. */
|
|
21
|
-
advance(ms) {
|
|
22
|
-
this.#now += ms;
|
|
23
|
-
const due = [...this.#waits].filter(([, waiting]) => waiting.at <= this.#now).sort(([, a], [, b]) => a.at - b.at);
|
|
24
|
-
for (const [id, waiting] of due) {
|
|
25
|
-
this.#waits.delete(id);
|
|
26
|
-
waiting.resolve(true);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/** The time of the earliest wait, or `null`. */
|
|
30
|
-
next() {
|
|
31
|
-
const times = [...this.#waits.values()].map((waiting) => waiting.at);
|
|
32
|
-
return times.length === 0 ? null : Math.min(...times);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Memory } from '../index.ts';
|
|
2
|
-
type PlaceRead = Awaited<ReturnType<Memory['read']>>;
|
|
3
|
-
export declare class FakeMemory implements Memory {
|
|
4
|
-
#private;
|
|
5
|
-
/** The next `count` writes are refused, as a memory another writer moved. */
|
|
6
|
-
refuseNext(count?: number): void;
|
|
7
|
-
read({ place }: {
|
|
8
|
-
place: string;
|
|
9
|
-
}): Promise<PlaceRead>;
|
|
10
|
-
list(): Promise<readonly string[]>;
|
|
11
|
-
write({ writes, expect, }: {
|
|
12
|
-
writes: Readonly<Record<string, Readonly<Record<string, Uint8Array | null>>>>;
|
|
13
|
-
expect: Readonly<Record<string, string | null>>;
|
|
14
|
-
}): Promise<Readonly<Record<string, string | null>> | null>;
|
|
15
|
-
}
|
|
16
|
-
export {};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
export class FakeMemory {
|
|
2
|
-
#places = new Map();
|
|
3
|
-
#refuse = 0;
|
|
4
|
-
/** The next `count` writes are refused, as a memory another writer moved. */
|
|
5
|
-
refuseNext(count = 1) {
|
|
6
|
-
this.#refuse += count;
|
|
7
|
-
}
|
|
8
|
-
async read({ place }) {
|
|
9
|
-
const held = this.#places.get(place);
|
|
10
|
-
if (held === undefined)
|
|
11
|
-
return { entries: {}, version: null };
|
|
12
|
-
return { entries: Object.fromEntries([...held.entries].map(([name, bytes]) => [name, bytes.slice()])), version: String(held.version) };
|
|
13
|
-
}
|
|
14
|
-
async list() {
|
|
15
|
-
return [...this.#places.keys()];
|
|
16
|
-
}
|
|
17
|
-
async write({ writes, expect, }) {
|
|
18
|
-
if (this.#refuse > 0) {
|
|
19
|
-
this.#refuse--;
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
for (const place of new Set([...Object.keys(writes), ...Object.keys(expect)])) {
|
|
23
|
-
const held = this.#places.get(place);
|
|
24
|
-
const version = held === undefined ? null : String(held.version);
|
|
25
|
-
if (!(place in expect) || expect[place] !== version)
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
const versions = {};
|
|
29
|
-
for (const [place, entries] of Object.entries(writes)) {
|
|
30
|
-
const held = this.#places.get(place) ?? { entries: new Map(), version: 0 };
|
|
31
|
-
for (const [name, bytes] of Object.entries(entries)) {
|
|
32
|
-
if (bytes === null)
|
|
33
|
-
held.entries.delete(name);
|
|
34
|
-
else
|
|
35
|
-
held.entries.set(name, bytes.slice());
|
|
36
|
-
}
|
|
37
|
-
held.version++;
|
|
38
|
-
if (held.entries.size === 0)
|
|
39
|
-
this.#places.delete(place);
|
|
40
|
-
else
|
|
41
|
-
this.#places.set(place, held);
|
|
42
|
-
versions[place] = held.entries.size === 0 ? null : String(held.version);
|
|
43
|
-
}
|
|
44
|
-
return versions;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { type Hooked } from '../index.ts';
|
|
2
|
-
import { FakeClock } from './fake-clock.ts';
|
|
3
|
-
/** How one link behaves, each way: its latency in milliseconds, and the share of boxes dropped, of replies lost, and of boxes delivered twice. */
|
|
4
|
-
export interface Link {
|
|
5
|
-
readonly latency?: number;
|
|
6
|
-
readonly drop?: number;
|
|
7
|
-
readonly lose?: number;
|
|
8
|
-
readonly duplicate?: number;
|
|
9
|
-
}
|
|
10
|
-
/** What became of one box: dropped before its door, delivered, its reply lost, or delivered twice. */
|
|
11
|
-
export interface Crossing {
|
|
12
|
-
readonly from: string;
|
|
13
|
-
readonly to: string;
|
|
14
|
-
readonly ward: string;
|
|
15
|
-
readonly outcome: 'dropped' | 'answered' | 'silent' | 'lost' | 'twice' | 'late';
|
|
16
|
-
}
|
|
17
|
-
/** The clock of a network, as a BenchGround reads it. */
|
|
18
|
-
export declare const clockOf: (network: FakeNetwork) => FakeClock;
|
|
19
|
-
export declare class FakeNetwork {
|
|
20
|
-
#private;
|
|
21
|
-
/** Every box that crossed between two hosts, in order. A box by pointer inside a host crosses nothing. */
|
|
22
|
-
readonly crossings: Crossing[];
|
|
23
|
-
/** `seed` draws every chance; `start` is the time its clock starts at. */
|
|
24
|
-
constructor({ seed, start }?: {
|
|
25
|
-
seed?: string;
|
|
26
|
-
start?: number;
|
|
27
|
-
});
|
|
28
|
-
/** The time on its clock, which every ground on it reads. */
|
|
29
|
-
now(): number;
|
|
30
|
-
/** Every ask, effect and reply the grounds started, run to where it waits on the clock or ends. */
|
|
31
|
-
settle(): Promise<void>;
|
|
32
|
-
/** The clock moved on by `ms` at once, every wait it passes fired, and what came due run. */
|
|
33
|
-
advance(ms: number): Promise<void>;
|
|
34
|
-
/** A host's carry. One that listens is dialled at its names; one that does not only dials, as a device. */
|
|
35
|
-
join(host: string, { names, listens, serve }?: {
|
|
36
|
-
names?: readonly string[];
|
|
37
|
-
listens?: boolean;
|
|
38
|
-
serve?: (request: Request) => Promise<Response>;
|
|
39
|
-
}): Hooked;
|
|
40
|
-
/** A name pointed at a host, as a DNS record moved. */
|
|
41
|
-
point(name: string, host: string): void;
|
|
42
|
-
/** How the link between two hosts behaves, both ways, until it is set again. */
|
|
43
|
-
link(a: string, b: string, link: Link): void;
|
|
44
|
-
/** No box crosses from `from` to `to` until `heal`; the other way stands. */
|
|
45
|
-
cut(from: string, to: string): void;
|
|
46
|
-
/** No box crosses between the two hosts, either way, until `heal`. */
|
|
47
|
-
partition(a: string, b: string): void;
|
|
48
|
-
/** Every cut mended. Links keep how they behave. */
|
|
49
|
-
heal(): void;
|
|
50
|
-
/** The host answers nothing, its doors forgotten, until `up`. */
|
|
51
|
-
down(host: string): void;
|
|
52
|
-
up(host: string): void;
|
|
53
|
-
/** The next `count` boxes between hosts are dropped before their door. */
|
|
54
|
-
dropNext(count?: number): void;
|
|
55
|
-
/** The next `count` replies are lost after the far door answered. */
|
|
56
|
-
loseNext(count?: number): void;
|
|
57
|
-
/** The next `count` boxes are delivered twice, and the first reply returns. */
|
|
58
|
-
duplicateNext(count?: number): void;
|
|
59
|
-
/**
|
|
60
|
-
* The clock moved on by `ms` in steps of `step`, the bench settling after
|
|
61
|
-
* each, so latencies end, retries leave and replies land as time passes.
|
|
62
|
-
*/
|
|
63
|
-
elapse(ms: number, { step }?: {
|
|
64
|
-
step?: number;
|
|
65
|
-
}): Promise<void>;
|
|
66
|
-
}
|