nervur 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +66 -101
  2. package/dist/being/being.d.ts +7 -15
  3. package/dist/being/being.js +16 -67
  4. package/dist/being/digest.d.ts +1 -1
  5. package/dist/being/digest.js +7 -27
  6. package/dist/being/faculty.d.ts +9 -0
  7. package/dist/being/faculty.js +68 -0
  8. package/dist/being/index.d.ts +4 -5
  9. package/dist/being/index.js +5 -5
  10. package/dist/being/types.d.ts +35 -62
  11. package/dist/being/types.js +3 -57
  12. package/dist/being/words.d.ts +13 -0
  13. package/dist/being/words.js +14 -0
  14. package/dist/contract/index.d.ts +26 -0
  15. package/dist/contract/index.js +19 -0
  16. package/dist/crypto/aes.d.ts +3 -0
  17. package/dist/crypto/aes.js +24 -0
  18. package/dist/crypto/bytes.d.ts +6 -0
  19. package/dist/crypto/bytes.js +33 -0
  20. package/dist/crypto/ed25519.d.ts +3 -0
  21. package/dist/crypto/ed25519.js +85 -0
  22. package/dist/crypto/hash.d.ts +2 -0
  23. package/dist/crypto/hash.js +11 -0
  24. package/dist/crypto/index.d.ts +7 -0
  25. package/dist/crypto/index.js +10 -0
  26. package/dist/crypto/json.d.ts +8 -0
  27. package/dist/crypto/json.js +250 -0
  28. package/dist/crypto/mlkem.d.ts +12 -0
  29. package/dist/crypto/mlkem.js +36 -0
  30. package/dist/crypto/subtle.d.ts +10 -0
  31. package/dist/crypto/subtle.js +29 -0
  32. package/dist/crypto/x25519.d.ts +2 -0
  33. package/dist/crypto/x25519.js +21 -0
  34. package/dist/folder/index.d.ts +13 -0
  35. package/dist/folder/index.js +125 -0
  36. package/dist/harbor/dock.d.ts +19 -0
  37. package/dist/harbor/dock.js +92 -0
  38. package/dist/harbor/harbor.d.ts +22 -0
  39. package/dist/harbor/harbor.js +84 -0
  40. package/dist/harbor/index.d.ts +4 -10
  41. package/dist/harbor/index.js +5 -18
  42. package/dist/harbor/registry.d.ts +7 -0
  43. package/dist/harbor/registry.js +13 -0
  44. package/dist/harbor/terrain.d.ts +8 -0
  45. package/dist/harbor/terrain.js +2 -0
  46. package/dist/index.d.ts +7 -0
  47. package/dist/index.js +14 -0
  48. package/dist/pointer/bodies.d.ts +20 -0
  49. package/dist/pointer/bodies.js +45 -0
  50. package/dist/pointer/index.d.ts +2 -0
  51. package/dist/pointer/index.js +4 -0
  52. package/dist/pointer/world.d.ts +34 -0
  53. package/dist/pointer/world.js +76 -0
  54. package/dist/quo/door.d.ts +34 -0
  55. package/dist/quo/door.js +172 -0
  56. package/dist/quo/index.d.ts +8 -0
  57. package/dist/quo/index.js +11 -0
  58. package/dist/quo/invitation.d.ts +7 -0
  59. package/dist/quo/invitation.js +15 -0
  60. package/dist/quo/keys.d.ts +40 -0
  61. package/dist/quo/keys.js +79 -0
  62. package/dist/quo/payload.d.ts +15 -0
  63. package/dist/quo/payload.js +55 -0
  64. package/dist/quo/relations.d.ts +40 -0
  65. package/dist/quo/relations.js +33 -0
  66. package/dist/quo/reply.d.ts +13 -0
  67. package/dist/quo/reply.js +35 -0
  68. package/dist/quo/seal.d.ts +42 -0
  69. package/dist/quo/seal.js +78 -0
  70. package/dist/quo/standing.d.ts +39 -0
  71. package/dist/quo/standing.js +90 -0
  72. package/dist/ward/allowance.d.ts +13 -9
  73. package/dist/ward/allowance.js +28 -67
  74. package/dist/ward/cells.d.ts +6 -6
  75. package/dist/ward/cells.js +67 -179
  76. package/dist/ward/index.d.ts +5 -10
  77. package/dist/ward/index.js +6 -13
  78. package/dist/ward/partition.d.ts +44 -60
  79. package/dist/ward/partition.js +56 -285
  80. package/dist/ward/stance.d.ts +29 -20
  81. package/dist/ward/stance.js +173 -406
  82. package/dist/ward/ward-being.d.ts +33 -0
  83. package/dist/ward/ward-being.js +76 -0
  84. package/dist/ward/ward.d.ts +43 -11
  85. package/dist/ward/ward.js +215 -363
  86. package/package.json +19 -33
  87. package/src/being/being.ts +31 -78
  88. package/src/being/digest.ts +7 -35
  89. package/src/being/faculty.ts +66 -0
  90. package/src/being/index.ts +5 -6
  91. package/src/being/types.ts +47 -145
  92. package/src/being/words.ts +31 -0
  93. package/src/contract/index.ts +44 -0
  94. package/src/crypto/aes.ts +26 -0
  95. package/src/crypto/bytes.ts +37 -0
  96. package/src/crypto/ed25519.ts +84 -0
  97. package/src/crypto/hash.ts +14 -0
  98. package/src/crypto/index.ts +10 -0
  99. package/src/crypto/json.ts +241 -0
  100. package/src/crypto/mlkem.ts +38 -0
  101. package/src/crypto/subtle.ts +33 -0
  102. package/src/crypto/x25519.ts +21 -0
  103. package/src/folder/index.ts +134 -0
  104. package/src/harbor/dock.ts +101 -0
  105. package/src/harbor/harbor.ts +105 -0
  106. package/src/harbor/index.ts +5 -19
  107. package/src/harbor/registry.ts +20 -0
  108. package/src/harbor/terrain.ts +13 -0
  109. package/src/index.ts +20 -0
  110. package/src/pointer/bodies.ts +47 -0
  111. package/src/pointer/index.ts +4 -0
  112. package/src/pointer/world.ts +91 -0
  113. package/src/quo/door.ts +178 -0
  114. package/src/quo/index.ts +11 -0
  115. package/src/quo/invitation.ts +15 -0
  116. package/src/quo/keys.ts +91 -0
  117. package/src/quo/payload.ts +61 -0
  118. package/src/quo/relations.ts +62 -0
  119. package/src/quo/reply.ts +38 -0
  120. package/src/quo/seal.ts +101 -0
  121. package/src/quo/standing.ts +111 -0
  122. package/src/stand/main.ts +19 -0
  123. package/src/stand/stand.ts +178 -0
  124. package/src/ward/allowance.ts +37 -75
  125. package/src/ward/cells.ts +63 -176
  126. package/src/ward/index.ts +6 -17
  127. package/src/ward/partition.ts +86 -326
  128. package/src/ward/stance.ts +185 -420
  129. package/src/ward/ward-being.ts +97 -0
  130. package/src/ward/ward.ts +229 -363
  131. package/dist/being/lent.d.ts +0 -32
  132. package/dist/being/lent.js +0 -72
  133. package/dist/being/silence.d.ts +0 -12
  134. package/dist/being/silence.js +0 -41
  135. package/dist/conformance/assert.d.ts +0 -11
  136. package/dist/conformance/assert.js +0 -106
  137. package/dist/conformance/beings.d.ts +0 -199
  138. package/dist/conformance/beings.js +0 -188
  139. package/dist/conformance/estate.d.ts +0 -5
  140. package/dist/conformance/estate.js +0 -388
  141. package/dist/conformance/index.d.ts +0 -80
  142. package/dist/conformance/index.js +0 -819
  143. package/dist/conformance/reach.d.ts +0 -10
  144. package/dist/conformance/reach.js +0 -72
  145. package/dist/conformance/store.d.ts +0 -5
  146. package/dist/conformance/store.js +0 -113
  147. package/dist/harbor/box.d.ts +0 -121
  148. package/dist/harbor/box.js +0 -121
  149. package/dist/harbor/core.d.ts +0 -55
  150. package/dist/harbor/core.js +0 -662
  151. package/dist/harbor/dial.d.ts +0 -9
  152. package/dist/harbor/dial.js +0 -81
  153. package/dist/harbor/memory.d.ts +0 -29
  154. package/dist/harbor/memory.js +0 -104
  155. package/dist/harbor/reach.d.ts +0 -36
  156. package/dist/harbor/reach.js +0 -199
  157. package/dist/harbor/store.d.ts +0 -35
  158. package/dist/harbor/store.js +0 -62
  159. package/dist/vector/cases.d.ts +0 -42
  160. package/dist/vector/cases.js +0 -223
  161. package/dist/vector/index.d.ts +0 -6
  162. package/dist/vector/index.js +0 -8
  163. package/dist/vector/stand.d.ts +0 -9
  164. package/dist/vector/stand.js +0 -77
  165. package/dist/vector/world.d.ts +0 -144
  166. package/dist/vector/world.js +0 -209
  167. package/dist/ward/arithmetic.d.ts +0 -31
  168. package/dist/ward/arithmetic.js +0 -249
  169. package/dist/ward/door.d.ts +0 -18
  170. package/dist/ward/door.js +0 -186
  171. package/dist/ward/ground.d.ts +0 -29
  172. package/dist/ward/ground.js +0 -56
  173. package/dist/ward/heirs.d.ts +0 -13
  174. package/dist/ward/heirs.js +0 -117
  175. package/dist/ward/json.d.ts +0 -2
  176. package/dist/ward/json.js +0 -163
  177. package/dist/ward/owner.d.ts +0 -13
  178. package/dist/ward/owner.js +0 -220
  179. package/dist/ward/seal.d.ts +0 -52
  180. package/dist/ward/seal.js +0 -150
  181. package/src/being/lent.ts +0 -72
  182. package/src/being/silence.ts +0 -46
  183. package/src/conformance/assert.ts +0 -100
  184. package/src/conformance/beings.ts +0 -188
  185. package/src/conformance/estate.ts +0 -412
  186. package/src/conformance/index.ts +0 -965
  187. package/src/conformance/reach.ts +0 -83
  188. package/src/conformance/store.ts +0 -125
  189. package/src/harbor/box.ts +0 -131
  190. package/src/harbor/core.ts +0 -699
  191. package/src/harbor/dial.ts +0 -112
  192. package/src/harbor/memory.ts +0 -123
  193. package/src/harbor/reach.ts +0 -221
  194. package/src/harbor/store.ts +0 -91
  195. package/src/vector/cases.ts +0 -257
  196. package/src/vector/index.ts +0 -11
  197. package/src/vector/stand.ts +0 -76
  198. package/src/vector/world.ts +0 -232
  199. package/src/ward/arithmetic.ts +0 -251
  200. package/src/ward/door.ts +0 -186
  201. package/src/ward/ground.ts +0 -142
  202. package/src/ward/heirs.ts +0 -116
  203. package/src/ward/json.ts +0 -144
  204. package/src/ward/owner.ts +0 -214
  205. package/src/ward/seal.ts +0 -178
