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/dist/ward/stance.js
CHANGED
|
@@ -1,413 +1,180 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { allow,
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// a
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// function here would leave as an ask with no method: the empty ask. She
|
|
25
|
-
// would have asked for work, been handed a blueprint, and spent a number on
|
|
26
|
-
// it. Nothing that cannot be sent is sent, and what never left is unreached,
|
|
27
|
-
// which is the one answer that says asking again is safe. This is the same
|
|
28
|
-
// rule args are already held to.
|
|
29
|
-
const named = (method) => method === undefined || typeof method === 'string';
|
|
30
|
-
const reserved = (id) => RESERVED_IDS.includes(id) || CALLS.includes(id) || unkeepable(id);
|
|
31
|
-
// The name of one relation, as her side files it. A relation is a ward and
|
|
32
|
-
// a heir, never a heir alone: the heir pk is outer, it rides in the clear on
|
|
33
|
-
// every lid, and anyone who reads one can quote it back inside an invitation
|
|
34
|
-
// naming a ward of their own. Filed under the heir alone, that invitation
|
|
35
|
-
// would share her knock record with the relation the heir really belongs to:
|
|
36
|
-
// it would sign with that relation's key, and an answer to it would set the
|
|
37
|
-
// answered flag the other relation is taken on. Both halves, always.
|
|
38
|
-
const nameOf = (inv) => (inv.heir === undefined ? `public:${inv.ward}` : `${inv.ward}:${inv.heir}`);
|
|
39
|
-
const valid = (inv) => {
|
|
40
|
-
const i = inv;
|
|
41
|
-
if (!i || typeof i !== 'object' || !isWardPk(i.ward))
|
|
42
|
-
return false;
|
|
43
|
-
if (i.heir === undefined)
|
|
44
|
-
return i.secret === undefined;
|
|
45
|
-
return isHex(i.heir) && isHex(i.secret);
|
|
46
|
-
};
|
|
47
|
-
// One relation, one ask at a time. The rotation is a conversation: a send
|
|
48
|
-
// reads the key that speaks now, announces the next, and moves to it once
|
|
49
|
-
// the far door has answered. Two sends interleaving on one relation read
|
|
50
|
-
// each other's half-written keys and the two sides diverge, which no later
|
|
51
|
-
// ask can repair. So each relation has a lane, and a send waits for the one
|
|
52
|
-
// before it. Beings may still ask concurrently: the lanes are per relation,
|
|
53
|
-
// and a slow relation never holds up another.
|
|
54
|
-
const lanes = new Map();
|
|
55
|
-
const lane = (name, run) => {
|
|
56
|
-
const mine = (lanes.get(name) ?? Promise.resolve()).then(run, run);
|
|
57
|
-
const quiet = mine.then(() => { }, () => { });
|
|
58
|
-
lanes.set(name, quiet);
|
|
59
|
-
void quiet.then(() => {
|
|
60
|
-
if (lanes.get(name) === quiet)
|
|
61
|
-
lanes.delete(name); // the lane is idle. nothing to remember.
|
|
2
|
+
// One being's stance: her cells, the ids she invited, the relations she
|
|
3
|
+
// holds, and boot. Every relation she holds speaks through `quo/`'s
|
|
4
|
+
// standing, one send at a time, inside her allowance.
|
|
5
|
+
import { readValue, writeValue } from '../crypto/index.js';
|
|
6
|
+
import { readInvitation, Standing as Wire, freshStanding } from '../quo/index.js';
|
|
7
|
+
import { OWNER, silence, word } from '../being/index.js';
|
|
8
|
+
import { allow, Bell, LATE } from './allowance.js';
|
|
9
|
+
import { Cells, DEPTH } from './cells.js';
|
|
10
|
+
export { DEPTH };
|
|
11
|
+
const RESERVED = new Set([OWNER]);
|
|
12
|
+
// How many times a take asks again after its knock brought no object.
|
|
13
|
+
const TAKES = 2;
|
|
14
|
+
// One relation, one send at a time.
|
|
15
|
+
class Lanes {
|
|
16
|
+
#tails = new Map();
|
|
17
|
+
run(name, work) {
|
|
18
|
+
const mine = (this.#tails.get(name) ?? Promise.resolve()).then(work, work);
|
|
19
|
+
const tail = mine.catch(() => { });
|
|
20
|
+
this.#tails.set(name, tail);
|
|
21
|
+
void tail.then(() => {
|
|
22
|
+
if (this.#tails.get(name) === tail)
|
|
23
|
+
this.#tails.delete(name);
|
|
62
24
|
});
|
|
63
25
|
return mine;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// door heard, spent the heir and rotated to the key she announced, or
|
|
132
|
-
// nothing arrived and the heir still stands. Only the far door knows,
|
|
133
|
-
// and it will not say.
|
|
134
|
-
//
|
|
135
|
-
// So she asks, and the asking tells her. Under her own key: if the door
|
|
136
|
-
// heard, that key is the one it admits, and she is answered, or told the
|
|
137
|
-
// door's word. If it did not, the key means nothing there, the ask is
|
|
138
|
-
// refused, and a refusal at that door writes nothing: the heir is
|
|
139
|
-
// untouched and still speaks. Then she knocks as the heir, as she would
|
|
140
|
-
// have. One extra round trip in the one case where a reply was lost, and
|
|
141
|
-
// the relation is not stranded.
|
|
142
|
-
//
|
|
143
|
-
// No stranger gains anything by it: whoever holds the invitation could
|
|
144
|
-
// always knock as the heir, and her own key is admitted only where the
|
|
145
|
-
// door already bound it to her.
|
|
146
|
-
if (inv.heir !== undefined && !k.spoke && k.sent) {
|
|
147
|
-
const own = { ward: inv.ward, heir: inv.heir, current: k.current, next: k.next, seq: k.seq };
|
|
148
|
-
const r = read(await inside.send(bind, own, method, args, wanted));
|
|
149
|
-
k.seq = own.seq;
|
|
150
|
-
k.current = own.current;
|
|
151
|
-
k.next = own.next;
|
|
152
|
-
inside.wrote();
|
|
153
|
-
if (!r.door) {
|
|
154
|
-
// Nothing the door said. Unreached and late are hers to hear now; bytes
|
|
155
|
-
// that were not Quo's leave her where she was, and the heir is tried.
|
|
156
|
-
if (!isSilence(r.answer))
|
|
157
|
-
return r.answer;
|
|
158
|
-
}
|
|
159
|
-
else if (!isSilence(r.answer)) {
|
|
160
|
-
// The door spoke to her own key, so it heard the first knock: this is
|
|
161
|
-
// the answer, or the door's word for her. Its silence alone cannot be
|
|
162
|
-
// told from a refusal, so silence is followed by the knock as the heir,
|
|
163
|
-
// which the door then refuses if it had heard, and she hears silence.
|
|
164
|
-
k.spoke = true;
|
|
165
|
-
if (answered(r))
|
|
166
|
-
bind.answered[name] = true;
|
|
167
|
-
return r.answer;
|
|
168
|
-
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const answerOf = (read) => {
|
|
29
|
+
if ('nothing' in read)
|
|
30
|
+
return word('unreached');
|
|
31
|
+
if ('silence' in read)
|
|
32
|
+
return silence;
|
|
33
|
+
if ('quo' in read)
|
|
34
|
+
return word(read.quo);
|
|
35
|
+
return readValue(read.object, DEPTH) ?? silence;
|
|
36
|
+
};
|
|
37
|
+
export class WardStance {
|
|
38
|
+
key;
|
|
39
|
+
occupants;
|
|
40
|
+
standings;
|
|
41
|
+
#inside;
|
|
42
|
+
#row;
|
|
43
|
+
#lanes = new Lanes();
|
|
44
|
+
#cells = new Cells(() => this.#told());
|
|
45
|
+
constructor(inside, key, row) {
|
|
46
|
+
this.key = key;
|
|
47
|
+
this.#inside = inside;
|
|
48
|
+
this.#row = row;
|
|
49
|
+
this.occupants = {
|
|
50
|
+
invite: (id, notes) => this.#invite(id, notes),
|
|
51
|
+
notes: (id) => (Object.hasOwn(row.occupants, id) ? row.occupants[id].notes : undefined),
|
|
52
|
+
ids: () => Object.keys(row.occupants),
|
|
53
|
+
remove: (id) => this.#removeOccupant(id),
|
|
54
|
+
};
|
|
55
|
+
this.standings = {
|
|
56
|
+
take: (id, invitation) => this.#take(id, invitation),
|
|
57
|
+
get: (id) => this.#standing(id),
|
|
58
|
+
ids: () => Object.keys(row.standings),
|
|
59
|
+
remove: (id) => this.#removeStanding(id),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
get cells() {
|
|
63
|
+
return this.#cells.guard(this.#row.cells);
|
|
64
|
+
}
|
|
65
|
+
get #live() {
|
|
66
|
+
return this.#inside.live(this.key, this.#row);
|
|
67
|
+
}
|
|
68
|
+
boot(className, key, id) {
|
|
69
|
+
return this.#live ? this.#inside.boot(this.key, className, key, id) : Promise.resolve(null);
|
|
70
|
+
}
|
|
71
|
+
async lend(contract, id) {
|
|
72
|
+
if (!this.#free(id, this.#row.standings))
|
|
73
|
+
return null;
|
|
74
|
+
const invitation = await this.#inside.offer(contract.name);
|
|
75
|
+
if (invitation === null)
|
|
76
|
+
return null;
|
|
77
|
+
const taken = await this.#take(id, invitation);
|
|
78
|
+
if (taken === null)
|
|
79
|
+
await this.#inside.retract(contract.name, invitation.heir);
|
|
80
|
+
return taken;
|
|
81
|
+
}
|
|
82
|
+
#free(id, of) {
|
|
83
|
+
return this.#live && typeof id === 'string' && !RESERVED.has(id) && !Object.hasOwn(of, id);
|
|
84
|
+
}
|
|
85
|
+
async #invite(id, notes = {}) {
|
|
86
|
+
const kept = writeValue(notes, DEPTH);
|
|
87
|
+
if (!this.#free(id, this.#row.occupants) || kept === undefined)
|
|
88
|
+
return null;
|
|
89
|
+
const { heir, invitation } = await this.#inside.door.invite();
|
|
90
|
+
if (!this.#free(id, this.#row.occupants)) {
|
|
91
|
+
this.#inside.door.remove(heir);
|
|
92
|
+
return null;
|
|
169
93
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
94
|
+
this.#row.occupants[id] = { heir, notes: JSON.parse(kept) };
|
|
95
|
+
this.#inside.partition.head.bind[heir] = { being: this.key, id };
|
|
96
|
+
this.#told();
|
|
97
|
+
this.#inside.partition.toldHead();
|
|
98
|
+
return invitation;
|
|
99
|
+
}
|
|
100
|
+
#removeOccupant(id) {
|
|
101
|
+
if (!this.#live || !Object.hasOwn(this.#row.occupants, id))
|
|
102
|
+
return false;
|
|
103
|
+
const { heir } = this.#row.occupants[id];
|
|
104
|
+
this.#inside.door.remove(heir);
|
|
105
|
+
delete this.#row.occupants[id];
|
|
106
|
+
delete this.#inside.partition.head.bind[heir];
|
|
107
|
+
this.#told();
|
|
108
|
+
this.#inside.partition.toldHead();
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
async #take(id, value) {
|
|
112
|
+
const invitation = readInvitation(value);
|
|
113
|
+
if (!invitation || !this.#free(id, this.#row.standings))
|
|
114
|
+
return null;
|
|
115
|
+
const wire = new Wire(invitation, freshStanding(), this.#inside.draw);
|
|
116
|
+
const read = await this.#lanes.run(`take:${id}`, async () => {
|
|
117
|
+
// A knock that brought nothing back may have bound: the standing asks
|
|
118
|
+
// under its own key, then knocks again as the same bytes.
|
|
119
|
+
let out = await this.#send(wire);
|
|
120
|
+
for (let tries = 0; tries < TAKES && ('nothing' in out || 'silence' in out); tries += 1)
|
|
121
|
+
out = await this.#send(wire);
|
|
122
|
+
return out;
|
|
123
|
+
});
|
|
124
|
+
if (!('object' in read) || !this.#free(id, this.#row.standings))
|
|
125
|
+
return null;
|
|
126
|
+
this.#row.standings[id] = { invitation, state: wire.state, seen: read.seen };
|
|
127
|
+
this.#told();
|
|
128
|
+
return id;
|
|
129
|
+
}
|
|
130
|
+
#removeStanding(id) {
|
|
131
|
+
if (!this.#live || !Object.hasOwn(this.#row.standings, id))
|
|
132
|
+
return false;
|
|
133
|
+
delete this.#row.standings[id];
|
|
134
|
+
this.#told();
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
#standing(id) {
|
|
138
|
+
if (!Object.hasOwn(this.#row.standings, id))
|
|
139
|
+
return undefined;
|
|
140
|
+
return { id, ask: (method, args, wanted) => this.#ask(id, method, args, wanted) };
|
|
141
|
+
}
|
|
142
|
+
async #ask(id, method, args = {}, wanted) {
|
|
143
|
+
const text = writeValue(args, DEPTH + 1);
|
|
144
|
+
if ((method !== undefined && typeof method !== 'string') || text === undefined || Array.isArray(args))
|
|
145
|
+
return word('unreached');
|
|
146
|
+
const bell = new Bell(this.#inside.clock, allow(wanted));
|
|
147
|
+
try {
|
|
148
|
+
const out = await bell.race(this.#lanes.run(`ask:${id}`, async () => {
|
|
149
|
+
const row = Object.hasOwn(this.#row.standings, id) ? this.#row.standings[id] : undefined;
|
|
150
|
+
if (!this.#live || row === undefined)
|
|
151
|
+
return word('dropped');
|
|
152
|
+
if (bell.rang)
|
|
153
|
+
return word('late');
|
|
154
|
+
const wire = new Wire(row.invitation, row.state, this.#inside.draw);
|
|
155
|
+
const read = await bell.race(this.#send(wire, method, text));
|
|
156
|
+
row.state = wire.state;
|
|
157
|
+
if (read === LATE)
|
|
158
|
+
return word('late');
|
|
159
|
+
if ('object' in read)
|
|
160
|
+
row.seen = read.seen;
|
|
161
|
+
this.#told();
|
|
162
|
+
await this.#inside.partition.kept();
|
|
163
|
+
return answerOf(read);
|
|
164
|
+
}));
|
|
165
|
+
return out === LATE ? word('late') : out;
|
|
182
166
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (answered(r))
|
|
186
|
-
bind.answered[name] = true;
|
|
187
|
-
inside.wrote();
|
|
188
|
-
return r.answer;
|
|
189
|
-
};
|
|
190
|
-
// The id she took this relation under, if she took it. One relation is one
|
|
191
|
-
// ward and one heir whichever door it is reached by, so the standing and the
|
|
192
|
-
// invitation answer to the same name.
|
|
193
|
-
const keyName = (k) => (k.heir === null ? `public:${k.ward}` : `${k.ward}:${k.heir}`);
|
|
194
|
-
const takenAs = (name) => Object.keys(bind.standings).find((id) => keyName(bind.standings[id]) === name);
|
|
195
|
-
// A being the ward has taken out reaches nothing through the stance she
|
|
196
|
-
// still holds. Her relations went with her, so every one of them reads as
|
|
197
|
-
// dropped, and nothing she asks for is made: a heir written here would name
|
|
198
|
-
// a being no door holds, and an id minted here would be handed to whoever
|
|
199
|
-
// is booted under her key next.
|
|
200
|
-
const gone = () => !inside.live();
|
|
201
|
-
const calls = {
|
|
202
|
-
knock: async (inv, method, args = {}, wanted) => {
|
|
203
|
-
if (gone())
|
|
204
|
-
return word('dropped');
|
|
205
|
-
if (!named(method))
|
|
206
|
-
return unreached();
|
|
207
|
-
if (!valid(inv))
|
|
208
|
-
return word('invitation'); // S1. nothing is sent
|
|
209
|
-
// She may knock again, and after take that knock is an ask: the relation
|
|
210
|
-
// has one home, so it answers on the standing's lane, under the
|
|
211
|
-
// standing's keys and its count. A second lane here would speak for the
|
|
212
|
-
// same relation, and the two would refuse each other.
|
|
213
|
-
const id = takenAs(nameOf(inv));
|
|
214
|
-
if (id !== undefined)
|
|
215
|
-
return standingAt(id).ask(method, args, wanted);
|
|
216
|
-
return bounded(wanted, (live) => lane(`knock:${nameOf(inv)}`, () => (live() ? knocking(inv, method, args, wanted, live) : Promise.resolve(word('late')))));
|
|
217
|
-
},
|
|
218
|
-
// Take consumes. Until now the relation lived in the knock record, under
|
|
219
|
-
// the ward and the heir; from now it lives in the standing, under her id.
|
|
220
|
-
// It never lives in both: two records seeded from one relation are two
|
|
221
|
-
// lanes speaking for one line of keys and one count, and whichever spoke
|
|
222
|
-
// last holds it while the other is refused. She would read that refusal as
|
|
223
|
-
// the far being falling quiet, which is a thing a sovereign being may do,
|
|
224
|
-
// so the loss would look exactly like an answer she is owed nothing of.
|
|
225
|
-
// One relation, one home, and the invitation is spent: knocking it again
|
|
226
|
-
// is another consumed it, and taking it again births nothing.
|
|
227
|
-
//
|
|
228
|
-
// So it is spoken on the relation's lane, like every other word spoken
|
|
229
|
-
// for it. A take reads the count and the keys a knock writes, and copies
|
|
230
|
-
// them into the standing. Off the lane it reads them between a knock's
|
|
231
|
-
// send and its answer: the standing is born holding a number the far door
|
|
232
|
-
// is about to honour and a key it is about to rotate past, and her first
|
|
233
|
-
// ask on it is refused. The relation she took correctly would be dead
|
|
234
|
-
// because a second knock on the same invitation was careless.
|
|
235
|
-
take: async (id, inv) => {
|
|
236
|
-
if (gone() || !valid(inv))
|
|
237
|
-
return null;
|
|
238
|
-
const name = nameOf(inv);
|
|
239
|
-
return lane(`knock:${name}`, () => {
|
|
240
|
-
if (!bind.answered[name])
|
|
241
|
-
return Promise.resolve(null);
|
|
242
|
-
// Standings and occupants are one namespace — invite and take each
|
|
243
|
-
// refuse a name the other holds — so a word reserved in one is
|
|
244
|
-
// reserved in both.
|
|
245
|
-
if (reserved(id) || at(cells.standings, id) || at(cells.occupants, id))
|
|
246
|
-
return Promise.resolve(null);
|
|
247
|
-
const k = bind.knocks[name];
|
|
248
|
-
put(bind.standings, id, { ward: inv.ward, heir: inv.heir ?? null, current: k.current, next: k.next, seq: k.seq }); // the count carries over: the standing goes on where the knock left off
|
|
249
|
-
put(cells.standings, id, { id, digest: null, blueprint: null, seen: null });
|
|
250
|
-
delete bind.knocks[name];
|
|
251
|
-
delete bind.answered[name];
|
|
252
|
-
inside.wrote();
|
|
253
|
-
return Promise.resolve(id);
|
|
254
|
-
});
|
|
255
|
-
},
|
|
256
|
-
remove: (id) => {
|
|
257
|
-
if (gone())
|
|
258
|
-
return;
|
|
259
|
-
drop(cells.standings, id);
|
|
260
|
-
drop(bind.standings, id);
|
|
261
|
-
inside.wrote();
|
|
262
|
-
},
|
|
263
|
-
};
|
|
264
|
-
const standingAt = (id) => ({
|
|
265
|
-
id,
|
|
266
|
-
// The keys are read now, not when the lane reaches this ask: an ask
|
|
267
|
-
// issued while the standing stood is answered even if she drops it in
|
|
268
|
-
// the next line. Waiting for the lane is not a reason to lose it.
|
|
269
|
-
ask: (method, args = {}, wanted) => {
|
|
270
|
-
if (gone())
|
|
271
|
-
return Promise.resolve(word('dropped'));
|
|
272
|
-
if (!named(method))
|
|
273
|
-
return Promise.resolve(unreached());
|
|
274
|
-
const keys = at(bind.standings, id);
|
|
275
|
-
if (!keys)
|
|
276
|
-
return Promise.resolve(word('dropped')); // S2. she dropped it between one line and the next
|
|
277
|
-
const rec = at(cells.standings, id);
|
|
278
|
-
return bounded(wanted, (live) => lane(`ask:${id}`, () => (live() ? asking(id, keys, rec, method, args, wanted, live) : Promise.resolve(word('late')))));
|
|
279
|
-
},
|
|
280
|
-
});
|
|
281
|
-
// The record an ask writes into is the one it was issued on. A standing
|
|
282
|
-
// dropped and re-taken under the same id while the ask was in flight is
|
|
283
|
-
// another relation, and the blueprint that came back is not hers.
|
|
284
|
-
const asking = async (id, keys, rec, method, args, wanted, live = () => true) => {
|
|
285
|
-
const out = await send(keys, method, args, wanted, rec, live);
|
|
286
|
-
if (method === undefined && !isSilence(out) && !isWord(out)) {
|
|
287
|
-
// A blueprint her cells cannot keep, nested past the bound or carrying
|
|
288
|
-
// a key no harbor writes, is not Quo's bytes: silence, and nothing written.
|
|
289
|
-
if (cellFault(out, 'blueprint') !== null)
|
|
290
|
-
return silence;
|
|
291
|
-
if (rec && live() && at(cells.standings, id) === rec) {
|
|
292
|
-
// Read as a blueprint before it is kept as one. A far describe is
|
|
293
|
-
// somebody else's code and may answer anything; what is not a
|
|
294
|
-
// blueprint is not written as one, and she holds no blueprint for
|
|
295
|
-
// that relation rather than one a side would walk and break on. The
|
|
296
|
-
// answer itself is hers either way: it is returned below, unread.
|
|
297
|
-
rec.blueprint = isBlueprint(out) ? out : null;
|
|
298
|
-
rec.digest = rec.seen = await digest(out);
|
|
299
|
-
}
|
|
167
|
+
finally {
|
|
168
|
+
bell.cancel();
|
|
300
169
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// A being may make. The ward refuses what it refuses the owner: a key
|
|
312
|
-
// already booted, a class the harbor does not hold, and a throw at birth
|
|
313
|
-
// leaves the partition as it was. Reaching into another is still the
|
|
314
|
-
// owner's alone: what a maker may have is a relation to what she made,
|
|
315
|
-
// by naming an id, and it is made the way every relation is. The being
|
|
316
|
-
// made mints an occupant for her maker under the maker's own key, so she
|
|
317
|
-
// knows who made her by that name and by nothing else; her maker knocks,
|
|
318
|
-
// takes it under the id she gave, and holds one standing like any other.
|
|
319
|
-
// Nothing half-lives: a relation that could not be made unmakes the
|
|
320
|
-
// being, who is a moment old and known to nobody.
|
|
321
|
-
boot: async (className, made, id) => {
|
|
322
|
-
if (gone())
|
|
323
|
-
return null;
|
|
324
|
-
if (id !== undefined && (reserved(id) || at(cells.standings, id) || at(cells.occupants, id)))
|
|
325
|
-
return null;
|
|
326
|
-
const born = inside.instantiate(className, made);
|
|
327
|
-
if (born === null || id === undefined)
|
|
328
|
-
return born;
|
|
329
|
-
const inv = await inside.relate(born, key);
|
|
330
|
-
if (inv) {
|
|
331
|
-
const out = await calls.knock(inv);
|
|
332
|
-
if (!isSilence(out) && !isWord(out) && (await calls.take(id, inv)) === id)
|
|
333
|
-
return born;
|
|
334
|
-
}
|
|
335
|
-
inside.unmake(born);
|
|
336
|
-
return null;
|
|
337
|
-
},
|
|
338
|
-
occupants: {
|
|
339
|
-
// rotation one: the ward mints the occupant's first key and gives the secret away.
|
|
340
|
-
// it keeps the pk beside the id and nothing else. the invitation IS the key.
|
|
341
|
-
invite: async (id, notes) => {
|
|
342
|
-
if (gone() || reserved(id) || at(cells.occupants, id) || at(cells.standings, id))
|
|
343
|
-
return null;
|
|
344
|
-
// Notes are values, like everything else the partition keeps, and a
|
|
345
|
-
// spread copies one level: an object inside them would stay the
|
|
346
|
-
// caller's, a handle into her cells that writes past the guard and
|
|
347
|
-
// never says `wrote`, so what a restart brought back would not be
|
|
348
|
-
// what she read. Held to the rule and copied whole, or no invitation.
|
|
349
|
-
if (notes !== undefined && cellFault(notes, 'notes') !== null)
|
|
350
|
-
return null;
|
|
351
|
-
// The key first, and nothing written until it exists: a record put
|
|
352
|
-
// before the mint would be a record a remove in the meantime drops
|
|
353
|
-
// with no heir to close, and the heir opened after it would name an
|
|
354
|
-
// id nobody holds, for a later invite under that id to resurrect.
|
|
355
|
-
const k = await inside.mintKey(bind);
|
|
356
|
-
if (reserved(id) || at(cells.occupants, id) || at(cells.standings, id))
|
|
357
|
-
return null; // taken while the key was minted
|
|
358
|
-
// The notes are the terms the inviter minted under, hers to read on her
|
|
359
|
-
// gate. She may write more later; nobody outside ever writes them.
|
|
360
|
-
put(cells.occupants, id, { id, notes: notes ? JSON.parse(JSON.stringify(notes)) : {} });
|
|
361
|
-
inside.openHeir(k.pk, key, id);
|
|
362
|
-
put(bind.occupants, id, k.pk);
|
|
363
|
-
inside.wrote();
|
|
364
|
-
return { ward: inside.pk, heir: k.pk, secret: k.seed };
|
|
365
|
-
},
|
|
366
|
-
remove: (id) => {
|
|
367
|
-
if (gone())
|
|
368
|
-
return;
|
|
369
|
-
drop(cells.occupants, id);
|
|
370
|
-
const heir = at(bind.occupants, id);
|
|
371
|
-
drop(bind.occupants, id);
|
|
372
|
-
if (heir)
|
|
373
|
-
inside.closeHeir(heir); // the heir dies with the id
|
|
374
|
-
inside.wrote();
|
|
375
|
-
},
|
|
376
|
-
},
|
|
377
|
-
standings,
|
|
378
|
-
// A standing at one of the things this device can do. The same three
|
|
379
|
-
// moves her boot makes, with the harbor's root doing the inviting instead
|
|
380
|
-
// of a being of this ward: ask the ground for the name, knock with what
|
|
381
|
-
// comes back, take it under the id she gave. She is handed the id.
|
|
382
|
-
//
|
|
383
|
-
// The invitation never reaches her, and that is the difference between
|
|
384
|
-
// this relation and every other she holds. Her own invitations are hers
|
|
385
|
-
// to give away, because giving one away is giving away her own relation.
|
|
386
|
-
// This one is the device's, minted for this ward alone, and a value she
|
|
387
|
-
// could copy is a capability she could hand to anyone. A being who wants
|
|
388
|
-
// to lend her device access to another does it in the open, by an ask of
|
|
389
|
-
// her own that forwards to this standing, where her gate reads who is
|
|
390
|
-
// asking and she can stop.
|
|
391
|
-
lend: async (name, id) => {
|
|
392
|
-
if (gone() || typeof name !== 'string' || typeof id !== 'string')
|
|
393
|
-
return null;
|
|
394
|
-
if (reserved(id) || at(cells.standings, id) || at(cells.occupants, id))
|
|
395
|
-
return null;
|
|
396
|
-
// Her half runs inside the harbor's call, so a knock that was refused
|
|
397
|
-
// and a take that lost the id both end with the harbor removing what it
|
|
398
|
-
// minted. Nothing half-lives here either.
|
|
399
|
-
const took = await inside.lend(name, async (inv) => {
|
|
400
|
-
// Read again: the ground was awaited, and a line of hers in between
|
|
401
|
-
// may have taken the id. Nothing is written for a relation she cannot
|
|
402
|
-
// hold, and the invitation goes back unspent.
|
|
403
|
-
if (gone() || reserved(id) || at(cells.standings, id) || at(cells.occupants, id))
|
|
404
|
-
return false;
|
|
405
|
-
const out = await calls.knock(inv);
|
|
406
|
-
if (isSilence(out) || isWord(out))
|
|
407
|
-
return false;
|
|
408
|
-
return (await calls.take(id, inv)) === id;
|
|
409
|
-
});
|
|
410
|
-
return took ? id : null;
|
|
411
|
-
},
|
|
412
|
-
};
|
|
170
|
+
}
|
|
171
|
+
async #send(wire, method, args) {
|
|
172
|
+
const sent = await wire.ask(method, args);
|
|
173
|
+
if (!sent)
|
|
174
|
+
return { nothing: true };
|
|
175
|
+
return wire.read(sent, await this.#inside.send(wire.invitation.ward, sent.bytes));
|
|
176
|
+
}
|
|
177
|
+
#told() {
|
|
178
|
+
this.#inside.partition.told(this.key);
|
|
179
|
+
}
|
|
413
180
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Being, type Answer, type Asker, type AskSpec, type BeingClass, type Blueprint, type Invitation, type JsonObject, type Stance } from '../being/index.ts';
|
|
2
|
+
export interface Steward {
|
|
3
|
+
pk(): string;
|
|
4
|
+
classOf(name: string): BeingClass | undefined;
|
|
5
|
+
beings(): Record<string, {
|
|
6
|
+
class: string;
|
|
7
|
+
public: boolean;
|
|
8
|
+
absent: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
boot(key: string, className: string): Promise<'booted' | 'key taken' | 'no such class' | 'threw at birth'>;
|
|
11
|
+
unboot(key: string): boolean;
|
|
12
|
+
publish(key: string | null): boolean;
|
|
13
|
+
invite(being: string, id: string, notes: JsonObject): Promise<Invitation | null>;
|
|
14
|
+
ask(being: string | null, method: string | undefined, args: JsonObject): Promise<Answer>;
|
|
15
|
+
}
|
|
16
|
+
export type WardBeingStance = Stance & {
|
|
17
|
+
readonly steward: Steward;
|
|
18
|
+
};
|
|
19
|
+
export declare const pilots: (asker: Asker, notes: JsonObject | undefined) => boolean;
|
|
20
|
+
export declare const ownerAsk: (description: string, input?: JsonObject) => AskSpec;
|
|
21
|
+
export declare class WardBeing extends Being {
|
|
22
|
+
static asks: Record<string, AskSpec>;
|
|
23
|
+
readonly steward: Steward;
|
|
24
|
+
constructor(stance: WardBeingStance);
|
|
25
|
+
describe(asker: Asker): Blueprint;
|
|
26
|
+
own(args: JsonObject): Promise<JsonObject>;
|
|
27
|
+
disown(args: JsonObject): JsonObject;
|
|
28
|
+
boot(args: JsonObject): Promise<JsonObject>;
|
|
29
|
+
unboot(args: JsonObject): JsonObject;
|
|
30
|
+
public(args: JsonObject): JsonObject;
|
|
31
|
+
invite(args: JsonObject): Promise<JsonObject>;
|
|
32
|
+
ask(args: JsonObject): Promise<JsonObject>;
|
|
33
|
+
}
|