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/ground/dock.js
DELETED
|
@@ -1,1159 +0,0 @@
|
|
|
1
|
-
var _a;
|
|
2
|
-
import { Being, need, s } from '../being/index.js';
|
|
3
|
-
import { WAIT_BOUND } from '../being/need.js';
|
|
4
|
-
import { ClassList } from '../bodies/class-list.js';
|
|
5
|
-
/** The dock's name: no house and no faculty takes it. */
|
|
6
|
-
export const DOCK = 'dock';
|
|
7
|
-
/** The kinds of the dock's beings, which the ground's offers name. */
|
|
8
|
-
export const KINDS = Object.freeze({
|
|
9
|
-
steward: 'org.nervur.dock',
|
|
10
|
-
faculty: 'org.nervur.dock.faculty',
|
|
11
|
-
house: 'org.nervur.dock.house',
|
|
12
|
-
secret: 'org.nervur.dock.secret',
|
|
13
|
-
shell: 'org.nervur.dock.shell',
|
|
14
|
-
});
|
|
15
|
-
/** The id of the dock's being that holds the shell's offer. */
|
|
16
|
-
const SHELL_ID = 'shell';
|
|
17
|
-
/** The prefix of each dock being's id, before the name its entry gives. */
|
|
18
|
-
export const IDS = Object.freeze({ faculty: 'faculty.', house: 'house.', secret: 'secret.' });
|
|
19
|
-
/** Any JSON value: a schema with no keyword. */
|
|
20
|
-
const ANY = Object.freeze({});
|
|
21
|
-
const OBJECT = { type: 'object', properties: {}, additionalProperties: true };
|
|
22
|
-
const NAME = s.object({ name: s.string() });
|
|
23
|
-
const WHY = s.object({ why: s.optional(s.string()) });
|
|
24
|
-
const STOOD = s.object({ ward: s.optional(s.string()), why: s.optional(s.string()) });
|
|
25
|
-
/**
|
|
26
|
-
* The ground's work, as its faculty offers it to the dock: a body raised
|
|
27
|
-
* and lowered, a house opened, closed or held asleep, the terrain's
|
|
28
|
-
* defaults, what an entry's faculty takes, the catalogue, a body's method
|
|
29
|
-
* called, and a house's places moved out and in. Each is awaited.
|
|
30
|
-
*/
|
|
31
|
-
export const GroundNeed = need('org.nervur.ground', {
|
|
32
|
-
terrain: { result: ANY, hints: { readOnly: true } },
|
|
33
|
-
check: { args: s.object({ name: s.string(), entry: OBJECT }), result: WHY, hints: { readOnly: true } },
|
|
34
|
-
raise: { args: s.object({ name: s.string(), entry: OBJECT, installed: s.optional(s.string()) }), result: ANY, hints: { idempotent: true }, wait: WAIT_BOUND },
|
|
35
|
-
lower: { args: s.object({ name: s.string(), why: s.optional(s.string()) }), hints: { idempotent: true }, wait: WAIT_BOUND },
|
|
36
|
-
open: { args: s.object({ name: s.string(), entry: OBJECT, seed: s.string(), bound: s.optional(s.integer({ minimum: 1 })) }), result: STOOD, hints: { idempotent: true }, wait: WAIT_BOUND },
|
|
37
|
-
close: { args: s.object({ name: s.string(), why: s.optional(s.string()) }), hints: { idempotent: true }, wait: WAIT_BOUND },
|
|
38
|
-
sleep: { args: s.object({ name: s.string(), entry: OBJECT, ward: s.string() }), hints: { idempotent: true } },
|
|
39
|
-
catalog: { result: ANY, hints: { readOnly: true } },
|
|
40
|
-
call: { args: s.object({ faculty: s.string(), method: s.string(), args: s.optional(OBJECT) }), result: ANY, hints: { idempotent: true }, wait: WAIT_BOUND },
|
|
41
|
-
placesOut: { args: s.object({ name: s.string(), entry: OBJECT }), result: ANY, hints: { readOnly: true }, wait: WAIT_BOUND },
|
|
42
|
-
placesIn: { args: s.object({ name: s.string(), entry: OBJECT, places: OBJECT }), hints: { idempotent: true }, wait: WAIT_BOUND },
|
|
43
|
-
});
|
|
44
|
-
/** A command run on the ground's machine, as its owner runs one: its exit code and what it printed. */
|
|
45
|
-
export const ShellNeed = need('org.nervur.shell', {
|
|
46
|
-
run: {
|
|
47
|
-
args: s.object({ command: s.string(), args: s.optional(s.array(s.string())), cwd: s.optional(s.string()), stdin: s.optional(s.string()) }),
|
|
48
|
-
result: s.object({ code: s.integer(), stdout: s.string(), stderr: s.string() }),
|
|
49
|
-
hints: { idempotent: true },
|
|
50
|
-
wait: WAIT_BOUND,
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
/** The ground's one listener, as a body offers it: the handler of every body, chained. It has no method a being calls. */
|
|
54
|
-
export const ListenerNeed = need('org.nervur.listener', {});
|
|
55
|
-
const NAMED = 'lowercase letters, digits, dots, dashes and underscores, at most fifty-six';
|
|
56
|
-
const NAME_RULE = /^[a-z0-9][a-z0-9._-]{0,55}$/;
|
|
57
|
-
const DOCKED = `the ${DOCK} is the library’s, and no entry names it`;
|
|
58
|
-
// The dock's own faculties: no owner's entry names, makes or grants either.
|
|
59
|
-
const OWN = {
|
|
60
|
-
ground: 'the ground is the library’s: no entry names it, makes it or grants it',
|
|
61
|
-
shell: 'the shell is the dock’s alone: no entry names it, makes it or grants it',
|
|
62
|
-
};
|
|
63
|
-
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
64
|
-
const isNames = (value) => Array.isArray(value) && value.every((name) => typeof name === 'string');
|
|
65
|
-
const sorted = (value) => {
|
|
66
|
-
if (Array.isArray(value))
|
|
67
|
-
return value.map(sorted);
|
|
68
|
-
if (!isObject(value))
|
|
69
|
-
return value;
|
|
70
|
-
return Object.fromEntries(Object.keys(value)
|
|
71
|
-
.sort()
|
|
72
|
-
.map((key) => [key, sorted(value[key])]));
|
|
73
|
-
};
|
|
74
|
-
const canonical = (value) => JSON.stringify(sorted(value));
|
|
75
|
-
const nameOf = (id, prefix) => id.slice(prefix.length);
|
|
76
|
-
const byName = ([a], [b]) => (a < b ? -1 : a > b ? 1 : 0);
|
|
77
|
-
const hasEntry = (being) => being !== undefined && isObject(being.cells.entry);
|
|
78
|
-
// An entry's optional fields where they hold something.
|
|
79
|
-
const some = (key, value) => (value === undefined || (Array.isArray(value) && value.length === 0) ? {} : { [key]: value });
|
|
80
|
-
/** A faculty's entry read from what the hand gave, or why it is no entry. */
|
|
81
|
-
export const facultyEntryOf = (value) => {
|
|
82
|
-
if (!isObject(value))
|
|
83
|
-
return 'an entry is an object';
|
|
84
|
-
const { make, from, args, secrets, faculties, kinds, ...rest } = value;
|
|
85
|
-
if (Object.keys(rest).length > 0)
|
|
86
|
-
return `an entry names ${Object.keys(rest).join(', ')}, which no entry holds`;
|
|
87
|
-
if (kinds !== undefined && !isNames(kinds))
|
|
88
|
-
return "an entry's kinds are a list of names";
|
|
89
|
-
if (typeof make !== 'string')
|
|
90
|
-
return "a faculty's entry names it in make";
|
|
91
|
-
if (from !== undefined && typeof from !== 'string')
|
|
92
|
-
return "an entry's from is a body's name";
|
|
93
|
-
if (args !== undefined && !isObject(args))
|
|
94
|
-
return "an entry's args are an object";
|
|
95
|
-
if (secrets !== undefined && !isNames(secrets))
|
|
96
|
-
return "an entry's secrets are a list of names";
|
|
97
|
-
if (faculties !== undefined && !isNames(faculties))
|
|
98
|
-
return "an entry's faculties are a list of names";
|
|
99
|
-
return { make, ...some('from', from), ...some('args', args), ...some('secrets', secrets), ...some('faculties', faculties), ...some('kinds', kinds) };
|
|
100
|
-
};
|
|
101
|
-
const namedOf = (held, what) => {
|
|
102
|
-
if (!isObject(held) || typeof held.faculty !== 'string')
|
|
103
|
-
return `an entry's ${what} names no faculty`;
|
|
104
|
-
return held;
|
|
105
|
-
};
|
|
106
|
-
/** A house's entry read from what the hand gave, or why it is no entry. */
|
|
107
|
-
export const houseEntryOf = (value) => {
|
|
108
|
-
if (!isObject(value))
|
|
109
|
-
return 'an entry is an object';
|
|
110
|
-
const { memory, classes, faculties, wait, ...rest } = value;
|
|
111
|
-
if (Object.keys(rest).length > 0)
|
|
112
|
-
return `an entry names ${Object.keys(rest).join(', ')}, which no entry holds`;
|
|
113
|
-
const code = namedOf(classes, 'classes');
|
|
114
|
-
if (typeof code === 'string')
|
|
115
|
-
return code;
|
|
116
|
-
const kept = memory === undefined ? undefined : namedOf(memory, 'memory');
|
|
117
|
-
if (typeof kept === 'string')
|
|
118
|
-
return kept;
|
|
119
|
-
if (faculties !== undefined && !isNames(faculties))
|
|
120
|
-
return "an entry's faculties are a list of names";
|
|
121
|
-
if (wait !== undefined && !(Number.isSafeInteger(wait) && wait > 0))
|
|
122
|
-
return "an entry's wait is whole milliseconds above zero";
|
|
123
|
-
return { classes: code, ...some('memory', kept), ...some('faculties', faculties), ...some('wait', wait) };
|
|
124
|
-
};
|
|
125
|
-
// A named body's args for its house: everything but the faculty.
|
|
126
|
-
const argsOf = ({ faculty: _faculty, ...args }) => args;
|
|
127
|
-
/** What a faculty's entry grants, by the being each grant relates it to, with the steward's notes on it. */
|
|
128
|
-
const facultyGrants = (entry) => {
|
|
129
|
-
const grants = new Map();
|
|
130
|
-
const add = (id, notes) => grants.set(id, { ...grants.get(id), ...notes });
|
|
131
|
-
if (entry.from !== undefined)
|
|
132
|
-
add(IDS.faculty + entry.from, { from: true });
|
|
133
|
-
(entry.faculties ?? []).forEach((name, at) => add(IDS.faculty + name, { faculties: at }));
|
|
134
|
-
(entry.secrets ?? []).forEach((name, at) => add(IDS.secret + name, { secrets: at }));
|
|
135
|
-
return grants;
|
|
136
|
-
};
|
|
137
|
-
/** What a house's entry grants, as a faculty's does. */
|
|
138
|
-
const houseGrants = (entry) => {
|
|
139
|
-
const grants = new Map();
|
|
140
|
-
const add = (id, notes) => grants.set(id, { ...grants.get(id), ...notes });
|
|
141
|
-
add(IDS.faculty + entry.classes.faculty, { classes: true });
|
|
142
|
-
if (entry.memory !== undefined)
|
|
143
|
-
add(IDS.faculty + entry.memory.faculty, { memory: true });
|
|
144
|
-
(entry.faculties ?? []).forEach((name, at) => add(IDS.faculty + name, { faculties: at }));
|
|
145
|
-
return grants;
|
|
146
|
-
};
|
|
147
|
-
// The names a list grant holds, in the order its notes give.
|
|
148
|
-
const listed = (standings, grant, prefix) => standings
|
|
149
|
-
.filter(({ id, steward }) => id.startsWith(prefix) && typeof steward[grant] === 'number')
|
|
150
|
-
.sort((a, b) => a.steward[grant] - b.steward[grant])
|
|
151
|
-
.map(({ id }) => nameOf(id, prefix));
|
|
152
|
-
const single = (standings, grant) => {
|
|
153
|
-
const found = standings.find(({ id, steward }) => id.startsWith(IDS.faculty) && steward[grant] === true);
|
|
154
|
-
return found === undefined ? undefined : nameOf(found.id, IDS.faculty);
|
|
155
|
-
};
|
|
156
|
-
/** A faculty's entry whole: what its twin's cells hold, and the names her standings give. */
|
|
157
|
-
const wholeFaculty = (cells, standings) => {
|
|
158
|
-
const held = cells.entry;
|
|
159
|
-
return {
|
|
160
|
-
make: held.make,
|
|
161
|
-
...some('from', single(standings, 'from')),
|
|
162
|
-
...some('args', held.args),
|
|
163
|
-
...some('secrets', listed(standings, 'secrets', IDS.secret)),
|
|
164
|
-
...some('faculties', listed(standings, 'faculties', IDS.faculty)),
|
|
165
|
-
...some('kinds', held.kinds),
|
|
166
|
-
};
|
|
167
|
-
};
|
|
168
|
-
/** A house's entry whole, as a twin's is. */
|
|
169
|
-
const wholeHouse = (cells, standings) => {
|
|
170
|
-
const held = cells.entry;
|
|
171
|
-
const code = single(standings, 'classes') ?? '';
|
|
172
|
-
const kept = single(standings, 'memory');
|
|
173
|
-
return {
|
|
174
|
-
classes: { faculty: code, ...held.classes },
|
|
175
|
-
...(held.memory === undefined || kept === undefined ? {} : { memory: { faculty: kept, ...held.memory } }),
|
|
176
|
-
...some('faculties', listed(standings, 'faculties', IDS.faculty)),
|
|
177
|
-
...some('wait', held.wait),
|
|
178
|
-
};
|
|
179
|
-
};
|
|
180
|
-
/** What a twin's cells keep of an entry: every value but the names, which are standings. */
|
|
181
|
-
const facultyCells = (entry) => ({ make: entry.make ?? '', ...some('args', entry.args), ...some('kinds', entry.kinds) });
|
|
182
|
-
/** What a house being's cells keep of an entry. */
|
|
183
|
-
const houseCells = (entry) => ({
|
|
184
|
-
classes: argsOf(entry.classes),
|
|
185
|
-
...(entry.memory === undefined ? {} : { memory: argsOf(entry.memory) }),
|
|
186
|
-
...some('wait', entry.wait),
|
|
187
|
-
});
|
|
188
|
-
/**
|
|
189
|
-
* The ladder's order: each body after the one whose registry it stands on
|
|
190
|
-
* and after each it calls. Each body in a cycle is named with it.
|
|
191
|
-
*/
|
|
192
|
-
const ladder = (entries) => {
|
|
193
|
-
const below = (entry) => [...(entry.from === undefined ? [] : [entry.from]), ...(entry.faculties ?? [])];
|
|
194
|
-
const order = [];
|
|
195
|
-
const cycles = new Map();
|
|
196
|
-
const seen = new Map();
|
|
197
|
-
const visit = (name, path) => {
|
|
198
|
-
if (seen.get(name) === 'done')
|
|
199
|
-
return;
|
|
200
|
-
if (seen.get(name) === 'visiting') {
|
|
201
|
-
const cycle = [...path.slice(path.indexOf(name)), name];
|
|
202
|
-
for (const one of cycle)
|
|
203
|
-
cycles.set(one, `a cycle: ${cycle.join(' → ')}`);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
seen.set(name, 'visiting');
|
|
207
|
-
for (const one of below(entries[name]))
|
|
208
|
-
if (Object.hasOwn(entries, one))
|
|
209
|
-
visit(one, [...path, name]);
|
|
210
|
-
seen.set(name, 'done');
|
|
211
|
-
order.push(name);
|
|
212
|
-
};
|
|
213
|
-
for (const name of Object.keys(entries).sort())
|
|
214
|
-
visit(name, []);
|
|
215
|
-
return { order, cycles };
|
|
216
|
-
};
|
|
217
|
-
const houseArgs = { name: s.string(), classes: OBJECT, memory: s.optional(OBJECT), faculties: s.optional(s.array(s.string())), wait: s.optional(s.integer({ minimum: 1 })) };
|
|
218
|
-
const facultyArgs = {
|
|
219
|
-
name: s.string(),
|
|
220
|
-
make: s.optional(s.string()),
|
|
221
|
-
from: s.optional(s.string()),
|
|
222
|
-
args: s.optional(OBJECT),
|
|
223
|
-
secrets: s.optional(s.array(s.string())),
|
|
224
|
-
faculties: s.optional(s.array(s.string())),
|
|
225
|
-
kinds: s.optional(s.array(s.string())),
|
|
226
|
-
};
|
|
227
|
-
const PLACES = OBJECT;
|
|
228
|
-
const WAIT_SHOWN = s.object({ wait: s.optional(s.integer({ minimum: 1 })), terrain: s.boolean() });
|
|
229
|
-
// Who changes the houses and the faculties: the hand, and a pilot the owner handed a standing.
|
|
230
|
-
const PILOTED = ['root', 'pilot'];
|
|
231
|
-
// A change is safe to repeat, and one that may install a faculty or open a house takes the longest any ask may.
|
|
232
|
-
const CHANGE = { idempotent: true };
|
|
233
|
-
const LIST = { readOnly: true, idempotent: true };
|
|
234
|
-
// The asks the steward alone makes of the beings she holds.
|
|
235
|
-
const HERS = { for: 'steward', hints: CHANGE, wait: WAIT_BOUND };
|
|
236
|
-
const SHOWN = { for: ['steward', 'root'], result: ANY, hints: LIST };
|
|
237
|
-
const HOUSES_SHOWN = s.array(s.object({ name: s.string(), entry: OBJECT, ward: s.optional(s.string()), why: s.optional(s.string()) }));
|
|
238
|
-
const FACULTIES_SHOWN = s.array(s.object({ name: s.string(), entry: OBJECT, terrain: s.optional(s.boolean()), serves: s.optional(s.string()), why: s.optional(s.string()) }));
|
|
239
|
-
const CATALOG = s.array(s.object({ from: s.optional(s.string()), faculties: s.array(s.object({ make: s.string(), takes: s.optional(OBJECT) })) }));
|
|
240
|
-
/**
|
|
241
|
-
* A ground's dock as a pilot holds it: the asks its steward answers for a
|
|
242
|
-
* standing the owner minted with `pilotsInvite`. A being far or near
|
|
243
|
-
* pilots a ground by `this.held(id, DockPilot)`.
|
|
244
|
-
*/
|
|
245
|
-
export const DockPilot = need('org.nervur.dock', {
|
|
246
|
-
housesAdd: { args: s.object(houseArgs), result: STOOD, hints: CHANGE, wait: WAIT_BOUND },
|
|
247
|
-
housesUpdate: { args: s.object(houseArgs), result: STOOD, hints: CHANGE, wait: WAIT_BOUND },
|
|
248
|
-
housesRemove: { args: NAME, hints: CHANGE, wait: WAIT_BOUND },
|
|
249
|
-
housesList: { result: HOUSES_SHOWN, hints: LIST, wait: WAIT_BOUND },
|
|
250
|
-
facultiesAdd: { args: s.object(facultyArgs), result: WHY, hints: CHANGE, wait: WAIT_BOUND },
|
|
251
|
-
facultiesUpdate: { args: s.object(facultyArgs), result: WHY, hints: CHANGE, wait: WAIT_BOUND },
|
|
252
|
-
facultiesRestart: { args: NAME, result: WHY, hints: CHANGE, wait: WAIT_BOUND },
|
|
253
|
-
facultiesRemove: { args: NAME, hints: CHANGE, wait: WAIT_BOUND },
|
|
254
|
-
facultiesList: { result: FACULTIES_SHOWN, hints: LIST, wait: WAIT_BOUND },
|
|
255
|
-
facultiesCatalog: { result: CATALOG, hints: LIST },
|
|
256
|
-
});
|
|
257
|
-
const RAISED = s.object({ installed: s.optional(s.string()), why: s.optional(s.string()), serves: s.optional(s.string()), port: s.optional(s.integer()) });
|
|
258
|
-
/** The dock's steward: every ask the hand makes of the ground. */
|
|
259
|
-
export class DockSteward extends Being.of({
|
|
260
|
-
kind: KINDS.steward,
|
|
261
|
-
description: 'The ground’s own steward: its houses, its faculties, its secrets, its moves and its shell.',
|
|
262
|
-
needs: { ground: GroundNeed },
|
|
263
|
-
cells: { wait: null },
|
|
264
|
-
roles: { pilot: (asker) => asker.notes.pilot === true },
|
|
265
|
-
asks: {
|
|
266
|
-
boot: {
|
|
267
|
-
for: 'root',
|
|
268
|
-
description: 'Stands the ladder from every entry, the terrain’s and the owner’s, and opens every house: each wake asks it once.',
|
|
269
|
-
hints: CHANGE,
|
|
270
|
-
wait: WAIT_BOUND,
|
|
271
|
-
},
|
|
272
|
-
housesAdd: {
|
|
273
|
-
for: PILOTED,
|
|
274
|
-
description: 'Opens a house on the bodies its entry names, and answers its ward, or why it stands closed; the same name and entry answer as it stands.',
|
|
275
|
-
args: s.object(houseArgs),
|
|
276
|
-
result: STOOD,
|
|
277
|
-
hints: CHANGE,
|
|
278
|
-
wait: WAIT_BOUND,
|
|
279
|
-
},
|
|
280
|
-
housesUpdate: {
|
|
281
|
-
for: PILOTED,
|
|
282
|
-
description: 'Lands a new entry over a house’s in one write, and opens the house again on it.',
|
|
283
|
-
args: s.object(houseArgs),
|
|
284
|
-
result: STOOD,
|
|
285
|
-
hints: CHANGE,
|
|
286
|
-
wait: WAIT_BOUND,
|
|
287
|
-
},
|
|
288
|
-
housesRemove: { for: PILOTED, description: 'Closes a house and drops its entry; its seed and its places stay.', args: NAME, hints: CHANGE, wait: WAIT_BOUND },
|
|
289
|
-
housesList: {
|
|
290
|
-
for: PILOTED,
|
|
291
|
-
description: 'Every house with its whole entry, open with its ward or closed with why.',
|
|
292
|
-
result: HOUSES_SHOWN,
|
|
293
|
-
hints: LIST,
|
|
294
|
-
wait: WAIT_BOUND,
|
|
295
|
-
},
|
|
296
|
-
facultiesAdd: {
|
|
297
|
-
for: PILOTED,
|
|
298
|
-
description: 'Raises a body from the registry its entry names, and answers why it is down where it is; the same name and entry answer as it stands.',
|
|
299
|
-
args: s.object(facultyArgs),
|
|
300
|
-
result: WHY,
|
|
301
|
-
hints: CHANGE,
|
|
302
|
-
wait: WAIT_BOUND,
|
|
303
|
-
},
|
|
304
|
-
facultiesUpdate: {
|
|
305
|
-
for: PILOTED,
|
|
306
|
-
description: 'Lands a new entry over a faculty’s in one write: its body goes down, installs where the entry moved, and goes up, and each house and body that uses it follows.',
|
|
307
|
-
args: s.object(facultyArgs),
|
|
308
|
-
result: WHY,
|
|
309
|
-
hints: CHANGE,
|
|
310
|
-
wait: WAIT_BOUND,
|
|
311
|
-
},
|
|
312
|
-
facultiesRestart: {
|
|
313
|
-
for: PILOTED,
|
|
314
|
-
description: 'Takes a body down and up again on its entry, and each house and body that uses it follows.',
|
|
315
|
-
args: NAME,
|
|
316
|
-
result: WHY,
|
|
317
|
-
hints: CHANGE,
|
|
318
|
-
wait: WAIT_BOUND,
|
|
319
|
-
},
|
|
320
|
-
facultiesRemove: { for: PILOTED, description: 'Takes down a body no house and no body uses, and drops its entry.', args: NAME, hints: CHANGE, wait: WAIT_BOUND },
|
|
321
|
-
facultiesList: {
|
|
322
|
-
for: PILOTED,
|
|
323
|
-
description: 'Every faculty entry whole, the terrain’s marked so and the owner’s, standing, or down with why. An entry names its secrets and holds none.',
|
|
324
|
-
result: FACULTIES_SHOWN,
|
|
325
|
-
hints: LIST,
|
|
326
|
-
wait: WAIT_BOUND,
|
|
327
|
-
},
|
|
328
|
-
facultiesCatalog: {
|
|
329
|
-
for: PILOTED,
|
|
330
|
-
description: 'Every registry of the ladder, the ground’s first and then each body’s that carries one, with each faculty it holds and what it takes: the schema its args meet and the secrets its entry names.',
|
|
331
|
-
result: CATALOG,
|
|
332
|
-
hints: LIST,
|
|
333
|
-
},
|
|
334
|
-
waitSet: {
|
|
335
|
-
for: PILOTED,
|
|
336
|
-
description: 'Sets the ground’s bound on every ask, in milliseconds, or drops it back to the terrain’s where none is given; every open house opens again on it.',
|
|
337
|
-
args: s.object({ wait: s.optional(s.integer({ minimum: 1 })) }),
|
|
338
|
-
result: WAIT_SHOWN,
|
|
339
|
-
hints: CHANGE,
|
|
340
|
-
wait: WAIT_BOUND,
|
|
341
|
-
},
|
|
342
|
-
waitShow: { for: PILOTED, description: 'The ground’s bound on every ask, and whether it is the terrain’s.', result: WAIT_SHOWN, hints: LIST },
|
|
343
|
-
secretsSet: {
|
|
344
|
-
for: 'root',
|
|
345
|
-
description: 'Keeps a secret, for the faculties whose entries name it. It answers nothing, so no answer holds it.',
|
|
346
|
-
args: s.object({ name: s.string(), value: s.string() }),
|
|
347
|
-
hints: CHANGE,
|
|
348
|
-
},
|
|
349
|
-
secretsRemove: { for: 'root', description: 'Drops a secret.', args: NAME, hints: CHANGE },
|
|
350
|
-
secretsList: {
|
|
351
|
-
for: 'root',
|
|
352
|
-
description: 'Every secret kept or named, whether it is kept, and the faculty entries that name it, and never a value.',
|
|
353
|
-
result: s.array(s.object({ name: s.string(), kept: s.boolean(), entries: s.array(s.string()) })),
|
|
354
|
-
hints: LIST,
|
|
355
|
-
wait: WAIT_BOUND,
|
|
356
|
-
},
|
|
357
|
-
movesOut: {
|
|
358
|
-
for: 'root',
|
|
359
|
-
description: 'Closes a house and drops its entry, and answers its seed and every place of its memory.',
|
|
360
|
-
args: NAME,
|
|
361
|
-
result: s.object({ seed: s.string(), places: PLACES }),
|
|
362
|
-
hints: { destructive: true },
|
|
363
|
-
wait: WAIT_BOUND,
|
|
364
|
-
},
|
|
365
|
-
movesIn: {
|
|
366
|
-
for: 'root',
|
|
367
|
-
description: 'Keeps a moved seed, writes its places into an empty memory, and opens the house on the bodies its entry names.',
|
|
368
|
-
args: s.object({ ...houseArgs, seed: s.string(), places: PLACES }),
|
|
369
|
-
result: STOOD,
|
|
370
|
-
wait: WAIT_BOUND,
|
|
371
|
-
},
|
|
372
|
-
callFaculty: {
|
|
373
|
-
for: 'root',
|
|
374
|
-
description: 'Calls a method of a body of the ladder by name, its args held to the method’s schema.',
|
|
375
|
-
args: s.object({ faculty: s.string(), method: s.string(), args: s.optional(OBJECT) }),
|
|
376
|
-
result: ANY,
|
|
377
|
-
wait: WAIT_BOUND,
|
|
378
|
-
},
|
|
379
|
-
shellRun: {
|
|
380
|
-
for: 'root',
|
|
381
|
-
description: 'Runs a command on the ground’s machine, with nothing of the ground in its environment, and answers its exit code and what it printed.',
|
|
382
|
-
args: s.object({ command: s.string(), args: s.optional(s.array(s.string())), cwd: s.optional(s.string()), stdin: s.optional(s.string()) }),
|
|
383
|
-
result: s.object({ code: s.integer(), stdout: s.string(), stderr: s.string() }),
|
|
384
|
-
wait: WAIT_BOUND,
|
|
385
|
-
},
|
|
386
|
-
pilotsInvite: {
|
|
387
|
-
for: 'root',
|
|
388
|
-
description: 'A new pilot: an occupant of hers that reaches the houses and faculties asks, as an invitation for the being that pilots.',
|
|
389
|
-
args: s.object({ id: s.string() }),
|
|
390
|
-
result: s.object({ invitation: s.handle() }),
|
|
391
|
-
},
|
|
392
|
-
pilotsDismiss: { for: 'root', description: 'Lets a pilot go.', args: s.object({ id: s.string() }) },
|
|
393
|
-
pilotsList: { for: 'root', description: 'Every pilot she holds.', result: s.array(s.string()), hints: LIST },
|
|
394
|
-
grants: { for: 'root', description: 'Every body the dock is offered: the twins she stands on.', result: s.array(s.string()), hints: LIST },
|
|
395
|
-
granted: { for: 'root', description: 'Bears the being that holds the shell’s offer, once the dock is offered it.', hints: CHANGE },
|
|
396
|
-
},
|
|
397
|
-
}) {
|
|
398
|
-
get #powers() {
|
|
399
|
-
return this.powers;
|
|
400
|
-
}
|
|
401
|
-
get #ground() {
|
|
402
|
-
return this.ground;
|
|
403
|
-
}
|
|
404
|
-
// Every being she holds, the twins and the house beings with their cells and standings.
|
|
405
|
-
async #beings() {
|
|
406
|
-
const faculties = new Map();
|
|
407
|
-
const houses = new Map();
|
|
408
|
-
const secrets = new Set();
|
|
409
|
-
for (const { id, kind, absent } of await this.#powers.list()) {
|
|
410
|
-
if (absent)
|
|
411
|
-
continue;
|
|
412
|
-
if (kind === KINDS.secret) {
|
|
413
|
-
// Read as her steward reads her, so her first ask has run.
|
|
414
|
-
await this.#powers.ask({ id });
|
|
415
|
-
secrets.add(nameOf(id, IDS.secret));
|
|
416
|
-
}
|
|
417
|
-
if (kind !== KINDS.faculty && kind !== KINDS.house)
|
|
418
|
-
continue;
|
|
419
|
-
const shown = (await this.#powers.ask({ id, method: 'shown' }));
|
|
420
|
-
const one = { id, kind, ...shown };
|
|
421
|
-
if (kind === KINDS.faculty)
|
|
422
|
-
faculties.set(nameOf(id, IDS.faculty), one);
|
|
423
|
-
else
|
|
424
|
-
houses.set(nameOf(id, IDS.house), one);
|
|
425
|
-
}
|
|
426
|
-
return { faculties, houses, secrets };
|
|
427
|
-
}
|
|
428
|
-
async #terrain() {
|
|
429
|
-
return (await this.#ground.terrain());
|
|
430
|
-
}
|
|
431
|
-
// A twin's entry whole: the owner's, or the terrain's where she holds none.
|
|
432
|
-
static #whole(twin, terrain, name) {
|
|
433
|
-
return hasEntry(twin) ? wholeFaculty(twin.cells, twin.standings) : terrain.entries[name];
|
|
434
|
-
}
|
|
435
|
-
// The bound every house opens on: hers, or the terrain's.
|
|
436
|
-
#bounded(terrain) {
|
|
437
|
-
const bound = this.cells.wait ?? terrain.wait;
|
|
438
|
-
return bound === undefined ? {} : { bound };
|
|
439
|
-
}
|
|
440
|
-
// Every twin's id and every secret's, which a grant may name.
|
|
441
|
-
static #relatable(held) {
|
|
442
|
-
return new Set([...[...held.faculties.keys()].map((one) => IDS.faculty + one), ...[...held.secrets].map((one) => IDS.secret + one)]);
|
|
443
|
-
}
|
|
444
|
-
// The name an entry takes, or why it takes none.
|
|
445
|
-
#named(name, what) {
|
|
446
|
-
if (!NAME_RULE.test(name))
|
|
447
|
-
this.fail(`a ${what} is named with ${NAMED}`);
|
|
448
|
-
if (name === DOCK)
|
|
449
|
-
this.fail(DOCKED);
|
|
450
|
-
}
|
|
451
|
-
// A faculty's entry held to what the dock refuses before anything lands.
|
|
452
|
-
#facultyEntry(name, given, terrain, held) {
|
|
453
|
-
this.#named(name, 'faculty');
|
|
454
|
-
if (Object.hasOwn(OWN, name))
|
|
455
|
-
this.fail(OWN[name]);
|
|
456
|
-
const entry = facultyEntryOf(given);
|
|
457
|
-
if (typeof entry === 'string')
|
|
458
|
-
this.fail(entry);
|
|
459
|
-
if (Object.hasOwn(terrain.primordial, name))
|
|
460
|
-
this.fail(`the ${name} is primordial: its entry is the host’s, and the drawer names none`);
|
|
461
|
-
if (entry.from === undefined && entry.make !== undefined && Object.hasOwn(OWN, entry.make))
|
|
462
|
-
this.fail(OWN[entry.make]);
|
|
463
|
-
const role = Object.entries(terrain.primordial).find(([, make]) => entry.from === undefined && make === entry.make)?.[0];
|
|
464
|
-
if (role !== undefined)
|
|
465
|
-
this.fail(`the faculty ${entry.make} is the ground’s ${role}, which is primordial, and the drawer names none`);
|
|
466
|
-
for (const callee of [...(entry.from === undefined ? [] : [entry.from]), ...(entry.faculties ?? [])]) {
|
|
467
|
-
if (Object.hasOwn(OWN, callee))
|
|
468
|
-
this.fail(OWN[callee]);
|
|
469
|
-
if (!held.faculties.has(callee))
|
|
470
|
-
this.fail(`the faculty ${name} is refused: no faculty ${callee} is here`);
|
|
471
|
-
}
|
|
472
|
-
return entry;
|
|
473
|
-
}
|
|
474
|
-
// What the ground's faculty says the entry fails of what its faculty takes, then each secret it names kept.
|
|
475
|
-
async #takes(name, entry, secrets) {
|
|
476
|
-
const { why } = (await this.#ground.check({ name, entry }));
|
|
477
|
-
const unkept = (entry.secrets ?? []).find((secret) => !secrets.has(secret));
|
|
478
|
-
const refused = why ?? (unkept === undefined ? undefined : `no secret ${unkept} is kept`);
|
|
479
|
-
if (refused !== undefined)
|
|
480
|
-
this.fail(`the faculty ${name} is refused: ${refused}`);
|
|
481
|
-
}
|
|
482
|
-
// A house's entry held to what the dock refuses before anything lands.
|
|
483
|
-
#houseEntry(name, given, held) {
|
|
484
|
-
this.#named(name, 'house');
|
|
485
|
-
const entry = houseEntryOf(given);
|
|
486
|
-
if (typeof entry === 'string')
|
|
487
|
-
this.fail(entry);
|
|
488
|
-
for (const faculty of [entry.classes.faculty, ...(entry.memory === undefined ? [] : [entry.memory.faculty]), ...(entry.faculties ?? [])]) {
|
|
489
|
-
if (Object.hasOwn(OWN, faculty))
|
|
490
|
-
this.fail(OWN[faculty]);
|
|
491
|
-
if (!held.faculties.has(faculty))
|
|
492
|
-
this.fail(`the house ${name} is refused: no faculty ${faculty} is here`);
|
|
493
|
-
}
|
|
494
|
-
return entry;
|
|
495
|
-
}
|
|
496
|
-
// Each grant introduced: the being that names another stands on it, the steward's notes naming the grant.
|
|
497
|
-
#introduce(from, grants, held = []) {
|
|
498
|
-
for (const [to, notes] of grants) {
|
|
499
|
-
// A grant she holds already with the same notes is written again nowhere.
|
|
500
|
-
if (held.some(({ id, steward }) => id === to && canonical(steward) === canonical(notes)))
|
|
501
|
-
continue;
|
|
502
|
-
this.#powers.introduce({ from, to, notes });
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
// Each being the new entry leaves out lets go of the being whose entry named it.
|
|
506
|
-
async #release(from, before, after, held) {
|
|
507
|
-
for (const to of before.keys())
|
|
508
|
-
if (!after.has(to) && held.has(to))
|
|
509
|
-
await this.#powers.ask({ id: to, method: 'release', args: { id: from } });
|
|
510
|
-
}
|
|
511
|
-
// Who uses a body: the houses that name it or receive it, and the bodies that stand on it or call it.
|
|
512
|
-
static #users(name, held) {
|
|
513
|
-
const serves = held.faculties.get(name)?.cells.serves;
|
|
514
|
-
const every = serves === 'carry' || serves === 'clock';
|
|
515
|
-
const naming = (one) => one.standings.some(({ id }) => id === IDS.faculty + name);
|
|
516
|
-
const houses = [...held.houses].filter(([, house]) => hasEntry(house) && (every || naming(house))).map(([house]) => house);
|
|
517
|
-
const faculties = [...held.faculties].filter(([, twin]) => naming(twin)).map(([faculty]) => faculty);
|
|
518
|
-
return { houses, faculties };
|
|
519
|
-
}
|
|
520
|
-
// A body and every body above it, the callers after their callees, and every house any of them reaches.
|
|
521
|
-
static #above(name, held) {
|
|
522
|
-
const faculties = [];
|
|
523
|
-
const houses = new Set();
|
|
524
|
-
const visit = (one) => {
|
|
525
|
-
if (faculties.includes(one))
|
|
526
|
-
return;
|
|
527
|
-
faculties.push(one);
|
|
528
|
-
const users = _a.#users(one, held);
|
|
529
|
-
for (const house of users.houses)
|
|
530
|
-
houses.add(house);
|
|
531
|
-
for (const faculty of users.faculties)
|
|
532
|
-
visit(faculty);
|
|
533
|
-
};
|
|
534
|
-
visit(name);
|
|
535
|
-
return { faculties, houses: [...houses].sort() };
|
|
536
|
-
}
|
|
537
|
-
// After the ladder moves: each body down stands again where it can, and each house closed opens.
|
|
538
|
-
async #mend() {
|
|
539
|
-
const terrain = await this.#terrain();
|
|
540
|
-
const held = await this.#beings();
|
|
541
|
-
const entries = {};
|
|
542
|
-
for (const [name, twin] of held.faculties) {
|
|
543
|
-
const whole = _a.#whole(twin, terrain, name);
|
|
544
|
-
if (whole !== undefined)
|
|
545
|
-
entries[name] = whole;
|
|
546
|
-
}
|
|
547
|
-
const { order, cycles } = ladder(entries);
|
|
548
|
-
for (const name of order) {
|
|
549
|
-
// The shell's body is the dock's offer, raised at the boot alone.
|
|
550
|
-
if (cycles.has(name) || Object.hasOwn(OWN, name) || typeof held.faculties.get(name)?.cells.why !== 'string')
|
|
551
|
-
continue;
|
|
552
|
-
await this.#powers.ask({ id: IDS.faculty + name, method: 'up' });
|
|
553
|
-
}
|
|
554
|
-
const bounded = this.#bounded(terrain);
|
|
555
|
-
for (const [, house] of [...held.houses].sort(byName)) {
|
|
556
|
-
if (!hasEntry(house) || typeof house.cells.why !== 'string')
|
|
557
|
-
continue;
|
|
558
|
-
await this.#powers.ask({ id: house.id, method: 'open', args: bounded });
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
// A body taken down with everything above it, `between` run while all are down, then the ladder mended.
|
|
562
|
-
async #cycle(name, between) {
|
|
563
|
-
const held = await this.#beings();
|
|
564
|
-
const { faculties, houses } = _a.#above(name, held);
|
|
565
|
-
const why = `the faculty ${name} is going down`;
|
|
566
|
-
for (const house of houses)
|
|
567
|
-
await this.#powers.ask({ id: IDS.house + house, method: 'close', args: { why } });
|
|
568
|
-
for (const faculty of [...faculties].reverse())
|
|
569
|
-
if (faculty !== name)
|
|
570
|
-
await this.#powers.ask({ id: IDS.faculty + faculty, method: 'down', args: { why } });
|
|
571
|
-
const stood = await between();
|
|
572
|
-
await this.#mend();
|
|
573
|
-
return stood;
|
|
574
|
-
}
|
|
575
|
-
async boot() {
|
|
576
|
-
const terrain = await this.#terrain();
|
|
577
|
-
const held = await this.#beings();
|
|
578
|
-
// A terrain's twin whose default the terrain does not name goes, where the owner named no entry in its place.
|
|
579
|
-
for (const [name, twin] of held.faculties) {
|
|
580
|
-
if (twin.cells.terrain === true && !hasEntry(twin) && terrain.entries[name] === undefined) {
|
|
581
|
-
this.#powers.remove({ id: twin.id });
|
|
582
|
-
held.faculties.delete(name);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
const entries = {};
|
|
586
|
-
for (const [name, twin] of held.faculties) {
|
|
587
|
-
const whole = _a.#whole(twin, terrain, name);
|
|
588
|
-
if (whole !== undefined)
|
|
589
|
-
entries[name] = whole;
|
|
590
|
-
}
|
|
591
|
-
for (const [name, entry] of Object.entries(terrain.entries))
|
|
592
|
-
entries[name] ??= entry;
|
|
593
|
-
const { order, cycles } = ladder(entries);
|
|
594
|
-
for (const name of order) {
|
|
595
|
-
const cycle = cycles.get(name);
|
|
596
|
-
const id = IDS.faculty + name;
|
|
597
|
-
const twin = held.faculties.get(name);
|
|
598
|
-
if (twin !== undefined) {
|
|
599
|
-
// Her body stands by a read, and her cells are written only where what came of it moved.
|
|
600
|
-
const raised = cycle === undefined ? (await this.#powers.ask({ id, method: 'stand' })) : { why: cycle };
|
|
601
|
-
if (_a.#moved(twin.cells, raised))
|
|
602
|
-
await this.#powers.ask({ id, method: 'record', args: raised });
|
|
603
|
-
// A terrain's entry grants what the terrain's code gives now.
|
|
604
|
-
if (!hasEntry(twin))
|
|
605
|
-
this.#introduce(id, facultyGrants(entries[name]), twin.standings);
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
// A default of the terrain's that no twin holds yet stands now, and its twin is borne.
|
|
609
|
-
const raised = cycle === undefined ? (await this.#ground.raise({ name, entry: entries[name] })) : { why: cycle };
|
|
610
|
-
this.#powers.bear({ kind: KINDS.faculty, id, args: { terrain: true, ...raised } });
|
|
611
|
-
this.#introduce(id, facultyGrants(entries[name]));
|
|
612
|
-
}
|
|
613
|
-
// The dock is offered each body its steward stands on: the terrain's shell, where it has one.
|
|
614
|
-
if (terrain.entries.shell !== undefined && !this.standings.list().some(({ id }) => id === `${IDS.faculty}shell`))
|
|
615
|
-
this.#powers.introduce({ from: 'steward', to: `${IDS.faculty}shell`, notes: { faculties: 0 } });
|
|
616
|
-
const bounded = this.#bounded(terrain);
|
|
617
|
-
for (const [name, house] of [...held.houses].sort(byName)) {
|
|
618
|
-
if (!hasEntry(house))
|
|
619
|
-
continue;
|
|
620
|
-
if (terrain.lazy && typeof house.cells.ward === 'string') {
|
|
621
|
-
await this.#ground.sleep({ name, entry: wholeHouse(house.cells, house.standings), ward: house.cells.ward });
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
const stood = (await this.#powers.ask({ id: house.id, method: 'opening', args: bounded }));
|
|
625
|
-
if ((stood.ward ?? house.cells.ward) !== house.cells.ward || (stood.why ?? null) !== (house.cells.why ?? null))
|
|
626
|
-
await this.#powers.ask({ id: house.id, method: 'record', args: stood });
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
// Whether what a raise gave moves any cell a twin holds of it.
|
|
630
|
-
static #moved(cells, raised) {
|
|
631
|
-
return ((raised.installed ?? cells.installed ?? null) !== (cells.installed ?? null) ||
|
|
632
|
-
(raised.why ?? null) !== (cells.why ?? null) ||
|
|
633
|
-
(raised.serves ?? null) !== (cells.serves ?? null) ||
|
|
634
|
-
(raised.port ?? null) !== (cells.port ?? null));
|
|
635
|
-
}
|
|
636
|
-
async grants() {
|
|
637
|
-
return this.standings
|
|
638
|
-
.list()
|
|
639
|
-
.filter(({ id }) => id.startsWith(IDS.faculty))
|
|
640
|
-
.map(({ id }) => nameOf(id, IDS.faculty));
|
|
641
|
-
}
|
|
642
|
-
// The being that holds the shell's offer, borne once the dock is offered it.
|
|
643
|
-
async granted() {
|
|
644
|
-
if ((await this.#powers.list()).some(({ id }) => id === SHELL_ID))
|
|
645
|
-
return;
|
|
646
|
-
this.#powers.bear({ kind: KINDS.shell, id: SHELL_ID });
|
|
647
|
-
}
|
|
648
|
-
// A house landed on its entry: a being that stays from before takes it, and a new one is borne on a fresh seed or a moved one.
|
|
649
|
-
async #land(name, entry, house, bounded, seed) {
|
|
650
|
-
const id = IDS.house + name;
|
|
651
|
-
if (house !== undefined) {
|
|
652
|
-
const stood = (await this.#powers.ask({ id, method: 'update', args: { entry, ...bounded } }));
|
|
653
|
-
this.#introduce(id, houseGrants(entry));
|
|
654
|
-
return stood;
|
|
655
|
-
}
|
|
656
|
-
const fresh = seed ?? Array.from(this.house.random({ length: 32 }), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
657
|
-
const stood = (await this.#ground.open({ name, entry, seed: fresh, ...bounded }));
|
|
658
|
-
this.#powers.bear({ kind: KINDS.house, id, args: { entry: houseCells(entry), seed: fresh, ...stood } });
|
|
659
|
-
this.#introduce(id, houseGrants(entry));
|
|
660
|
-
return stood;
|
|
661
|
-
}
|
|
662
|
-
async housesAdd(given) {
|
|
663
|
-
const { name, ...rest } = given;
|
|
664
|
-
const held = await this.#beings();
|
|
665
|
-
const entry = this.#houseEntry(name, rest, held);
|
|
666
|
-
const house = held.houses.get(name);
|
|
667
|
-
const bounded = this.#bounded(await this.#terrain());
|
|
668
|
-
if (hasEntry(house)) {
|
|
669
|
-
if (canonical(wholeHouse(house.cells, house.standings)) !== canonical(entry))
|
|
670
|
-
this.fail(`the house ${name} stands with another entry; update it`);
|
|
671
|
-
// A house that did not open is tried again, as its entry stands.
|
|
672
|
-
if (typeof house.cells.why === 'string')
|
|
673
|
-
return (await this.#powers.ask({ id: house.id, method: 'open', args: bounded }));
|
|
674
|
-
return { ward: house.cells.ward };
|
|
675
|
-
}
|
|
676
|
-
return this.#land(name, entry, house, bounded);
|
|
677
|
-
}
|
|
678
|
-
async housesUpdate(given) {
|
|
679
|
-
const { name, ...rest } = given;
|
|
680
|
-
const held = await this.#beings();
|
|
681
|
-
const house = held.houses.get(name);
|
|
682
|
-
if (!hasEntry(house))
|
|
683
|
-
return this.fail(`no house ${name} is here to update`);
|
|
684
|
-
const entry = this.#houseEntry(name, rest, held);
|
|
685
|
-
const bounded = this.#bounded(await this.#terrain());
|
|
686
|
-
const before = wholeHouse(house.cells, house.standings);
|
|
687
|
-
if (canonical(before) === canonical(entry)) {
|
|
688
|
-
if (typeof house.cells.why === 'string')
|
|
689
|
-
return (await this.#powers.ask({ id: house.id, method: 'open', args: bounded }));
|
|
690
|
-
return { ward: house.cells.ward };
|
|
691
|
-
}
|
|
692
|
-
const stood = await this.#land(name, entry, house, bounded);
|
|
693
|
-
await this.#release(house.id, houseGrants(before), houseGrants(entry), _a.#relatable(held));
|
|
694
|
-
return stood;
|
|
695
|
-
}
|
|
696
|
-
async housesRemove({ name }) {
|
|
697
|
-
const held = await this.#beings();
|
|
698
|
-
const house = held.houses.get(name);
|
|
699
|
-
if (!hasEntry(house))
|
|
700
|
-
return;
|
|
701
|
-
await this.#powers.ask({ id: house.id, method: 'remove' });
|
|
702
|
-
await this.#release(house.id, houseGrants(wholeHouse(house.cells, house.standings)), new Map(), _a.#relatable(held));
|
|
703
|
-
}
|
|
704
|
-
async housesList() {
|
|
705
|
-
const held = await this.#beings();
|
|
706
|
-
return [...held.houses]
|
|
707
|
-
.filter(([, house]) => hasEntry(house))
|
|
708
|
-
.sort(byName)
|
|
709
|
-
.map(([name, house]) => ({
|
|
710
|
-
name,
|
|
711
|
-
entry: wholeHouse(house.cells, house.standings),
|
|
712
|
-
...(typeof house.cells.ward === 'string' && typeof house.cells.why !== 'string' ? { ward: house.cells.ward } : {}),
|
|
713
|
-
...(typeof house.cells.why === 'string' ? { why: house.cells.why } : {}),
|
|
714
|
-
}));
|
|
715
|
-
}
|
|
716
|
-
async facultiesAdd(given) {
|
|
717
|
-
const { name, ...rest } = given;
|
|
718
|
-
const terrain = await this.#terrain();
|
|
719
|
-
const held = await this.#beings();
|
|
720
|
-
const entry = this.#facultyEntry(name, rest, terrain, held);
|
|
721
|
-
const twin = held.faculties.get(name);
|
|
722
|
-
if (twin !== undefined) {
|
|
723
|
-
const whole = _a.#whole(twin, terrain, name);
|
|
724
|
-
if (whole === undefined || canonical(whole) !== canonical(entry))
|
|
725
|
-
this.fail(`the faculty ${name} stands with another entry; update it`);
|
|
726
|
-
return typeof twin.cells.why === 'string' ? { why: twin.cells.why } : {};
|
|
727
|
-
}
|
|
728
|
-
await this.#takes(name, entry, held.secrets);
|
|
729
|
-
const raised = (await this.#ground.raise({ name, entry }));
|
|
730
|
-
const id = IDS.faculty + name;
|
|
731
|
-
this.#powers.bear({ kind: KINDS.faculty, id, args: { terrain: false, entry: facultyCells(entry), ...raised } });
|
|
732
|
-
this.#introduce(id, facultyGrants(entry));
|
|
733
|
-
await this.#mend();
|
|
734
|
-
return raised.why === undefined ? {} : { why: raised.why };
|
|
735
|
-
}
|
|
736
|
-
async facultiesUpdate(given) {
|
|
737
|
-
const { name, ...rest } = given;
|
|
738
|
-
const terrain = await this.#terrain();
|
|
739
|
-
const held = await this.#beings();
|
|
740
|
-
const twin = held.faculties.get(name);
|
|
741
|
-
if (twin === undefined && !Object.hasOwn(OWN, name) && !Object.hasOwn(terrain.primordial, name))
|
|
742
|
-
return this.fail(`no faculty ${name} stands here to update`);
|
|
743
|
-
const entry = this.#facultyEntry(name, rest, terrain, held);
|
|
744
|
-
const before = _a.#whole(twin, terrain, name);
|
|
745
|
-
if (before !== undefined && canonical(before) === canonical(entry))
|
|
746
|
-
return typeof twin.cells.why === 'string' ? { why: twin.cells.why } : {};
|
|
747
|
-
await this.#takes(name, entry, held.secrets);
|
|
748
|
-
const id = IDS.faculty + name;
|
|
749
|
-
const stood = await this.#cycle(name, async () => (await this.#powers.ask({ id, method: 'update', args: { entry } })));
|
|
750
|
-
this.#introduce(id, facultyGrants(entry));
|
|
751
|
-
if (before !== undefined)
|
|
752
|
-
await this.#release(id, facultyGrants(before), facultyGrants(entry), _a.#relatable(held));
|
|
753
|
-
return stood.why === undefined ? {} : { why: stood.why };
|
|
754
|
-
}
|
|
755
|
-
async facultiesRestart({ name }) {
|
|
756
|
-
if (Object.hasOwn(OWN, name))
|
|
757
|
-
return this.fail(OWN[name]);
|
|
758
|
-
const held = await this.#beings();
|
|
759
|
-
if (!held.faculties.has(name))
|
|
760
|
-
return this.fail(`no faculty ${name} stands here to restart`);
|
|
761
|
-
const stood = await this.#cycle(name, async () => (await this.#powers.ask({ id: IDS.faculty + name, method: 'restart' })));
|
|
762
|
-
return stood.why === undefined ? {} : { why: stood.why };
|
|
763
|
-
}
|
|
764
|
-
async facultiesRemove({ name }) {
|
|
765
|
-
if (Object.hasOwn(OWN, name))
|
|
766
|
-
return this.fail(OWN[name]);
|
|
767
|
-
const terrain = await this.#terrain();
|
|
768
|
-
const held = await this.#beings();
|
|
769
|
-
const twin = held.faculties.get(name);
|
|
770
|
-
if (twin === undefined)
|
|
771
|
-
return;
|
|
772
|
-
if (!hasEntry(twin))
|
|
773
|
-
return this.fail(`the faculty ${name} is the terrain’s; update it`);
|
|
774
|
-
const { houses, faculties } = _a.#users(name, held);
|
|
775
|
-
const users = [...houses.map((house) => `the house ${house}`), ...faculties.map((faculty) => `the faculty ${faculty}`)];
|
|
776
|
-
if (users.length > 0)
|
|
777
|
-
return this.fail(`the faculty ${name} is in use by ${users.join(', ')}`);
|
|
778
|
-
await this.#powers.ask({ id: twin.id, method: 'remove' });
|
|
779
|
-
await this.#release(twin.id, facultyGrants(wholeFaculty(twin.cells, twin.standings)), new Map(), _a.#relatable(held));
|
|
780
|
-
// A terrain's entry under the same name stands again in its place.
|
|
781
|
-
if (terrain.entries[name] === undefined)
|
|
782
|
-
this.#powers.remove({ id: twin.id });
|
|
783
|
-
else {
|
|
784
|
-
this.#introduce(twin.id, facultyGrants(terrain.entries[name]));
|
|
785
|
-
await this.#powers.ask({ id: twin.id, method: 'up' });
|
|
786
|
-
await this.#mend();
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
async facultiesList() {
|
|
790
|
-
const terrain = await this.#terrain();
|
|
791
|
-
const held = await this.#beings();
|
|
792
|
-
return [...held.faculties].sort(byName).flatMap(([name, twin]) => {
|
|
793
|
-
const entry = _a.#whole(twin, terrain, name);
|
|
794
|
-
if (entry === undefined)
|
|
795
|
-
return [];
|
|
796
|
-
return [
|
|
797
|
-
{
|
|
798
|
-
name,
|
|
799
|
-
entry: entry,
|
|
800
|
-
...(hasEntry(twin) ? {} : { terrain: true }),
|
|
801
|
-
...(typeof twin.cells.serves === 'string' ? { serves: twin.cells.serves } : {}),
|
|
802
|
-
...(typeof twin.cells.why === 'string' ? { why: twin.cells.why } : {}),
|
|
803
|
-
},
|
|
804
|
-
];
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
async facultiesCatalog() {
|
|
808
|
-
return (await this.#ground.catalog());
|
|
809
|
-
}
|
|
810
|
-
async waitSet({ wait }) {
|
|
811
|
-
this.cells.wait = wait ?? null;
|
|
812
|
-
const terrain = await this.#terrain();
|
|
813
|
-
const bound = wait ?? terrain.wait;
|
|
814
|
-
const held = await this.#beings();
|
|
815
|
-
// Every open house opens again on the bound as it stands now, once its calls in flight end.
|
|
816
|
-
for (const [, house] of [...held.houses].sort(byName)) {
|
|
817
|
-
if (!hasEntry(house) || typeof house.cells.why === 'string')
|
|
818
|
-
continue;
|
|
819
|
-
await this.#powers.ask({ id: house.id, method: 'open', args: bound === undefined ? {} : { bound } });
|
|
820
|
-
}
|
|
821
|
-
return { ...(bound === undefined ? {} : { wait: bound }), terrain: wait === undefined };
|
|
822
|
-
}
|
|
823
|
-
async waitShow() {
|
|
824
|
-
const bound = this.#bounded(await this.#terrain()).bound;
|
|
825
|
-
return { ...(bound === undefined ? {} : { wait: bound }), terrain: this.cells.wait === null };
|
|
826
|
-
}
|
|
827
|
-
async secretsSet({ name, value }) {
|
|
828
|
-
if (!NAME_RULE.test(name))
|
|
829
|
-
this.fail(`a secret is named with ${NAMED}`);
|
|
830
|
-
const held = await this.#beings();
|
|
831
|
-
if (held.secrets.has(name))
|
|
832
|
-
await this.#powers.ask({ id: IDS.secret + name, method: 'set', args: { value } });
|
|
833
|
-
else
|
|
834
|
-
this.#powers.bear({ kind: KINDS.secret, id: IDS.secret + name, args: { value } });
|
|
835
|
-
}
|
|
836
|
-
async secretsRemove({ name }) {
|
|
837
|
-
const held = await this.#beings();
|
|
838
|
-
if (held.secrets.has(name))
|
|
839
|
-
this.#powers.remove({ id: IDS.secret + name });
|
|
840
|
-
}
|
|
841
|
-
async secretsList() {
|
|
842
|
-
const terrain = await this.#terrain();
|
|
843
|
-
const held = await this.#beings();
|
|
844
|
-
const naming = Object.fromEntries([...held.secrets].map((name) => [name, []]));
|
|
845
|
-
for (const [faculty, twin] of held.faculties)
|
|
846
|
-
for (const secret of _a.#whole(twin, terrain, faculty)?.secrets ?? [])
|
|
847
|
-
(naming[secret] ??= []).push(faculty);
|
|
848
|
-
return Object.entries(naming)
|
|
849
|
-
.sort(byName)
|
|
850
|
-
.map(([name, entries]) => ({ name, kept: held.secrets.has(name), entries: entries.sort() }));
|
|
851
|
-
}
|
|
852
|
-
async movesOut({ name }) {
|
|
853
|
-
const held = await this.#beings();
|
|
854
|
-
const house = held.houses.get(name);
|
|
855
|
-
if (!hasEntry(house))
|
|
856
|
-
return this.fail(`no house ${name} is here`);
|
|
857
|
-
const entry = wholeHouse(house.cells, house.standings);
|
|
858
|
-
// Closed and its entry dropped first, so nothing lands after the copy and no ground runs it twice.
|
|
859
|
-
await this.#powers.ask({ id: house.id, method: 'remove' });
|
|
860
|
-
await this.#release(house.id, houseGrants(entry), new Map(), _a.#relatable(held));
|
|
861
|
-
const { places } = (await this.#ground.placesOut({ name, entry }));
|
|
862
|
-
return { seed: house.cells.seed, places };
|
|
863
|
-
}
|
|
864
|
-
async movesIn(given) {
|
|
865
|
-
const { name, seed, places, ...rest } = given;
|
|
866
|
-
if (!/^[0-9a-f]{64}$/.test(seed))
|
|
867
|
-
return this.fail('a seed is sixty-four lowercase hex digits');
|
|
868
|
-
const held = await this.#beings();
|
|
869
|
-
const house = held.houses.get(name);
|
|
870
|
-
if (hasEntry(house))
|
|
871
|
-
return this.fail(`the house ${name} stands here already`);
|
|
872
|
-
const entry = this.#houseEntry(name, rest, held);
|
|
873
|
-
// A house moves into a memory of its own, never over another's places, and onto no other seed.
|
|
874
|
-
await this.#ground.placesIn({ name, entry, places: {} });
|
|
875
|
-
if (house !== undefined && house.cells.seed !== seed)
|
|
876
|
-
return this.fail(`the drawer keeps another seed for ${name}`);
|
|
877
|
-
await this.#ground.placesIn({ name, entry, places });
|
|
878
|
-
return this.#land(name, entry, house, this.#bounded(await this.#terrain()), seed);
|
|
879
|
-
}
|
|
880
|
-
async callFaculty({ faculty, method, args }) {
|
|
881
|
-
return (await this.#ground.call({ faculty, method, ...(args === undefined ? {} : { args }) }));
|
|
882
|
-
}
|
|
883
|
-
// The shell is reached through the being that holds its offer, where the dock is offered one.
|
|
884
|
-
async shellRun(args) {
|
|
885
|
-
const held = (await this.#powers.list()).find(({ id }) => id === SHELL_ID);
|
|
886
|
-
if (held === undefined || held.absent)
|
|
887
|
-
return this.fail('no shell stands on this ground');
|
|
888
|
-
return (await this.#powers.ask({ id: SHELL_ID, method: 'run', args }));
|
|
889
|
-
}
|
|
890
|
-
pilotsInvite({ id }) {
|
|
891
|
-
return { invitation: this.invite(id, { notes: { pilot: true } }) };
|
|
892
|
-
}
|
|
893
|
-
pilotsDismiss({ id }) {
|
|
894
|
-
this.occupants.dismiss(id);
|
|
895
|
-
}
|
|
896
|
-
pilotsList() {
|
|
897
|
-
return this.occupants
|
|
898
|
-
.list()
|
|
899
|
-
.filter(({ notes }) => notes.pilot === true)
|
|
900
|
-
.map(({ id }) => id);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
_a = DockSteward;
|
|
904
|
-
// What a dock being shows her steward: her cells, and the standings the steward introduced, never her steward.
|
|
905
|
-
const shownOf = (being) => ({
|
|
906
|
-
cells: being.cells,
|
|
907
|
-
standings: being.standings
|
|
908
|
-
.list()
|
|
909
|
-
.filter(({ id }) => id !== 'steward')
|
|
910
|
-
.map(({ id, steward }) => ({ id, steward })),
|
|
911
|
-
});
|
|
912
|
-
/**
|
|
913
|
-
* A faculty's twin: her cells hold the owner's entry, or mark the
|
|
914
|
-
* terrain's, what her body installed, and whether it stands or is down
|
|
915
|
-
* with why. Going up and down is her state machine, and each change the
|
|
916
|
-
* steward asks of her lands in her own write.
|
|
917
|
-
*/
|
|
918
|
-
export class DockFaculty extends Being.of({
|
|
919
|
-
kind: KINDS.faculty,
|
|
920
|
-
description: 'The twin of one body of the ladder: its entry, what it installed, and whether it stands.',
|
|
921
|
-
needs: { ground: GroundNeed },
|
|
922
|
-
cells: { terrain: false, entry: null, installed: null, why: null, serves: null, port: null },
|
|
923
|
-
asks: {
|
|
924
|
-
born: { for: 'steward', args: s.object({ terrain: s.boolean(), entry: s.optional(OBJECT), installed: s.optional(s.string()), why: s.optional(s.string()), serves: s.optional(s.string()), port: s.optional(s.integer()) }) },
|
|
925
|
-
shown: SHOWN,
|
|
926
|
-
up: { ...HERS, result: WHY, description: 'Raises her body on her entry, installing where it moved.' },
|
|
927
|
-
stand: { for: 'steward', result: RAISED, hints: LIST, wait: WAIT_BOUND, description: 'Raises her body on her entry and answers what came of it, writing nothing: a boot writes only what moved.' },
|
|
928
|
-
record: { ...HERS, args: RAISED, description: 'Keeps what came of raising her body.' },
|
|
929
|
-
down: { ...HERS, args: s.object({ why: s.string() }), description: 'Takes her body down, and says why.' },
|
|
930
|
-
update: { ...HERS, args: s.object({ entry: OBJECT }), result: WHY, description: 'Lands a new entry over hers, and raises her body on it.' },
|
|
931
|
-
restart: { ...HERS, result: WHY, description: 'Takes her body down and up again.' },
|
|
932
|
-
remove: { ...HERS, description: 'Takes her body down and drops the owner’s entry.' },
|
|
933
|
-
release: { ...HERS, args: s.object({ id: s.string() }), description: 'Lets go of a being whose entry leaves her out.' },
|
|
934
|
-
},
|
|
935
|
-
}) {
|
|
936
|
-
get #name() {
|
|
937
|
-
return nameOf(this.id, IDS.faculty);
|
|
938
|
-
}
|
|
939
|
-
get #ground() {
|
|
940
|
-
return this.ground;
|
|
941
|
-
}
|
|
942
|
-
born({ terrain, entry, ...raised }) {
|
|
943
|
-
this.cells.terrain = terrain;
|
|
944
|
-
this.cells.entry = entry === undefined ? null : entry;
|
|
945
|
-
this.#keep(raised);
|
|
946
|
-
}
|
|
947
|
-
// What came of raising her body, kept.
|
|
948
|
-
#keep({ installed, why, serves, port }) {
|
|
949
|
-
this.cells.installed = installed ?? this.cells.installed;
|
|
950
|
-
this.cells.why = why ?? null;
|
|
951
|
-
this.cells.serves = serves ?? null;
|
|
952
|
-
this.cells.port = port ?? null;
|
|
953
|
-
}
|
|
954
|
-
async stand() {
|
|
955
|
-
const entry = await this.#whole();
|
|
956
|
-
if (entry === undefined)
|
|
957
|
-
return { why: 'the terrain names no entry for it' };
|
|
958
|
-
return (await this.#ground.raise({ name: this.#name, entry, ...(this.cells.installed === null ? {} : { installed: this.cells.installed }) }));
|
|
959
|
-
}
|
|
960
|
-
record(raised) {
|
|
961
|
-
this.#keep(raised);
|
|
962
|
-
}
|
|
963
|
-
shown() {
|
|
964
|
-
return shownOf(this);
|
|
965
|
-
}
|
|
966
|
-
async #whole() {
|
|
967
|
-
if (this.cells.entry !== null)
|
|
968
|
-
return wholeFaculty(this.cells, shownOf(this).standings);
|
|
969
|
-
return (await this.#ground.terrain()).entries[this.#name];
|
|
970
|
-
}
|
|
971
|
-
async #raise(entry) {
|
|
972
|
-
if (entry === undefined) {
|
|
973
|
-
this.cells.why = 'the terrain names no entry for it';
|
|
974
|
-
return { why: this.cells.why };
|
|
975
|
-
}
|
|
976
|
-
const raised = (await this.#ground.raise({ name: this.#name, entry, ...(this.cells.installed === null ? {} : { installed: this.cells.installed }) }));
|
|
977
|
-
this.#keep(raised);
|
|
978
|
-
return raised.why === undefined ? {} : { why: raised.why };
|
|
979
|
-
}
|
|
980
|
-
async up() {
|
|
981
|
-
return this.#raise(await this.#whole());
|
|
982
|
-
}
|
|
983
|
-
async down({ why }) {
|
|
984
|
-
await this.#ground.lower({ name: this.#name, why });
|
|
985
|
-
this.cells.why = why;
|
|
986
|
-
this.cells.serves = null;
|
|
987
|
-
this.cells.port = null;
|
|
988
|
-
}
|
|
989
|
-
async update({ entry }) {
|
|
990
|
-
const whole = entry;
|
|
991
|
-
const grants = facultyGrants(whole);
|
|
992
|
-
for (const { id } of shownOf(this).standings)
|
|
993
|
-
if (!grants.has(id))
|
|
994
|
-
this.standings.drop(id);
|
|
995
|
-
await this.#ground.lower({ name: this.#name });
|
|
996
|
-
this.cells.entry = facultyCells(whole);
|
|
997
|
-
return this.#raise(whole);
|
|
998
|
-
}
|
|
999
|
-
async restart() {
|
|
1000
|
-
const whole = await this.#whole();
|
|
1001
|
-
await this.#ground.lower({ name: this.#name });
|
|
1002
|
-
return this.#raise(whole);
|
|
1003
|
-
}
|
|
1004
|
-
async remove() {
|
|
1005
|
-
await this.#ground.lower({ name: this.#name });
|
|
1006
|
-
for (const { id } of shownOf(this).standings)
|
|
1007
|
-
this.standings.drop(id);
|
|
1008
|
-
this.cells.entry = null;
|
|
1009
|
-
this.cells.why = 'its entry is dropped';
|
|
1010
|
-
this.cells.serves = null;
|
|
1011
|
-
}
|
|
1012
|
-
release({ id }) {
|
|
1013
|
-
if (this.occupants.list().some((one) => one.id === `being:${id}`))
|
|
1014
|
-
this.occupants.dismiss(`being:${id}`);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
/**
|
|
1018
|
-
* A house's being: her cells hold its entry, its seed and its ward, and
|
|
1019
|
-
* whether it stands open or closed with why. The seed stays with her once
|
|
1020
|
-
* the entry is dropped, so the house added again is the same ward.
|
|
1021
|
-
*/
|
|
1022
|
-
export class DockHouse extends Being.of({
|
|
1023
|
-
kind: KINDS.house,
|
|
1024
|
-
description: 'One house of the ground: its entry, its seed, its ward, and whether it is open.',
|
|
1025
|
-
needs: { ground: GroundNeed },
|
|
1026
|
-
cells: { entry: null, seed: '', ward: null, why: null },
|
|
1027
|
-
asks: {
|
|
1028
|
-
born: { for: 'steward', args: s.object({ entry: OBJECT, seed: s.string(), ward: s.optional(s.string()), why: s.optional(s.string()) }) },
|
|
1029
|
-
shown: SHOWN,
|
|
1030
|
-
open: {
|
|
1031
|
-
for: ['steward', 'root'],
|
|
1032
|
-
description: 'Opens the house on her entry where it is not open on it, and answers its ward or why it stays closed.',
|
|
1033
|
-
args: s.object({ bound: s.optional(s.integer({ minimum: 1 })) }),
|
|
1034
|
-
result: STOOD,
|
|
1035
|
-
hints: CHANGE,
|
|
1036
|
-
wait: WAIT_BOUND,
|
|
1037
|
-
},
|
|
1038
|
-
opening: {
|
|
1039
|
-
for: 'steward',
|
|
1040
|
-
description: 'Opens the house as `open` does and answers what came of it, writing nothing: a boot writes only what moved.',
|
|
1041
|
-
args: s.object({ bound: s.optional(s.integer({ minimum: 1 })) }),
|
|
1042
|
-
result: STOOD,
|
|
1043
|
-
hints: LIST,
|
|
1044
|
-
wait: WAIT_BOUND,
|
|
1045
|
-
},
|
|
1046
|
-
record: { ...HERS, args: STOOD, description: 'Keeps what came of opening the house.' },
|
|
1047
|
-
close: { ...HERS, args: s.object({ why: s.string() }), description: 'Closes the house, and says why.' },
|
|
1048
|
-
update: { ...HERS, args: s.object({ entry: OBJECT, bound: s.optional(s.integer({ minimum: 1 })) }), result: STOOD, description: 'Lands a new entry over hers, and opens the house on it.' },
|
|
1049
|
-
remove: { ...HERS, description: 'Closes the house and drops her entry; her seed stays.' },
|
|
1050
|
-
},
|
|
1051
|
-
}) {
|
|
1052
|
-
get #name() {
|
|
1053
|
-
return nameOf(this.id, IDS.house);
|
|
1054
|
-
}
|
|
1055
|
-
get #ground() {
|
|
1056
|
-
return this.ground;
|
|
1057
|
-
}
|
|
1058
|
-
born({ entry, seed, ward, why }) {
|
|
1059
|
-
this.cells.entry = entry;
|
|
1060
|
-
this.cells.seed = seed;
|
|
1061
|
-
this.cells.ward = ward ?? null;
|
|
1062
|
-
this.cells.why = why ?? null;
|
|
1063
|
-
}
|
|
1064
|
-
shown() {
|
|
1065
|
-
return shownOf(this);
|
|
1066
|
-
}
|
|
1067
|
-
async #open(entry, bound) {
|
|
1068
|
-
const stood = (await this.#ground.open({ name: this.#name, entry, seed: this.cells.seed, ...(bound === undefined ? {} : { bound }) }));
|
|
1069
|
-
this.cells.ward = stood.ward ?? this.cells.ward;
|
|
1070
|
-
this.cells.why = stood.why ?? null;
|
|
1071
|
-
return stood;
|
|
1072
|
-
}
|
|
1073
|
-
async open({ bound }) {
|
|
1074
|
-
if (this.cells.entry === null)
|
|
1075
|
-
return this.fail(`the house ${this.#name} holds no entry`);
|
|
1076
|
-
return this.#open(wholeHouse(this.cells, shownOf(this).standings), bound);
|
|
1077
|
-
}
|
|
1078
|
-
async opening({ bound }) {
|
|
1079
|
-
if (this.cells.entry === null)
|
|
1080
|
-
return this.fail(`the house ${this.#name} holds no entry`);
|
|
1081
|
-
return (await this.#ground.open({ name: this.#name, entry: wholeHouse(this.cells, shownOf(this).standings), seed: this.cells.seed, ...(bound === undefined ? {} : { bound }) }));
|
|
1082
|
-
}
|
|
1083
|
-
record({ ward, why }) {
|
|
1084
|
-
this.cells.ward = ward ?? this.cells.ward;
|
|
1085
|
-
this.cells.why = why ?? null;
|
|
1086
|
-
}
|
|
1087
|
-
async close({ why }) {
|
|
1088
|
-
await this.#ground.close({ name: this.#name, why });
|
|
1089
|
-
this.cells.why = why;
|
|
1090
|
-
}
|
|
1091
|
-
async update({ entry, bound }) {
|
|
1092
|
-
const whole = entry;
|
|
1093
|
-
const grants = houseGrants(whole);
|
|
1094
|
-
for (const { id } of shownOf(this).standings)
|
|
1095
|
-
if (!grants.has(id))
|
|
1096
|
-
this.standings.drop(id);
|
|
1097
|
-
await this.#ground.close({ name: this.#name, why: 'it is being updated' });
|
|
1098
|
-
this.cells.entry = houseCells(whole);
|
|
1099
|
-
return this.#open(whole, bound);
|
|
1100
|
-
}
|
|
1101
|
-
async remove() {
|
|
1102
|
-
await this.#ground.close({ name: this.#name });
|
|
1103
|
-
for (const { id } of shownOf(this).standings)
|
|
1104
|
-
this.standings.drop(id);
|
|
1105
|
-
this.cells.entry = null;
|
|
1106
|
-
this.cells.why = null;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
/**
|
|
1110
|
-
* A secret: her one cell holds its value. Her asks answer nothing, and the
|
|
1111
|
-
* hand never reads her cells, so only the bodies whose twins stand on her
|
|
1112
|
-
* receive it, when they go up.
|
|
1113
|
-
*/
|
|
1114
|
-
export class DockSecret extends Being.of({
|
|
1115
|
-
kind: KINDS.secret,
|
|
1116
|
-
description: 'One secret, for the faculties whose entries name it.',
|
|
1117
|
-
cells: { value: '' },
|
|
1118
|
-
asks: {
|
|
1119
|
-
born: { for: 'steward', args: s.object({ value: s.string() }) },
|
|
1120
|
-
set: { for: 'steward', args: s.object({ value: s.string() }), hints: CHANGE },
|
|
1121
|
-
release: { ...HERS, args: s.object({ id: s.string() }) },
|
|
1122
|
-
},
|
|
1123
|
-
}) {
|
|
1124
|
-
born({ value }) {
|
|
1125
|
-
this.cells.value = value;
|
|
1126
|
-
}
|
|
1127
|
-
set({ value }) {
|
|
1128
|
-
this.cells.value = value;
|
|
1129
|
-
}
|
|
1130
|
-
release({ id }) {
|
|
1131
|
-
if (this.occupants.list().some((one) => one.id === `being:${id}`))
|
|
1132
|
-
this.occupants.dismiss(`being:${id}`);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
/**
|
|
1136
|
-
* The being that holds the shell: the terrain's `shell` body, offered to
|
|
1137
|
-
* her kind alone as any body is offered to a house, where the dock's
|
|
1138
|
-
* steward stands on its twin. The steward's `shellRun` asks her.
|
|
1139
|
-
*/
|
|
1140
|
-
export class DockShell extends Being.of({
|
|
1141
|
-
kind: KINDS.shell,
|
|
1142
|
-
description: 'The ground’s shell, for the dock’s steward alone.',
|
|
1143
|
-
needs: { shell: ShellNeed },
|
|
1144
|
-
asks: {
|
|
1145
|
-
run: {
|
|
1146
|
-
for: 'steward',
|
|
1147
|
-
args: s.object({ command: s.string(), args: s.optional(s.array(s.string())), cwd: s.optional(s.string()), stdin: s.optional(s.string()) }),
|
|
1148
|
-
result: s.object({ code: s.integer(), stdout: s.string(), stderr: s.string() }),
|
|
1149
|
-
hints: CHANGE,
|
|
1150
|
-
wait: WAIT_BOUND,
|
|
1151
|
-
},
|
|
1152
|
-
},
|
|
1153
|
-
}) {
|
|
1154
|
-
async run(args) {
|
|
1155
|
-
return this.shell.run(args);
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
/** The dock's classes: the library's, the same on every ground. */
|
|
1159
|
-
export const dockClasses = () => new ClassList({ steward: DockSteward, beings: [DockFaculty, DockHouse, DockSecret, DockShell] });
|