package/src/ward/ward.ts CHANGED
@@ -1,410 +1,276 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // A ward that is its own ward. Booted by a harbor with one object, the
3
- // ground, and hands back two pointers: the door and the ask. Boots itself as
4
- // the first being in its own map, and every being it later instantiates gets
5
- // a stance built by the same code. Mints every key, seals every ask that
6
- // leaves, opens every one that arrives.
7
- import { silence, isSilence, unreached, isWord, word } from '../being/silence.ts';
8
- import { OWNER, isInvitation } from '../being/types.ts';
9
- import type { Asker, BeingLike, Cells, Invitation, JsonObject, Reply, Stance, Wanted, Word } from '../being/types.ts';
10
- import { BOX, OFFER, RETRACT, type Ground, type WardPointers } from './ground.ts';
11
- import { at, drop, open, put, emptyBind, emptyCells, HEAD, MINTED, type Bind, type Partition, type StandingKeys } from './partition.ts';
12
- import { Heirs } from './heirs.ts';
13
- import { makeDoor, type Door } from './door.ts';
14
- import { buildStance } from './stance.ts';
15
- import { ownerAnswer } from './owner.ts';
16
- import { allow, within, LATE } from './allowance.ts';
17
- import { beingKey, openReply, sealAsk, wardKey, wardPadlock, wardSignPk, type ReplyPayload, type WardKey } from './seal.ts';
18
- import { unhex } from './arithmetic.ts';
19
- import { guardCells, unguarded, cellFault } from './cells.ts';
2
+ // The ward: one door, one partition, and the beings behind the door. It
3
+ // unpacks from its seed, its memory and a catalogue of classes, the
4
+ // ward-being first and every other being from her own row, and it stands
5
+ // what is behind its door for `quo/`.
6
+ import { readValue, writeValue } from '../crypto/index.ts';
7
+ import { Door, readInvitation, WardKey, type Behind, type Choice, type Invitation, type Payload } from '../quo/index.ts';
8
+ import type { Carrier, Clock, Entropy, Rows } from '../contract/index.ts';
9
+ import { digest, isSilence, isWord, OWNER, silence, WARD, word, type Answer, type Asker, type BeingClass, type BeingLike, type JsonObject, type Reply } from '../being/index.ts';
10
+ import { Partition, PartitionRelations, type BeingRow } from './partition.ts';
11
+ import { DEPTH, WardStance, type Inside } from './stance.ts';
12
+ import { WardBeing, type Steward, type WardBeingStance } from './ward-being.ts';
20
13
 
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. 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 } };
14
+ // Where a ward finds a class by the name its row keeps.
15
+ export interface Catalogue {
16
+ classOf(name: string): BeingClass | undefined;
17
+ }
18
+
19
+ export type WardParts = {
20
+ readonly seed: Uint8Array | string;
21
+ readonly memory: Rows;
22
+ readonly catalogue: Catalogue;
23
+ readonly entropy: Entropy;
24
+ readonly clock: Clock;
25
+ readonly carrier: Carrier;
26
+ // The ward-being: `WardBeing`, or a being that extends it.
27
+ readonly root?: (stance: WardBeingStance) => WardBeing;
28
+ // An invitation on the dock, taken by the ward-being under `dock` when
29
+ // she holds no such standing yet.
30
+ readonly dock?: Invitation;
31
+ };
26
32
 
