@quo-systems/quo 0.2.10 → 0.2.12
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/SPEC.md +141 -22
- package/dist/being/being.js +7 -1
- package/dist/being/digest.js +15 -1
- 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 +2 -0
- 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 +109 -18
- package/dist/conformance/index.d.ts +5 -2
- package/dist/conformance/index.js +164 -6
- package/dist/harbor/core.d.ts +3 -2
- package/dist/harbor/core.js +86 -25
- package/dist/harbor/dial.js +28 -13
- 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 +6 -12
- package/dist/harbor/reach.js +10 -10
- package/dist/ward/allowance.js +7 -2
- package/dist/ward/arithmetic.d.ts +1 -0
- package/dist/ward/arithmetic.js +11 -3
- package/dist/ward/door.d.ts +2 -2
- package/dist/ward/door.js +14 -5
- package/dist/ward/ground.d.ts +4 -1
- package/dist/ward/ground.js +38 -1
- package/dist/ward/heirs.d.ts +1 -2
- package/dist/ward/heirs.js +13 -14
- package/dist/ward/index.d.ts +1 -0
- package/dist/ward/index.js +3 -0
- package/dist/ward/owner.d.ts +4 -4
- package/dist/ward/owner.js +36 -19
- 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 +38 -13
- package/dist/ward/stance.d.ts +3 -2
- package/dist/ward/stance.js +48 -20
- package/dist/ward/ward.d.ts +1 -1
- package/dist/ward/ward.js +40 -19
- package/package.json +1 -1
- package/src/being/being.ts +7 -1
- package/src/being/digest.ts +15 -2
- package/src/being/index.ts +2 -2
- package/src/being/silence.ts +14 -0
- package/src/being/types.ts +29 -1
- package/src/conformance/assert.ts +37 -4
- package/src/conformance/beings.ts +41 -10
- package/src/conformance/estate.ts +106 -17
- package/src/conformance/index.ts +187 -12
- package/src/harbor/core.ts +98 -25
- package/src/harbor/dial.ts +41 -14
- package/src/harbor/index.ts +3 -0
- package/src/harbor/memory.ts +8 -14
- package/src/harbor/reach.ts +10 -11
- package/src/ward/allowance.ts +7 -2
- package/src/ward/arithmetic.ts +14 -5
- package/src/ward/door.ts +16 -7
- package/src/ward/ground.ts +43 -1
- package/src/ward/heirs.ts +13 -14
- package/src/ward/index.ts +3 -0
- package/src/ward/owner.ts +45 -26
- package/src/ward/partition.ts +109 -5
- package/src/ward/seal.ts +39 -12
- package/src/ward/stance.ts +53 -23
- package/src/ward/ward.ts +46 -25
- package/vectors/framing.json +16 -10
- package/vectors/wire.json +4 -4
package/src/ward/stance.ts
CHANGED
|
@@ -3,24 +3,30 @@
|
|
|
3
3
|
// outer is in what she holds: ids in, values out, keys in the bind table.
|
|
4
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
11
|
import { cellFault } from './cells.ts';
|
|
12
12
|
|
|
13
13
|
// What the stance needs from its ward, and no more.
|
|
14
14
|
export type Inside = {
|
|
15
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;
|
|
16
21
|
mintKey(bind: Bind): Promise<{ seed: string; pk: string }>; // a fresh signing key, recorded as minted
|
|
17
22
|
openHeir(heir: string, being: string, id: string): void;
|
|
18
23
|
closeHeir(heir: string): void;
|
|
19
24
|
// one send for every destination. the far door's reply, silence, or unreached.
|
|
20
|
-
// signs as keys.current, announces keys.next
|
|
21
|
-
|
|
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>;
|
|
22
28
|
// a new being of this ward, as the owner's boot makes one. the key, or null.
|
|
23
|
-
instantiate(
|
|
29
|
+
instantiate(className: string, key: string): string | null;
|
|
24
30
|
// mint an occupant on a being of this ward, for the being who made her.
|
|
25
31
|
relate(key: string, id: string): Promise<Invitation | null>;
|
|
26
32
|
// take a being of this ward out again, with every relation she holds.
|
|
@@ -52,7 +58,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
52
58
|
const i = inv as SealedInvitation;
|
|
53
59
|
if (!i || typeof i !== 'object' || !isWardPk(i.ward)) return false;
|
|
54
60
|
if (i.heir === undefined) return i.secret === undefined;
|
|
55
|
-
return
|
|
61
|
+
return isHex(i.heir) && isHex(i.secret);
|
|
56
62
|
};
|
|
57
63
|
|
|
58
64
|
// One relation, one ask at a time. The rotation is a conversation: a send
|
|
@@ -114,13 +120,19 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
114
120
|
const honoured = (r: Read): boolean => r.door && !(isWord(r.answer) && wordOf(r.answer) !== 'threw');
|
|
115
121
|
const answered = (r: Read): boolean => r.door && !isSilence(r.answer) && !isWord(r.answer);
|
|
116
122
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
120
132
|
return r.answer;
|
|
121
133
|
};
|
|
122
134
|
|
|
123
|
-
const knocking = async (inv: SealedInvitation, method: string | undefined, args: JsonObject, wanted: Wanted | undefined): Promise<Answer> => {
|
|
135
|
+
const knocking = async (inv: SealedInvitation, method: string | undefined, args: JsonObject, wanted: Wanted | undefined, live: () => boolean = () => true): Promise<Answer> => {
|
|
124
136
|
const name = nameOf(inv);
|
|
125
137
|
// Taken while this knock waited its turn: the relation lives in the
|
|
126
138
|
// standing now and speaks on its lane, under its keys and its count.
|
|
@@ -128,10 +140,11 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
128
140
|
if (taken !== undefined) {
|
|
129
141
|
const keys = at(bind.standings, taken);
|
|
130
142
|
if (!keys) return word('dropped');
|
|
131
|
-
return lane(`ask:${taken}`, () => asking(taken, keys, at(cells.standings, taken), method, args, wanted));
|
|
143
|
+
return lane(`ask:${taken}`, () => asking(taken, keys, at(cells.standings, taken), method, args, wanted, live));
|
|
132
144
|
}
|
|
133
145
|
// her own key for this relation, minted once: rotation two. reused if she knocks again.
|
|
134
146
|
const k = (bind.knocks[name] ??= { current: (await inside.mintKey(bind)).seed, next: null, spoke: false, sent: false, seq: 0 });
|
|
147
|
+
prune(bind.knocks, bind.answered); // this one is the newest, so it stays and the oldest never answered goes
|
|
135
148
|
// The first knock on a heir speaks as the heir, the key the inviter
|
|
136
149
|
// handed out, and announces her own. Once the door has honoured it the
|
|
137
150
|
// heir is spent, whatever she then said, and her own key speaks; a heir
|
|
@@ -156,7 +169,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
156
169
|
// door already bound it to her.
|
|
157
170
|
if (inv.heir !== undefined && !k.spoke && k.sent) {
|
|
158
171
|
const own: StandingKeys = { ward: inv.ward, heir: inv.heir, current: k.current, next: k.next, seq: k.seq };
|
|
159
|
-
const r = read(await inside.send(bind, own,
|
|
172
|
+
const r = read(await inside.send(bind, own, method, args, wanted));
|
|
160
173
|
k.seq = own.seq;
|
|
161
174
|
k.current = own.current;
|
|
162
175
|
k.next = own.next;
|
|
@@ -178,7 +191,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
178
191
|
|
|
179
192
|
const first = inv.heir !== undefined && !k.spoke;
|
|
180
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 };
|
|
181
|
-
const r = read(await inside.send(bind, keys,
|
|
194
|
+
const r = read(await inside.send(bind, keys, method, args, wanted));
|
|
182
195
|
k.sent = true; // bytes went out. whether they arrived is the far door's to know.
|
|
183
196
|
// The number she spoke under, and the key she announced, kept whoever
|
|
184
197
|
// answered and kept when nobody did. A reply lost on the way back is a
|
|
@@ -201,8 +214,16 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
201
214
|
const keyName = (k: StandingKeys) => (k.heir === null ? `public:${k.ward}` : `${k.ward}:${k.heir}`);
|
|
202
215
|
const takenAs = (name: string): string | undefined => Object.keys(bind.standings).find((id) => keyName(bind.standings[id]) === name);
|
|
203
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
|
+
|
|
204
224
|
const calls = {
|
|
205
225
|
knock: async (inv: Invitation, method?: string, args: JsonObject = {}, wanted?: Wanted): Promise<Answer> => {
|
|
226
|
+
if (gone()) return word('dropped');
|
|
206
227
|
if (!valid(inv)) return word('invitation'); // S1. nothing is sent
|
|
207
228
|
// She may knock again, and after take that knock is an ask: the relation
|
|
208
229
|
// has one home, so it answers on the standing's lane, under the
|
|
@@ -210,7 +231,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
210
231
|
// same relation, and the two would refuse each other.
|
|
211
232
|
const id = takenAs(nameOf(inv));
|
|
212
233
|
if (id !== undefined) return standingAt(id).ask(method, args, wanted);
|
|
213
|
-
return bounded(wanted, (live) => lane(`knock:${nameOf(inv)}`, () => (live() ? knocking(inv, method, args, wanted) : Promise.resolve(word('late')))));
|
|
234
|
+
return bounded(wanted, (live) => lane(`knock:${nameOf(inv)}`, () => (live() ? knocking(inv, method, args, wanted, live) : Promise.resolve(word('late')))));
|
|
214
235
|
},
|
|
215
236
|
// Take consumes. Until now the relation lived in the knock record, under
|
|
216
237
|
// the ward and the heir; from now it lives in the standing, under her id.
|
|
@@ -230,7 +251,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
230
251
|
// ask on it is refused. The relation she took correctly would be dead
|
|
231
252
|
// because a second knock on the same invitation was careless.
|
|
232
253
|
take: async (id: string, inv: Invitation): Promise<string | null> => {
|
|
233
|
-
if (!valid(inv)) return null;
|
|
254
|
+
if (gone() || !valid(inv)) return null;
|
|
234
255
|
const name = nameOf(inv);
|
|
235
256
|
return lane(`knock:${name}`, () => {
|
|
236
257
|
if (!bind.answered[name]) return Promise.resolve(null);
|
|
@@ -248,6 +269,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
248
269
|
});
|
|
249
270
|
},
|
|
250
271
|
remove: (id: string): void => {
|
|
272
|
+
if (gone()) return;
|
|
251
273
|
drop(cells.standings, id);
|
|
252
274
|
drop(bind.standings, id);
|
|
253
275
|
inside.wrote();
|
|
@@ -260,24 +282,30 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
260
282
|
// issued while the standing stood is answered even if she drops it in
|
|
261
283
|
// the next line. Waiting for the lane is not a reason to lose it.
|
|
262
284
|
ask: (method?: string, args: JsonObject = {}, wanted?: Wanted): Promise<Answer> => {
|
|
285
|
+
if (gone()) return Promise.resolve(word('dropped') as Answer);
|
|
263
286
|
const keys = at(bind.standings, id);
|
|
264
287
|
if (!keys) return Promise.resolve(word('dropped') as Answer); // S2. she dropped it between one line and the next
|
|
265
288
|
const rec = at(cells.standings, id);
|
|
266
|
-
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')))));
|
|
267
290
|
},
|
|
268
291
|
});
|
|
269
292
|
|
|
270
293
|
// The record an ask writes into is the one it was issued on. A standing
|
|
271
294
|
// dropped and re-taken under the same id while the ask was in flight is
|
|
272
295
|
// another relation, and the blueprint that came back is not hers.
|
|
273
|
-
const asking = async (id: string, keys: StandingKeys, rec: Cells['standings'][string] | undefined, method: string | undefined, args: JsonObject, wanted: Wanted | undefined): Promise<Answer> => {
|
|
274
|
-
const out = await send(keys, method, args, wanted, rec);
|
|
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);
|
|
275
298
|
if (method === undefined && !isSilence(out) && !isWord(out)) {
|
|
276
299
|
// A blueprint her cells cannot keep, nested past the bound or carrying
|
|
277
300
|
// a key no harbor writes, is not Quo's bytes: silence, and nothing written.
|
|
278
301
|
if (cellFault(out, 'blueprint') !== null) return silence;
|
|
279
|
-
if (rec && at(cells.standings, id) === rec) {
|
|
280
|
-
|
|
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;
|
|
281
309
|
rec.digest = rec.seen = await digest(out);
|
|
282
310
|
}
|
|
283
311
|
}
|
|
@@ -304,8 +332,9 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
304
332
|
// Nothing half-lives: a relation that could not be made unmakes the
|
|
305
333
|
// being, who is a moment old and known to nobody.
|
|
306
334
|
boot: async (className: string, made: string, id?: string): Promise<string | null> => {
|
|
335
|
+
if (gone()) return null;
|
|
307
336
|
if (id !== undefined && (reserved(id) || at(cells.standings, id) || at(cells.occupants, id))) return null;
|
|
308
|
-
const born = inside.instantiate(
|
|
337
|
+
const born = inside.instantiate(className, made);
|
|
309
338
|
if (born === null || id === undefined) return born;
|
|
310
339
|
const inv = await inside.relate(born, key);
|
|
311
340
|
if (inv) {
|
|
@@ -319,7 +348,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
319
348
|
// rotation one: the ward mints the occupant's first key and gives the secret away.
|
|
320
349
|
// it keeps the pk beside the id and nothing else. the invitation IS the key.
|
|
321
350
|
invite: async (id: string, notes?: JsonObject): Promise<Invitation | null> => {
|
|
322
|
-
if (reserved(id) || at(cells.occupants, id) || at(cells.standings, id)) return null;
|
|
351
|
+
if (gone() || reserved(id) || at(cells.occupants, id) || at(cells.standings, id)) return null;
|
|
323
352
|
// The key first, and nothing written until it exists: a record put
|
|
324
353
|
// before the mint would be a record a remove in the meantime drops
|
|
325
354
|
// with no heir to close, and the heir opened after it would name an
|
|
@@ -335,6 +364,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
|
|
|
335
364
|
return { ward: inside.pk, heir: k.pk, secret: k.seed };
|
|
336
365
|
},
|
|
337
366
|
remove: (id: string): void => {
|
|
367
|
+
if (gone()) return;
|
|
338
368
|
drop(cells.occupants, id);
|
|
339
369
|
const heir = at(bind.occupants, id);
|
|
340
370
|
drop(bind.occupants, id);
|
package/src/ward/ward.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { silence, isSilence, unreached, isWord, word } from '../being/silence.ts
|
|
|
8
8
|
import { OWNER } from '../being/types.ts';
|
|
9
9
|
import type { Asker, BeingLike, Cells, JsonObject, Reply, Stance, Wanted, Word } from '../being/types.ts';
|
|
10
10
|
import type { Ground, WardPointers } from './ground.ts';
|
|
11
|
-
import { at, drop, open, put, emptyBind, emptyCells, type Bind, type Partition, type StandingKeys } from './partition.ts';
|
|
11
|
+
import { at, drop, open, put, emptyBind, emptyCells, MINTED, type Bind, type Partition, type StandingKeys } from './partition.ts';
|
|
12
12
|
import { Heirs } from './heirs.ts';
|
|
13
13
|
import { makeDoor, type Door } from './door.ts';
|
|
14
14
|
import { buildStance } from './stance.ts';
|
|
@@ -19,11 +19,10 @@ import { unhex } from './arithmetic.ts';
|
|
|
19
19
|
import { guardCells, unguarded, cellFault } from './cells.ts';
|
|
20
20
|
|
|
21
21
|
// One being of the ward as the ward holds her: her door, her bind table, her
|
|
22
|
-
// stance and her cells. The owner's asks see this and nothing more.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const MINTED = 8;
|
|
22
|
+
// stance and her cells. The owner's asks see this and nothing more. Not
|
|
23
|
+
// `Booted`, which is a harbor's word for a ward it started: this is a ward's
|
|
24
|
+
// word for a being living in it, and the two are one module apart.
|
|
25
|
+
export type Resident = Door & { bind: Bind; stance: Stance; cells: Cells & { class?: string } };
|
|
27
26
|
|
|
28
27
|
// The ward. One call in, two pointers out. Async because keys are derived.
|
|
29
28
|
export async function Ward(ground: Ground): Promise<WardPointers> {
|
|
@@ -37,7 +36,8 @@ class Self implements BeingLike {
|
|
|
37
36
|
readonly g: Ground;
|
|
38
37
|
readonly p: Partition;
|
|
39
38
|
readonly heirs: Heirs;
|
|
40
|
-
readonly doors = new Map<string,
|
|
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
41
|
readonly door: WardPointers['door'];
|
|
42
42
|
readonly ask: WardPointers['ask'];
|
|
43
43
|
|
|
@@ -52,7 +52,7 @@ class Self implements BeingLike {
|
|
|
52
52
|
this.p = open(ground.memory); // the partition: the ward's own state, and every being's row
|
|
53
53
|
this.heirs = new Heirs(this.p, () => this.#wrote());
|
|
54
54
|
this.door = makeDoor(key, this.heirs, this.doors, () => this.p.public, (n) => ground.random(n)); // pointer one
|
|
55
|
-
this.#boot(this.pk, this); // the ward's ward is itself
|
|
55
|
+
this.#boot(this.pk, () => this); // the ward's ward is itself
|
|
56
56
|
// a restart is silent: every being in the cells is constructed again, unasked.
|
|
57
57
|
// A throw on a boot the owner asked for is that boot's error, and nothing
|
|
58
58
|
// half-lives. This boot nobody asked for, and there is nobody to hand an
|
|
@@ -64,9 +64,10 @@ class Self implements BeingLike {
|
|
|
64
64
|
// read them. Her row stays hers: nobody boots another class over it, the
|
|
65
65
|
// owner sees her as absent, and the owner may unboot her.
|
|
66
66
|
for (const [k, c] of Object.entries(this.p.beings)) {
|
|
67
|
-
|
|
67
|
+
const className = c.class;
|
|
68
|
+
if (k === this.pk || !className) continue;
|
|
68
69
|
try {
|
|
69
|
-
this.#boot(k,
|
|
70
|
+
this.#boot(k, (stance) => this.g.instantiate(className, stance));
|
|
70
71
|
} catch {
|
|
71
72
|
/* she is not here this run. the ward is, and so is everyone else. */
|
|
72
73
|
}
|
|
@@ -87,14 +88,18 @@ class Self implements BeingLike {
|
|
|
87
88
|
// can invite. A stranger cannot be a named asker here, because the door
|
|
88
89
|
// names nobody the ward did not bind.
|
|
89
90
|
async answer(asker: Asker, method?: string, args: JsonObject = {}): Promise<Reply> {
|
|
90
|
-
|
|
91
|
+
// Nobody is not an owner. The door names nobody the ward did not bind, so
|
|
92
|
+
// the only way here unnamed is a partition that says the ward is its own
|
|
93
|
+
// public being, which no owner ask will write. Silence, not an error
|
|
94
|
+
// object: an unnamed asker is a stranger, and a stranger hears one thing.
|
|
95
|
+
if (asker.id === undefined) return silence;
|
|
91
96
|
return ownerAnswer(
|
|
92
97
|
{
|
|
93
98
|
pk: this.pk,
|
|
94
99
|
doors: this.doors,
|
|
95
100
|
absent: () => this.#absent(),
|
|
96
101
|
publicKey: () => this.p.public,
|
|
97
|
-
instantiate: (
|
|
102
|
+
instantiate: (className, key) => this.#instantiate(className, key),
|
|
98
103
|
unboot: (key) => this.#unboot(key),
|
|
99
104
|
setPublic: (key) => {
|
|
100
105
|
this.p.public = key;
|
|
@@ -128,13 +133,21 @@ class Self implements BeingLike {
|
|
|
128
133
|
// row is hers, with every relation in it, and no class is booted over it.
|
|
129
134
|
// A throw at birth is that boot's refusal and nothing half-lives; the
|
|
130
135
|
// ward says which, since the owner hears objects.
|
|
131
|
-
#instantiate(
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
#instantiate(className: string, key: string): Resident | 'threw' | null {
|
|
137
|
+
// The three refusals are read before the constructor runs, and the
|
|
138
|
+
// constructor can reach the stance and boot: a class that boots its own
|
|
139
|
+
// key from inside itself would pass all three, since neither its door nor
|
|
140
|
+
// its row exists yet, and the two objects would then share one row. A key
|
|
141
|
+
// being born is a key taken, for as long as the birth lasts.
|
|
142
|
+
if (typeof key !== 'string' || typeof className !== 'string' || !key || this.doors.has(key) || at(this.p.beings, key) || this.#birthing.has(key)) return null;
|
|
143
|
+
let door: Resident | null;
|
|
144
|
+
this.#birthing.add(key);
|
|
134
145
|
try {
|
|
135
|
-
door = this.#boot(key, className);
|
|
146
|
+
door = this.#boot(key, (stance) => this.g.instantiate(className, stance));
|
|
136
147
|
} catch {
|
|
137
148
|
return 'threw';
|
|
149
|
+
} finally {
|
|
150
|
+
this.#birthing.delete(key);
|
|
138
151
|
}
|
|
139
152
|
if (!door) return null;
|
|
140
153
|
unguarded(door.cells).class = className; // so a restart finds her. the ward's key, written behind her guard
|
|
@@ -178,20 +191,27 @@ class Self implements BeingLike {
|
|
|
178
191
|
// Nothing is written until there is somebody to write it for: a class the
|
|
179
192
|
// harbor does not know makes no being, and a boot that made nobody must
|
|
180
193
|
// leave the partition as it found it.
|
|
181
|
-
|
|
194
|
+
//
|
|
195
|
+
// `make` is who to build with the stance this raises. Two callers and two
|
|
196
|
+
// makers: the ground's `instantiate` for a being of the ward, and the ward
|
|
197
|
+
// itself, which is the first being in its own map and is already made.
|
|
198
|
+
#boot(key: string, make: (stance: Stance) => BeingLike | null): Resident | null {
|
|
182
199
|
const cells = guardCells(at(this.p.beings, key) ?? emptyCells(), () => this.#wrote());
|
|
183
200
|
const bind = at(this.p.bind, key) ?? emptyBind();
|
|
184
201
|
const stance = buildStance(
|
|
185
202
|
{
|
|
186
203
|
pk: this.pk,
|
|
204
|
+
// This stance, not merely this key: unboot and boot again under the
|
|
205
|
+
// same key makes a new being, and the old stance is not hers.
|
|
206
|
+
live: () => this.doors.get(key)?.stance === stance,
|
|
187
207
|
mintKey: (b) => this.#mintKey(b),
|
|
188
208
|
openHeir: (heir, being, id) => this.heirs.open(heir, being, id),
|
|
189
209
|
closeHeir: (heir) => this.heirs.close(heir),
|
|
190
|
-
send: (b, keys,
|
|
210
|
+
send: (b, keys, method, args, wanted) => this.#send(b, keys, method, args, wanted),
|
|
191
211
|
// A throw at birth is null to her, not a throw in her method: she asked
|
|
192
212
|
// for a being and got none, and her own answer is still hers to give.
|
|
193
|
-
instantiate: (
|
|
194
|
-
const made = this.#instantiate(
|
|
213
|
+
instantiate: (className, k) => {
|
|
214
|
+
const made = this.#instantiate(className, k);
|
|
195
215
|
return made && made !== 'threw' ? made.key : null;
|
|
196
216
|
},
|
|
197
217
|
// The two halves of a maker's way back, each the ward's own call: the
|
|
@@ -205,11 +225,11 @@ class Self implements BeingLike {
|
|
|
205
225
|
cells,
|
|
206
226
|
bind,
|
|
207
227
|
);
|
|
208
|
-
const being =
|
|
228
|
+
const being = make(stance);
|
|
209
229
|
if (!being || typeof being.answer !== 'function') return null; // a being is anything with an answer, and nothing without one
|
|
210
230
|
put(this.p.beings, key, cells);
|
|
211
231
|
put(this.p.bind, key, bind);
|
|
212
|
-
const door:
|
|
232
|
+
const door: Resident = { key, cells, bind, stance, being };
|
|
213
233
|
this.doors.set(key, door);
|
|
214
234
|
this.#wrote();
|
|
215
235
|
return door;
|
|
@@ -232,7 +252,7 @@ class Self implements BeingLike {
|
|
|
232
252
|
// next, seals to the far ward, opens the reply with the ephemeral secret,
|
|
233
253
|
// and rotates to the announced key once the far door has answered under
|
|
234
254
|
// the current one.
|
|
235
|
-
async #send(bind: Bind, keys: StandingKeys,
|
|
255
|
+
async #send(bind: Bind, keys: StandingKeys, method: string | undefined, args: JsonObject, wanted?: Wanted): Promise<ReplyPayload | typeof silence | Word> {
|
|
236
256
|
// What she asked for, held to what this ward allows. Asking for nothing is
|
|
237
257
|
// the default, and asking for more than the ceiling is the ceiling: budget
|
|
238
258
|
// is granted by a ward, never minted by a being.
|
|
@@ -240,8 +260,9 @@ class Self implements BeingLike {
|
|
|
240
260
|
const by = (await beingKey(unhex(keys.current))).pk;
|
|
241
261
|
// A public being is reached without a heir, and the door keeps nothing for
|
|
242
262
|
// whoever asked: no key it vouched for, so nothing to announce and nothing
|
|
243
|
-
// to rotate to. A standing on her signs with one key for life.
|
|
244
|
-
|
|
263
|
+
// to rotate to. A standing on her signs with one key for life. Every other
|
|
264
|
+
// ask announces its next: there is no send that does not.
|
|
265
|
+
if (keys.next === null && keys.heir !== null) keys.next = (await this.#mintKey(bind)).seed;
|
|
245
266
|
const next = keys.next === null ? null : (await beingKey(unhex(keys.next))).pk;
|
|
246
267
|
// Her count for this relation, one higher every call and never reused. The
|
|
247
268
|
// far door honours each number once. One relation sends one at a time, so
|
package/vectors/framing.json
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
{
|
|
8
8
|
"name": "a ward pk from a fixed seed",
|
|
9
9
|
"seed": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
10
|
-
"pk": "
|
|
11
|
-
"signPk": "
|
|
12
|
-
"padlockPk": "
|
|
10
|
+
"pk": "6d773f8156c80eb9f34d0734e889d824a0d0515f527b853771208ef1ed3feb281fc3da3532de3b20deafd145e30c3de979917e96c22f6c3113a731379eeb3200",
|
|
11
|
+
"signPk": "6d773f8156c80eb9f34d0734e889d824a0d0515f527b853771208ef1ed3feb28",
|
|
12
|
+
"padlockPk": "1fc3da3532de3b20deafd145e30c3de979917e96c22f6c3113a731379eeb3200"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"name": "a being key from a fixed secret",
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
"canonical": "{\"asks\":{\"hello\":{\"note\":\"z\"}},\"list\":[1,2],\"note\":null}",
|
|
23
23
|
"digest": "269c9e90f39f6fcae9deab916e3a4148788dec5ba0d06023edc8e52180c773db"
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
"name": "the digest where JCS is hard: UTF-16 key order and ES numbers",
|
|
27
|
+
"blueprint": "{\"asks\":[],\"notes\":{\"1\":\"One\",\"\u20ac\":\"Euro Sign\",\"\\r\":\"Carriage Return\",\"\ufb33\":\"Hebrew Letter Dalet With Dagesh\",\"\ud83d\ude00\":\"Emoji: Grinning Face\",\"\u0080\":\"Control\",\"\u00f6\":\"Latin Small Letter O With Diaeresis\"},\"numbers\":[1e+21,0,0.1,1e-7,9007199254740992,0.3333333333333333]}",
|
|
28
|
+
"canonical": "{\"asks\":[],\"notes\":{\"\\r\":\"Carriage Return\",\"1\":\"One\",\"\u0080\":\"Control\",\"\u00f6\":\"Latin Small Letter O With Diaeresis\",\"\u20ac\":\"Euro Sign\",\"\ud83d\ude00\":\"Emoji: Grinning Face\",\"\ufb33\":\"Hebrew Letter Dalet With Dagesh\"},\"numbers\":[1e+21,0,0.1,1e-7,9007199254740992,0.3333333333333333]}",
|
|
29
|
+
"digest": "dc5306089d6bf46d9f91d25290b770c0f18955f1192525b51389fd4c23e02089"
|
|
30
|
+
},
|
|
25
31
|
{
|
|
26
32
|
"name": "the signed ask body for nobody, and its signature: to is inside, first",
|
|
27
33
|
"to": "",
|
|
@@ -42,37 +48,37 @@
|
|
|
42
48
|
"name": "a sealed ask to a heir: the ephemeral pk, then the box, and nothing outside it",
|
|
43
49
|
"to": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
44
50
|
"ephemeralSeed": "3333333333333333333333333333333333333333333333333333333333333333",
|
|
45
|
-
"bytes": "
|
|
51
|
+
"bytes": "7b0d47d93427f8311160781c7c733fd89f88970aef490d8aa0ee19a4cb8a1b140f393feeee5faf9a2707cd3e47c06e7edfd9f5b705ea3ee1a2a2a25b591b37a0af5a068c6455d3cc2dc2948615faf29b182a81ea3d73c166e68f4bb1578c3fbaf22f1dea223d9b84e7e5b90f65139bba894cee2bf68ced2192e548c174bc593dd16d124eb21d3bbde81169bab147676bb11869e08dedb2b15a782d6236ff2ae6b9fa578be8008707ca238f28d39f5029a0340b2e5299e97e44ce9140f8a40ad038d28c931d7872bcfa06f1e29ec964491d4b4ee425ab55b3a09a501a50b6c36f159962247650ec31c42b0c711d97dfc832086366e7c856d332e5bccce04c44c7b67be20bb35d52cccf3a4bf1055fa2a38246c61adc2114da603bd419885b79eb9e3f429d4e26e808beb562d5c60e635649acda8170834f68aee612b56416d369cc72edc8ac1b6204e7"
|
|
46
52
|
},
|
|
47
53
|
{
|
|
48
54
|
"name": "a sealed ask for nobody: the same shape, with to null inside",
|
|
49
55
|
"to": "",
|
|
50
56
|
"ephemeralSeed": "3333333333333333333333333333333333333333333333333333333333333333",
|
|
51
|
-
"bytes": "
|
|
57
|
+
"bytes": "7b0d47d93427f8311160781c7c733fd89f88970aef490d8aa0ee19a4cb8a1b140f393feeee5fe38e2a0a807d44d82d259cd9a4ef05ea6ae6f7f4a20c0f4f6ff9fe09018b3c0187cb74c796d510a9f2cb4d2ad5e8652bc435b48c4fe9018b66eda77f4ebc253ac395f3f4ef4372488dbd8957a368ad8fa162d1e9118121e8452dc46e5a59bd4a2ffbff4a60edf24c603ab44b60f5cbb2e4a7572e7e2634eb76b2e7a15ecffd11de46996598739cdb0e6ae1734771089cbe2b08c6807f88d64cf37b26b15e6a32d654377acd67283bc2cfea8117f24d135dec11d10d05c6e65e67cc09a5334fe6ce0ed4bbcb06f54b304e15725b956f00d5c8f6e3b48e4b634b743c581e15f86f7b17b52a96"
|
|
52
58
|
},
|
|
53
59
|
{
|
|
54
60
|
"name": "the sealed silence reply",
|
|
55
61
|
"reply": "{\"silence\":true}",
|
|
56
62
|
"replySeed": "4444444444444444444444444444444444444444444444444444444444444444",
|
|
57
|
-
"bytes": "
|
|
63
|
+
"bytes": "ff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b76ec8fe27c6573176a8974f123537984bdd8baea7cbd4e9b81f38db22c5917e9674bc082183e532adba06227f7e023955f16afcb723ca826a579fc08af3306824c8f8012687532ad79d81b77a7c84a429d151cc593b81312bc44433571a0b4aa"
|
|
58
64
|
},
|
|
59
65
|
{
|
|
60
66
|
"name": "a sealed object reply",
|
|
61
67
|
"reply": "{\"object\":{\"hi\":\"quo\"},\"seen\":null}",
|
|
62
68
|
"replySeed": "4444444444444444444444444444444444444444444444444444444444444444",
|
|
63
|
-
"bytes": "
|
|
69
|
+
"bytes": "ff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b76ec93e97a657e002d9135a7394f3ec33bcd723c9d8947909f4cc659d8dfeed743f5b6012ed80af39f9d1bc16c995f69944a0c94244e349308704f2070a98b59a5a4f3e9b1e2a1bfe2e7bcba2446de1fbf84dc15cc5fa5ac03a175cce7d2eaa07b0e1148416580932306d41dc961f145fa7141"
|
|
64
70
|
},
|
|
65
71
|
{
|
|
66
72
|
"name": "a sealed word reply: the ward's word to a key it has bound",
|
|
67
73
|
"reply": "{\"quo\":\"removed\"}",
|
|
68
74
|
"replySeed": "4444444444444444444444444444444444444444444444444444444444444444",
|
|
69
|
-
"bytes": "
|
|
75
|
+
"bytes": "ff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b76ec8dfe7f2227567dce23ea274378db6487fe336070ff4780aec6e6cb89197c05df879e39252f523fc910bf2268ded0d2f323f395a4e2352a5004424b828c43f3163595c67c120e5ea029cfecec4abe575fb4327449cd6003634f30b3cc056a54"
|
|
70
76
|
},
|
|
71
77
|
{
|
|
72
78
|
"name": "an invitation for a heir: the ward pk, the heir pk, the heir secret, and nothing else",
|
|
73
79
|
"wardSeed": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
74
80
|
"heirSecret": "4444444444444444444444444444444444444444444444444444444444444444",
|
|
75
|
-
"invitation": "{\"ward\":\"
|
|
81
|
+
"invitation": "{\"ward\":\"6d773f8156c80eb9f34d0734e889d824a0d0515f527b853771208ef1ed3feb281fc3da3532de3b20deafd145e30c3de979917e96c22f6c3113a731379eeb3200\",\"heir\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"secret\":\"4444444444444444444444444444444444444444444444444444444444444444\"}"
|
|
76
82
|
},
|
|
77
83
|
{
|
|
78
84
|
"name": "a sealed knock: the first ask under a heir, by the heir, announcing her own key, to the heir",
|
|
@@ -80,7 +86,7 @@
|
|
|
80
86
|
"ownSecret": "5555555555555555555555555555555555555555555555555555555555555555",
|
|
81
87
|
"ephemeralSeed": "6666666666666666666666666666666666666666666666666666666666666666",
|
|
82
88
|
"payload": "{\"to\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"by\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"next\":\"c6822637c7d310ec57627be00ba259d253749f4aaf644470cffbe53a35f73242\",\"seq\":1,\"time\":30000,\"method\":\"hello\",\"args\":{\"name\":\"quo\"}}",
|
|
83
|
-
"bytes": "
|
|
89
|
+
"bytes": "219e4d800da968d2a5fcb009c784f4746c7138edb9ee4844b739e830b05cf424e0590708caeb7c3c01e2ae8a2fa26a18bc504b84e06e80b104eab60c233b6ab42709b03434573a932c55e1977311a6358713251329e652d3d4bf212e4fe2062629f886a825315d8338de702e40457e2371eb4cd3b6633e0e986ea724fe6128a664bc61c7113106858540f20df784583e3ede4f125841a5d359cc94784bcee04d303b26b1008392420ae8f0930650c6aeefa3ed6138ab791a92870cb4f4d2d8e634e216cfdec86e8b3ceeef0b4f8a49477e95c04423808150ef381f7ef5390f3182f825a7be03e48edd981d66c3131ed9cca37c6348ef7f8dccc01018ff8417e09c6958b77ea269c58a47683f2b369ca13a434117d38f2f726b8a422e5004db32613ddc300d6f90805597d96ed51ef22bdd6353507857ab50edc2a3719e9081491a4722728275e263c9f6c286a1b39ab7cbd755805f2610410073ece392af0b3b960e68b0143a05674398927a57e2d99396b577ef7d1c618d32c9af619a79954ad413d7b5fe4cf3"
|
|
84
90
|
}
|
|
85
91
|
]
|
|
86
92
|
}
|
package/vectors/wire.json
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
{
|
|
8
8
|
"name": "an ask frame: kind, id, pk, bytes",
|
|
9
9
|
"id": 1,
|
|
10
|
-
"pk": "
|
|
10
|
+
"pk": "6d773f8156c80eb9f34d0734e889d824a0d0515f527b853771208ef1ed3feb281fc3da3532de3b20deafd145e30c3de979917e96c22f6c3113a731379eeb3200",
|
|
11
11
|
"bytes": "010203",
|
|
12
|
-
"frame": "
|
|
12
|
+
"frame": "00000000016d773f8156c80eb9f34d0734e889d824a0d0515f527b853771208ef1ed3feb281fc3da3532de3b20deafd145e30c3de979917e96c22f6c3113a731379eeb3200010203"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"name": "a reply frame: kind, id, bytes",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
{
|
|
26
26
|
"name": "the announce frame, text",
|
|
27
27
|
"pks": [
|
|
28
|
-
"
|
|
28
|
+
"6d773f8156c80eb9f34d0734e889d824a0d0515f527b853771208ef1ed3feb281fc3da3532de3b20deafd145e30c3de979917e96c22f6c3113a731379eeb3200"
|
|
29
29
|
],
|
|
30
|
-
"frame": "{\"announce\":[\"
|
|
30
|
+
"frame": "{\"announce\":[\"6d773f8156c80eb9f34d0734e889d824a0d0515f527b853771208ef1ed3feb281fc3da3532de3b20deafd145e30c3de979917e96c22f6c3113a731379eeb3200\"],\"suite\":1}",
|
|
31
31
|
"suite": 1
|
|
32
32
|
},
|
|
33
33
|
{
|