nervur 0.19.2 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -101
- package/dist/being/being.d.ts +7 -15
- package/dist/being/being.js +16 -67
- package/dist/being/digest.d.ts +1 -1
- package/dist/being/digest.js +7 -27
- package/dist/being/faculty.d.ts +9 -0
- package/dist/being/faculty.js +68 -0
- package/dist/being/index.d.ts +4 -5
- package/dist/being/index.js +5 -5
- package/dist/being/types.d.ts +35 -62
- package/dist/being/types.js +3 -57
- package/dist/being/words.d.ts +13 -0
- package/dist/being/words.js +14 -0
- package/dist/contract/index.d.ts +26 -0
- package/dist/contract/index.js +19 -0
- package/dist/crypto/aes.d.ts +3 -0
- package/dist/crypto/aes.js +24 -0
- package/dist/crypto/bytes.d.ts +6 -0
- package/dist/crypto/bytes.js +33 -0
- package/dist/crypto/ed25519.d.ts +3 -0
- package/dist/crypto/ed25519.js +85 -0
- package/dist/crypto/hash.d.ts +2 -0
- package/dist/crypto/hash.js +11 -0
- package/dist/crypto/index.d.ts +7 -0
- package/dist/crypto/index.js +10 -0
- package/dist/crypto/json.d.ts +8 -0
- package/dist/crypto/json.js +250 -0
- package/dist/crypto/mlkem.d.ts +12 -0
- package/dist/crypto/mlkem.js +36 -0
- package/dist/crypto/subtle.d.ts +10 -0
- package/dist/crypto/subtle.js +29 -0
- package/dist/crypto/x25519.d.ts +2 -0
- package/dist/crypto/x25519.js +21 -0
- package/dist/folder/index.d.ts +13 -0
- package/dist/folder/index.js +125 -0
- package/dist/harbor/dock.d.ts +19 -0
- package/dist/harbor/dock.js +92 -0
- package/dist/harbor/harbor.d.ts +22 -0
- package/dist/harbor/harbor.js +84 -0
- package/dist/harbor/index.d.ts +4 -10
- package/dist/harbor/index.js +5 -18
- package/dist/harbor/registry.d.ts +7 -0
- package/dist/harbor/registry.js +13 -0
- package/dist/harbor/terrain.d.ts +8 -0
- package/dist/harbor/terrain.js +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/pointer/bodies.d.ts +20 -0
- package/dist/pointer/bodies.js +45 -0
- package/dist/pointer/index.d.ts +2 -0
- package/dist/pointer/index.js +4 -0
- package/dist/pointer/world.d.ts +34 -0
- package/dist/pointer/world.js +76 -0
- package/dist/quo/door.d.ts +34 -0
- package/dist/quo/door.js +172 -0
- package/dist/quo/index.d.ts +8 -0
- package/dist/quo/index.js +11 -0
- package/dist/quo/invitation.d.ts +7 -0
- package/dist/quo/invitation.js +15 -0
- package/dist/quo/keys.d.ts +40 -0
- package/dist/quo/keys.js +79 -0
- package/dist/quo/payload.d.ts +15 -0
- package/dist/quo/payload.js +55 -0
- package/dist/quo/relations.d.ts +40 -0
- package/dist/quo/relations.js +33 -0
- package/dist/quo/reply.d.ts +13 -0
- package/dist/quo/reply.js +35 -0
- package/dist/quo/seal.d.ts +42 -0
- package/dist/quo/seal.js +78 -0
- package/dist/quo/standing.d.ts +39 -0
- package/dist/quo/standing.js +90 -0
- package/dist/ward/allowance.d.ts +13 -9
- package/dist/ward/allowance.js +28 -67
- package/dist/ward/cells.d.ts +6 -6
- package/dist/ward/cells.js +67 -179
- package/dist/ward/index.d.ts +5 -10
- package/dist/ward/index.js +6 -13
- package/dist/ward/partition.d.ts +44 -60
- package/dist/ward/partition.js +56 -285
- package/dist/ward/stance.d.ts +29 -20
- package/dist/ward/stance.js +173 -406
- package/dist/ward/ward-being.d.ts +33 -0
- package/dist/ward/ward-being.js +76 -0
- package/dist/ward/ward.d.ts +43 -11
- package/dist/ward/ward.js +215 -363
- package/package.json +19 -33
- package/src/being/being.ts +31 -78
- package/src/being/digest.ts +7 -35
- package/src/being/faculty.ts +66 -0
- package/src/being/index.ts +5 -6
- package/src/being/types.ts +47 -145
- package/src/being/words.ts +31 -0
- package/src/contract/index.ts +44 -0
- package/src/crypto/aes.ts +26 -0
- package/src/crypto/bytes.ts +37 -0
- package/src/crypto/ed25519.ts +84 -0
- package/src/crypto/hash.ts +14 -0
- package/src/crypto/index.ts +10 -0
- package/src/crypto/json.ts +241 -0
- package/src/crypto/mlkem.ts +38 -0
- package/src/crypto/subtle.ts +33 -0
- package/src/crypto/x25519.ts +21 -0
- package/src/folder/index.ts +134 -0
- package/src/harbor/dock.ts +101 -0
- package/src/harbor/harbor.ts +105 -0
- package/src/harbor/index.ts +5 -19
- package/src/harbor/registry.ts +20 -0
- package/src/harbor/terrain.ts +13 -0
- package/src/index.ts +20 -0
- package/src/pointer/bodies.ts +47 -0
- package/src/pointer/index.ts +4 -0
- package/src/pointer/world.ts +91 -0
- package/src/quo/door.ts +178 -0
- package/src/quo/index.ts +11 -0
- package/src/quo/invitation.ts +15 -0
- package/src/quo/keys.ts +91 -0
- package/src/quo/payload.ts +61 -0
- package/src/quo/relations.ts +62 -0
- package/src/quo/reply.ts +38 -0
- package/src/quo/seal.ts +101 -0
- package/src/quo/standing.ts +111 -0
- package/src/stand/main.ts +19 -0
- package/src/stand/stand.ts +178 -0
- package/src/ward/allowance.ts +37 -75
- package/src/ward/cells.ts +63 -176
- package/src/ward/index.ts +6 -17
- package/src/ward/partition.ts +86 -326
- package/src/ward/stance.ts +185 -420
- package/src/ward/ward-being.ts +97 -0
- package/src/ward/ward.ts +229 -363
- package/dist/being/lent.d.ts +0 -32
- package/dist/being/lent.js +0 -72
- package/dist/being/silence.d.ts +0 -12
- package/dist/being/silence.js +0 -41
- package/dist/conformance/assert.d.ts +0 -11
- package/dist/conformance/assert.js +0 -106
- package/dist/conformance/beings.d.ts +0 -199
- package/dist/conformance/beings.js +0 -188
- package/dist/conformance/estate.d.ts +0 -5
- package/dist/conformance/estate.js +0 -388
- package/dist/conformance/index.d.ts +0 -80
- package/dist/conformance/index.js +0 -819
- package/dist/conformance/reach.d.ts +0 -10
- package/dist/conformance/reach.js +0 -72
- package/dist/conformance/store.d.ts +0 -5
- package/dist/conformance/store.js +0 -113
- package/dist/harbor/box.d.ts +0 -121
- package/dist/harbor/box.js +0 -121
- package/dist/harbor/core.d.ts +0 -55
- package/dist/harbor/core.js +0 -662
- package/dist/harbor/dial.d.ts +0 -9
- package/dist/harbor/dial.js +0 -81
- package/dist/harbor/memory.d.ts +0 -29
- package/dist/harbor/memory.js +0 -104
- package/dist/harbor/reach.d.ts +0 -36
- package/dist/harbor/reach.js +0 -199
- package/dist/harbor/store.d.ts +0 -35
- package/dist/harbor/store.js +0 -62
- package/dist/vector/cases.d.ts +0 -42
- package/dist/vector/cases.js +0 -223
- package/dist/vector/index.d.ts +0 -6
- package/dist/vector/index.js +0 -8
- package/dist/vector/stand.d.ts +0 -9
- package/dist/vector/stand.js +0 -77
- package/dist/vector/world.d.ts +0 -144
- package/dist/vector/world.js +0 -209
- package/dist/ward/arithmetic.d.ts +0 -31
- package/dist/ward/arithmetic.js +0 -249
- package/dist/ward/door.d.ts +0 -18
- package/dist/ward/door.js +0 -186
- package/dist/ward/ground.d.ts +0 -29
- package/dist/ward/ground.js +0 -56
- package/dist/ward/heirs.d.ts +0 -13
- package/dist/ward/heirs.js +0 -117
- package/dist/ward/json.d.ts +0 -2
- package/dist/ward/json.js +0 -163
- package/dist/ward/owner.d.ts +0 -13
- package/dist/ward/owner.js +0 -220
- package/dist/ward/seal.d.ts +0 -52
- package/dist/ward/seal.js +0 -150
- package/src/being/lent.ts +0 -72
- package/src/being/silence.ts +0 -46
- package/src/conformance/assert.ts +0 -100
- package/src/conformance/beings.ts +0 -188
- package/src/conformance/estate.ts +0 -412
- package/src/conformance/index.ts +0 -965
- package/src/conformance/reach.ts +0 -83
- package/src/conformance/store.ts +0 -125
- package/src/harbor/box.ts +0 -131
- package/src/harbor/core.ts +0 -699
- package/src/harbor/dial.ts +0 -112
- package/src/harbor/memory.ts +0 -123
- package/src/harbor/reach.ts +0 -221
- package/src/harbor/store.ts +0 -91
- package/src/vector/cases.ts +0 -257
- package/src/vector/index.ts +0 -11
- package/src/vector/stand.ts +0 -76
- package/src/vector/world.ts +0 -232
- package/src/ward/arithmetic.ts +0 -251
- package/src/ward/door.ts +0 -186
- package/src/ward/ground.ts +0 -142
- package/src/ward/heirs.ts +0 -116
- package/src/ward/json.ts +0 -144
- package/src/ward/owner.ts +0 -214
- package/src/ward/seal.ts +0 -178
package/src/ward/ward.ts
CHANGED
|
@@ -1,410 +1,276 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { makeDoor, type Door } from './door.ts';
|
|
14
|
-
import { buildStance } from './stance.ts';
|
|
15
|
-
import { ownerAnswer } from './owner.ts';
|
|
16
|
-
import { allow, within, LATE } from './allowance.ts';
|
|
17
|
-
import { beingKey, openReply, sealAsk, wardKey, wardPadlock, wardSignPk, type ReplyPayload, type WardKey } from './seal.ts';
|
|
18
|
-
import { unhex } from './arithmetic.ts';
|
|
19
|
-
import { guardCells, unguarded, cellFault } from './cells.ts';
|
|
2
|
+
// The ward: one door, one partition, and the beings behind the door. It
|
|
3
|
+
// unpacks from its seed, its memory and a catalogue of classes, the
|
|
4
|
+
// ward-being first and every other being from her own row, and it stands
|
|
5
|
+
// what is behind its door for `quo/`.
|
|
6
|
+
import { readValue, writeValue } from '../crypto/index.ts';
|
|
7
|
+
import { Door, readInvitation, WardKey, type Behind, type Choice, type Invitation, type Payload } from '../quo/index.ts';
|
|
8
|
+
import type { Carrier, Clock, Entropy, Rows } from '../contract/index.ts';
|
|
9
|
+
import { digest, isSilence, isWord, OWNER, silence, WARD, word, type Answer, type Asker, type BeingClass, type BeingLike, type JsonObject, type Reply } from '../being/index.ts';
|
|
10
|
+
import { Partition, PartitionRelations, type BeingRow } from './partition.ts';
|
|
11
|
+
import { DEPTH, WardStance, type Inside } from './stance.ts';
|
|
12
|
+
import { WardBeing, type Steward, type WardBeingStance } from './ward-being.ts';
|
|
20
13
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
// Where a ward finds a class by the name its row keeps.
|
|
15
|
+
export interface Catalogue {
|
|
16
|
+
classOf(name: string): BeingClass | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type WardParts = {
|
|
20
|
+
readonly seed: Uint8Array | string;
|
|
21
|
+
readonly memory: Rows;
|
|
22
|
+
readonly catalogue: Catalogue;
|
|
23
|
+
readonly entropy: Entropy;
|
|
24
|
+
readonly clock: Clock;
|
|
25
|
+
readonly carrier: Carrier;
|
|
26
|
+
// The ward-being: `WardBeing`, or a being that extends it.
|
|
27
|
+
readonly root?: (stance: WardBeingStance) => WardBeing;
|
|
28
|
+
// An invitation on the dock, taken by the ward-being under `dock` when
|
|
29
|
+
// she holds no such standing yet.
|
|
30
|
+
readonly dock?: Invitation;
|
|
31
|
+
};
|
|
26
32
|
|
|
27
|
-
// The
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
// The standing every hosted ward's ward-being holds on the dock.
|
|
34
|
+
export const DOCK = 'dock';
|
|
35
|
+
|
|
36
|
+
// A ward-being that lends to her own ward's beings, as the dock does.
|
|
37
|
+
export interface Lender {
|
|
38
|
+
offer(contract: string): Promise<Invitation | null>;
|
|
39
|
+
withdraw(contract: string, heir: string): Promise<void>;
|
|
31
40
|
}
|
|
41
|
+
const lends = (being: unknown): being is Lender => typeof (being as Partial<Lender>).offer === 'function';
|
|
32
42
|
|
|
33
|
-
class
|
|
34
|
-
|
|
35
|
-
readonly pk: string;
|
|
36
|
-
readonly g: Ground;
|
|
37
|
-
readonly p: Partition;
|
|
38
|
-
readonly heirs: Heirs;
|
|
39
|
-
readonly doors = new Map<string, Resident>();
|
|
40
|
-
readonly #birthing = new Set<string>(); // keys whose constructor is running: taken, though no door and no row say so yet
|
|
41
|
-
readonly door: WardPointers['door'];
|
|
42
|
-
readonly ask: WardPointers['ask'];
|
|
43
|
+
type Birth = 'booted' | 'key taken' | 'no such class' | 'threw at birth';
|
|
44
|
+
type Resident = { being: BeingLike; row: BeingRow; stance: WardStance };
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
static async boot(ground: Ground): Promise<Self> {
|
|
50
|
-
const w = new Self(ground, await wardKey(ground.seed));
|
|
51
|
-
await w.#takeBox();
|
|
52
|
-
w.#reborn();
|
|
53
|
-
return w;
|
|
46
|
+
class RootStance extends WardStance implements WardBeingStance {
|
|
47
|
+
readonly steward: Steward;
|
|
48
|
+
constructor(inside: Inside, row: BeingRow, steward: Steward) {
|
|
49
|
+
super(inside, WARD, row);
|
|
50
|
+
this.steward = steward;
|
|
54
51
|
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class Ward implements Behind, Inside {
|
|
55
|
+
readonly key: WardKey;
|
|
56
|
+
readonly partition: Partition;
|
|
57
|
+
readonly door: Door;
|
|
58
|
+
readonly clock: Clock;
|
|
59
|
+
readonly #parts: WardParts;
|
|
60
|
+
readonly #residents = new Map<string, Resident>();
|
|
61
|
+
readonly #birthing = new Set<string>();
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!inv || !mine || mine.standings[BOX]) return;
|
|
63
|
-
const out = await mine.standings.knock(inv);
|
|
64
|
-
if (isSilence(out) || isWord(out)) return;
|
|
65
|
-
await mine.standings.take(BOX, inv);
|
|
63
|
+
private constructor(parts: WardParts, key: WardKey) {
|
|
64
|
+
this.#parts = parts;
|
|
65
|
+
this.key = key;
|
|
66
|
+
this.clock = parts.clock;
|
|
67
|
+
this.partition = new Partition(parts.memory);
|
|
68
|
+
this.door = new Door(key, new PartitionRelations(this.partition), this, parts.entropy.drawer);
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
async
|
|
72
|
-
const
|
|
73
|
-
if (!
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
// The ward-being first, then every being her row names. A class the
|
|
72
|
+
// catalogue lacks, or one that throws, leaves that being absent and her
|
|
73
|
+
// row as it was.
|
|
74
|
+
static async unpack(parts: WardParts): Promise<Ward> {
|
|
75
|
+
const ward = new Ward(parts, await WardKey.from(parts.seed));
|
|
76
|
+
if (!ward.partition.being(WARD)) {
|
|
77
|
+
ward.partition.beings[WARD] = { class: '', cells: {}, occupants: {}, standings: {} };
|
|
78
|
+
ward.partition.told(WARD);
|
|
79
|
+
ward.partition.toldHead();
|
|
80
|
+
}
|
|
81
|
+
const own = ward.partition.being(WARD)!;
|
|
82
|
+
const stance = new RootStance(ward, own, ward.#steward);
|
|
83
|
+
const being = parts.root ? parts.root(stance) : new WardBeing(stance);
|
|
84
|
+
own.class = being.constructor.name;
|
|
85
|
+
ward.#residents.set(WARD, { being, row: own, stance });
|
|
86
|
+
if (parts.dock && !Object.hasOwn(own.standings, DOCK)) await stance.standings.take(DOCK, parts.dock);
|
|
87
|
+
for (const [key, row] of Object.entries(ward.partition.beings)) {
|
|
88
|
+
const C = key === WARD ? undefined : parts.catalogue.classOf(row.class);
|
|
89
|
+
if (C) ward.#bear(key, row, C);
|
|
90
|
+
}
|
|
91
|
+
return ward;
|
|
79
92
|
}
|
|
80
93
|
|
|
81
|
-
|
|
82
|
-
this.
|
|
83
|
-
this.key = key;
|
|
84
|
-
this.pk = key.pk;
|
|
85
|
-
this.p = open(ground.memory.rows); // the partition: the ward's own state, and every being's row
|
|
86
|
-
// Opening is itself a write: a memory nobody has written comes back with
|
|
87
|
-
// a version stamped on it and the ward's own tables under it. A ward that
|
|
88
|
-
// said nothing here would have every being of it kept and the head that
|
|
89
|
-
// says which version they are written under kept by nobody, so the next
|
|
90
|
-
// boot would read a partition of no version and refuse it.
|
|
91
|
-
this.#wrote(HEAD);
|
|
92
|
-
this.heirs = new Heirs(this.p, () => this.#wrote(HEAD));
|
|
93
|
-
this.door = makeDoor(
|
|
94
|
-
key,
|
|
95
|
-
this.heirs,
|
|
96
|
-
this.doors,
|
|
97
|
-
() => this.p.public,
|
|
98
|
-
(n) => ground.random(n),
|
|
99
|
-
() => this.#keep(),
|
|
100
|
-
); // pointer one
|
|
101
|
-
this.#boot(this.pk, () => this); // the ward's ward is itself
|
|
102
|
-
// pointer two. the owner. the same answer every named asker gets, asker filled in as owner.
|
|
103
|
-
this.ask = async (method, args = {}) => {
|
|
104
|
-
try {
|
|
105
|
-
const out = await this.answer({ id: OWNER }, method, args as JsonObject);
|
|
106
|
-
// The same question the door asks, in the same place: after she has
|
|
107
|
-
// answered and before the owner holds the answer. Silence is what a
|
|
108
|
-
// failed ask says here, so a store that refused is silence, and the
|
|
109
|
-
// owner reads it as it reads every other ask that did not take.
|
|
110
|
-
if (!(await this.#keep().catch(() => false))) return silence;
|
|
111
|
-
return isSilence(out) || isWord(out) ? silence : out;
|
|
112
|
-
} catch {
|
|
113
|
-
return silence;
|
|
114
|
-
}
|
|
115
|
-
};
|
|
94
|
+
get pk(): string {
|
|
95
|
+
return this.key.pk;
|
|
116
96
|
}
|
|
117
97
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// A throw on a boot the owner asked for is that boot's error, and nothing
|
|
121
|
-
// half-lives. This boot nobody asked for, and there is nobody to hand an
|
|
122
|
-
// error to. Any constructor may throw, a default that is not a value, an
|
|
123
|
-
// author's mistake, a shape she does not recognise after an upgrade, and
|
|
124
|
-
// let through here it is one being taking down the ward and every other
|
|
125
|
-
// being in it, on a restart. She is absent instead: no door, so silence
|
|
126
|
-
// at the door, and her cells sit untouched, waiting for the run that can
|
|
127
|
-
// read them. Her row stays hers: nobody boots another class over it, the
|
|
128
|
-
// owner sees her as absent, and the owner may unboot her.
|
|
129
|
-
for (const [k, c] of Object.entries(this.p.beings)) {
|
|
130
|
-
const className = c.class;
|
|
131
|
-
if (k === this.pk || !className) continue;
|
|
132
|
-
try {
|
|
133
|
-
this.#boot(k, (stance) => this.g.instantiate(className, stance));
|
|
134
|
-
} catch {
|
|
135
|
-
/* she is not here this run. the ward is, and so is everyone else. */
|
|
136
|
-
}
|
|
137
|
-
}
|
|
98
|
+
get draw() {
|
|
99
|
+
return this.#parts.entropy.drawer;
|
|
138
100
|
}
|
|
139
101
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// can invite. A stranger cannot be a named asker here, because the door
|
|
143
|
-
// names nobody the ward did not bind.
|
|
144
|
-
async answer(asker: Asker, method?: string, args: JsonObject = {}): Promise<Reply> {
|
|
145
|
-
// Nobody is not an owner. The door names nobody the ward did not bind, so
|
|
146
|
-
// the only way here unnamed is a partition that says the ward is its own
|
|
147
|
-
// public being, which no owner ask will write. Silence, not an error
|
|
148
|
-
// object: an unnamed asker is a stranger, and a stranger hears one thing.
|
|
149
|
-
if (asker.id === undefined) return silence;
|
|
150
|
-
return ownerAnswer(
|
|
151
|
-
{
|
|
152
|
-
pk: this.pk,
|
|
153
|
-
doors: this.doors,
|
|
154
|
-
absent: () => this.#absent(),
|
|
155
|
-
publicKey: () => this.p.public,
|
|
156
|
-
instantiate: (className, key) => this.#instantiate(className, key),
|
|
157
|
-
unboot: (key) => this.#unboot(key),
|
|
158
|
-
setPublic: (key) => {
|
|
159
|
-
this.p.public = key;
|
|
160
|
-
this.#wrote(HEAD);
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
asker,
|
|
164
|
-
method,
|
|
165
|
-
args,
|
|
166
|
-
);
|
|
102
|
+
get wardBeing(): WardBeing {
|
|
103
|
+
return this.#residents.get(WARD)!.being as WardBeing;
|
|
167
104
|
}
|
|
168
105
|
|
|
169
|
-
//
|
|
106
|
+
// The unsealed ask: the ward-being, asked by whoever holds this ward.
|
|
107
|
+
async root(method?: string, args: JsonObject = {}): Promise<Answer> {
|
|
108
|
+
return this.#kept(WARD, () => this.#answer(WARD, { id: OWNER }, method, args));
|
|
109
|
+
}
|
|
170
110
|
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
111
|
+
// A being's answer, kept. Where the memory refuses, what she wrote is
|
|
112
|
+
// put back and her answer is silence. What her relations stand on is not
|
|
113
|
+
// put back: a relation's keys move only as far as its standing follows.
|
|
114
|
+
async #kept(key: string, work: () => Promise<Answer>): Promise<Answer> {
|
|
115
|
+
const row = this.partition.being(key);
|
|
116
|
+
const before = row && JSON.stringify(row.cells);
|
|
117
|
+
const out = await work();
|
|
118
|
+
if (await this.partition.kept()) return out;
|
|
119
|
+
if (row && before !== undefined) {
|
|
120
|
+
for (const k of Object.keys(row.cells)) delete row.cells[k];
|
|
121
|
+
for (const [k, v] of Object.entries(JSON.parse(before) as JsonObject)) Object.defineProperty(row.cells, k, { value: v, enumerable: true, writable: true, configurable: true });
|
|
122
|
+
this.partition.told(key);
|
|
123
|
+
}
|
|
124
|
+
return silence;
|
|
178
125
|
}
|
|
179
126
|
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return this.g.memory.kept();
|
|
127
|
+
// Behind: the zero head reaches the public being, a heir the being that
|
|
128
|
+
// invited it.
|
|
129
|
+
get zero(): boolean {
|
|
130
|
+
const key = this.partition.head.public;
|
|
131
|
+
return key !== null && this.#residents.has(key);
|
|
186
132
|
}
|
|
187
133
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
return
|
|
134
|
+
async answer(heir: string | null, p: Payload): Promise<Choice> {
|
|
135
|
+
const bound = heir === null ? undefined : this.partition.head.bind[heir];
|
|
136
|
+
const key = heir === null ? this.partition.head.public : bound?.being;
|
|
137
|
+
const asker: Asker = bound ? { id: bound.id } : {};
|
|
138
|
+
const args = p.args === undefined ? {} : readValue(p.args, DEPTH + 1);
|
|
139
|
+
if (key === null || key === undefined || typeof args !== 'object' || args === null || Array.isArray(args)) return { silence: true };
|
|
140
|
+
const out = await this.#kept(key, () => this.#answer(key, asker, p.method, args));
|
|
141
|
+
const object = isSilence(out) || isWord(out) ? undefined : writeValue(out, DEPTH);
|
|
142
|
+
if (object === undefined) return { silence: true };
|
|
143
|
+
const being = this.#residents.get(key)?.being;
|
|
144
|
+
const seen = p.method === undefined || !being?.describe ? null : await digest(being.describe(asker));
|
|
145
|
+
return { object, seen };
|
|
194
146
|
}
|
|
195
147
|
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// The three refusals are read before the constructor runs, and the
|
|
203
|
-
// constructor can reach the stance and boot: a class that boots its own
|
|
204
|
-
// key from inside itself would pass all three, since neither its door nor
|
|
205
|
-
// its row exists yet, and the two objects would then share one row. A key
|
|
206
|
-
// being born is a key taken, for as long as the birth lasts.
|
|
207
|
-
if (typeof key !== 'string' || typeof className !== 'string' || !key || this.doors.has(key) || at(this.p.beings, key) || this.#birthing.has(key)) return null;
|
|
208
|
-
let door: Resident | null;
|
|
209
|
-
this.#birthing.add(key);
|
|
148
|
+
// Every path that reaches a being's answer. A throw, a word out of her,
|
|
149
|
+
// or nothing at all is silence.
|
|
150
|
+
async #answer(key: string, asker: Asker, method: string | undefined, args: JsonObject): Promise<Answer> {
|
|
151
|
+
const resident = this.#residents.get(key);
|
|
152
|
+
if (!resident) return silence;
|
|
153
|
+
let out: Reply;
|
|
210
154
|
try {
|
|
211
|
-
|
|
155
|
+
out = await resident.being.answer(asker, method, args);
|
|
212
156
|
} catch {
|
|
213
|
-
return
|
|
214
|
-
} finally {
|
|
215
|
-
this.#birthing.delete(key);
|
|
157
|
+
return silence;
|
|
216
158
|
}
|
|
217
|
-
|
|
218
|
-
unguarded(door.cells).class = className; // so a restart finds her. the ward's key, written behind her guard
|
|
219
|
-
this.#wrote(key);
|
|
220
|
-
return door;
|
|
159
|
+
return out === undefined || isWord(out) ? silence : out;
|
|
221
160
|
}
|
|
222
161
|
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
if (
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
for (const heir of Object.values(bind?.occupants ?? {})) this.heirs.close(heir);
|
|
245
|
-
}
|
|
246
|
-
// A ward whose public being is gone answers arrivals for no heir with
|
|
247
|
-
// silence, as a ward that never had one does.
|
|
248
|
-
if (this.p.public === key) this.p.public = null;
|
|
249
|
-
drop(this.p.beings, key);
|
|
250
|
-
drop(this.p.bind, key);
|
|
251
|
-
this.doors.delete(key);
|
|
252
|
-
this.#wrote(key); // she is gone, and her row goes with her
|
|
253
|
-
this.#wrote(HEAD); // her heirs were closed, and the public being may have been her
|
|
254
|
-
return [...occupants, ...standings];
|
|
162
|
+
// Inside: what every stance of this ward stands on.
|
|
163
|
+
|
|
164
|
+
// One path through a door for every ask, this ward's own included.
|
|
165
|
+
async send(pk: string, bytes: Uint8Array): Promise<Uint8Array | null> {
|
|
166
|
+
if (pk === this.pk) return (await this.door.arrive(bytes)).bytes;
|
|
167
|
+
return this.#parts.carrier.carry(pk, bytes);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// A lend: the ward-being lends herself, as the dock does, or asks the dock
|
|
171
|
+
// through her standing.
|
|
172
|
+
async offer(contract: string): Promise<Invitation | null> {
|
|
173
|
+
const own = this.#residents.get(WARD)!;
|
|
174
|
+
if (lends(own.being)) return own.being.offer(contract);
|
|
175
|
+
const out = await own.stance.standings.get(DOCK)?.ask('lend', { contract });
|
|
176
|
+
return readInvitation((out as { invitation?: unknown } | undefined)?.invitation);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async retract(contract: string, heir: string): Promise<void> {
|
|
180
|
+
const own = this.#residents.get(WARD)!;
|
|
181
|
+
if (lends(own.being)) return own.being.withdraw(contract, heir);
|
|
182
|
+
await own.stance.standings.get(DOCK)?.ask('retract', { contract, heir });
|
|
255
183
|
}
|
|
256
184
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// leave the partition as it found it.
|
|
260
|
-
//
|
|
261
|
-
// `make` is who to build with the stance this raises. Two callers and two
|
|
262
|
-
// makers: the ground's `instantiate` for a being of the ward, and the ward
|
|
263
|
-
// itself, which is the first being in its own map and is already made.
|
|
264
|
-
#boot(key: string, make: (stance: Stance) => BeingLike | null): Resident | null {
|
|
265
|
-
const cells = guardCells(at(this.p.beings, key) ?? emptyCells(), () => this.#wrote(key));
|
|
266
|
-
const bind = at(this.p.bind, key) ?? emptyBind();
|
|
267
|
-
const stance: Stance = buildStance(
|
|
268
|
-
{
|
|
269
|
-
pk: this.pk,
|
|
270
|
-
// This stance, not merely this key: unboot and boot again under the
|
|
271
|
-
// same key makes a new being, and the old stance is not hers.
|
|
272
|
-
live: () => this.doors.get(key)?.stance === stance,
|
|
273
|
-
mintKey: (b) => this.#mintKey(b, key),
|
|
274
|
-
openHeir: (heir, being, id) => this.heirs.open(heir, being, id),
|
|
275
|
-
closeHeir: (heir) => this.heirs.close(heir),
|
|
276
|
-
send: (b, keys, method, args, wanted) => this.#send(b, keys, method, args, wanted, key),
|
|
277
|
-
// A throw at birth is null to her, not a throw in her method: she asked
|
|
278
|
-
// for a being and got none, and her own answer is still hers to give.
|
|
279
|
-
instantiate: (className, k) => {
|
|
280
|
-
const made = this.#instantiate(className, k);
|
|
281
|
-
return made && made !== 'threw' ? made.key : null;
|
|
282
|
-
},
|
|
283
|
-
// The two halves of a maker's way back, each the ward's own call: the
|
|
284
|
-
// being made mints an occupant for her maker, and a relation that
|
|
285
|
-
// never came about takes her out again.
|
|
286
|
-
relate: async (k, id) => (await this.doors.get(k)?.stance.occupants.invite(id)) ?? null,
|
|
287
|
-
unmake: (k) => void this.#unboot(k),
|
|
288
|
-
lend: (name, take) => this.#lend(name, take),
|
|
289
|
-
wrote: () => this.#wrote(key),
|
|
290
|
-
},
|
|
291
|
-
key,
|
|
292
|
-
cells,
|
|
293
|
-
bind,
|
|
294
|
-
);
|
|
295
|
-
const being = make(stance);
|
|
296
|
-
if (!being || typeof being.answer !== 'function') return null; // a being is anything with an answer, and nothing without one
|
|
297
|
-
put(this.p.beings, key, cells);
|
|
298
|
-
put(this.p.bind, key, bind);
|
|
299
|
-
const door: Resident = { key, cells, bind, stance, being };
|
|
300
|
-
this.doors.set(key, door);
|
|
301
|
-
this.#wrote(key);
|
|
302
|
-
return door;
|
|
185
|
+
live(key: string, row: BeingRow): boolean {
|
|
186
|
+
return this.#residents.get(key)?.row === row;
|
|
303
187
|
}
|
|
304
188
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
189
|
+
// A being boots another of this ward. With an id, the new being invites
|
|
190
|
+
// her maker under the maker's key, and the maker takes it under id; a
|
|
191
|
+
// relation that could not be made unmakes the new being.
|
|
192
|
+
async boot(maker: string, className: string, key: string, id?: string): Promise<string | null> {
|
|
193
|
+
if ((await this.#make(key, className)) !== 'booted') return null;
|
|
194
|
+
if (id === undefined) return key;
|
|
195
|
+
const invitation = await this.#residents.get(key)!.stance.occupants.invite(maker);
|
|
196
|
+
const taken = invitation ? await this.#residents.get(maker)?.stance.standings.take(id, invitation) : null;
|
|
197
|
+
if (taken) return key;
|
|
198
|
+
this.#unboot(key);
|
|
199
|
+
return null;
|
|
315
200
|
}
|
|
316
201
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
async #send(bind: Bind, keys: StandingKeys, method: string | undefined, args: JsonObject, wanted: Wanted | undefined, row: string): Promise<ReplyPayload | typeof silence | Word> {
|
|
323
|
-
// The harbor is told a call is in flight for as long as this one lasts,
|
|
324
|
-
// and told again when it ends. Everything this ward writes between the
|
|
325
|
-
// two is half of a relation: the count taken here, the key rotated when
|
|
326
|
-
// the reply opens, and whatever the being writes on either side of her
|
|
327
|
-
// own ask. A harbor may write those rows down; what it may not do is put
|
|
328
|
-
// the ward back to them.
|
|
329
|
-
const lower = this.g.memory.during();
|
|
202
|
+
async #make(key: string, className: string): Promise<Birth> {
|
|
203
|
+
if (key === '' || key === WARD || this.partition.being(key) || this.#birthing.has(key)) return 'key taken';
|
|
204
|
+
const C = this.#parts.catalogue.classOf(className);
|
|
205
|
+
if (!C) return 'no such class';
|
|
206
|
+
this.#birthing.add(key);
|
|
330
207
|
try {
|
|
331
|
-
|
|
208
|
+
const row: BeingRow = { class: className, cells: {}, occupants: {}, standings: {} };
|
|
209
|
+
this.partition.beings[key] = row;
|
|
210
|
+
if (!this.#bear(key, row, C)) {
|
|
211
|
+
delete this.partition.beings[key];
|
|
212
|
+
return 'threw at birth';
|
|
213
|
+
}
|
|
214
|
+
this.partition.told(key);
|
|
215
|
+
return 'booted';
|
|
332
216
|
} finally {
|
|
333
|
-
|
|
217
|
+
this.#birthing.delete(key);
|
|
334
218
|
}
|
|
335
219
|
}
|
|
336
220
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// the default, and asking for more than the ceiling is the ceiling: budget
|
|
340
|
-
// is granted by a ward, never minted by a being.
|
|
341
|
-
const budget = allow(wanted);
|
|
342
|
-
const by = (await beingKey(unhex(keys.current))).pk;
|
|
343
|
-
// A public being is reached without a heir, and the door keeps nothing for
|
|
344
|
-
// whoever asked: no key it vouched for, so nothing to announce and nothing
|
|
345
|
-
// to rotate to. A standing on her signs with one key for life. Every other
|
|
346
|
-
// ask announces its next: there is no send that does not.
|
|
347
|
-
if (keys.next === null && keys.heir !== null) keys.next = (await this.#mintKey(bind, row)).seed;
|
|
348
|
-
const next = keys.next === null ? null : (await beingKey(unhex(keys.next))).pk;
|
|
349
|
-
// Her count for this relation, one higher every call and never reused. The
|
|
350
|
-
// far door honours each number once. One relation sends one at a time, so
|
|
351
|
-
// the numbers leave in the order they were taken.
|
|
352
|
-
const seq = (keys.seq += 1);
|
|
353
|
-
// Named here, before the bytes go out, and not at the end with the keys.
|
|
354
|
-
// The far door honours a number once, and it honours it before this side
|
|
355
|
-
// hears anything back: a reply that is lost, a wait that ran out, a road
|
|
356
|
-
// that took the bytes and said nothing, all end this call before the end
|
|
357
|
-
// of it is reached. A count named only there is a count a restart forgets
|
|
358
|
-
// while the door remembers, and she then speaks a number that door has
|
|
359
|
-
// already spent, which it refuses as the replay it cannot tell it from.
|
|
360
|
-
// So the rule is the send and not the reply: what she spoke under is
|
|
361
|
-
// written down because she spoke it.
|
|
362
|
-
this.#wrote(row);
|
|
363
|
-
// Args that will not go through the seal are args that never left. A
|
|
364
|
-
// being holds three answers and a throw is not one of them, so an ask
|
|
365
|
-
// that cannot be sealed is told what is true of it: no door was reached.
|
|
366
|
-
// One object of values, by the rule her cells keep and the far door
|
|
367
|
-
// reads: a Date would arrive as a string, a function would not arrive at
|
|
368
|
-
// all, a list or a string would be refused as a stranger's bytes, and
|
|
369
|
-
// she would never know the far being was asked something other than
|
|
370
|
-
// what she said.
|
|
371
|
-
if (args === null || typeof args !== 'object' || Array.isArray(args) || cellFault(args, 'args') !== null) return unreached();
|
|
372
|
-
let sealed;
|
|
221
|
+
#bear(key: string, row: BeingRow, C: BeingClass): boolean {
|
|
222
|
+
const stance = new WardStance(this, key, row);
|
|
373
223
|
try {
|
|
374
|
-
|
|
224
|
+
this.#residents.set(key, { being: new C(stance), row, stance });
|
|
225
|
+
return true;
|
|
375
226
|
} catch {
|
|
376
|
-
|
|
227
|
+
this.#residents.delete(key);
|
|
228
|
+
return false;
|
|
377
229
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
// What comes back late is not read.
|
|
387
|
-
//
|
|
388
|
-
// A wait that ran out is `late`, never unreached. Unreached promises
|
|
389
|
-
// nothing was delivered and is safe to retry; a bound that expired knows
|
|
390
|
-
// no such thing, because the far door may have heard and be working still.
|
|
391
|
-
//
|
|
392
|
-
// A harbor answers bytes or nothing. One that throws instead has answered
|
|
393
|
-
// nothing in a louder voice, and is read as nothing: no door was reached.
|
|
394
|
-
const carried = (keys.ward === this.pk ? this.door(new Uint8Array(bytes)).then((r) => r.bytes) : this.g.carry(keys.ward, bytes)).then(
|
|
395
|
-
(b) => b,
|
|
396
|
-
() => undefined,
|
|
397
|
-
);
|
|
398
|
-
const out = await within(budget.time, carried);
|
|
399
|
-
if (out === LATE) return word('late');
|
|
400
|
-
if (out === undefined) return unreached(); // nothing came back. no door was reached.
|
|
401
|
-
const reply = await openReply(out, ephemeral, wardSignPk(keys.ward));
|
|
402
|
-
if (!reply) return silence; // did not open, or not from that ward: not Quo's bytes
|
|
403
|
-
if ('object' in reply && keys.next !== null) {
|
|
404
|
-
keys.current = keys.next; // the far door holds `next` as announced. move to it.
|
|
405
|
-
keys.next = null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#unboot(key: string): boolean {
|
|
233
|
+
const row = key === WARD ? undefined : this.partition.being(key);
|
|
234
|
+
if (!row) return false;
|
|
235
|
+
for (const { heir } of Object.values(row.occupants)) {
|
|
236
|
+
this.door.remove(heir);
|
|
237
|
+
delete this.partition.head.bind[heir];
|
|
406
238
|
}
|
|
407
|
-
this
|
|
408
|
-
|
|
239
|
+
if (this.partition.head.public === key) this.partition.head.public = null;
|
|
240
|
+
delete this.partition.beings[key];
|
|
241
|
+
this.#residents.delete(key);
|
|
242
|
+
this.partition.toldHead();
|
|
243
|
+
this.partition.told(key);
|
|
244
|
+
return true;
|
|
409
245
|
}
|
|
246
|
+
|
|
247
|
+
// What the ward-being asks of her ward.
|
|
248
|
+
readonly #steward: Steward = {
|
|
249
|
+
pk: () => this.pk,
|
|
250
|
+
classOf: (name) => this.#parts.catalogue.classOf(name),
|
|
251
|
+
beings: () => {
|
|
252
|
+
const out: ReturnType<Steward['beings']> = {};
|
|
253
|
+
for (const [key, row] of Object.entries(this.partition.beings)) {
|
|
254
|
+
if (key !== WARD) out[key] = { class: row.class, public: this.partition.head.public === key, absent: !this.#residents.has(key) };
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
},
|
|
258
|
+
boot: (key, className) => this.#make(key, className),
|
|
259
|
+
unboot: (key) => this.#unboot(key),
|
|
260
|
+
publish: (key) => {
|
|
261
|
+
if (key !== null && (key === WARD || !this.partition.being(key))) return false;
|
|
262
|
+
this.partition.head.public = key;
|
|
263
|
+
this.partition.toldHead();
|
|
264
|
+
return true;
|
|
265
|
+
},
|
|
266
|
+
invite: (being, id, notes): Promise<Invitation | null> => {
|
|
267
|
+
const resident = being === WARD ? undefined : this.#residents.get(being);
|
|
268
|
+
return resident ? resident.stance.occupants.invite(id, notes) : Promise.resolve(null);
|
|
269
|
+
},
|
|
270
|
+
ask: (being, method, args) => {
|
|
271
|
+
const key = being ?? this.partition.head.public;
|
|
272
|
+
if (key === null || key === WARD) return Promise.resolve(word('unreached'));
|
|
273
|
+
return this.#answer(key, being === null ? {} : { id: OWNER }, method, args);
|
|
274
|
+
},
|
|
275
|
+
};
|
|
410
276
|
}
|