27
- // The ward. One call in, two pointers out. Async because keys are derived.
28
- export async function Ward(ground: Ground): Promise<WardPointers> {
29
- const w = await Self.boot(ground);
30
- return { door: w.door, ask: w.ask };
33
+ // The standing every hosted ward's ward-being holds on the dock.
34
+ export const DOCK = 'dock';
35
+
36
+ // A ward-being that lends to her own ward's beings, as the dock does.
37
+ export interface Lender {
38
+ offer(contract: string): Promise<Invitation | null>;
39
+ withdraw(contract: string, heir: string): Promise<void>;
31
40
  }
41
+ const lends = (being: unknown): being is Lender => typeof (being as Partial<Lender>).offer === 'function';
32
42
 
33
- class Self implements BeingLike {
34
- readonly key: WardKey;
35
- readonly pk: string;
36
- readonly g: Ground;
37
- readonly p: Partition;
38
- readonly heirs: Heirs;
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
- readonly door: WardPointers['door'];
42
- readonly ask: WardPointers['ask'];
43
+ type Birth = 'booted' | 'key taken' | 'no such class' | 'threw at birth';
44
+ type Resident = { being: BeingLike; row: BeingRow; stance: WardStance };
43
45
 
44
- // Birth, in order: the ward as its own first being, the box taken, and
45
- // only then every being the partition names constructed again. A being
46
- // who takes what her box lends does it at her birth, so the box must be
47
- // hers to ask before she is born, or a restart would be the one birth at
48
- // which the device lends nothing.
49
- static async boot(ground: Ground): Promise<Self> {
50
- const w = new Self(ground, await wardKey(ground.seed));
51
- await w.#takeBox();
52
- w.#reborn();
53
- return w;
46
+ class RootStance extends WardStance implements WardBeingStance {
47
+ readonly steward: Steward;
48
+ constructor(inside: Inside, row: BeingRow, steward: Steward) {
49
+ super(inside, WARD, row);
50
+ this.steward = steward;
54
51
  }
52
+ }
53
+
54
+ export class Ward implements Behind, Inside {
55
+ readonly key: WardKey;
56
+ readonly partition: Partition;
57
+ readonly door: Door;
58
+ readonly clock: Clock;
59
+ readonly #parts: WardParts;
60
+ readonly #residents = new Map<string, Resident>();
61
+ readonly #birthing = new Set<string>();
55
62
 
56
- // The device, taken as one standing under `BOX` by the ward's own being,
57
- // once: a ward woken from its partition already holds it. A knock that is
58
- // not answered leaves the ward up and lending nothing this run.
59
- async #takeBox(): Promise<void> {
60
- const inv = this.g.box;
61
- const mine = this.doors.get(this.pk)?.stance;
62
- if (!inv || !mine || mine.standings[BOX]) return;
63
- const out = await mine.standings.knock(inv);
64
- if (isSilence(out) || isWord(out)) return;
65
- await mine.standings.take(BOX, inv);
63
+ private constructor(parts: WardParts, key: WardKey) {
64
+ this.#parts = parts;
65
+ this.key = key;
66
+ this.clock = parts.clock;
67
+ this.partition = new Partition(parts.memory);
68
+ this.door = new Door(key, new PartitionRelations(this.partition), this, parts.entropy.drawer);
66
69
  }
67
70
 
68
- // What this device lends this ward's beings: the box's being, asked to
69
- // offer a name, answers the lent being's own invitation, and the taker knocks
70
- // and takes it. The ward reads nothing in the value but that it is one.
71
- async #lend(name: string, take: (inv: Invitation) => Promise<boolean>): Promise<boolean> {
72
- const box = this.doors.get(this.pk)?.stance.standings[BOX];
73
- if (!box) return false;
74
- const inv = await box.ask(OFFER, { name });
75
- if (!isInvitation(inv)) return false;
76
- const took = await take(inv);
77
- if (!took) await box.ask(RETRACT, { name, heir: inv.heir ?? null });
78
- return took;
71
+ // The ward-being first, then every being her row names. A class the
72
+ // catalogue lacks, or one that throws, leaves that being absent and her
73
+ // row as it was.
74
+ static async unpack(parts: WardParts): Promise<Ward> {
75
+ const ward = new Ward(parts, await WardKey.from(parts.seed));
76
+ if (!ward.partition.being(WARD)) {
77
+ ward.partition.beings[WARD] = { class: '', cells: {}, occupants: {}, standings: {} };
78
+ ward.partition.told(WARD);
79
+ ward.partition.toldHead();
80
+ }
81
+ const own = ward.partition.being(WARD)!;
82
+ const stance = new RootStance(ward, own, ward.#steward);
83
+ const being = parts.root ? parts.root(stance) : new WardBeing(stance);
84
+ own.class = being.constructor.name;
85
+ ward.#residents.set(WARD, { being, row: own, stance });
86
+ if (parts.dock && !Object.hasOwn(own.standings, DOCK)) await stance.standings.take(DOCK, parts.dock);
87
+ for (const [key, row] of Object.entries(ward.partition.beings)) {
88
+ const C = key === WARD ? undefined : parts.catalogue.classOf(row.class);
89
+ if (C) ward.#bear(key, row, C);
90
+ }
91
+ return ward;
79
92
  }
80
93
 
81
- private constructor(ground: Ground, key: WardKey) {
82
- this.g = ground;
83
- this.key = key;
84
- this.pk = key.pk;
85
- this.p = open(ground.memory.rows); // the partition: the ward's own state, and every being's row
86
- // Opening is itself a write: a memory nobody has written comes back with
87
- // a version stamped on it and the ward's own tables under it. A ward that
88
- // said nothing here would have every being of it kept and the head that
89
- // says which version they are written under kept by nobody, so the next
90
- // boot would read a partition of no version and refuse it.
91
- this.#wrote(HEAD);
92
- this.heirs = new Heirs(this.p, () => this.#wrote(HEAD));
93
- this.door = makeDoor(
94
- key,
95
- this.heirs,
96
- this.doors,
97
- () => this.p.public,
98
- (n) => ground.random(n),
99
- () => this.#keep(),
100
- ); // pointer one
101
- this.#boot(this.pk, () => this); // the ward's ward is itself
102
- // pointer two. the owner. the same answer every named asker gets, asker filled in as owner.
103
- this.ask = async (method, args = {}) => {
104
- try {
105
- const out = await this.answer({ id: OWNER }, method, args as JsonObject);
106
- // The same question the door asks, in the same place: after she has
107
- // answered and before the owner holds the answer. Silence is what a
108
- // failed ask says here, so a store that refused is silence, and the
109
- // owner reads it as it reads every other ask that did not take.
110
- if (!(await this.#keep().catch(() => false))) return silence;
111
- return isSilence(out) || isWord(out) ? silence : out;
112
- } catch {
113
- return silence;
114
- }
115
- };
94
+ get pk(): string {
95
+ return this.key.pk;
116
96
  }
117
97
 
118
- #reborn(): void {
119
- // a restart is silent: every being in the cells is constructed again, unasked.
120
- // A throw on a boot the owner asked for is that boot's error, and nothing
121
- // half-lives. This boot nobody asked for, and there is nobody to hand an
122
- // error to. Any constructor may throw, a default that is not a value, an
123
- // author's mistake, a shape she does not recognise after an upgrade, and
124
- // let through here it is one being taking down the ward and every other
125
- // being in it, on a restart. She is absent instead: no door, so silence
126
- // at the door, and her cells sit untouched, waiting for the run that can
127
- // read them. Her row stays hers: nobody boots another class over it, the
128
- // owner sees her as absent, and the owner may unboot her.
129
- for (const [k, c] of Object.entries(this.p.beings)) {
130
- const className = c.class;
131
- if (k === this.pk || !className) continue;
132
- try {
133
- this.#boot(k, (stance) => this.g.instantiate(className, stance));
134
- } catch {
135
- /* she is not here this run. the ward is, and so is everyone else. */
136
- }
137
- }
98
+ get draw() {
99
+ return this.#parts.entropy.drawer;
138
100
  }
139
101
 
140
- // ---- the ward as a being. asked only by its owners: the root, through
141
- // the ask pointer, and its occupants, through the door, whom only the root
142
- // can invite. A stranger cannot be a named asker here, because the door
143
- // names nobody the ward did not bind.
144
- async answer(asker: Asker, method?: string, args: JsonObject = {}): Promise<Reply> {
145
- // Nobody is not an owner. The door names nobody the ward did not bind, so
146
- // the only way here unnamed is a partition that says the ward is its own
147
- // public being, which no owner ask will write. Silence, not an error
148
- // object: an unnamed asker is a stranger, and a stranger hears one thing.
149
- if (asker.id === undefined) return silence;
150
- return ownerAnswer(
151
- {
152
- pk: this.pk,
153
- doors: this.doors,
154
- absent: () => this.#absent(),
155
- publicKey: () => this.p.public,
156
- instantiate: (className, key) => this.#instantiate(className, key),
157
- unboot: (key) => this.#unboot(key),
158
- setPublic: (key) => {
159
- this.p.public = key;
160
- this.#wrote(HEAD);
161
- },
162
- },
163
- asker,
164
- method,
165
- args,
166
- );
102
+ get wardBeing(): WardBeing {
103
+ return this.#residents.get(WARD)!.being as WardBeing;
167
104
  }
168
105
 
169
- // ---- ward functions. on the object. no stance reaches them.
106
+ // The unsealed ask: the ward-being, asked by whoever holds this ward.
107
+ async root(method?: string, args: JsonObject = {}): Promise<Answer> {
108
+ return this.#kept(WARD, () => this.#answer(WARD, { id: OWNER }, method, args));
109
+ }
170
110
 
171
- // The partition was written, and which row of it: a being by her key, or
172
- // the head. The harbor is told, and nothing more: what it does with the
173
- // word is its own, and the ward never learns. Every write says its row,
174
- // because a harbor told only that something moved must keep all of it, and
175
- // a being's row is the one part of a partition that grows without limit.
176
- #wrote(row: string): void {
177
- this.g.memory.told(row);
111
+ // A being's answer, kept. Where the memory refuses, what she wrote is
112
+ // put back and her answer is silence. What her relations stand on is not
113
+ // put back: a relation's keys move only as far as its standing follows.
114
+ async #kept(key: string, work: () => Promise<Answer>): Promise<Answer> {
115
+ const row = this.partition.being(key);
116
+ const before = row && JSON.stringify(row.cells);
117
+ const out = await work();
118
+ if (await this.partition.kept()) return out;
119
+ if (row && before !== undefined) {
120
+ for (const k of Object.keys(row.cells)) delete row.cells[k];
121
+ for (const [k, v] of Object.entries(JSON.parse(before) as JsonObject)) Object.defineProperty(row.cells, k, { value: v, enumerable: true, writable: true, configurable: true });
122
+ this.partition.told(key);
123
+ }
124
+ return silence;
178
125
  }
179
126
 
180
- // Whether what was written is kept, asked once at the end of an arrival
181
- // and never during one. A harbor that keeps nothing was never going to
182
- // refuse anything, so its silence here is yes: a ward driven with no store
183
- // under it answers exactly as it did before a harbor could say no.
184
- async #keep(): Promise<boolean> {
185
- return this.g.memory.kept();
127
+ // Behind: the zero head reaches the public being, a heir the being that
128
+ // invited it.
129
+ get zero(): boolean {
130
+ const key = this.partition.head.public;
131
+ return key !== null && this.#residents.has(key);
186
132
  }
187
133
 
188
- // The rows no door holds this run: beings whose class threw at birth or
189
- // is not the harbor's to give. Key to class.
190
- #absent(): Record<string, string | null> {
191
- const out: Record<string, string | null> = {};
192
- for (const [k, c] of Object.entries(this.p.beings)) if (k !== this.pk && !this.doors.has(k)) put(out, k, c.class ?? null);
193
- return out;
134
+ async answer(heir: string | null, p: Payload): Promise<Choice> {
135
+ const bound = heir === null ? undefined : this.partition.head.bind[heir];
136
+ const key = heir === null ? this.partition.head.public : bound?.being;
137
+ const asker: Asker = bound ? { id: bound.id } : {};
138
+ const args = p.args === undefined ? {} : readValue(p.args, DEPTH + 1);
139
+ if (key === null || key === undefined || typeof args !== 'object' || args === null || Array.isArray(args)) return { silence: true };
140
+ const out = await this.#kept(key, () => this.#answer(key, asker, p.method, args));
141
+ const object = isSilence(out) || isWord(out) ? undefined : writeValue(out, DEPTH);
142
+ if (object === undefined) return { silence: true };
143
+ const being = this.#residents.get(key)?.being;
144
+ const seen = p.method === undefined || !being?.describe ? null : await digest(being.describe(asker));
145
+ return { object, seen };
194
146
  }
195
147
 
196
- // A new being under a key nobody holds. A key with a row in the partition
197
- // is booted, whether a door holds it this run or not: an absent being's
198
- // row is hers, with every relation in it, and no class is booted over it.
199
- // A throw at birth is that boot's refusal and nothing half-lives; the
200
- // ward says which, since the owner hears objects.
201
- #instantiate(className: string, key: string): Resident | 'threw' | null {
202
- // The three refusals are read before the constructor runs, and the
203
- // constructor can reach the stance and boot: a class that boots its own
204
- // key from inside itself would pass all three, since neither its door nor
205
- // its row exists yet, and the two objects would then share one row. A key
206
- // being born is a key taken, for as long as the birth lasts.
207
- if (typeof key !== 'string' || typeof className !== 'string' || !key || this.doors.has(key) || at(this.p.beings, key) || this.#birthing.has(key)) return null;
208
- let door: Resident | null;
209
- this.#birthing.add(key);
148
+ // Every path that reaches a being's answer. A throw, a word out of her,
149
+ // or nothing at all is silence.
150
+ async #answer(key: string, asker: Asker, method: string | undefined, args: JsonObject): Promise<Answer> {
151
+ const resident = this.#residents.get(key);
152
+ if (!resident) return silence;
153
+ let out: Reply;
210
154
  try {
211
- door = this.#boot(key, (stance) => this.g.instantiate(className, stance));
155
+ out = await resident.being.answer(asker, method, args);
212
156
  } catch {
213
- return 'threw';
214
- } finally {
215
- this.#birthing.delete(key);
157
+ return silence;
216
158
  }
217
- if (!door) return null;
218
- unguarded(door.cells).class = className; // so a restart finds her. the ward's key, written behind her guard
219
- this.#wrote(key);
220
- return door;
159
+ return out === undefined || isWord(out) ? silence : out;
221
160
  }
222
161
 
223
- // The inverse of boot, and the only way a being leaves a ward. Her
224
- // relations go first and by the same calls she would have used herself,
225
- // so every occupant hears `removed` at the door from the keys it kept,
226
- // rather than meeting a being who is simply not there any more. What is
227
- // left after that is her cells and her bind table, and they go with her:
228
- // a row naming a being no door holds would be read on the next restart
229
- // and boot her again. An absent being leaves the same way: she has no
230
- // stance to speak for her, so the ward closes her heirs itself.
231
- #unboot(key: string): string[] | null {
232
- if (key === this.pk) return null;
233
- const door = this.doors.get(key);
234
- const cells = door?.cells ?? at(this.p.beings, key);
235
- if (!cells) return null;
236
- const ids = (r: unknown) => (r !== null && typeof r === 'object' ? Object.keys(r) : []);
237
- const occupants = ids(cells.occupants),
238
- standings = ids(cells.standings);
239
- if (door) {
240
- for (const id of occupants) door.stance.occupants.remove(id);
241
- for (const id of standings) door.stance.standings.remove(id);
242
- } else {
243
- const bind = at(this.p.bind, key);
244
- for (const heir of Object.values(bind?.occupants ?? {})) this.heirs.close(heir);
245
- }
246
- // A ward whose public being is gone answers arrivals for no heir with
247
- // silence, as a ward that never had one does.
248
- if (this.p.public === key) this.p.public = null;
249
- drop(this.p.beings, key);
250
- drop(this.p.bind, key);
251
- this.doors.delete(key);
252
- this.#wrote(key); // she is gone, and her row goes with her
253
- this.#wrote(HEAD); // her heirs were closed, and the public being may have been her
254
- return [...occupants, ...standings];
162
+ // Inside: what every stance of this ward stands on.
163
+
164
+ // One path through a door for every ask, this ward's own included.
165
+ async send(pk: string, bytes: Uint8Array): Promise<Uint8Array | null> {
166
+ if (pk === this.pk) return (await this.door.arrive(bytes)).bytes;
167
+ return this.#parts.carrier.carry(pk, bytes);
168
+ }
169
+
170
+ // A lend: the ward-being lends herself, as the dock does, or asks the dock
171
+ // through her standing.
172
+ async offer(contract: string): Promise<Invitation | null> {
173
+ const own = this.#residents.get(WARD)!;
174
+ if (lends(own.being)) return own.being.offer(contract);
175
+ const out = await own.stance.standings.get(DOCK)?.ask('lend', { contract });
176
+ return readInvitation((out as { invitation?: unknown } | undefined)?.invitation);
177
+ }
178
+
179
+ async retract(contract: string, heir: string): Promise<void> {
180
+ const own = this.#residents.get(WARD)!;
181
+ if (lends(own.being)) return own.being.withdraw(contract, heir);
182
+ await own.stance.standings.get(DOCK)?.ask('retract', { contract, heir });
255
183
  }
256
184
 
257
- // Nothing is written until there is somebody to write it for: a class the
258
- // harbor does not know makes no being, and a boot that made nobody must
259
- // leave the partition as it found it.
260
- //
261
- // `make` is who to build with the stance this raises. Two callers and two
262
- // makers: the ground's `instantiate` for a being of the ward, and the ward
263
- // itself, which is the first being in its own map and is already made.
264
- #boot(key: string, make: (stance: Stance) => BeingLike | null): Resident | null {
265
- const cells = guardCells(at(this.p.beings, key) ?? emptyCells(), () => this.#wrote(key));
266
- const bind = at(this.p.bind, key) ?? emptyBind();
267
- const stance: Stance = buildStance(
268
- {
269
- pk: this.pk,
270
- // This stance, not merely this key: unboot and boot again under the
271
- // same key makes a new being, and the old stance is not hers.
272
- live: () => this.doors.get(key)?.stance === stance,
273
- mintKey: (b) => this.#mintKey(b, key),
274
- openHeir: (heir, being, id) => this.heirs.open(heir, being, id),
275
- closeHeir: (heir) => this.heirs.close(heir),
276
- send: (b, keys, method, args, wanted) => this.#send(b, keys, method, args, wanted, key),
277
- // A throw at birth is null to her, not a throw in her method: she asked
278
- // for a being and got none, and her own answer is still hers to give.
279
- instantiate: (className, k) => {
280
- const made = this.#instantiate(className, k);
281
- return made && made !== 'threw' ? made.key : null;
282
- },
283
- // The two halves of a maker's way back, each the ward's own call: the
284
- // being made mints an occupant for her maker, and a relation that
285
- // never came about takes her out again.
286
- relate: async (k, id) => (await this.doors.get(k)?.stance.occupants.invite(id)) ?? null,
287
- unmake: (k) => void this.#unboot(k),
288
- lend: (name, take) => this.#lend(name, take),
289
- wrote: () => this.#wrote(key),
290
- },
291
- key,
292
- cells,
293
- bind,
294
- );
295
- const being = make(stance);
296
- if (!being || typeof being.answer !== 'function') return null; // a being is anything with an answer, and nothing without one
297
- put(this.p.beings, key, cells);
298
- put(this.p.bind, key, bind);
299
- const door: Resident = { key, cells, bind, stance, being };
300
- this.doors.set(key, door);
301
- this.#wrote(key);
302
- return door;
185
+ live(key: string, row: BeingRow): boolean {
186
+ return this.#residents.get(key)?.row === row;
303
187
  }
304
188
 
305
- async #mintKey(bind: Bind, row: string): Promise<{ seed: string; pk: string }> {
306
- const k = await beingKey(this.g.random(32));
307
- // The last few she minted, and no more. A relation rotates on every ask,
308
- // so a list of all of them is a partition that grows for as long as she
309
- // keeps talking and can never be written down. Nothing is read from this
310
- // but the keys of the moment.
311
- bind.minted.push(k.pk);
312
- if (bind.minted.length > MINTED) bind.minted.splice(0, bind.minted.length - MINTED);
313
- this.#wrote(row);
314
- return k;
189
+ // A being boots another of this ward. With an id, the new being invites
190
+ // her maker under the maker's key, and the maker takes it under id; a
191
+ // relation that could not be made unmakes the new being.
192
+ async boot(maker: string, className: string, key: string, id?: string): Promise<string | null> {
193
+ if ((await this.#make(key, className)) !== 'booted') return null;
194
+ if (id === undefined) return key;
195
+ const invitation = await this.#residents.get(key)!.stance.occupants.invite(maker);
196
+ const taken = invitation ? await this.#residents.get(maker)?.stance.standings.take(id, invitation) : null;
197
+ if (taken) return key;
198
+ this.#unboot(key);
199
+ return null;
315
200
  }
316
201
 
317
- // One send for every door, the ward's own included. Mine: never leaves.
318
- // Not mine: carry. Signs as the standing's current key, announces its
319
- // next, seals to the far ward, opens the reply with the ephemeral secret,
320
- // and rotates to the announced key once the far door has answered under
321
- // the current one.
322
- async #send(bind: Bind, keys: StandingKeys, method: string | undefined, args: JsonObject, wanted: Wanted | undefined, row: string): Promise<ReplyPayload | typeof silence | Word> {
323
- // The harbor is told a call is in flight for as long as this one lasts,
324
- // and told again when it ends. Everything this ward writes between the
325
- // two is half of a relation: the count taken here, the key rotated when
326
- // the reply opens, and whatever the being writes on either side of her
327
- // own ask. A harbor may write those rows down; what it may not do is put
328
- // the ward back to them.
329
- const lower = this.g.memory.during();
202
+ async #make(key: string, className: string): Promise<Birth> {
203
+ if (key === '' || key === WARD || this.partition.being(key) || this.#birthing.has(key)) return 'key taken';
204
+ const C = this.#parts.catalogue.classOf(className);
205
+ if (!C) return 'no such class';
206
+ this.#birthing.add(key);
330
207
  try {
331
- return await this.#sent(bind, keys, method, args, wanted, row);
208
+ const row: BeingRow = { class: className, cells: {}, occupants: {}, standings: {} };
209
+ this.partition.beings[key] = row;
210
+ if (!this.#bear(key, row, C)) {
211
+ delete this.partition.beings[key];
212
+ return 'threw at birth';
213
+ }
214
+ this.partition.told(key);
215
+ return 'booted';
332
216
  } finally {
333
- lower?.();
217
+ this.#birthing.delete(key);
334
218
  }
335
219
  }
336
220
 
337
- async #sent(bind: Bind, keys: StandingKeys, method: string | undefined, args: JsonObject, wanted: Wanted | undefined, row: string): Promise<ReplyPayload | typeof silence | Word> {
338
- // What she asked for, held to what this ward allows. Asking for nothing is
339
- // the default, and asking for more than the ceiling is the ceiling: budget
340
- // is granted by a ward, never minted by a being.
341
- const budget = allow(wanted);
342
- const by = (await beingKey(unhex(keys.current))).pk;
343
- // A public being is reached without a heir, and the door keeps nothing for
344
- // whoever asked: no key it vouched for, so nothing to announce and nothing
345
- // to rotate to. A standing on her signs with one key for life. Every other
346
- // ask announces its next: there is no send that does not.
347
- if (keys.next === null && keys.heir !== null) keys.next = (await this.#mintKey(bind, row)).seed;
348
- const next = keys.next === null ? null : (await beingKey(unhex(keys.next))).pk;
349
- // Her count for this relation, one higher every call and never reused. The
350
- // far door honours each number once. One relation sends one at a time, so
351
- // the numbers leave in the order they were taken.
352
- const seq = (keys.seq += 1);
353
- // Named here, before the bytes go out, and not at the end with the keys.
354
- // The far door honours a number once, and it honours it before this side
355
- // hears anything back: a reply that is lost, a wait that ran out, a road
356
- // that took the bytes and said nothing, all end this call before the end
357
- // of it is reached. A count named only there is a count a restart forgets
358
- // while the door remembers, and she then speaks a number that door has
359
- // already spent, which it refuses as the replay it cannot tell it from.
360
- // So the rule is the send and not the reply: what she spoke under is
361
- // written down because she spoke it.
362
- this.#wrote(row);
363
- // Args that will not go through the seal are args that never left. A
364
- // being holds three answers and a throw is not one of them, so an ask
365
- // that cannot be sealed is told what is true of it: no door was reached.
366
- // One object of values, by the rule her cells keep and the far door
367
- // reads: a Date would arrive as a string, a function would not arrive at
368
- // all, a list or a string would be refused as a stranger's bytes, and
369
- // she would never know the far being was asked something other than
370
- // what she said.
371
- if (args === null || typeof args !== 'object' || Array.isArray(args) || cellFault(args, 'args') !== null) return unreached();
372
- let sealed;
221
+ #bear(key: string, row: BeingRow, C: BeingClass): boolean {
222
+ const stance = new WardStance(this, key, row);
373
223
  try {
374
- sealed = await sealAsk(keys.heir, { by, next, seq, time: budget.time, method, args }, unhex(keys.current), wardPadlock(keys.ward), this.g.random(32));
224
+ this.#residents.set(key, { being: new C(stance), row, stance });
225
+ return true;
375
226
  } catch {
376
- return unreached();
227
+ this.#residents.delete(key);
228
+ return false;
377
229
  }
378
- const { bytes, ephemeral } = sealed;
379
- // The wait is bounded here, and again around the lane in the stance. Two
380
- // bounds and not one, because they end two different things: the stance's
381
- // ends the wait a being is held in, and this one ends the occupancy of the
382
- // relation's lane. A being holds three answers and a wait that does not
383
- // end is none of them, and a lane nobody ever leaves is a relation the
384
- // next ask never reaches. Only a bound on the wire breaks the second, and
385
- // taking it out would leave one quiet far side holding the lane for good.
386
- // What comes back late is not read.
387
- //
388
- // A wait that ran out is `late`, never unreached. Unreached promises
389
- // nothing was delivered and is safe to retry; a bound that expired knows
390
- // no such thing, because the far door may have heard and be working still.
391
- //
392
- // A harbor answers bytes or nothing. One that throws instead has answered
393
- // nothing in a louder voice, and is read as nothing: no door was reached.
394
- const carried = (keys.ward === this.pk ? this.door(new Uint8Array(bytes)).then((r) => r.bytes) : this.g.carry(keys.ward, bytes)).then(
395
- (b) => b,
396
- () => undefined,
397
- );
398
- const out = await within(budget.time, carried);
399
- if (out === LATE) return word('late');
400
- if (out === undefined) return unreached(); // nothing came back. no door was reached.
401
- const reply = await openReply(out, ephemeral, wardSignPk(keys.ward));
402
- if (!reply) return silence; // did not open, or not from that ward: not Quo's bytes
403
- if ('object' in reply && keys.next !== null) {
404
- keys.current = keys.next; // the far door holds `next` as announced. move to it.
405
- keys.next = null;
230
+ }
231
+
232
+ #unboot(key: string): boolean {
233
+ const row = key === WARD ? undefined : this.partition.being(key);
234
+ if (!row) return false;
235
+ for (const { heir } of Object.values(row.occupants)) {
236
+ this.door.remove(heir);
237
+ delete this.partition.head.bind[heir];
406
238
  }
407
- this.#wrote(row); // the count moved, and the keys may have
408
- return reply;
239
+ if (this.partition.head.public === key) this.partition.head.public = null;
240
+ delete this.partition.beings[key];
241
+ this.#residents.delete(key);
242
+ this.partition.toldHead();
243
+ this.partition.told(key);
244
+ return true;
409
245
  }
246
+
247
+ // What the ward-being asks of her ward.
248
+ readonly #steward: Steward = {
249
+ pk: () => this.pk,
250
+ classOf: (name) => this.#parts.catalogue.classOf(name),
251
+ beings: () => {
252
+ const out: ReturnType<Steward['beings']> = {};
253
+ for (const [key, row] of Object.entries(this.partition.beings)) {
254
+ if (key !== WARD) out[key] = { class: row.class, public: this.partition.head.public === key, absent: !this.#residents.has(key) };
255
+ }
256
+ return out;
257
+ },
258
+ boot: (key, className) => this.#make(key, className),
259
+ unboot: (key) => this.#unboot(key),
260
+ publish: (key) => {
261
+ if (key !== null && (key === WARD || !this.partition.being(key))) return false;
262
+ this.partition.head.public = key;
263
+ this.partition.toldHead();
264
+ return true;
265
+ },
266
+ invite: (being, id, notes): Promise<Invitation | null> => {
267
+ const resident = being === WARD ? undefined : this.#residents.get(being);
268
+ return resident ? resident.stance.occupants.invite(id, notes) : Promise.resolve(null);
269
+ },
270
+ ask: (being, method, args) => {
271
+ const key = being ?? this.partition.head.public;
272
+ if (key === null || key === WARD) return Promise.resolve(word('unreached'));
273
+ return this.#answer(key, being === null ? {} : { id: OWNER }, method, args);
274
+ },
275
+ };
410
276
  }