nervur 0.18.0 → 0.19.1
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 +7 -7
- package/dist/being/index.d.ts +1 -0
- package/dist/being/index.js +1 -0
- package/dist/being/lent.d.ts +32 -0
- package/dist/being/lent.js +63 -0
- package/dist/being/types.js +1 -1
- package/dist/conformance/beings.d.ts +2 -1
- package/dist/conformance/beings.js +7 -2
- package/dist/conformance/index.d.ts +0 -1
- package/dist/conformance/index.js +8 -5
- package/dist/harbor/box.d.ts +121 -0
- package/dist/harbor/box.js +121 -0
- package/dist/harbor/core.d.ts +5 -5
- package/dist/harbor/core.js +73 -72
- package/dist/harbor/index.d.ts +3 -1
- package/dist/harbor/index.js +3 -0
- package/dist/harbor/memory.d.ts +8 -4
- package/dist/harbor/memory.js +43 -11
- package/dist/harbor/reach.js +9 -1
- package/dist/harbor/store.js +1 -1
- package/dist/vector/cases.d.ts +4 -3
- package/dist/vector/cases.js +33 -5
- package/dist/vector/stand.js +2 -1
- package/dist/vector/world.d.ts +1 -0
- package/dist/vector/world.js +17 -6
- package/dist/ward/arithmetic.js +6 -2
- package/dist/ward/ground.d.ts +13 -7
- package/dist/ward/ground.js +29 -11
- package/dist/ward/heirs.js +4 -2
- package/dist/ward/index.d.ts +2 -1
- package/dist/ward/index.js +1 -0
- package/dist/ward/json.d.ts +2 -0
- package/dist/ward/json.js +163 -0
- package/dist/ward/partition.js +2 -2
- package/dist/ward/seal.js +14 -9
- package/dist/ward/stance.d.ts +0 -5
- package/dist/ward/ward.d.ts +1 -1
- package/dist/ward/ward.js +62 -27
- package/package.json +7 -5
- package/src/being/index.ts +1 -0
- package/src/being/lent.ts +66 -0
- package/src/being/types.ts +1 -1
- package/src/conformance/beings.ts +8 -3
- package/src/conformance/index.ts +18 -15
- package/src/harbor/box.ts +131 -0
- package/src/harbor/core.ts +23 -22
- package/src/harbor/index.ts +4 -1
- package/src/harbor/memory.ts +52 -12
- package/src/harbor/reach.ts +7 -1
- package/src/harbor/store.ts +1 -1
- package/src/vector/cases.ts +37 -9
- package/src/vector/stand.ts +2 -1
- package/src/vector/world.ts +18 -6
- package/src/ward/arithmetic.ts +6 -2
- package/src/ward/ground.ts +85 -103
- package/src/ward/heirs.ts +4 -2
- package/src/ward/index.ts +2 -1
- package/src/ward/json.ts +144 -0
- package/src/ward/partition.ts +2 -2
- package/src/ward/seal.ts +12 -8
- package/src/ward/stance.ts +1 -1
- package/src/ward/ward.ts +59 -27
package/dist/harbor/core.js
CHANGED
|
@@ -78,20 +78,19 @@ export class Harbor {
|
|
|
78
78
|
for (const say of this.announcers)
|
|
79
79
|
say();
|
|
80
80
|
}
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
constructor(store, loader = async () => ({})
|
|
81
|
+
// The box, as one invitation per ward: what this device hands a ward it
|
|
82
|
+
// boots so that the ward takes the box's being as a standing. Set once
|
|
83
|
+
// the harbor's own ward is standing, which is the usual order: a terrain
|
|
84
|
+
// puts that ward up first, then boots the rest on a ground that can reach
|
|
85
|
+
// it. A terrain says which ward is handed one by overriding `boxFor`, and
|
|
86
|
+
// a stranger's ward is handed none.
|
|
87
|
+
offering;
|
|
88
|
+
constructor(store, loader = async () => ({})) {
|
|
89
89
|
this.store = store;
|
|
90
90
|
this.loader = loader;
|
|
91
|
-
this.lend = lend;
|
|
92
91
|
}
|
|
93
|
-
|
|
94
|
-
return this.
|
|
92
|
+
boxFor(name, _record) {
|
|
93
|
+
return this.offering?.(name) ?? Promise.resolve(undefined);
|
|
95
94
|
}
|
|
96
95
|
// Boot every ward the store keeps, and learn the hints. One ward that will
|
|
97
96
|
// not host, a partition this kit cannot read or a loader that throws, is
|
|
@@ -498,73 +497,75 @@ export class Harbor {
|
|
|
498
497
|
const objects = new WeakMap(); // cells -> the object instantiate made. a side's hand, never the ward's; it follows the cells out when she is unbooted
|
|
499
498
|
const ground = {
|
|
500
499
|
seed,
|
|
501
|
-
memory,
|
|
502
500
|
// This ward's own classes first, then the harbor's: a ward that names a
|
|
503
501
|
// class of its own is answered with hers.
|
|
504
502
|
instantiate: maker(objects, classes, this.classes),
|
|
505
503
|
carry: (pk, bytes) => this.carry(pk, new Uint8Array(bytes)),
|
|
506
504
|
random,
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
505
|
+
memory: {
|
|
506
|
+
rows: memory,
|
|
507
|
+
told: (row) => this.#wrote(name, row),
|
|
508
|
+
// What the ward wrote is in the store when this resolves, and it says
|
|
509
|
+
// whether the store took it. The ward calls it at the end of every
|
|
510
|
+
// arrival, before it seals, so a call through a pointer has already
|
|
511
|
+
// waited for the store when it comes back: a restart right after one
|
|
512
|
+
// finds everything, and a refusal is that arrival's own answer rather
|
|
513
|
+
// than a number counted behind it. A ward that wrote nothing has
|
|
514
|
+
// nothing to save, so asking it what it holds is free, which is what
|
|
515
|
+
// makes the ask pointer the way to find out rather than an expensive
|
|
516
|
+
// one.
|
|
517
|
+
//
|
|
518
|
+
// A ward on its way out keeps nothing and refuses nothing: an arrival
|
|
519
|
+
// racing an unhost is not a store saying no, and saying so would be a
|
|
520
|
+
// lie in the one direction that matters.
|
|
521
|
+
kept: async () => {
|
|
522
|
+
const s = this.#saving.get(name);
|
|
523
|
+
if (!s || s.gone)
|
|
524
|
+
return true;
|
|
525
|
+
const ok = await this.#flush(name);
|
|
526
|
+
// A refusal is heard once, by the first arrival to ask after it: the
|
|
527
|
+
// save that met it may have been a timer's, which had nobody to tell.
|
|
528
|
+
// Under a call it is left standing, so the call that carried the
|
|
529
|
+
// refused write is refused too, and the outermost arrival hears it.
|
|
530
|
+
const no = !ok || s.fault;
|
|
531
|
+
s.fault = false; // heard. the next arrival is answered by the store and not by what happened before it
|
|
532
|
+
return !no;
|
|
533
|
+
},
|
|
534
|
+
// The ward says a call is in flight, and says when it ends. The harbor
|
|
535
|
+
// goes on writing under it; what it does not do is take a save made
|
|
536
|
+
// there as the point a refused ward is put back to, and a refusal that
|
|
537
|
+
// lands under a call waits here for the call to end before it puts the
|
|
538
|
+
// ward back. The ward is the one that knows, because a being reaching
|
|
539
|
+
// another being of her own ward goes to that ward's door directly and
|
|
540
|
+
// the harbor never sees that call at all.
|
|
541
|
+
during: () => {
|
|
542
|
+
const s = this.#saving.get(name);
|
|
543
|
+
if (!s)
|
|
544
|
+
return () => { };
|
|
545
|
+
s.calls += 1;
|
|
546
|
+
let lowered = false;
|
|
547
|
+
return () => {
|
|
548
|
+
if (lowered)
|
|
549
|
+
return;
|
|
550
|
+
lowered = true;
|
|
551
|
+
s.calls -= 1;
|
|
552
|
+
if (s.calls > 0 || s.gone)
|
|
553
|
+
return;
|
|
554
|
+
// The moment the ward is whole again: the point catches up with the
|
|
555
|
+
// call that just ended, or a put back that was owed is done now, so
|
|
556
|
+
// a being stands at what is kept before her next word rather than
|
|
557
|
+
// at the next save.
|
|
558
|
+
if (s.back)
|
|
559
|
+
void this.#flush(name);
|
|
560
|
+
else
|
|
561
|
+
this.#settle(name, s);
|
|
562
|
+
};
|
|
563
|
+
},
|
|
563
564
|
},
|
|
564
565
|
};
|
|
565
|
-
const
|
|
566
|
-
if (
|
|
567
|
-
ground.
|
|
566
|
+
const box = await this.boxFor(name, kept.record);
|
|
567
|
+
if (box)
|
|
568
|
+
ground.box = box;
|
|
568
569
|
// What the store holds for this ward at birth is what it was just handed
|
|
569
570
|
// or just read, row by row. It is copied once here so that a being whose
|
|
570
571
|
// very first write of this run is refused is put back too, rather than
|
|
@@ -593,7 +594,7 @@ export class Harbor {
|
|
|
593
594
|
// here. It answers nothing, because a caller inside the process reads
|
|
594
595
|
// the fault off the harbor and an arrival is the only thing that needs a
|
|
595
596
|
// verdict of its own.
|
|
596
|
-
const save = async () => void (await ground.
|
|
597
|
+
const save = async () => void (await ground.memory.kept());
|
|
597
598
|
const door = (bytes) => w.door(bytes);
|
|
598
599
|
const ask = async (method, args) => {
|
|
599
600
|
if (saving.gone)
|
package/dist/harbor/index.d.ts
CHANGED
|
@@ -4,5 +4,7 @@ export { dial, type Dialer } from './dial.ts';
|
|
|
4
4
|
export { MemoryStore, values, rowsOf, type Store, type Kept, type WardRecord } from './store.ts';
|
|
5
5
|
export { HEAD, fromRows, rowsIn } from '../ward/partition.ts';
|
|
6
6
|
export { request, Socket, SUITE, REFUSED, type Reach, type Carry, type Line, type Announce } from './reach.ts';
|
|
7
|
-
export type { Ground, WardPointers,
|
|
7
|
+
export type { Ground, WardPointers, Memory } from '../ward/ground.ts';
|
|
8
|
+
export { BOX, OFFER, RETRACT, volatile } from '../ward/ground.ts';
|
|
9
|
+
export { Box, BOX_SEED, BOX_ASKS, stand, hold, offerTo, roll } from './box.ts';
|
|
8
10
|
export { maker, entropy, learnPk } from '../ward/ground.ts';
|
package/dist/harbor/index.js
CHANGED
|
@@ -11,6 +11,9 @@ export { MemoryStore, values, rowsOf } from './store.js';
|
|
|
11
11
|
// a being, and the partition back from the rows it was cut into.
|
|
12
12
|
export { HEAD, fromRows, rowsIn } from '../ward/partition.js';
|
|
13
13
|
export { request, Socket, SUITE, REFUSED } from './reach.js';
|
|
14
|
+
export { BOX, OFFER, RETRACT, volatile } from '../ward/ground.js';
|
|
15
|
+
// The box as one being, and the acts a harbor runs to stand it.
|
|
16
|
+
export { Box, BOX_SEED, BOX_ASKS, stand, hold, offerTo, roll } from './box.js';
|
|
14
17
|
// What a harbor builds a ground out of. Convenience, never contract: a kit
|
|
15
18
|
// writing its own harbor may write these three again.
|
|
16
19
|
export { maker, entropy, learnPk } from '../ward/ground.js';
|
package/dist/harbor/memory.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { BeingClass, BeingLike } from '../being/types.ts';
|
|
2
|
-
import { type Ground, type WardPointers
|
|
1
|
+
import type { BeingClass, BeingLike, Invitation } from '../being/types.ts';
|
|
2
|
+
import { type Ground, type WardPointers } from '../ward/ground.ts';
|
|
3
3
|
export type Booted = WardPointers & {
|
|
4
4
|
pk: string;
|
|
5
5
|
};
|
|
@@ -20,6 +20,10 @@ export declare class MemoryHarbor {
|
|
|
20
20
|
random: Ground['random'];
|
|
21
21
|
route(farPk: string, bytes: Uint8Array): Promise<Uint8Array | undefined>;
|
|
22
22
|
link(harbor: MemoryHarbor): void;
|
|
23
|
-
boot(seed: string, Ward: WardFactory, classes: Record<string, BeingClass>,
|
|
24
|
-
reboot(seed: string, Ward: WardFactory, classes: Record<string, BeingClass>,
|
|
23
|
+
boot(seed: string, Ward: WardFactory, classes: Record<string, BeingClass>, box?: Invitation): Promise<Booted>;
|
|
24
|
+
reboot(seed: string, Ward: WardFactory, classes: Record<string, BeingClass>, box?: Invitation): Promise<Booted>;
|
|
25
|
+
up(Ward: WardFactory, lent?: Record<string, BeingClass>): Promise<Booted>;
|
|
26
|
+
get box(): Booted | undefined;
|
|
27
|
+
host(seed: string, Ward: WardFactory, classes: Record<string, BeingClass>): Promise<Booted>;
|
|
28
|
+
hosted(Ward: WardFactory, classes: Record<string, BeingClass>): Promise<string[]>;
|
|
25
29
|
}
|
package/dist/harbor/memory.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { maker, entropy, learnPk } from '../ward/ground.js';
|
|
1
|
+
import { maker, entropy, learnPk, volatile } from '../ward/ground.js';
|
|
2
2
|
import { hex } from '../ward/arithmetic.js';
|
|
3
|
+
import { Box, BOX_SEED, stand, offerTo, roll } from './box.js';
|
|
3
4
|
export class MemoryHarbor {
|
|
4
5
|
doors = new Map(); // ward pk -> door
|
|
5
6
|
down = new Set(); // ward pks this harbor cannot reach right now
|
|
@@ -36,22 +37,22 @@ export class MemoryHarbor {
|
|
|
36
37
|
this.peers.add(harbor);
|
|
37
38
|
harbor.peers.add(this);
|
|
38
39
|
}
|
|
39
|
-
// boot by seed. the harbor gets a door and an ask, and learns the pk by
|
|
40
|
-
// `
|
|
41
|
-
//
|
|
42
|
-
async boot(seed, Ward, classes,
|
|
40
|
+
// boot by seed. the harbor gets a door and an ask, and learns the pk by
|
|
41
|
+
// asking. `box` is the invitation on this device's box being, for this
|
|
42
|
+
// ward: left out, the ward holds no box and every lend is null.
|
|
43
|
+
async boot(seed, Ward, classes, box) {
|
|
43
44
|
this.partitions.set(seed, {});
|
|
44
|
-
return this.reboot(seed, Ward, classes,
|
|
45
|
+
return this.reboot(seed, Ward, classes, box);
|
|
45
46
|
}
|
|
46
47
|
// a restart: same seed, same partition, fresh ward. the ward's files are the harbor's to keep.
|
|
47
|
-
async reboot(seed, Ward, classes,
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
48
|
+
async reboot(seed, Ward, classes, box) {
|
|
49
|
+
const rows = this.partitions.get(seed);
|
|
50
|
+
if (!rows)
|
|
50
51
|
throw new Error(`no partition for ${seed}`);
|
|
51
52
|
const ground = {
|
|
52
53
|
seed,
|
|
53
|
-
memory,
|
|
54
|
-
...(
|
|
54
|
+
memory: volatile(rows),
|
|
55
|
+
...(box ? { box } : {}),
|
|
55
56
|
// One objects map for the whole harbor, not one per ward: every ward
|
|
56
57
|
// here is in this process and a test reaches any being of any of them.
|
|
57
58
|
instantiate: maker(this.objects, classes),
|
|
@@ -69,4 +70,35 @@ export class MemoryHarbor {
|
|
|
69
70
|
this.wards.set(seed, booted);
|
|
70
71
|
return booted;
|
|
71
72
|
}
|
|
73
|
+
// Act one. The box's own ward, the box's being and the lent beings named,
|
|
74
|
+
// each booted under its name and held by her under it. Run again on a
|
|
75
|
+
// harbor whose box ward stands, it changes nothing.
|
|
76
|
+
async up(Ward, lent = {}) {
|
|
77
|
+
const classes = { Box };
|
|
78
|
+
for (const C of Object.values(lent))
|
|
79
|
+
classes[C.name] = C;
|
|
80
|
+
const box = this.wards.get(BOX_SEED) ?? (await this.boot(BOX_SEED, Ward, classes));
|
|
81
|
+
await stand(box, lent);
|
|
82
|
+
return box;
|
|
83
|
+
}
|
|
84
|
+
get box() {
|
|
85
|
+
return this.wards.get(BOX_SEED);
|
|
86
|
+
}
|
|
87
|
+
// Act two, one ward: on the roll and handed one invitation on the box's being.
|
|
88
|
+
// A harbor with no box ward up hosts as `boot` does, with nothing.
|
|
89
|
+
async host(seed, Ward, classes) {
|
|
90
|
+
const box = this.box;
|
|
91
|
+
const inv = box ? await offerTo(box, seed) : undefined;
|
|
92
|
+
return this.partitions.has(seed) ? this.reboot(seed, Ward, classes, inv) : this.boot(seed, Ward, classes, inv);
|
|
93
|
+
}
|
|
94
|
+
// Act two, whole: the box's being says who is hosted, and each comes up.
|
|
95
|
+
async hosted(Ward, classes) {
|
|
96
|
+
const box = this.box;
|
|
97
|
+
if (!box)
|
|
98
|
+
return [];
|
|
99
|
+
const seeds = await roll(box);
|
|
100
|
+
for (const seed of seeds)
|
|
101
|
+
await this.host(seed, Ward, classes);
|
|
102
|
+
return seeds;
|
|
103
|
+
}
|
|
72
104
|
}
|
package/dist/harbor/reach.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// A reach that sent the bytes and lost the line afterwards answers nothing
|
|
21
21
|
// at all, and the ward's own bound ends the ask.
|
|
22
22
|
import { concat, hex, unhex } from '../ward/arithmetic.js';
|
|
23
|
+
import { readJson } from '../ward/json.js';
|
|
23
24
|
const never = () => new Promise(() => { }); // sent, and no word since: the ward's bound decides
|
|
24
25
|
// A request reach to one listener.
|
|
25
26
|
export function request(url) {
|
|
@@ -111,7 +112,9 @@ export class Socket {
|
|
|
111
112
|
line.addEventListener('message', (e) => {
|
|
112
113
|
if (typeof e.data === 'string') {
|
|
113
114
|
try {
|
|
114
|
-
const m =
|
|
115
|
+
const m = readJson(e.data);
|
|
116
|
+
if (m === null || typeof m !== 'object')
|
|
117
|
+
return;
|
|
115
118
|
// Absent is this suite: a side that says nothing says one.
|
|
116
119
|
if (m.suite !== undefined && m.suite !== SUITE) {
|
|
117
120
|
this.far = [];
|
|
@@ -129,9 +132,14 @@ export class Socket {
|
|
|
129
132
|
return;
|
|
130
133
|
}
|
|
131
134
|
const buf = e.data instanceof ArrayBuffer ? new Uint8Array(e.data) : ArrayBuffer.isView(e.data) ? new Uint8Array(e.data.buffer, e.data.byteOffset, e.data.byteLength) : undefined;
|
|
135
|
+
// A frame that is none of the three is not a frame: an unknown kind, an
|
|
136
|
+
// ask too short to hold a pk, a nothing with bytes after its id. It is
|
|
137
|
+
// dropped, and tells no ask waiting on its id that nothing was delivered.
|
|
132
138
|
if (!buf || buf.length < 5)
|
|
133
139
|
return;
|
|
134
140
|
const kind = buf[0], id = new DataView(buf.buffer, buf.byteOffset, buf.byteLength).getUint32(1);
|
|
141
|
+
if (kind > NONE || (kind === ASK && buf.length < 69) || (kind === NONE && buf.length !== 5))
|
|
142
|
+
return;
|
|
135
143
|
if (kind === ASK) {
|
|
136
144
|
const pk = hex(buf.subarray(5, 69));
|
|
137
145
|
// A deliver that rejects has delivered nothing this side can vouch
|
package/dist/harbor/store.js
CHANGED
|
@@ -18,7 +18,7 @@ export const values = (p) => JSON.parse(JSON.stringify(p));
|
|
|
18
18
|
// is the store dropping what it holds for her.
|
|
19
19
|
export const rowsOf = (partition, rows) => rows.map((row) => {
|
|
20
20
|
const value = rowOf(partition, row);
|
|
21
|
-
return [row, value === undefined ? undefined : values(value)];
|
|
21
|
+
return [row, value === undefined ? undefined : (values(value))];
|
|
22
22
|
});
|
|
23
23
|
// One store's copy, brought up to date with the rows it was told about.
|
|
24
24
|
const keep = (row, partition, rows) => {
|
package/dist/vector/cases.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Json } from '../being/types.ts';
|
|
1
|
+
import type { Json, JsonObject } from '../being/types.ts';
|
|
2
2
|
import type { Booted } from '../harbor/memory.ts';
|
|
3
3
|
import type { World, Sealed } from './world.ts';
|
|
4
4
|
export type DoorRecord = {
|
|
@@ -11,8 +11,8 @@ export type DoorRecord = {
|
|
|
11
11
|
ask: string;
|
|
12
12
|
reply: string;
|
|
13
13
|
opens: Json | null;
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
draws: number;
|
|
15
|
+
blueprint?: JsonObject;
|
|
16
16
|
wrote: boolean;
|
|
17
17
|
};
|
|
18
18
|
export type Pin = () => Promise<void>;
|
|
@@ -34,6 +34,7 @@ export type Case = {
|
|
|
34
34
|
export declare function stand(c: Case): Promise<{
|
|
35
35
|
w: World;
|
|
36
36
|
before: string;
|
|
37
|
+
draws: number;
|
|
37
38
|
arrival: Arrival;
|
|
38
39
|
}>;
|
|
39
40
|
export declare function doorCases(): Promise<DoorRecord[]>;
|
package/dist/vector/cases.js
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
import { Ward } from '../ward/ward.js';
|
|
19
19
|
import * as seal from '../ward/seal.js';
|
|
20
20
|
import { hex } from '../ward/arithmetic.js';
|
|
21
|
-
import {
|
|
21
|
+
import { digest } from '../being/digest.js';
|
|
22
|
+
import { world, sealed, bound, secret, rnd, drawn, partitionDigest, Caller } from './world.js';
|
|
22
23
|
// Stand one case: its world, its setup, its ask. What comes back is enough
|
|
23
24
|
// to knock, and enough for a verifier to check the hand before the door.
|
|
24
25
|
export async function stand(c) {
|
|
@@ -27,14 +28,36 @@ export async function stand(c) {
|
|
|
27
28
|
const arrival = await c.arrive(w, async () => {
|
|
28
29
|
before = await partitionDigest(w.b, c.ward);
|
|
29
30
|
});
|
|
30
|
-
|
|
31
|
+
// Where the stream stands now, which is where the door is about to draw
|
|
32
|
+
// from. A kit that stood this case its own way sets its stream here.
|
|
33
|
+
return { w, before, draws: drawn(), arrival };
|
|
34
|
+
}
|
|
35
|
+
// The blueprint a reply's `seen` was taken over. The host is the only being
|
|
36
|
+
// in this world whose shape reaches a stranger, and the asker is the id her
|
|
37
|
+
// heir was opened for, so there is one blueprint to find and it is checked
|
|
38
|
+
// against the digest before it is written down.
|
|
39
|
+
async function shown(w, seen) {
|
|
40
|
+
for (const id of Object.keys(w.host.cells.occupants)) {
|
|
41
|
+
const bp = w.host.describe({ id });
|
|
42
|
+
if ((await digest(bp)) === seen)
|
|
43
|
+
return bp;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
31
46
|
}
|
|
32
47
|
async function record(c) {
|
|
33
|
-
const { w, before, arrival } = await stand(c);
|
|
48
|
+
const { w, before, draws, arrival } = await stand(c);
|
|
34
49
|
const judged = await arrival.door.door(arrival.ask.bytes);
|
|
35
50
|
const after = await partitionDigest(w.b, c.ward);
|
|
36
51
|
const opens = arrival.ask.open ? (await arrival.ask.open(judged.bytes)) : null;
|
|
37
|
-
|
|
52
|
+
// A reply that carries `seen` carries a being's shape on the wire, which is
|
|
53
|
+
// the one place in this corpus where what a being answers reaches a
|
|
54
|
+
// stranger. So the blueprint that digest is taken over is written down
|
|
55
|
+
// beside it: a kit stands a being that answers exactly this to the empty
|
|
56
|
+
// ask, and its door's bytes are these bytes. Every other record is the
|
|
57
|
+
// door alone and needs no being at all.
|
|
58
|
+
const seen = opens !== null && typeof opens === 'object' && !Array.isArray(opens) ? (opens).seen : undefined;
|
|
59
|
+
const blueprint = typeof seen === 'string' ? await shown(w, seen) : undefined;
|
|
60
|
+
return { case: c.case, name: c.name, state: c.state, ward: c.ward, kind: arrival.kind, heard: judged.heard, draws, ask: hex(arrival.ask.bytes), reply: hex(judged.bytes), opens, ...(blueprint ? { blueprint } : {}), wrote: before !== after };
|
|
38
61
|
}
|
|
39
62
|
// One after another and never at once: the cases share one stream, and two
|
|
40
63
|
// of them in flight together would draw from it in whatever order the
|
|
@@ -108,10 +131,15 @@ export const cases = [
|
|
|
108
131
|
name: 'she is not there',
|
|
109
132
|
state: 'the ward restarted without her class: her heirs stand and she does not',
|
|
110
133
|
ward: 'B',
|
|
134
|
+
// Under the key she bound: the heir was spent by her knock and admits
|
|
135
|
+
// nothing now, so an arrival signed with its secret would be D6 and not
|
|
136
|
+
// this case. Her own key is what the door holds for her, and it is the
|
|
137
|
+
// key that hears the word.
|
|
111
138
|
arrive: async (w, pin) => {
|
|
139
|
+
const b = await bound(w);
|
|
112
140
|
const WB = await w.b.reboot('B', Ward, { Caller });
|
|
113
141
|
await pin();
|
|
114
|
-
return at(WB,
|
|
142
|
+
return at(WB, b.inv.heir, 'word', { signer: b.own });
|
|
115
143
|
},
|
|
116
144
|
},
|
|
117
145
|
{
|
package/dist/vector/stand.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
// world runs on.
|
|
19
19
|
import { SUITE } from '../harbor/reach.js';
|
|
20
20
|
import { hex } from '../ward/arithmetic.js';
|
|
21
|
+
import { readJson } from '../ward/json.js';
|
|
21
22
|
import { caseNamed, stand } from './cases.js';
|
|
22
23
|
import { partitionDigest } from './world.js';
|
|
23
24
|
const CORS = {
|
|
@@ -39,7 +40,7 @@ export class Stand {
|
|
|
39
40
|
if (method === 'POST' && p === '/stand') {
|
|
40
41
|
let named;
|
|
41
42
|
try {
|
|
42
|
-
named =
|
|
43
|
+
named = readJson(body);
|
|
43
44
|
}
|
|
44
45
|
catch {
|
|
45
46
|
return NONE;
|
package/dist/vector/world.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Being } from '../being/being.ts';
|
|
|
5
5
|
import type { Answer, Asker, BeingClass, Blueprint, Invitation, JsonObject, Reply, Wanted } from '../being/types.ts';
|
|
6
6
|
export declare function splitmix64(seed: bigint): () => Uint8Array;
|
|
7
7
|
export declare const STREAM_SEED = 20250901n;
|
|
8
|
+
export declare const drawn: () => number;
|
|
8
9
|
export declare const rnd: (n?: number) => Uint8Array;
|
|
9
10
|
export declare const must: (ok: boolean, what: string) => void;
|
|
10
11
|
export declare const P: (h: MemoryHarbor, seed: string) => Partition;
|
package/dist/vector/world.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Nothing here draws from the device. A door's reply is sealed under a fresh
|
|
9
9
|
// ephemeral key and a fresh nonce, so the bytes are fixed only where the
|
|
10
10
|
// entropy is, and every draw in this file comes from one written-down stream.
|
|
11
|
-
// That is what lets `
|
|
11
|
+
// That is what lets `quo/vectors/door.json` be a corpus and not a
|
|
12
12
|
// sample, and what lets a kit stand in vector mode for a verifier that holds
|
|
13
13
|
// no key: the spec's chapter of that name is the contract this file keeps.
|
|
14
14
|
import { MemoryHarbor } from '../harbor/memory.js';
|
|
@@ -20,10 +20,10 @@ import { silence, unreached, isSilence, isUnreached, told } from '../being/silen
|
|
|
20
20
|
import { digest } from '../being/digest.js';
|
|
21
21
|
// ---- the stream
|
|
22
22
|
//
|
|
23
|
-
// SplitMix64
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
23
|
+
// SplitMix64, written out in the spec's vector-mode chapter with both
|
|
24
|
+
// multipliers and all three shifts, because a kit that reproduces it by
|
|
25
|
+
// recognising it is a kit that guessed. This is that, and it moves only
|
|
26
|
+
// when that does. It is
|
|
27
27
|
// not a cipher and is not meant to be one. Its whole job is that the bytes
|
|
28
28
|
// this file produces are the same bytes twice.
|
|
29
29
|
const GAMMA = 0x9e3779b97f4a7c15n;
|
|
@@ -46,13 +46,23 @@ export function splitmix64(seed) {
|
|
|
46
46
|
// stream to when it reproduces `door.json`.
|
|
47
47
|
export const STREAM_SEED = 20250901n;
|
|
48
48
|
let draw = splitmix64(STREAM_SEED);
|
|
49
|
+
// How many sixty-four bit words have been spent since the stream was reset.
|
|
50
|
+
// A record carries this number at the moment of its arrival, so a kit that
|
|
51
|
+
// built the door's state its own way sets its stream here and draws what
|
|
52
|
+
// this one would have drawn. It is the whole of what the setup's history is
|
|
53
|
+
// worth to a stranger: not how the world was made, but where the stream
|
|
54
|
+
// stands when the door is about to answer.
|
|
55
|
+
let spent = 0;
|
|
56
|
+
export const drawn = () => spent;
|
|
49
57
|
// Entropy, from the stream and never from the device. Every signer secret,
|
|
50
58
|
// every ephemeral seed, every byte of noise and every key a ward in this
|
|
51
59
|
// world mints comes through here, in the order the code asks for it.
|
|
52
60
|
export const rnd = (n = 32) => {
|
|
53
61
|
const out = new Uint8Array(n);
|
|
54
|
-
for (let i = 0; i < n; i += 8)
|
|
62
|
+
for (let i = 0; i < n; i += 8) {
|
|
55
63
|
out.set(draw().subarray(0, Math.min(8, n - i)), i);
|
|
64
|
+
spent += 1;
|
|
65
|
+
}
|
|
56
66
|
return out;
|
|
57
67
|
};
|
|
58
68
|
// A claim the world stands on. It throws, because a world that is not the
|
|
@@ -149,6 +159,7 @@ export const classes = { Host, Caller };
|
|
|
149
159
|
let counted = 1000;
|
|
150
160
|
export async function world() {
|
|
151
161
|
draw = splitmix64(STREAM_SEED); // a fresh world is a fresh stream: two runs, one corpus
|
|
162
|
+
spent = 0;
|
|
152
163
|
counted = 1000;
|
|
153
164
|
const a = new MemoryHarbor(), b = new MemoryHarbor();
|
|
154
165
|
a.random = rnd;
|
package/dist/ward/arithmetic.js
CHANGED
|
@@ -59,8 +59,10 @@ export function sameBytes(a, b) {
|
|
|
59
59
|
diff |= a[at] ^ b[at];
|
|
60
60
|
return diff === 0;
|
|
61
61
|
}
|
|
62
|
-
// The eight small-order points of Ed25519, in their canonical encoding
|
|
63
|
-
//
|
|
62
|
+
// The eight small-order points of Ed25519, in their canonical encoding, and
|
|
63
|
+
// the two points with x = 0 spelled with the sign bit set, which a decoder
|
|
64
|
+
// that ignores the bit reads as the same point. A public key among them
|
|
65
|
+
// verifies nothing. An encoding whose y coordinate is
|
|
64
66
|
// not reduced, y at or above the field's prime p, names one of the same
|
|
65
67
|
// points under another spelling, and a terrain's verify may accept a zero
|
|
66
68
|
// signature under it; the field has room for the spelling, so it is refused
|
|
@@ -74,6 +76,8 @@ const SMALL_ORDER = [
|
|
|
74
76
|
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a',
|
|
75
77
|
'26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85',
|
|
76
78
|
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa',
|
|
79
|
+
'0100000000000000000000000000000000000000000000000000000000000080',
|
|
80
|
+
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
|
77
81
|
].map(unhex);
|
|
78
82
|
// y >= p, read little-endian with the sign bit masked off: the top byte is
|
|
79
83
|
// 0x7f under the mask, every middle byte is 0xff, and the low byte is at
|
package/dist/ward/ground.d.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import type { Stance, BeingLike, BeingClass, Invitation } from '../being/types.ts';
|
|
2
|
-
export type
|
|
2
|
+
export type Memory = {
|
|
3
|
+
readonly rows: Record<string, unknown>;
|
|
4
|
+
told(row: string): void;
|
|
5
|
+
kept(): Promise<boolean>;
|
|
6
|
+
during(): () => void;
|
|
7
|
+
};
|
|
8
|
+
export declare const volatile: (rows?: Record<string, unknown>) => Memory;
|
|
9
|
+
export { BOX } from '../being/lent.ts';
|
|
10
|
+
export declare const OFFER = "offer";
|
|
11
|
+
export declare const RETRACT = "retract";
|
|
3
12
|
export type Ground = {
|
|
4
13
|
seed: string | Uint8Array;
|
|
5
|
-
|
|
14
|
+
random(n: number): Uint8Array;
|
|
15
|
+
memory: Memory;
|
|
6
16
|
instantiate(className: string, stance: Stance): BeingLike | null;
|
|
7
|
-
wrote?: (row: string) => void;
|
|
8
|
-
keep?: () => Promise<boolean>;
|
|
9
|
-
calling?: () => () => void;
|
|
10
17
|
carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | undefined>;
|
|
11
|
-
|
|
12
|
-
lend?: Lend;
|
|
18
|
+
box?: Invitation;
|
|
13
19
|
};
|
|
14
20
|
export type WardPointers = {
|
|
15
21
|
door(bytes: Uint8Array): Promise<{
|