@quo-systems/quo 0.2.9 → 0.2.11
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 +19 -10
- package/SPEC.md +301 -110
- package/dist/being/being.d.ts +2 -2
- package/dist/being/being.js +34 -12
- package/dist/being/digest.js +26 -8
- package/dist/being/index.d.ts +2 -2
- package/dist/being/index.js +2 -2
- package/dist/being/silence.d.ts +2 -0
- package/dist/being/silence.js +12 -0
- package/dist/being/types.d.ts +4 -2
- package/dist/being/types.js +25 -0
- package/dist/conformance/assert.js +40 -6
- package/dist/conformance/beings.d.ts +48 -5
- package/dist/conformance/beings.js +39 -8
- package/dist/conformance/estate.js +110 -21
- package/dist/conformance/index.d.ts +5 -2
- package/dist/conformance/index.js +165 -7
- package/dist/harbor/core.d.ts +5 -2
- package/dist/harbor/core.js +195 -45
- package/dist/harbor/dial.js +32 -15
- package/dist/harbor/index.d.ts +1 -0
- package/dist/harbor/index.js +3 -0
- package/dist/harbor/memory.d.ts +3 -3
- package/dist/harbor/memory.js +7 -12
- package/dist/harbor/reach.js +42 -17
- package/dist/ward/allowance.js +15 -4
- package/dist/ward/arithmetic.d.ts +1 -0
- package/dist/ward/arithmetic.js +22 -6
- package/dist/ward/cells.d.ts +3 -1
- package/dist/ward/cells.js +79 -21
- package/dist/ward/door.d.ts +3 -2
- package/dist/ward/door.js +38 -10
- package/dist/ward/ground.d.ts +5 -1
- package/dist/ward/ground.js +38 -1
- package/dist/ward/heirs.d.ts +2 -3
- package/dist/ward/heirs.js +19 -13
- package/dist/ward/index.d.ts +1 -0
- package/dist/ward/index.js +3 -0
- package/dist/ward/owner.d.ts +6 -27
- package/dist/ward/owner.js +59 -33
- package/dist/ward/partition.d.ts +3 -0
- package/dist/ward/partition.js +109 -4
- package/dist/ward/seal.d.ts +1 -0
- package/dist/ward/seal.js +41 -13
- package/dist/ward/stance.d.ts +7 -3
- package/dist/ward/stance.js +156 -66
- package/dist/ward/ward.d.ts +10 -0
- package/dist/ward/ward.js +123 -51
- package/package.json +4 -2
- package/src/being/being.ts +33 -11
- package/src/being/digest.ts +28 -13
- package/src/being/index.ts +2 -2
- package/src/being/silence.ts +14 -0
- package/src/being/types.ts +39 -5
- package/src/conformance/assert.ts +37 -4
- package/src/conformance/beings.ts +41 -10
- package/src/conformance/estate.ts +107 -20
- package/src/conformance/index.ts +188 -13
- package/src/harbor/core.ts +203 -46
- package/src/harbor/dial.ts +46 -17
- package/src/harbor/index.ts +3 -0
- package/src/harbor/memory.ts +8 -13
- package/src/harbor/reach.ts +47 -21
- package/src/ward/allowance.ts +15 -4
- package/src/ward/arithmetic.ts +25 -8
- package/src/ward/cells.ts +76 -25
- package/src/ward/door.ts +38 -12
- package/src/ward/ground.ts +52 -3
- package/src/ward/heirs.ts +19 -13
- package/src/ward/index.ts +3 -0
- package/src/ward/owner.ts +65 -46
- package/src/ward/partition.ts +109 -5
- package/src/ward/seal.ts +41 -12
- package/src/ward/stance.ts +163 -64
- package/src/ward/ward.ts +124 -52
- package/vectors/arithmetic.json +7 -0
- package/vectors/framing.json +30 -15
- package/vectors/wire.json +4 -4
package/src/ward/stance.ts
CHANGED
|
@@ -1,25 +1,38 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// The one stance builder. Used for every being the ward boots. Nothing
|
|
3
3
|
// outer is in what she holds: ids in, values out, keys in the bind table.
|
|
4
|
-
import { silence, isSilence,
|
|
4
|
+
import { silence, isSilence, isWord, word, wordOf } from '../being/silence.ts';
|
|
5
5
|
import { digest } from '../being/digest.ts';
|
|
6
|
-
import { RESERVED_IDS } from '../being/types.ts';
|
|
6
|
+
import { RESERVED_IDS, isBlueprint } from '../being/types.ts';
|
|
7
7
|
import type { Answer, Cells, Invitation, JsonObject, Silence, Stance, Standing, Standings, Wanted, Word } from '../being/types.ts';
|
|
8
|
-
import { at, put, drop, type Bind, type StandingKeys } from './partition.ts';
|
|
8
|
+
import { at, put, drop, prune, type Bind, type StandingKeys } from './partition.ts';
|
|
9
9
|
import { allow, within, LATE } from './allowance.ts';
|
|
10
|
-
import { isWardPk, type ReplyPayload } from './seal.ts';
|
|
10
|
+
import { isHex, isWardPk, type ReplyPayload } from './seal.ts';
|
|
11
|
+
import { cellFault } from './cells.ts';
|
|
11
12
|
|
|
12
13
|
// What the stance needs from its ward, and no more.
|
|
13
14
|
export type Inside = {
|
|
14
15
|
pk: string;
|
|
16
|
+
// Is the being this stance was built for still one of the ward's? Unboot
|
|
17
|
+
// takes her door and her rows, but a class whose code is still running
|
|
18
|
+
// holds the stance closure, and everything in it still points at live
|
|
19
|
+
// objects. Read before every call, so a being taken out reaches nothing.
|
|
20
|
+
live(): boolean;
|
|
15
21
|
mintKey(bind: Bind): Promise<{ seed: string; pk: string }>; // a fresh signing key, recorded as minted
|
|
16
22
|
openHeir(heir: string, being: string, id: string): void;
|
|
17
23
|
closeHeir(heir: string): void;
|
|
18
24
|
// one send for every destination. the far door's reply, silence, or unreached.
|
|
19
|
-
// signs as keys.current, announces keys.next
|
|
20
|
-
|
|
25
|
+
// signs as keys.current, mints and announces keys.next where the relation has
|
|
26
|
+
// a heir to rotate on, and rotates keys on an answer.
|
|
27
|
+
send(bind: Bind, keys: StandingKeys, method: string | undefined, args: JsonObject, wanted: Wanted | undefined): Promise<ReplyPayload | Silence | Word>;
|
|
21
28
|
// a new being of this ward, as the owner's boot makes one. the key, or null.
|
|
22
|
-
instantiate(
|
|
29
|
+
instantiate(className: string, key: string): string | null;
|
|
30
|
+
// mint an occupant on a being of this ward, for the being who made her.
|
|
31
|
+
relate(key: string, id: string): Promise<Invitation | null>;
|
|
32
|
+
// take a being of this ward out again, with every relation she holds.
|
|
33
|
+
unmake(key: string): void;
|
|
34
|
+
// the bind table was written: a record put, dropped or moved on.
|
|
35
|
+
wrote(): void;
|
|
23
36
|
};
|
|
24
37
|
|
|
25
38
|
// An invitation as it travels: the far ward's pk, the heir pk, the heir's secret seed. Or just the ward, for its public being.
|
|
@@ -45,7 +58,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
45
58
|
const i = inv as SealedInvitation;
|
|
46
59
|
if (!i || typeof i !== 'object' || !isWardPk(i.ward)) return false;
|
|
47
60
|
if (i.heir === undefined) return i.secret === undefined;
|
|
48
|
-
return
|
|
61
|
+
return isHex(i.heir) && isHex(i.secret);
|
|
49
62
|
};
|
|
50
63
|
|
|
51
64
|
// One relation, one ask at a time. The rotation is a conversation: a send
|
|
@@ -72,9 +85,9 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
72
85
|
// Every ask a being makes, bounded. The bound is outside the lane on
|
|
73
86
|
// purpose: an ask that waits its turn behind a relation that comes back
|
|
74
87
|
// round never reaches a send, and a bound that only watched the wire would
|
|
75
|
-
// never see it hang. A bound that ran out is
|
|
76
|
-
// unreached promises nothing was delivered and is safe to ask
|
|
77
|
-
// wait that gave up knows no such thing.
|
|
88
|
+
// never see it hang. A bound that ran out is the word late, never
|
|
89
|
+
// unreached: unreached promises nothing was delivered and is safe to ask
|
|
90
|
+
// again, and a wait that gave up knows no such thing.
|
|
78
91
|
//
|
|
79
92
|
// The bell rings before the work is reached, too: an ask that was still
|
|
80
93
|
// waiting its turn when the wait ran out is never sent. Sent then, it would
|
|
@@ -89,53 +102,96 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
89
102
|
return out === LATE ? word('late') : out;
|
|
90
103
|
};
|
|
91
104
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
// What one send came back as, read in one place for every path that sends.
|
|
106
|
+
// A reply that opened and was signed by the far ward is the door speaking,
|
|
107
|
+
// whatever it said: an object, its silence, or a word. Bytes that were not
|
|
108
|
+
// Quo's, late and unreached are not the door speaking, and say nothing
|
|
109
|
+
// about whether it heard.
|
|
110
|
+
type Read = { answer: Answer; door: boolean; seen: string | null };
|
|
111
|
+
const read = (r: ReplyPayload | Silence | Word): Read => {
|
|
112
|
+
if (isSilence(r) || isWord(r)) return { answer: r, door: false, seen: null };
|
|
113
|
+
if ('silence' in r) return { answer: silence, door: true, seen: null };
|
|
114
|
+
if ('quo' in r) return { answer: word(r.quo), door: true, seen: null }; // the far door's word for her, sealed to her lid
|
|
115
|
+
return { answer: r.object, door: true, seen: r.seen };
|
|
99
116
|
};
|
|
117
|
+
// Whether the door spent the heir this send spoke under. An object, the
|
|
118
|
+
// door's silence and threw all mean it was honoured. A refusal word wrote
|
|
119
|
+
// nothing and says so. Anything that is not the door speaking says nothing.
|
|
120
|
+
const honoured = (r: Read): boolean => r.door && !(isWord(r.answer) && wordOf(r.answer) !== 'threw');
|
|
121
|
+
const answered = (r: Read): boolean => r.door && !isSilence(r.answer) && !isWord(r.answer);
|
|
100
122
|
|
|
101
|
-
|
|
123
|
+
// The ask is bounded twice with one budget, once around the lane and once
|
|
124
|
+
// around the wire, and the two bells are microseconds apart. A reply that
|
|
125
|
+
// lands between them is a reply she was already told was late: its keys are
|
|
126
|
+
// rotated, since the far door rotated too and nothing may un-rotate that,
|
|
127
|
+
// but nothing of it is written into her cells. What she was handed is the
|
|
128
|
+
// last word on what she saw.
|
|
129
|
+
const send = async (keys: StandingKeys, method: string | undefined, args: JsonObject, wanted: Wanted | undefined, rec?: Cells['standings'][string], live: () => boolean = () => true): Promise<Answer> => {
|
|
130
|
+
const r = read(await inside.send(bind, keys, method, args, wanted));
|
|
131
|
+
if (rec && method !== undefined && r.seen !== null && live()) rec.seen = r.seen; // the digest rode along
|
|
132
|
+
return r.answer;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const knocking = async (inv: SealedInvitation, method: string | undefined, args: JsonObject, wanted: Wanted | undefined, live: () => boolean = () => true): Promise<Answer> => {
|
|
102
136
|
const name = nameOf(inv);
|
|
137
|
+
// Taken while this knock waited its turn: the relation lives in the
|
|
138
|
+
// standing now and speaks on its lane, under its keys and its count.
|
|
139
|
+
const taken = takenAs(name);
|
|
140
|
+
if (taken !== undefined) {
|
|
141
|
+
const keys = at(bind.standings, taken);
|
|
142
|
+
if (!keys) return word('dropped');
|
|
143
|
+
return lane(`ask:${taken}`, () => asking(taken, keys, at(cells.standings, taken), method, args, wanted, live));
|
|
144
|
+
}
|
|
103
145
|
// her own key for this relation, minted once: rotation two. reused if she knocks again.
|
|
104
146
|
const k = (bind.knocks[name] ??= { current: (await inside.mintKey(bind)).seed, next: null, spoke: false, sent: false, seq: 0 });
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// spent
|
|
109
|
-
//
|
|
147
|
+
prune(bind.knocks, bind.answered); // this one is the newest, so it stays and the oldest never answered goes
|
|
148
|
+
// The first knock on a heir speaks as the heir, the key the inviter
|
|
149
|
+
// handed out, and announces her own. Once the door has honoured it the
|
|
150
|
+
// heir is spent, whatever she then said, and her own key speaks; a heir
|
|
151
|
+
// spent once stays spent.
|
|
152
|
+
//
|
|
153
|
+
// She sent once as the heir and the door did not speak: nothing came
|
|
154
|
+
// back, or bytes that were not Quo's, or the wait ran out. Either the
|
|
155
|
+
// door heard, spent the heir and rotated to the key she announced, or
|
|
156
|
+
// nothing arrived and the heir still stands. Only the far door knows,
|
|
157
|
+
// and it will not say.
|
|
110
158
|
//
|
|
111
159
|
// So she asks, and the asking tells her. Under her own key: if the door
|
|
112
|
-
// heard, that key is the one it admits, and she is answered
|
|
113
|
-
// not, the key means nothing there, the ask is
|
|
114
|
-
// that door writes nothing
|
|
115
|
-
// she knocks as the heir, as she would
|
|
116
|
-
// one case where a reply was lost, and
|
|
160
|
+
// heard, that key is the one it admits, and she is answered, or told the
|
|
161
|
+
// door's word. If it did not, the key means nothing there, the ask is
|
|
162
|
+
// refused, and a refusal at that door writes nothing: the heir is
|
|
163
|
+
// untouched and still speaks. Then she knocks as the heir, as she would
|
|
164
|
+
// have. One extra round trip in the one case where a reply was lost, and
|
|
165
|
+
// the relation is not stranded.
|
|
117
166
|
//
|
|
118
167
|
// No stranger gains anything by it: whoever holds the invitation could
|
|
119
168
|
// always knock as the heir, and her own key is admitted only where the
|
|
120
169
|
// door already bound it to her.
|
|
121
170
|
if (inv.heir !== undefined && !k.spoke && k.sent) {
|
|
122
171
|
const own: StandingKeys = { ward: inv.ward, heir: inv.heir, current: k.current, next: k.next, seq: k.seq };
|
|
123
|
-
const r = await inside.send(bind, own,
|
|
172
|
+
const r = read(await inside.send(bind, own, method, args, wanted));
|
|
124
173
|
k.seq = own.seq;
|
|
125
174
|
k.current = own.current;
|
|
126
175
|
k.next = own.next;
|
|
127
|
-
|
|
128
|
-
if (!
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return r.
|
|
176
|
+
inside.wrote();
|
|
177
|
+
if (!r.door) {
|
|
178
|
+
// Nothing the door said. Unreached and late are hers to hear now; bytes
|
|
179
|
+
// that were not Quo's leave her where she was, and the heir is tried.
|
|
180
|
+
if (!isSilence(r.answer)) return r.answer;
|
|
181
|
+
} else if (!isSilence(r.answer)) {
|
|
182
|
+
// The door spoke to her own key, so it heard the first knock: this is
|
|
183
|
+
// the answer, or the door's word for her. Its silence alone cannot be
|
|
184
|
+
// told from a refusal, so silence is followed by the knock as the heir,
|
|
185
|
+
// which the door then refuses if it had heard, and she hears silence.
|
|
186
|
+
k.spoke = true;
|
|
187
|
+
if (answered(r)) bind.answered[name] = true;
|
|
188
|
+
return r.answer;
|
|
132
189
|
}
|
|
133
|
-
// Refused. The door never heard her, so the heir is still hers to spend.
|
|
134
190
|
}
|
|
135
191
|
|
|
136
192
|
const first = inv.heir !== undefined && !k.spoke;
|
|
137
193
|
const keys: StandingKeys = first ? { ward: inv.ward, heir: inv.heir!, current: inv.secret!, next: k.current, seq: k.seq } : { ward: inv.ward, heir: inv.heir ?? null, current: k.current, next: k.next, seq: k.seq };
|
|
138
|
-
const r = await inside.send(bind, keys,
|
|
194
|
+
const r = read(await inside.send(bind, keys, method, args, wanted));
|
|
139
195
|
k.sent = true; // bytes went out. whether they arrived is the far door's to know.
|
|
140
196
|
// The number she spoke under, and the key she announced, kept whoever
|
|
141
197
|
// answered and kept when nobody did. A reply lost on the way back is a
|
|
@@ -146,16 +202,10 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
146
202
|
k.current = keys.current;
|
|
147
203
|
k.next = keys.next;
|
|
148
204
|
}
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
k.spoke = !(!isSilence(r) && !isWord(r) && 'quo' in r && r.quo !== 'threw');
|
|
154
|
-
if (isSilence(r) || isWord(r)) return r;
|
|
155
|
-
if ('silence' in r) return silence;
|
|
156
|
-
if ('quo' in r) return word(r.quo);
|
|
157
|
-
bind.answered[name] = true;
|
|
158
|
-
return r.object;
|
|
205
|
+
if (honoured(r)) k.spoke = true;
|
|
206
|
+
if (answered(r)) bind.answered[name] = true;
|
|
207
|
+
inside.wrote();
|
|
208
|
+
return r.answer;
|
|
159
209
|
};
|
|
160
210
|
|
|
161
211
|
// The id she took this relation under, if she took it. One relation is one
|
|
@@ -164,16 +214,24 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
164
214
|
const keyName = (k: StandingKeys) => (k.heir === null ? `public:${k.ward}` : `${k.ward}:${k.heir}`);
|
|
165
215
|
const takenAs = (name: string): string | undefined => Object.keys(bind.standings).find((id) => keyName(bind.standings[id]) === name);
|
|
166
216
|
|
|
217
|
+
// A being the ward has taken out reaches nothing through the stance she
|
|
218
|
+
// still holds. Her relations went with her, so every one of them reads as
|
|
219
|
+
// dropped, and nothing she asks for is made: a heir written here would name
|
|
220
|
+
// a being no door holds, and an id minted here would be handed to whoever
|
|
221
|
+
// is booted under her key next.
|
|
222
|
+
const gone = () => !inside.live();
|
|
223
|
+
|
|
167
224
|
const calls = {
|
|
168
225
|
knock: async (inv: Invitation, method?: string, args: JsonObject = {}, wanted?: Wanted): Promise<Answer> => {
|
|
226
|
+
if (gone()) return word('dropped');
|
|
169
227
|
if (!valid(inv)) return word('invitation'); // S1. nothing is sent
|
|
170
228
|
// She may knock again, and after take that knock is an ask: the relation
|
|
171
229
|
// has one home, so it answers on the standing's lane, under the
|
|
172
230
|
// standing's keys and its count. A second lane here would speak for the
|
|
173
231
|
// same relation, and the two would refuse each other.
|
|
174
232
|
const id = takenAs(nameOf(inv));
|
|
175
|
-
if (id !== undefined) return
|
|
176
|
-
return bounded(wanted, (live) => lane(`knock:${nameOf(inv)}`, () => (live() ? knocking(inv, method, args, wanted) : Promise.resolve(word('late')))));
|
|
233
|
+
if (id !== undefined) return standingAt(id).ask(method, args, wanted);
|
|
234
|
+
return bounded(wanted, (live) => lane(`knock:${nameOf(inv)}`, () => (live() ? knocking(inv, method, args, wanted, live) : Promise.resolve(word('late')))));
|
|
177
235
|
},
|
|
178
236
|
// Take consumes. Until now the relation lived in the knock record, under
|
|
179
237
|
// the ward and the heir; from now it lives in the standing, under her id.
|
|
@@ -193,7 +251,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
193
251
|
// ask on it is refused. The relation she took correctly would be dead
|
|
194
252
|
// because a second knock on the same invitation was careless.
|
|
195
253
|
take: async (id: string, inv: Invitation): Promise<string | null> => {
|
|
196
|
-
if (!valid(inv)) return null;
|
|
254
|
+
if (gone() || !valid(inv)) return null;
|
|
197
255
|
const name = nameOf(inv);
|
|
198
256
|
return lane(`knock:${name}`, () => {
|
|
199
257
|
if (!bind.answered[name]) return Promise.resolve(null);
|
|
@@ -206,12 +264,15 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
206
264
|
put(cells.standings, id, { id, digest: null, blueprint: null, seen: null });
|
|
207
265
|
delete bind.knocks[name];
|
|
208
266
|
delete bind.answered[name];
|
|
267
|
+
inside.wrote();
|
|
209
268
|
return Promise.resolve(id);
|
|
210
269
|
});
|
|
211
270
|
},
|
|
212
271
|
remove: (id: string): void => {
|
|
272
|
+
if (gone()) return;
|
|
213
273
|
drop(cells.standings, id);
|
|
214
274
|
drop(bind.standings, id);
|
|
275
|
+
inside.wrote();
|
|
215
276
|
},
|
|
216
277
|
};
|
|
217
278
|
|
|
@@ -221,20 +282,31 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
221
282
|
// issued while the standing stood is answered even if she drops it in
|
|
222
283
|
// the next line. Waiting for the lane is not a reason to lose it.
|
|
223
284
|
ask: (method?: string, args: JsonObject = {}, wanted?: Wanted): Promise<Answer> => {
|
|
285
|
+
if (gone()) return Promise.resolve(word('dropped') as Answer);
|
|
224
286
|
const keys = at(bind.standings, id);
|
|
225
287
|
if (!keys) return Promise.resolve(word('dropped') as Answer); // S2. she dropped it between one line and the next
|
|
226
288
|
const rec = at(cells.standings, id);
|
|
227
|
-
return bounded(wanted, (live) => lane(`ask:${id}`, () => (live() ? asking(id, keys, rec, method, args, wanted) : Promise.resolve(word('late')))));
|
|
289
|
+
return bounded(wanted, (live) => lane(`ask:${id}`, () => (live() ? asking(id, keys, rec, method, args, wanted, live) : Promise.resolve(word('late')))));
|
|
228
290
|
},
|
|
229
291
|
});
|
|
230
292
|
|
|
231
|
-
|
|
232
|
-
|
|
293
|
+
// The record an ask writes into is the one it was issued on. A standing
|
|
294
|
+
// dropped and re-taken under the same id while the ask was in flight is
|
|
295
|
+
// another relation, and the blueprint that came back is not hers.
|
|
296
|
+
const asking = async (id: string, keys: StandingKeys, rec: Cells['standings'][string] | undefined, method: string | undefined, args: JsonObject, wanted: Wanted | undefined, live: () => boolean = () => true): Promise<Answer> => {
|
|
297
|
+
const out = await send(keys, method, args, wanted, rec, live);
|
|
233
298
|
if (method === undefined && !isSilence(out) && !isWord(out)) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
299
|
+
// A blueprint her cells cannot keep, nested past the bound or carrying
|
|
300
|
+
// a key no harbor writes, is not Quo's bytes: silence, and nothing written.
|
|
301
|
+
if (cellFault(out, 'blueprint') !== null) return silence;
|
|
302
|
+
if (rec && live() && at(cells.standings, id) === rec) {
|
|
303
|
+
// Read as a blueprint before it is kept as one. A far describe is
|
|
304
|
+
// somebody else's code and may answer anything; what is not a
|
|
305
|
+
// blueprint is not written as one, and she holds no blueprint for
|
|
306
|
+
// that relation rather than one a side would walk and break on. The
|
|
307
|
+
// answer itself is hers either way: it is returned below, unread.
|
|
308
|
+
rec.blueprint = isBlueprint(out) ? out : null;
|
|
309
|
+
rec.digest = rec.seen = await digest(out);
|
|
238
310
|
}
|
|
239
311
|
}
|
|
240
312
|
return out;
|
|
@@ -251,26 +323,53 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
251
323
|
cells,
|
|
252
324
|
// A being may make. The ward refuses what it refuses the owner: a key
|
|
253
325
|
// already booted, a class the harbor does not hold, and a throw at birth
|
|
254
|
-
// leaves the partition as it was. Reaching into
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
326
|
+
// leaves the partition as it was. Reaching into another is still the
|
|
327
|
+
// owner's alone: what a maker may have is a relation to what she made,
|
|
328
|
+
// by naming an id, and it is made the way every relation is. The being
|
|
329
|
+
// made mints an occupant for her maker under the maker's own key, so she
|
|
330
|
+
// knows who made her by that name and by nothing else; her maker knocks,
|
|
331
|
+
// takes it under the id she gave, and holds one standing like any other.
|
|
332
|
+
// Nothing half-lives: a relation that could not be made unmakes the
|
|
333
|
+
// being, who is a moment old and known to nobody.
|
|
334
|
+
boot: async (className: string, made: string, id?: string): Promise<string | null> => {
|
|
335
|
+
if (gone()) return null;
|
|
336
|
+
if (id !== undefined && (reserved(id) || at(cells.standings, id) || at(cells.occupants, id))) return null;
|
|
337
|
+
const born = inside.instantiate(className, made);
|
|
338
|
+
if (born === null || id === undefined) return born;
|
|
339
|
+
const inv = await inside.relate(born, key);
|
|
340
|
+
if (inv) {
|
|
341
|
+
const out = await calls.knock(inv);
|
|
342
|
+
if (!isSilence(out) && !isWord(out) && (await calls.take(id, inv)) === id) return born;
|
|
343
|
+
}
|
|
344
|
+
inside.unmake(born);
|
|
345
|
+
return null;
|
|
346
|
+
},
|
|
258
347
|
occupants: {
|
|
259
348
|
// rotation one: the ward mints the occupant's first key and gives the secret away.
|
|
260
349
|
// it keeps the pk beside the id and nothing else. the invitation IS the key.
|
|
261
|
-
invite: async (id: string): Promise<Invitation | null> => {
|
|
262
|
-
if (reserved(id) || at(cells.occupants, id) || at(cells.standings, id)) return null;
|
|
263
|
-
|
|
350
|
+
invite: async (id: string, notes?: JsonObject): Promise<Invitation | null> => {
|
|
351
|
+
if (gone() || reserved(id) || at(cells.occupants, id) || at(cells.standings, id)) return null;
|
|
352
|
+
// The key first, and nothing written until it exists: a record put
|
|
353
|
+
// before the mint would be a record a remove in the meantime drops
|
|
354
|
+
// with no heir to close, and the heir opened after it would name an
|
|
355
|
+
// id nobody holds, for a later invite under that id to resurrect.
|
|
264
356
|
const k = await inside.mintKey(bind);
|
|
357
|
+
if (reserved(id) || at(cells.occupants, id) || at(cells.standings, id)) 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 ? { ...notes } : {} });
|
|
265
361
|
inside.openHeir(k.pk, key, id);
|
|
266
362
|
put(bind.occupants, id, k.pk);
|
|
363
|
+
inside.wrote();
|
|
267
364
|
return { ward: inside.pk, heir: k.pk, secret: k.seed };
|
|
268
365
|
},
|
|
269
366
|
remove: (id: string): void => {
|
|
367
|
+
if (gone()) return;
|
|
270
368
|
drop(cells.occupants, id);
|
|
271
369
|
const heir = at(bind.occupants, id);
|
|
272
370
|
drop(bind.occupants, id);
|
|
273
371
|
if (heir) inside.closeHeir(heir); // the heir dies with the id
|
|
372
|
+
inside.wrote();
|
|
274
373
|
},
|
|
275
374
|
},
|
|
276
375
|
standings,
|