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.
Files changed (62) hide show
  1. package/README.md +7 -7
  2. package/dist/being/index.d.ts +1 -0
  3. package/dist/being/index.js +1 -0
  4. package/dist/being/lent.d.ts +32 -0
  5. package/dist/being/lent.js +63 -0
  6. package/dist/being/types.js +1 -1
  7. package/dist/conformance/beings.d.ts +2 -1
  8. package/dist/conformance/beings.js +7 -2
  9. package/dist/conformance/index.d.ts +0 -1
  10. package/dist/conformance/index.js +8 -5
  11. package/dist/harbor/box.d.ts +121 -0
  12. package/dist/harbor/box.js +121 -0
  13. package/dist/harbor/core.d.ts +5 -5
  14. package/dist/harbor/core.js +73 -72
  15. package/dist/harbor/index.d.ts +3 -1
  16. package/dist/harbor/index.js +3 -0
  17. package/dist/harbor/memory.d.ts +8 -4
  18. package/dist/harbor/memory.js +43 -11
  19. package/dist/harbor/reach.js +9 -1
  20. package/dist/harbor/store.js +1 -1
  21. package/dist/vector/cases.d.ts +4 -3
  22. package/dist/vector/cases.js +33 -5
  23. package/dist/vector/stand.js +2 -1
  24. package/dist/vector/world.d.ts +1 -0
  25. package/dist/vector/world.js +17 -6
  26. package/dist/ward/arithmetic.js +6 -2
  27. package/dist/ward/ground.d.ts +13 -7
  28. package/dist/ward/ground.js +29 -11
  29. package/dist/ward/heirs.js +4 -2
  30. package/dist/ward/index.d.ts +2 -1
  31. package/dist/ward/index.js +1 -0
  32. package/dist/ward/json.d.ts +2 -0
  33. package/dist/ward/json.js +163 -0
  34. package/dist/ward/partition.js +2 -2
  35. package/dist/ward/seal.js +14 -9
  36. package/dist/ward/stance.d.ts +0 -5
  37. package/dist/ward/ward.d.ts +1 -1
  38. package/dist/ward/ward.js +62 -27
  39. package/package.json +7 -5
  40. package/src/being/index.ts +1 -0
  41. package/src/being/lent.ts +66 -0
  42. package/src/being/types.ts +1 -1
  43. package/src/conformance/beings.ts +8 -3
  44. package/src/conformance/index.ts +18 -15
  45. package/src/harbor/box.ts +131 -0
  46. package/src/harbor/core.ts +23 -22
  47. package/src/harbor/index.ts +4 -1
  48. package/src/harbor/memory.ts +52 -12
  49. package/src/harbor/reach.ts +7 -1
  50. package/src/harbor/store.ts +1 -1
  51. package/src/vector/cases.ts +37 -9
  52. package/src/vector/stand.ts +2 -1
  53. package/src/vector/world.ts +18 -6
  54. package/src/ward/arithmetic.ts +6 -2
  55. package/src/ward/ground.ts +85 -103
  56. package/src/ward/heirs.ts +4 -2
  57. package/src/ward/index.ts +2 -1
  58. package/src/ward/json.ts +144 -0
  59. package/src/ward/partition.ts +2 -2
  60. package/src/ward/seal.ts +12 -8
  61. package/src/ward/stance.ts +1 -1
  62. package/src/ward/ward.ts +59 -27
@@ -78,20 +78,19 @@ export class Harbor {
78
78
  for (const say of this.announcers)
79
79
  say();
80
80
  }
81
- // What this device lends the beings of the wards it hosts, by name. One
82
- // for the harbor, and `lendFor` says which ward may ask it: every ward,
83
- // unless a terrain says a stranger's ward is lent nothing.
84
- // Set at birth or once the harbor's own ward is standing, which is the
85
- // usual order: a terrain puts that ward up first, then boots the rest on a
86
- // ground that can reach it.
87
- lend;
88
- constructor(store, loader = async () => ({}), lend) {
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
- lendFor(_name, _record) {
94
- return this.lend;
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
- wrote: (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
- keep: 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
- calling: () => {
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
- };
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 lend = this.lendFor(name, kept.record);
566
- if (lend)
567
- ground.lend = lend;
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.keep?.());
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)
@@ -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, Lend } from '../ward/ground.ts';
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';
@@ -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';
@@ -1,5 +1,5 @@
1
- import type { BeingClass, BeingLike } from '../being/types.ts';
2
- import { type Ground, type WardPointers, type Lend } from '../ward/ground.ts';
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>, lend?: Lend): Promise<Booted>;
24
- reboot(seed: string, Ward: WardFactory, classes: Record<string, BeingClass>, lend?: Lend): Promise<Booted>;
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
  }
@@ -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 asking.
40
- // `lend` is what this device lends the ward's beings, and a test's stand-in
41
- // for a real terrain's: left out, every name answers null.
42
- async boot(seed, Ward, classes, lend) {
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, lend);
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, lend) {
48
- const memory = this.partitions.get(seed);
49
- if (!memory)
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
- ...(lend ? { lend } : {}),
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
  }
@@ -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 = JSON.parse(e.data);
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
@@ -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) => {
@@ -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
- before: string;
15
- after: string;
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[]>;
@@ -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 { world, sealed, bound, secret, rnd, partitionDigest, Caller } from './world.js';
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
- return { w, before, arrival };
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
- return { case: c.case, name: c.name, state: c.state, ward: c.ward, kind: arrival.kind, heard: judged.heard, ask: hex(arrival.ask.bytes), reply: hex(judged.bytes), opens, before, after, wrote: before !== after };
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, w.inv.heir, 'word', { signer: secret(w.inv), next: 'a'.repeat(64) });
142
+ return at(WB, b.inv.heir, 'word', { signer: b.own });
115
143
  },
116
144
  },
117
145
  {
@@ -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 = JSON.parse(new TextDecoder().decode(body));
43
+ named = readJson(body);
43
44
  }
44
45
  catch {
45
46
  return NONE;
@@ -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;
@@ -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 `protocol/vectors/door.json` be a corpus and not a
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. Sixteen lines a kit in any language writes from this comment
24
- // alone: the state starts at the seed, every draw adds the golden gamma
25
- // 0x9e3779b97f4a7c15, mixes with two xor-shift-multiplies, and the sixty-four
26
- // bit result is spent eight bytes at a time, least significant first. It is
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;
@@ -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. A
63
- // public key among them verifies nothing. An encoding whose y coordinate is
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
@@ -1,15 +1,21 @@
1
1
  import type { Stance, BeingLike, BeingClass, Invitation } from '../being/types.ts';
2
- export type Lend = (name: string, take: (invitation: Invitation) => Promise<boolean>) => Promise<boolean>;
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
- memory: Record<string, unknown>;
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
- random(n: number): Uint8Array;
12
- lend?: Lend;
18
+ box?: Invitation;
13
19
  };
14
20
  export type WardPointers = {
15
21
  door(bytes: Uint8Array): Promise<{