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
@@ -1,699 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // The harbor core: what every harbor on a device is. It boots wards from a
3
- // store, keeps the map of ward pk to door for its own wards and pk to reach
4
- // for foreign ones, carries bytes to a pk, and hands bytes from the wire to
5
- // one door. It names no terrain: the store, the class loader and the lease
6
- // are the terrain's, handed in or laid over it. A disk harbor is this plus
7
- // files and a pid; a browser harbor is this plus IndexedDB and a lock; an
8
- // edge harbor is this over an object's storage. None of them is in this
9
- // tree, and every one of them passes the conformance suite untouched.
10
- //
11
- // The directory is filled four ways, in this order: the harbor's own
12
- // doors; a socket a dialer holds to it, bound once the door behind the
13
- // dialer's claim has proved it holds the key, and unbound at close; the
14
- // claims of a listener this harbor dialed, proved the same way and reached
15
- // through that line; and a hint, a pk at a URL. A hint never displaces a
16
- // reach proved at a door. The directory lives in memory and is kept by no
17
- // store: whoever learns a hint keeps it where it keeps everything, in a
18
- // being's cells, and hands it back at every boot. And one fallback: a dialer with nothing in its
19
- // directory for a pk sends down the socket it holds, because the listener
20
- // it dialed is the rendezvous and may hold that pk on another socket. Bytes
21
- // that arrive from the wire go to an own door or a held socket and never
22
- // onward by request or by fallback; that one rule is the rendezvous.
23
- import { Ward } from '../ward/ward.ts';
24
- import { maker, entropy as random, learnPk, type Ground, type WardPointers } from '../ward/ground.ts';
25
- import type { BeingClass, BeingLike, Invitation } from '../being/types.ts';
26
- import { silence } from '../being/silence.ts';
27
- import { request, type Reach } from './reach.ts';
28
- import { isWardPk, openReply, wardSignPk } from '../ward/seal.ts';
29
- import { concat, sealingPair, KEY } from '../ward/arithmetic.ts';
30
- import { within, LATE } from '../ward/allowance.ts';
31
- import type { Kept, Store, WardRecord } from './store.ts';
32
- import { restoreRow, rowsIn, type Partition } from '../ward/partition.ts';
33
- import { rowsOf } from './store.ts';
34
-
35
- // What a harbor hands its own device for one ward it serves. The two
36
- // pointers, and beside them what the device needs to be a device: the pk it
37
- // routes by, the name it is kept under, the record, a save the caller may
38
- // wait for.
39
- //
40
- // And the beings the harbor made. `instantiate` is in the ground, so the
41
- // harbor is the one that constructs every being of every ward it serves: it
42
- // holds those objects because it made them, and `being` and `keys` say what
43
- // it made. This is not a third path to a being. It is the device's own code
44
- // reaching its own object in its own process, the same reach a being has on
45
- // one she booted herself, and nothing of it crosses an edge. What a harbor
46
- // never does is read the partition to decide anything, and the partition is
47
- // not here: it holds every seed the ward has, and a side that wants to know
48
- // which beings there are or which one is public asks the ward, which is
49
- // what the ask pointer is for.
50
- export type Hosted = WardPointers & { pk: string; name: string; record: WardRecord; being(key: string): BeingLike | undefined; keys(): string[]; save(): Promise<void> };
51
-
52
- // One ward's saves, in a line: the ward says it wrote, the harbor writes the
53
- // partition once the line is free, and never twice at once, so an older
54
- // snapshot cannot land after a newer one. A save that fails is the harbor's
55
- // to count and never a door's to answer: the door answered bytes, and a
56
- // disk that is full is not a reason the far side may hear.
57
- // `dirty` holds the rows the ward has named since the last write, and never
58
- // what changed in them: the harbor keeps the partition and reads none of it,
59
- // so what a row is worth is the store's to read off the object when it
60
- // writes. Empty is nothing to do.
61
- //
62
- // `kept` is what the store took, row by row, as values: the copy handed to
63
- // the last save that succeeded, and undefined for a row that save dropped.
64
- // It is what a refused row is put back to. The harbor never asks a store what
65
- // it holds to find that out, because the one moment it would ask is from
66
- // inside a save the store has just refused, and a read taken there is the
67
- // read least likely to be true: the edge answers it with a partition missing
68
- // its head row, and a ward put back from that loses its heirs.
69
- //
70
- // `kept` moves only at a save taken while no call of this ward is in flight.
71
- // `calls` counts the calls the ward says are in flight. `fault` is a refusal
72
- // no arrival has heard yet, because a save taken by a timer answers nobody
73
- // and the refusal is the answer of an ask, never a number counted behind it.
74
- // `back` is a put back the ward is owed: a store that says no under a call is
75
- // answered no there and put back when the call ends, because rolling back
76
- // rows a call has written would leave that call's own keep with nothing dirty
77
- // and answer yes over a write that is gone. `under` is what the store took
78
- // while a call was in flight, row by row: when the call ends the ward is whole
79
- // again, so a row the store already holds unchanged becomes part of the point
80
- // and a row that moved since is named for one more save. Without that the
81
- // point would stay wherever the last quiet save left it, and a ward whose
82
- // beings talk to each other would be put back to before the relation.
83
- type Saving = {
84
- memory: Record<string, unknown>;
85
- dirty: Set<string>;
86
- kept: Map<string, Record<string, unknown> | undefined>;
87
- running: Promise<boolean> | null;
88
- timer: ReturnType<typeof setTimeout> | undefined;
89
- gone: boolean;
90
- calls: number;
91
- fault: boolean;
92
- back: boolean;
93
- under: Map<string, Record<string, unknown> | undefined>;
94
- };
95
- // A reach in the directory, and whether this harbor holds it as a socket a
96
- // dialer opened: only those, and its own doors, take bytes from the wire.
97
- export type Bound = { reach: Reach; held: boolean };
98
- // How the code half arrives: the classes a record's `code` names, loaded by
99
- // the terrain. The browser has a bundle and no files; a daemon has a folder.
100
- export type Loader = (record: WardRecord) => Promise<Record<string, BeingClass>>;
101
-
102
- export const DEFAULT_CODE = 'classes/index.ts';
103
- // The device's entropy, spelled once. A harbor draws it for a ward's seed at
104
- // birth, for the lid of every probe it sends, and it hands the same function
105
- // to every ward it boots as the ground's `random`.
106
- // How long a probe waits for the door behind a claim. A claim that answers
107
- // nothing in this time is not bound; the next announce is another chance.
108
- const PROBE = 10_000;
109
- // How many pks one announce may claim, and how many are proven at once.
110
- const ANNOUNCE = 64;
111
- const PROVING = 8;
112
- // How many times each reach has announced: a proof from an older announce
113
- // binds nothing when it lands.
114
- const announces = new WeakMap<Reach, number>();
115
- // The reaches that are hints, so a hint can be replaced and put down without
116
- // touching a reach a door proved.
117
- const hinted = new WeakSet<Reach>();
118
-
119
- export class Harbor {
120
- readonly store: Store;
121
- readonly loader: Loader;
122
- readonly wards = new Map<string, Hosted>(); // name -> pointers
123
- readonly doors = new Map<string, WardPointers['door']>(); // ward pk -> door
124
- readonly reaches = new Map<string, Bound>(); // the directory: foreign ward pk -> reach
125
- readonly down = new Set<string>(); // pks this harbor will not reach right now: weather, for a test
126
- readonly refused = new Map<string, number>(); // own ward pk -> arrivals its door would not admit. what a terrain does with it is its own
127
- readonly faults = new Map<string, number>(); // ward name -> saves the store refused. what a terrain does with it is its own
128
- // Told every time a save is refused, with the ward's name and how many of
129
- // its saves the store has now refused. The refusal is already the answer of
130
- // the ask that caused it, so nobody is waiting on this; it is for the box's
131
- // own record, a journal line or a counter, and a terrain that keeps none
132
- // leaves it alone.
133
- onFault: (name: string, faults: number) => void = () => {};
134
- readonly unbooted: string[] = []; // the wards the store keeps that would not host this run, by name
135
- readonly classes: Record<string, BeingClass> = {}; // classes handed in-process, beside the loader's
136
- readonly #saving = new Map<string, Saving>();
137
- // The dialers' sockets, in the order they were dialed: where a pk nobody
138
- // here knows is sent, because the listener a harbor dialed is a rendezvous
139
- // and may hold that pk for someone else. It is a list because a harbor may
140
- // dial more than one, and a rendezvous is a listener and nothing more, so
141
- // there is never only one of them. A pk is tried down the list until one
142
- // carries it: with a single slot, a harbor holding two lines answered
143
- // unreached for a pk the other line could have reached.
144
- readonly fallbacks: Reach[] = [];
145
- // How a dialer says what this harbor holds now. An announce is a claim and
146
- // a claim is worth nothing until the door behind it answers a probe, so
147
- // saying it again costs a proof and buys nothing to an impostor. What is
148
- // not free is never saying it again: a ward booted, adopted or dropped
149
- // after a line opened would be announced only by the next line, and a line
150
- // that does not drop never opens again, so it would be unreachable through
151
- // that listener for as long as the socket stayed healthy.
152
- readonly announcers = new Set<() => void>();
153
- #announce(): void {
154
- for (const say of this.announcers) say();
155
- }
156
-
157
- // The box, as one invitation per ward: what this device hands a ward it
158
- // boots so that the ward takes the box's being as a standing. Set once
159
- // the harbor's own ward is standing, which is the usual order: a terrain
160
- // puts that ward up first, then boots the rest on a ground that can reach
161
- // it. A terrain says which ward is handed one by overriding `boxFor`, and
162
- // a stranger's ward is handed none.
163
- offering: ((name: string) => Promise<Invitation | undefined>) | undefined;
164
-
165
- constructor(store: Store, loader: Loader = async () => ({})) {
166
- this.store = store;
167
- this.loader = loader;
168
- }
169
-
170
- protected boxFor(name: string, _record: WardRecord): Promise<Invitation | undefined> {
171
- return this.offering?.(name) ?? Promise.resolve(undefined);
172
- }
173
-
174
- // Boot every ward the store keeps. One ward that will not host, a
175
- // partition this kit cannot read or a loader that throws, is named in
176
- // `unbooted` and takes nobody else down with it.
177
- async boot(): Promise<void> {
178
- for (const name of await this.store.list()) {
179
- // A ward this harbor already hosts is not booted twice. A terrain that
180
- // keeps a ward of its own puts it up before this runs, so that a being
181
- // born in any other ward can lend from her first line.
182
- if (this.wards.has(name)) continue;
183
- try {
184
- const kept = await this.store.load(name);
185
- if (kept) await this.host(name, kept);
186
- } catch {
187
- this.unbooted.push(name);
188
- }
189
- }
190
- }
191
-
192
- // The object a ward writes into, by the name it is kept under: the
193
- // partition the harbor was handed at boot and keeps so it can save it.
194
- // Nothing in the harbor reads it, and it is not on `Hosted`, because a
195
- // side that holds a ward would then hold every seed in it and reach any
196
- // being past every door. It is here for one reader, the conformance
197
- // suite, whose census is ward state read straight and whose forgeries are
198
- // ward state written straight, the way `MemoryHarbor.partitions` is. A
199
- // side asks the ward.
200
- partitionOf(name: string): Record<string, unknown> | undefined {
201
- return this.#saving.get(name)?.memory;
202
- }
203
-
204
- // ---- saving
205
-
206
- // Every ward's writes kept and none waiting: no row named, no save armed
207
- // and none in flight. A write can name a row while another ward is being
208
- // saved, so this goes round until a pass finds nothing. A process about to
209
- // exit waits here, since its exit is not a turn the timer is given.
210
- async quiet(): Promise<void> {
211
- for (;;) {
212
- const busy = [...this.#saving].filter(([, s]) => !s.gone && (s.dirty.size > 0 || s.timer !== undefined || s.running !== null));
213
- if (busy.length === 0) return;
214
- for (const [name, s] of busy) await (s.running ?? this.#flush(name));
215
- }
216
- }
217
-
218
- // The ward wrote one row. Its partition is saved once the line is free, and
219
- // once for every burst of writes, with every row named in that burst.
220
- #wrote(name: string, row: string): void {
221
- const s = this.#saving.get(name);
222
- if (!s || s.gone) return;
223
- s.dirty.add(row);
224
- if (s.timer === undefined) s.timer = setTimeout(() => void this.#flush(name), 0);
225
- }
226
- // The rows a store refused, put back in the ward's memory to what the
227
- // store holds for them. A being whose row was refused would otherwise go
228
- // on answering out of a memory that runs ahead of the device: her next
229
- // write is a row past the same ceiling, refused again, and everything she
230
- // was told between now and the reload is gone with it. Standing her at
231
- // what is kept costs her the writes that were never kept, which is what
232
- // the asker was told, and leaves a small write after it one the store
233
- // takes.
234
- //
235
- // The shape is read where the shape lives. The harbor hands each row and
236
- // the copy it kept to the partition, which knows which part of itself a row
237
- // is; nothing here reads a being, a bind table or a head. And nothing here
238
- // asks the store: what it took is what this harbor handed it, which is
239
- // `kept`, and a row nobody has handed it since this ward was hosted is a
240
- // row the ward has not moved, so there is nothing to put back.
241
- // Every row together, and never the refused ones alone. A relation's state
242
- // is spread over rows, the head for the door's heirs and a being's own row
243
- // for the keys she speaks under: put one back and the door stands ahead of
244
- // the knocker and the relation never speaks again. So the ward is put back
245
- // to the whole point, which is what the store held at the last save taken
246
- // while no call was in flight. A row that point never knew is left where it
247
- // is and stays named: no save has ever taken it, so there is nothing to put
248
- // it back to. The rows that moved come back, so the store is put back with
249
- // them and a reload finds no half-done row.
250
- #standAtKept(s: Saving): string[] {
251
- if (s.gone) return [];
252
- const moved: string[] = [];
253
- for (const row of s.kept.keys()) {
254
- const was = s.kept.get(row);
255
- const now = rowsOf(s.memory, [row])[0][1];
256
- if (JSON.stringify(was) === JSON.stringify(now)) continue;
257
- restoreRow(s.memory as unknown as Partition, row, was);
258
- moved.push(row);
259
- }
260
- for (const row of moved) s.dirty.delete(row);
261
- return moved;
262
- }
263
-
264
- // The ward back to the point, and the store with it. The store is written
265
- // because a reload reads it and not the memory: a refusal that put only the
266
- // memory back would leave the rows the store did take standing ahead of the
267
- // ward until the next restart, and one half of a relation ahead of the
268
- // other. What goes out is what the store already took once, so it is a
269
- // write the store has no reason to refuse; one that refuses it anyway is
270
- // counted like any other, and there is nothing further to put back to.
271
- async #putBack(name: string, s: Saving): Promise<void> {
272
- // What the store took under the refused call is not a point to stand at.
273
- // It is also what has to be written over: a row the ward never moved
274
- // after that save comes back unchanged in memory and is still ahead of
275
- // the point in the store, so the rows that go out are the ones the memory
276
- // moved and the ones the store took under the call, together.
277
- const under = [...s.under.keys()];
278
- s.under.clear();
279
- const moved = this.#standAtKept(s);
280
- const rows = [...new Set([...moved, ...under])];
281
- if (rows.length === 0 || s.gone) return;
282
- try {
283
- await this.store.save(name, s.memory, rows);
284
- for (const row of rows) s.dirty.delete(row);
285
- } catch {
286
- // A store that will not take back what it took once is a store this
287
- // ward cannot be put back in. The count says so, the memory stands at
288
- // the point all the same, and the rows stay named for the next save.
289
- this.#fault(name);
290
- }
291
- }
292
-
293
- // The call ended and the ward is whole, so the point catches up with it. A
294
- // row the store took under the call and nobody has touched since is where
295
- // the ward stands, so it joins the point as it is; a row that moved after
296
- // that save is named for one more, and that save is quiet and moves the
297
- // point itself. Nothing is written twice for a ward whose call left its
298
- // rows where the store already has them.
299
- #settle(name: string, s: Saving): void {
300
- for (const [row, value] of s.under) {
301
- const now = rowsOf(s.memory, [row])[0][1];
302
- if (JSON.stringify(value) === JSON.stringify(now)) s.kept.set(row, value);
303
- else s.dirty.add(row);
304
- }
305
- s.under.clear();
306
- if (s.dirty.size > 0) void this.#flush(name);
307
- }
308
-
309
- // One refusal, counted and told. What a terrain does with the count is its
310
- // own; the arrival that caused it is told by the answer, not by this.
311
- #fault(name: string): void {
312
- const faults = (this.faults.get(name) ?? 0) + 1;
313
- this.faults.set(name, faults);
314
- try {
315
- this.onFault(name, faults);
316
- } catch {
317
- /* a box that cannot write its own journal is not a ward's problem */
318
- }
319
- }
320
-
321
- // Answers whether everything it wrote was kept. False is a store that
322
- // refused, and it is the ward's answer to the arrival that caused it, not
323
- // a number in a map nobody reads. A flush that had nothing to write kept
324
- // everything it was given, which is true and is why a read is never
325
- // refused.
326
- async #flush(name: string): Promise<boolean> {
327
- const s = this.#saving.get(name);
328
- if (!s) return true;
329
- if (s.timer !== undefined) {
330
- clearTimeout(s.timer);
331
- s.timer = undefined;
332
- }
333
- // A flush already in flight is the one that will write these rows, and
334
- // its verdict is theirs: two arrivals whose writes went out together are
335
- // both refused when that write was refused, because neither of them is
336
- // in the store.
337
- if (s.running) return s.running;
338
- s.running = (async () => {
339
- // A put back that was owed to a refusal under a call is done here, the
340
- // first moment the ward is whole again. Nothing is written over it
341
- // until it is done, because what is in memory is half of a call the
342
- // store already said no to.
343
- // The pen is held from the refusal to the put back that answers it.
344
- // What a save would write between the two is about to be put back
345
- // anyway, so writing it buys nothing and costs the one thing that
346
- // matters: a store written there holds rows of two moments, one side of
347
- // a call kept and the other about to be undone, and a box that goes
348
- // down before the put back leaves it torn with nobody left to mend it.
349
- if (s.back) {
350
- if (s.calls > 0) return false; // the call that owed it is still in flight, and a put back inside one would roll back rows it is still writing
351
- s.back = false;
352
- await this.#putBack(name, s);
353
- return false; // the verdict of the refusal that owed it, and nothing is written over a put back in the same save
354
- }
355
- while (s.dirty.size > 0 && !s.gone) {
356
- // Taken before the write, so a row named while this one is in flight
357
- // is written by the next turn of the loop and not lost with it.
358
- const rows = [...s.dirty];
359
- s.dirty.clear();
360
- // The copy of what is about to be written, taken before the store is
361
- // asked and kept only if the store takes it. It is the same values
362
- // the store is about to write down, so it costs one copy of the rows
363
- // of this save and nothing of the rows it does not name.
364
- const writing = rowsOf(s.memory, rows);
365
- try {
366
- const quiet = s.calls === 0; // read before the store is asked: the point is where the ward stood when this save was taken
367
- await this.store.save(name, s.memory, rows);
368
- // The store took it, and a restart finds it. Whether it is a point
369
- // a refused ward may be put back to is another question: a save
370
- // taken under a call holds half of a relation, so it is written and
371
- // is not somewhere to stand.
372
- for (const [row, value] of writing) (quiet ? s.kept : s.under).set(row, value);
373
- } catch {
374
- this.#fault(name);
375
- // A row the store would not take is a row that is not kept, so it
376
- // stays named and the verdict is no. The loop ends here rather than
377
- // trying again: what the next attempt writes is the same bytes to
378
- // the same store, and it is the next arrival that asks for one, not
379
- // this one in a circle. Whoever wrote it is told, which is the
380
- // whole point, and the row waits where a reader can still see it.
381
- for (const row of rows) s.dirty.add(row);
382
- // The refusal waits for an arrival to hear it: a save taken by a
383
- // timer answers nobody, and a ward whose row was refused between
384
- // two arrivals would otherwise have the refusal counted and the
385
- // next door go on saying yes. And the ward goes back to the point
386
- // now, unless a call is in flight, in which case the put back is
387
- // owed until that call ends.
388
- s.fault = true;
389
- if (s.calls > 0) {
390
- s.back = true;
391
- return false;
392
- }
393
- await this.#putBack(name, s);
394
- return false;
395
- }
396
- }
397
- return true;
398
- })();
399
- try {
400
- return await s.running;
401
- } finally {
402
- s.running = null;
403
- }
404
- }
405
-
406
- // ---- the directory
407
-
408
- // Bytes to one of this harbor's own doors, and the one bit the door says
409
- // beside its reply, counted.
410
- async #own(pk: string, door: WardPointers['door'], bytes: Uint8Array): Promise<Uint8Array> {
411
- const r = await door(bytes);
412
- if (!r.heard) this.refused.set(pk, (this.refused.get(pk) ?? 0) + 1);
413
- return new Uint8Array(r.bytes);
414
- }
415
-
416
- async carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | undefined> {
417
- if (this.down.has(pk)) return undefined;
418
- const door = this.doors.get(pk);
419
- if (door) return this.#own(pk, door, bytes);
420
- const bound = this.reaches.get(pk);
421
- if (bound) return bound.reach.carry(pk, bytes);
422
- for (const reach of this.fallbacks) {
423
- const back = await reach.carry(pk, bytes);
424
- if (back !== undefined) return back;
425
- }
426
- return undefined; // nobody here knows it, and no listener we dialed holds it
427
- }
428
-
429
- async deliver(pk: string, bytes: Uint8Array): Promise<Uint8Array | undefined> {
430
- if (this.down.has(pk)) return undefined;
431
- const door = this.doors.get(pk);
432
- if (door) return this.#own(pk, door, bytes);
433
- const bound = this.reaches.get(pk);
434
- return bound?.held ? bound.reach.carry(pk, bytes) : undefined;
435
- }
436
-
437
- // A hint, taken on the word of whoever gives it. A hint already standing
438
- // for the pk is replaced, since a URL is the one part of a ward that moves;
439
- // a reach proven at a door is never displaced by one.
440
- hint(pk: string, url: string): void {
441
- const standing = this.reaches.get(pk);
442
- if (standing && !hinted.has(standing.reach)) return; // a reach proven at a door beats a hint, held or reached through a dialed line
443
- const reach = request(url);
444
- hinted.add(reach);
445
- this.reaches.set(pk, { reach, held: false });
446
- }
447
-
448
- // A hint kept only once the door behind it has proven it holds the pk's
449
- // key, the same probe an announce is proven by. A URL that answers for
450
- // another ward, or for nobody, is refused and nothing is kept: whoever
451
- // hands a harbor a hint could otherwise point one ward's asks at a place
452
- // that drops them. True is kept.
453
- async learn(pk: string, url: string): Promise<boolean> {
454
- const standing = this.reaches.get(pk);
455
- if (standing && !hinted.has(standing.reach)) return true; // already reached, and proven at a door
456
- const reach = request(url);
457
- if (!(await this.prove(pk, reach))) return false;
458
- this.hint(pk, url);
459
- return true;
460
- }
461
-
462
- // A hint put down. A reach proven at a door is the line's, and goes when
463
- // the line does, never by a word from here.
464
- forget(pk: string): void {
465
- const standing = this.reaches.get(pk);
466
- if (standing && hinted.has(standing.reach)) this.reaches.delete(pk);
467
- }
468
-
469
- // An announce is a claim, and a claim binds nothing until proven: anyone
470
- // who can reach a listener could otherwise name a pk that is not theirs and
471
- // take its reachability. The proof is the door itself, as it already is. A
472
- // lid this harbor minted and noise after it is a box that does not open,
473
- // and the door answers one such box with silence sealed to the lid and
474
- // signed by the ward key. Only the holder of that ward's seed can write
475
- // that reply, the lid is fresh so nothing replays, and a box that does not
476
- // open writes nothing at the ward. This is the one box a harbor ever opens,
477
- // the one it sealed itself, and it reads nothing from it but that it is
478
- // the silence a door owes such a box, signed by the claimed key.
479
- async prove(pk: string, reach: Reach): Promise<boolean> {
480
- if (!isWardPk(pk)) return false;
481
- try {
482
- const lid = await sealingPair(random(KEY));
483
- const back = await within(PROBE, reach.carry(pk, concat([lid.pk, random(KEY)])));
484
- if (back === LATE || back === undefined) return false;
485
- const reply = await openReply(back, lid.secret, wardSignPk(pk));
486
- return reply !== null && 'silence' in reply;
487
- } catch {
488
- return false;
489
- }
490
- }
491
-
492
- // Bind what the far side claims, each pk proven at its door first. Both
493
- // sides bind this way: the listener the dialer's claims, the dialer the
494
- // listener's, since a claim is a claim whichever end made it. An announce
495
- // names at most this many pks, and they are proven a few at a time: a
496
- // side that announced a thousand would otherwise cost a thousand keys, a
497
- // thousand frames and a thousand waits at once, on its word alone.
498
- async bind(pks: string[], reach: Reach, held: boolean): Promise<void> {
499
- // An announce is the whole of what that side holds now, so a pk this
500
- // reach was bound for and does not claim now is unbound at once: a ward
501
- // that left a dialer is not reachable through it, proof or no proof.
502
- // And it is the newest word: a proof still in flight from an earlier
503
- // announce on this reach binds nothing when it lands, since the side
504
- // has spoken again since.
505
- const said = (announces.get(reach) ?? 0) + 1;
506
- announces.set(reach, said);
507
- for (const [pk, b] of this.reaches) if (b.reach === reach && !pks.includes(pk)) this.reaches.delete(pk);
508
- const claimed = [...new Set(pks)].slice(0, ANNOUNCE);
509
- const prove = async (pk: string) => {
510
- if (this.doors.has(pk)) return;
511
- // A line already held for this pk is not displaced by another line that
512
- // proves the same pk. A proof says only that the ward is reachable that
513
- // way, and a relay that forwards to the real ward proves as well as the
514
- // ward's own line does: taking the binding would add a hop the relay
515
- // chooses, and can drop. The first held line keeps it until it closes,
516
- // which unbind says.
517
- const standing = this.reaches.get(pk);
518
- if (standing?.held && standing.reach !== reach) return;
519
- if (!(await this.prove(pk, reach))) return;
520
- if (announces.get(reach) !== said) return; // a newer announce has spoken since
521
- if (this.reaches.get(pk)?.held && this.reaches.get(pk)!.reach !== reach) return; // one took it while this was proving
522
- this.reaches.set(pk, { reach, held });
523
- };
524
- for (let i = 0; i < claimed.length; i += PROVING) await Promise.all(claimed.slice(i, i + PROVING).map(prove));
525
- }
526
- unbind(reach: Reach): void {
527
- for (const [pk, b] of this.reaches) if (b.reach === reach) this.reaches.delete(pk);
528
- }
529
-
530
- // ---- wards
531
-
532
- // Mint a seed and boot an empty ward under a name: a world born here.
533
- async create(name: string, user = '', code = DEFAULT_CODE): Promise<Hosted> {
534
- const kept: Kept = { seed: random(KEY), partition: {}, record: { pk: '', code, user } };
535
- await this.store.put(name, kept);
536
- const hosted = await this.host(name, kept);
537
- await this.store.record(name, hosted.record);
538
- return hosted;
539
- }
540
-
541
- // Stop serving a ward and take it out of the store: what was kept comes
542
- // back, for another harbor to put. The partition is written first, and
543
- // never again under this name: whoever still holds the old pointers holds
544
- // a ward that saves nothing and answers its owner silence.
545
- async drop(name: string): Promise<Kept | undefined> {
546
- const h = this.wards.get(name);
547
- if (h) {
548
- await h.save();
549
- // Gone for whoever still holds the old pointers, whose save closure has
550
- // this row in hand, and out of the map, which now says what this harbor
551
- // serves and nothing else.
552
- const s = this.#saving.get(name);
553
- if (s) s.gone = true;
554
- this.#saving.delete(name);
555
- this.wards.delete(name);
556
- this.doors.delete(h.pk);
557
- this.#announce(); // this harbor no longer claims it: a listener unbinds a pk an announce stops naming
558
- }
559
- return this.store.take(name);
560
- }
561
-
562
- // Put what another harbor dropped, and boot it: same seed, same pk. A ward
563
- // that will not boot is not adopted. This is the one path where a partition
564
- // written somewhere else arrives, so it is the one place the ward's own
565
- // shape check is met, and the name is refused for as long as it is kept:
566
- // a partition put and left would hold the name against every later try,
567
- // with a copy of it in a store that can never serve it. It goes back out,
568
- // and the only copy is the one the caller is still holding.
569
- async adopt(name: string, kept: Kept): Promise<Hosted> {
570
- await this.store.put(name, kept);
571
- try {
572
- return await this.host(name, kept);
573
- } catch (e) {
574
- await this.store.take(name);
575
- throw e;
576
- }
577
- }
578
-
579
- // A ward from what the store keeps: the ground, once, and two pointers.
580
- async host(name: string, kept: Kept): Promise<Hosted> {
581
- const { seed, partition: memory } = kept;
582
- const classes = await this.loader(kept.record);
583
- const objects = new WeakMap<object, BeingLike>(); // cells -> the object instantiate made. a side's hand, never the ward's; it follows the cells out when she is unbooted
584
- const ground: Ground = {
585
- seed,
586
- // This ward's own classes first, then the harbor's: a ward that names a
587
- // class of its own is answered with hers.
588
- instantiate: maker(objects, classes, this.classes),
589
- carry: (pk, bytes) => this.carry(pk, new Uint8Array(bytes)),
590
- random,
591
- memory: {
592
- rows: memory,
593
- told: (row) => this.#wrote(name, row),
594
- // What the ward wrote is in the store when this resolves, and it says
595
- // whether the store took it. The ward calls it at the end of every
596
- // arrival, before it seals, so a call through a pointer has already
597
- // waited for the store when it comes back: a restart right after one
598
- // finds everything, and a refusal is that arrival's own answer rather
599
- // than a number counted behind it. A ward that wrote nothing has
600
- // nothing to save, so asking it what it holds is free, which is what
601
- // makes the ask pointer the way to find out rather than an expensive
602
- // one.
603
- //
604
- // A ward on its way out keeps nothing and refuses nothing: an arrival
605
- // racing an unhost is not a store saying no, and saying so would be a
606
- // lie in the one direction that matters.
607
- kept: async () => {
608
- const s = this.#saving.get(name);
609
- if (!s || s.gone) return true;
610
- const ok = await this.#flush(name);
611
- // A refusal is heard once, by the first arrival to ask after it: the
612
- // save that met it may have been a timer's, which had nobody to tell.
613
- // Under a call it is left standing, so the call that carried the
614
- // refused write is refused too, and the outermost arrival hears it.
615
- const no = !ok || s.fault;
616
- s.fault = false; // heard. the next arrival is answered by the store and not by what happened before it
617
- return !no;
618
- },
619
- // The ward says a call is in flight, and says when it ends. The harbor
620
- // goes on writing under it; what it does not do is take a save made
621
- // there as the point a refused ward is put back to, and a refusal that
622
- // lands under a call waits here for the call to end before it puts the
623
- // ward back. The ward is the one that knows, because a being reaching
624
- // another being of her own ward goes to that ward's door directly and
625
- // the harbor never sees that call at all.
626
- during: () => {
627
- const s = this.#saving.get(name);
628
- if (!s) return () => {};
629
- s.calls += 1;
630
- let lowered = false;
631
- return () => {
632
- if (lowered) return;
633
- lowered = true;
634
- s.calls -= 1;
635
- if (s.calls > 0 || s.gone) return;
636
- // The moment the ward is whole again: the point catches up with the
637
- // call that just ended, or a put back that was owed is done now, so
638
- // a being stands at what is kept before her next word rather than
639
- // at the next save.
640
- if (s.back) void this.#flush(name);
641
- else this.#settle(name, s);
642
- };
643
- },
644
- },
645
- };
646
- const box = await this.boxFor(name, kept.record);
647
- if (box) ground.box = box;
648
- // What the store holds for this ward at birth is what it was just handed
649
- // or just read, row by row. It is copied once here so that a being whose
650
- // very first write of this run is refused is put back too, rather than
651
- // waiting for a save of hers to succeed before the harbor knows what she
652
- // was. One copy of a partition per hosted ward, paid at boot and never
653
- // again: after this, a row's copy is replaced by the save that kept it.
654
- const saving: Saving = { memory, dirty: new Set(), kept: new Map(rowsOf(memory, rowsIn(memory))), running: null, timer: undefined, gone: false, calls: 0, fault: false, back: false, under: new Map() };
655
- this.#saving.set(name, saving);
656
- // A ward that will not be born leaves nothing behind: a partition of a
657
- // shape this kit cannot read throws here, and a name the harbor does not
658
- // serve must not have a saving row saying it does. Learning the pk is
659
- // inside this, because a ward that will not say its pk is a ward this
660
- // harbor cannot route to and is no more born than one that threw.
661
- let w: WardPointers, pk: string;
662
- try {
663
- w = await Ward(ground);
664
- pk = await learnPk(w);
665
- } catch (e) {
666
- saving.gone = true;
667
- this.#saving.delete(name);
668
- throw e;
669
- }
670
- // The same keep, for a caller that holds the ward rather than asks it:
671
- // a side that wrote through `being` and wants the store current says so
672
- // here. It answers nothing, because a caller inside the process reads
673
- // the fault off the harbor and an arrival is the only thing that needs a
674
- // verdict of its own.
675
- const save = async (): Promise<void> => void (await ground.memory.kept());
676
- const door = (bytes: Uint8Array) => w.door(bytes);
677
- const ask = async (method?: string, args?: Record<string, unknown>) => {
678
- if (saving.gone) return silence;
679
- return w.ask(method, args);
680
- };
681
- // The objects map is keyed by the cells the ward handed instantiate, so
682
- // the way back to a being is her cells; the partition is read for that
683
- // one lookup and for nothing else, and a key with no object is a being
684
- // who is not here this run. `keys` is what the map holds, so a caller
685
- // never needs the beings table to find out what to ask for.
686
- const beings = memory.beings as Record<string, object> | undefined;
687
- const being = (key: string) => {
688
- const cells = beings && Object.hasOwn(beings, key) ? beings[key] : undefined;
689
- return cells ? objects.get(cells) : undefined;
690
- };
691
- const keys = () => Object.keys(beings ?? {}).filter((k) => being(k) !== undefined);
692
- const hosted: Hosted = { door, ask, pk, name, record: { ...kept.record, pk }, being, keys, save };
693
- this.wards.set(name, hosted);
694
- this.doors.set(hosted.pk, door);
695
- await save();
696
- this.#announce(); // reachable through every line this harbor already holds, not only the next one
697
- return hosted;
698
- }
699
- }