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,353 +1,113 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // The partition. The ward's cells, all the way down. Values only, so the
3
- // harbor may persist it as it likes. Everything durable a ward has is here,
4
- // every secret included. Nothing here is ever in a being's cells.
5
- import { unguarded } from './cells.ts';
6
- import type { Cells } from '../being/types.ts';
7
-
8
- // The shape of everything below. A ward stamps what it writes and refuses
9
- // what it cannot read: the partition outlives the ward that wrote it, and a
10
- // ward that guessed at a shape it did not know would not fail, it would answer
11
- // wrongly on relations it had silently misread.
2
+ // The partition: everything one ward keeps, as rows of values in the memory
3
+ // its terrain opened for it.
12
4
  //
13
- // One value, and it does not move until 1.0.0. Before a release there is
14
- // nobody holding a partition of another shape to tell apart: if the types
15
- // below change, the partition is thrown away, not migrated to and not counted.
16
- // A number that climbed with every change would be a changelog, and the only
17
- // question asked here is answered by equality — is this a partition this ward
18
- // can read. At 1.0.0 this changes once, and from then on it means something.
19
- export const VERSION = 'pre-1.0.0';
20
-
21
- // Every version this ward can read, newest last. Today it is one, and the
22
- // seam below is what makes it able to be more than one later: a partition
23
- // written under an older readable version is carried forward by `upgrade`
24
- // before the ward touches it. The seam is here before 1.0.0 because after
25
- // 1.0.0 there is nowhere to put it. A partition holds every secret and every
26
- // relation a world has, so the first shape change after the freeze cannot be
27
- // answered by throwing it away, and a ward that meets a shape it has no seam
28
- // for refuses to boot with everything still inside it.
29
- export const READS: readonly string[] = [VERSION];
30
-
31
- // Carry a partition written under an older readable version forward to this
32
- // one. There is nothing to carry today, because READS has one member. Each
33
- // step added here is from one member of READS to the next.
34
- function upgrade(p: Partition): Partition {
35
- return p;
36
- }
37
-
38
- // One occupant, as the door knows her: by the heir pk her invitation carried.
39
- // `current` is the pk that may speak now. `announced` is the pk `current`
40
- // vouched for on its last call, and may speak next. Both may speak until one
41
- // does, so a lost reply after a rotation strands nobody. `fresh` is true
42
- // until the knock: the heir is a key the inviter handed out, and it dies the
43
- // moment it is used.
44
- export type Heir = {
45
- being: string; // the key of the being who minted the id
46
- id: string; // her id for the occupant
47
- current: string; // pk hex
48
- announced: string | null; // pk hex
49
- fresh: boolean;
50
- // Once-only delivery. The highest number honoured, and which numbers below
51
- // it are already spent. Durable, because a door that forgets what it
52
- // honoured honours it again after a restart.
53
- mark: number;
54
- spent: number[];
55
- // Reserved, and nothing writes it. A door refuses a number at or below the
56
- // span, which is right for an accident and for an interception. A kit that
57
- // grows retry may want the other answer, giving back what it already said
58
- // rather than one silence, and that answer has to live somewhere durable.
59
- // The slot is cut now because a Heir that grows a field after 1.0.0 is a
60
- // partition shape change, and this is the cheapest thing in this file.
61
- last?: { seq: number; reply: string };
62
- };
63
-
64
- // One standing, as the caller's ward keeps it: where to send, how to seal,
65
- // which key to sign with, and the key it has announced for next time.
66
- export type StandingKeys = {
67
- ward: string; // the far ward's pk, 128 hex: signing pk then padlock
68
- heir: string | null; // the heir pk this standing was born on. null: the far ward's public being
69
- current: string; // seed hex of the signing key that speaks now
70
- next: string | null; // seed hex of the key announced on the last call, if any
71
- seq: number; // the last number she spoke under. the next call is this plus one
5
+ // rows.ward the door's lock, heirs and gone, which being and id
6
+ // each heir belongs to, and the public being
7
+ // rows.beings[key] one being: her class, cells, occupants and standings
8
+ //
9
+ // The ward writes into the rows directly and tells the memory which row it
10
+ // wrote: the head as the empty name, which no being's key is, or the
11
+ // being's key.
12
+ import type { Lock } from '../crypto/index.ts';
13
+ import { GONE, keptLock, lockOf, type HeirRecord, type Invitation, type KeptLock, type Relations, type StandingState } from '../quo/index.ts';
14
+ import type { Rows } from '../contract/index.ts';
15
+ import type { JsonObject } from '../being/index.ts';
16
+
17
+ export const HEAD_ROW = '';
18
+
19
+ export type Head = {
20
+ lock?: KeptLock;
21
+ heirs: Record<string, HeirRecord>;
22
+ gone: string[];
23
+ bind: Record<string, { being: string; id: string }>;
24
+ public: string | null;
72
25
  };
73
26
 
74
- export type Bind = {
75
- standings: Record<string, StandingKeys>; // id -> keys
76
- occupants: Record<string, string>; // id -> heir pk
77
- knocks: Record<string, { current: string; next: string | null; spoke: boolean; sent: boolean; seq: number }>; // <ward pk>:<heir pk>, or public:<ward pk> -> her keys for a knock, before take. spoke: bytes came back once. sent: bytes went out once, answered or not
78
- answered: Record<string, true>; // <ward pk>:<heir pk>, or public:<ward pk>: knocked and answered
79
- minted: string[]; // the last eight pks her side minted. nothing in the ward reads it: it is there to be looked at, and a kit that keeps it empty holds the same ward
27
+ export type StandingRow = { invitation: Invitation; state: StandingState; seen: string | null };
28
+ export type BeingRow = {
29
+ class: string;
30
+ cells: JsonObject;
31
+ occupants: Record<string, { heir: string; notes: JsonObject }>;
32
+ standings: Record<string, StandingRow>;
80
33
  };
81
34
 
82
- // A relation she removed, as the door remembers it: the keys that may still
83
- // speak for it, so their holder hears `removed` and a stranger still hears
84
- // silence. Nothing else survives the removal. The list is bounded, oldest
85
- // out, because a door that remembered every relation ever ended would grow
86
- // for as long as she keeps inviting.
87
- export type Gone = { current: string; announced: string | null };
88
- export const GONE = 256;
89
-
90
- // The last few pks a relation minted, and no more. A relation rotates on
91
- // every ask, so a list of all of them is a partition that grows for as long
92
- // as she keeps talking and can never be written down. It lives here, beside
93
- // the other bound on the durable shape, because it is a fact about what a
94
- // partition may hold and not about how a ward mints.
95
- export const MINTED = 8;
35
+ export class Partition {
36
+ readonly memory: Rows;
96
37
 
97
- // A knock record is a key she minted for one invitation, kept until she takes
98
- // it. Only take deletes one, so a being who knocks and never takes would grow
99
- // her bind table for the life of the ward, one secret seed at a time. The
100
- // list is bounded like `gone`: past the count the oldest go, and a relation
101
- // that answered outlives one that never did, since an answered knock is one
102
- // she may still take.
103
- export const KNOCKS = 64;
104
- export const prune = (knocks: Record<string, unknown>, answered: Record<string, true>): void => {
105
- const names = Object.keys(knocks);
106
- if (names.length <= KNOCKS) return;
107
- const order = [...names.filter((n) => !answered[n]), ...names.filter((n) => answered[n])];
108
- for (const old of order.slice(0, names.length - KNOCKS)) {
109
- delete knocks[old];
110
- delete answered[old];
38
+ constructor(memory: Rows) {
39
+ this.memory = memory;
40
+ const rows = memory.rows as { ward?: Head; beings?: Record<string, BeingRow> };
41
+ rows.ward ??= { heirs: {}, gone: [], bind: {}, public: null };
42
+ rows.beings ??= {};
111
43
  }
112
- };
113
44
 
114
- export type Partition = {
115
- version: string;
116
- beings: Record<string, Cells & { class?: string }>; // key -> her cells
117
- bind: Record<string, Bind>; // key -> her bind table
118
- heirs: Record<string, Heir>; // heir pk -> occupant, across every being of the ward
119
- gone: Record<string, Gone>; // heir pk -> the last keys of a relation she removed
120
- public: string | null; // the one public being's key
121
- };
45
+ get head(): Head {
46
+ return this.memory.rows.ward as Head;
47
+ }
122
48
 
123
- // The shape, read before anything acts on it. A partition this ward wrote is
124
- // this shape by construction; one it adopted was written by a hand, another
125
- // kit, or a file that was cut short, and every field below is one the ward,
126
- // the door or the heirs then act on without looking again. A `spent` that is
127
- // not an array rejects the door's promise on the first bound ask, a `mark`
128
- // that is not a number honours every number ever sent and grows without
129
- // bound, a being row that is null refuses the whole ward at birth by
130
- // accident and says nothing about why. So the shape is read once, here, and
131
- // what is not it throws with the path that failed, which names the trouble
132
- // to whoever is holding the file.
133
- //
134
- // The rule, so the reader does not grow into a schema: read every field the
135
- // ward acts on, and refuse only what would make it act wrongly. So `minted`
136
- // must be a list, because she pushes to it, and the truth in `answered` is
137
- // not read, because anything there says answered and nothing else follows.
138
- // `last` is reserved and nobody reads it, so nobody reads its shape either;
139
- // the run that gives it a reader gives it a line here. A being's own cells
140
- // are hers, of any shape JSON carries, and only the two records the ward
141
- // keeps inside them are read.
142
- const fault = (path: string, want: string): never => {
143
- throw new Error(`partition ${path} is not ${want}`);
144
- };
145
- const table = (v: unknown, path: string): Record<string, unknown> => (typeof v === 'object' && v !== null && !Array.isArray(v) ? (v as Record<string, unknown>) : fault(path, 'an object'));
146
- const list = (v: unknown, path: string): unknown[] => (Array.isArray(v) ? (v as unknown[]) : fault(path, 'an array'));
147
- const text = (v: unknown, path: string): void => void (typeof v === 'string' || fault(path, 'a string'));
148
- const orNull = (v: unknown, path: string): void => void (v === null || typeof v === 'string' || fault(path, 'a string or null'));
149
- const whole = (v: unknown, path: string): void => void (Number.isSafeInteger(v) || fault(path, 'a whole number'));
150
- const flag = (v: unknown, path: string): void => void (typeof v === 'boolean' || fault(path, 'true or false'));
49
+ get beings(): Record<string, BeingRow> {
50
+ return this.memory.rows.beings as Record<string, BeingRow>;
51
+ }
151
52
 
152
- function read(p: Partition): Partition {
153
- for (const [key, cells] of Object.entries(table(p.beings, 'beings'))) {
154
- const c = table(cells, `beings.${key}`);
155
- table(c.standings, `beings.${key}.standings`);
156
- table(c.occupants, `beings.${key}.occupants`);
157
- if (c.class !== undefined) text(c.class, `beings.${key}.class`);
53
+ being(key: string): BeingRow | undefined {
54
+ return Object.hasOwn(this.beings, key) ? this.beings[key] : undefined;
158
55
  }
159
- for (const [key, bind] of Object.entries(table(p.bind, 'bind'))) {
160
- const b = table(bind, `bind.${key}`);
161
- for (const [id, keys] of Object.entries(table(b.standings, `bind.${key}.standings`))) {
162
- const w = `bind.${key}.standings.${id}`;
163
- const s = table(keys, w);
164
- text(s.ward, `${w}.ward`);
165
- orNull(s.heir, `${w}.heir`);
166
- text(s.current, `${w}.current`);
167
- orNull(s.next, `${w}.next`);
168
- whole(s.seq, `${w}.seq`);
169
- }
170
- for (const [id, pk] of Object.entries(table(b.occupants, `bind.${key}.occupants`))) text(pk, `bind.${key}.occupants.${id}`);
171
- for (const [name, keys] of Object.entries(table(b.knocks, `bind.${key}.knocks`))) {
172
- const w = `bind.${key}.knocks.${name}`;
173
- const k = table(keys, w);
174
- text(k.current, `${w}.current`);
175
- orNull(k.next, `${w}.next`);
176
- flag(k.spoke, `${w}.spoke`);
177
- flag(k.sent, `${w}.sent`);
178
- whole(k.seq, `${w}.seq`);
179
- }
180
- table(b.answered, `bind.${key}.answered`);
181
- for (const [i, pk] of list(b.minted, `bind.${key}.minted`).entries()) text(pk, `bind.${key}.minted[${i}]`);
56
+
57
+ told(key: string): void {
58
+ this.memory.told(key);
182
59
  }
183
- for (const [pk, heir] of Object.entries(table(p.heirs, 'heirs'))) {
184
- const h = table(heir, `heirs.${pk}`);
185
- text(h.being, `heirs.${pk}.being`);
186
- text(h.id, `heirs.${pk}.id`);
187
- text(h.current, `heirs.${pk}.current`);
188
- orNull(h.announced, `heirs.${pk}.announced`);
189
- flag(h.fresh, `heirs.${pk}.fresh`);
190
- whole(h.mark, `heirs.${pk}.mark`);
191
- for (const [i, n] of list(h.spent, `heirs.${pk}.spent`).entries()) whole(n, `heirs.${pk}.spent[${i}]`);
60
+
61
+ toldHead(): void {
62
+ this.memory.told(HEAD_ROW);
192
63
  }
193
- for (const [pk, gone] of Object.entries(table(p.gone, 'gone'))) {
194
- const g = table(gone, `gone.${pk}`);
195
- text(g.current, `gone.${pk}.current`);
196
- orNull(g.announced, `gone.${pk}.announced`);
64
+
65
+ kept(): Promise<boolean> {
66
+ return this.memory.kept().catch(() => false);
197
67
  }
198
- orNull(p.public, 'public');
199
- return p;
200
68
  }
201
69
 
202
- // The one place a ward reads state that another ward wrote. A fresh
203
- // memory becomes a partition of this version; anything already written must
204
- // say it is this version and be the shape this version names, and a ward that
205
- // cannot read it does not boot. Birth is where a ward is allowed to be loud:
206
- // silence is the door's word, for asks that were made, and no ask has been
207
- // made yet.
208
- export function open(memory: Record<string, unknown>): Partition {
209
- const p = memory as Partition;
210
- if (Object.keys(memory).length > 0 && p.version !== VERSION) {
211
- if (!READS.includes(p.version)) throw new Error(`partition is version ${p.version}, this ward reads ${READS.join(', ')}`);
212
- upgrade(p).version = VERSION;
70
+ // The door's memory, written into the head.
71
+ export class PartitionRelations implements Relations {
72
+ readonly #partition: Partition;
73
+
74
+ constructor(partition: Partition) {
75
+ this.#partition = partition;
213
76
  }
214
- p.version ??= VERSION;
215
- p.beings ??= {};
216
- p.bind ??= {};
217
- p.heirs ??= {};
218
- p.gone ??= {};
219
- p.public ??= null;
220
- return read(p);
221
- }
222
77
 
223
- // One record, reached by a key a being or an owner chose. A bare lookup finds
224
- // every name on Object's prototype, so `__proto__` answers with the prototype
225
- // itself: read, it is there where nothing was put; written, it reaches every
226
- // object alive in the runtime. Own keys only, both ways, and a write that
227
- // names one of those lands on the record where it belongs.
228
- export const at = <T>(rec: Record<string, T>, key: string): T | undefined => (Object.hasOwn(rec, key) ? rec[key] : undefined);
229
- export const put = <T>(rec: Record<string, T>, key: string, value: T): T => {
230
- Object.defineProperty(rec, key, { value, writable: true, enumerable: true, configurable: true });
231
- return value;
232
- };
233
- export const drop = (rec: Record<string, unknown>, key: string): void => {
234
- if (Object.hasOwn(rec, key)) delete rec[key];
235
- };
78
+ get #head(): Head {
79
+ return this.#partition.head;
80
+ }
236
81
 
237
- export const emptyCells = (): Cells => ({ standings: {}, occupants: {} });
238
- export const emptyBind = (): Bind => ({ standings: {}, occupants: {}, knocks: {}, answered: {}, minted: [] });
82
+ lock(): Lock | undefined {
83
+ return lockOf(this.#head.lock);
84
+ }
239
85
 
240
- // ---- the partition in rows, for a store that keeps it
241
- //
242
- // A ward hands a harbor one object, so a being who writes one cell has every
243
- // other being in her ward written down beside her. What a being holds is the
244
- // one thing here that grows without limit, so she is a row: her cells and her
245
- // bind table, under her key. Everything else is one head row, and every part
246
- // of it is bounded already, one record per occupant and two lists that drop
247
- // their oldest.
248
- //
249
- // The split is here, where the shape is, and not in the harbor, which keeps
250
- // what it is given and reads none of it. A store is handed the rows that
251
- // moved; what it does with that is its own, and one that writes every row is
252
- // as correct as one that writes the few.
253
- export const HEAD = ''; // the row that is not a being: no being's key is empty
254
- // What a put back never moves: the two sides of a relation. `beings` and
255
- // `bind` are the tables the rows hang under and are never written whole.
256
- // `heirs` and `gone` are the door's side of every relation this ward holds,
257
- // and a being's own `bind` row is her side of hers.
258
- //
259
- // A relation cannot be put back, only broken. Her keys rotate when a reply
260
- // opens and the door's rotate when it honours, so the two move at different
261
- // moments and neither side holds the seed of a key it has moved past. Stand
262
- // either one at an earlier moment and it speaks under a key the other will
263
- // not admit, with no way back: the seed is gone. The count needs no help
264
- // here, because a door honours any unseen number inside its span and a
265
- // caller's count climbs at every attempt, so numbers find each other again
266
- // on their own. Keys do not.
267
- //
268
- // Ahead of the device is survivable and behind the far side is not, so what
269
- // a refusal puts back is what a being wrote and never what her relations are
270
- // standing on. What it costs is that her keys are then ahead of what the
271
- // store kept, which a restart can undo: a smaller hole, and the same one a
272
- // restart has anyway.
273
- const STANDING = new Set(['beings', 'bind', 'heirs', 'gone']);
86
+ setLock(lock: Lock): void {
87
+ this.#head.lock = keptLock(lock);
88
+ this.#partition.toldHead();
89
+ }
274
90
 
275
- // One row's value: a being, or the head, which is everything the partition
276
- // holds that is not filed under a being. The head is read as what is left
277
- // rather than as a list of names, so a store keeps whatever it was handed and
278
- // hands the same thing back, and a field this file grows later needs no line
279
- // in a store to survive a round trip.
280
- //
281
- // Undefined where the key names nobody, which is a row the store drops.
282
- export const rowOf = (p: Partition, row: string): Record<string, unknown> | undefined => {
283
- const all = p as unknown as Record<string, unknown>;
284
- if (row === HEAD) return Object.fromEntries(Object.entries(all).filter(([k]) => k !== 'beings' && k !== 'bind'));
285
- const cells = at(p.beings ?? {}, row);
286
- return cells === undefined ? undefined : { cells, bind: at(p.bind ?? {}, row) ?? emptyBind() };
287
- };
288
- export const rowsIn = (p: Record<string, unknown>): string[] => [HEAD, ...Object.keys((p.beings as Record<string, unknown>) ?? {})];
91
+ get(heir: string): HeirRecord | undefined {
92
+ return Object.hasOwn(this.#head.heirs, heir) ? this.#head.heirs[heir] : undefined;
93
+ }
289
94
 
290
- // One row put back to what a store holds for it. A store that refused a row
291
- // left the ward's memory ahead of the device, and everything written to that
292
- // being afterwards would be lost at the next reload while she went on
293
- // answering out of a memory nobody keeps. So the row is written back to the
294
- // last value that was kept, and she stands at what is on the device.
295
- //
296
- // In place, always. A booted being holds her cells and her bind table by
297
- // reference: her stance, her guard and her door all point at the objects the
298
- // partition already has, so a row replaced by a fresh object would strand her
299
- // on cells nothing keeps. Only the head may be written whole, because
300
- // everything under it is read through the partition each time.
301
- //
302
- // A row the store does not hold is a being it never kept, and what is kept
303
- // for her is nothing: her row goes, the way the store's own is already gone.
304
- export const restoreRow = (p: Partition, row: string, value: Record<string, unknown> | undefined): void => {
305
- const all = p as unknown as Record<string, unknown>;
306
- if (row === HEAD) {
307
- for (const k of Object.keys(all)) if (!STANDING.has(k)) drop(all, k);
308
- for (const [k, v] of Object.entries(value ?? {})) if (!STANDING.has(k)) put(all, k, v);
309
- return;
95
+ set(heir: string, record: HeirRecord): void {
96
+ const head = this.#head;
97
+ head.heirs[heir] = record;
98
+ if (record.state === 'kept') {
99
+ head.gone.push(heir);
100
+ while (head.gone.length > GONE) delete head.heirs[head.gone.shift()!];
101
+ }
102
+ this.#partition.toldHead();
310
103
  }
311
- if (value === undefined) {
312
- drop(p.beings ?? {}, row);
313
- // Her bind goes with her: a being the store never kept has no relation
314
- // anybody is standing on, so there is nothing to leave forward.
315
- drop(p.bind ?? {}, row);
316
- return;
104
+
105
+ delete(heir: string): void {
106
+ delete this.#head.heirs[heir];
107
+ this.#partition.toldHead();
317
108
  }
318
- // Behind the guard and never through it: a being's cells in a partition
319
- // are the guarded object her stance writes through, and the guard is hers.
320
- // What the harbor puts back is not a write of hers, so it says nothing to
321
- // the ward and marks nothing dirty.
322
- const into = (rows: Record<string, unknown>, kept: Record<string, unknown>): void => {
323
- const guarded = at(rows, row) as Cells | undefined;
324
- if (guarded === undefined) return void put(rows, row, kept);
325
- const held = unguarded(guarded) as unknown as Record<string, unknown>;
326
- for (const k of Object.keys(held)) drop(held, k);
327
- for (const [k, v] of Object.entries(kept)) put(held, k, v);
328
- };
329
- into(p.beings ?? {}, (value.cells ?? emptyCells()) as unknown as Record<string, unknown>);
330
- };
331
109
 
332
- // The partition back from its rows. A head that is absent is a partition
333
- // nobody has written, and `open` makes an empty one of it.
334
- export const fromRows = (rows: Record<string, Record<string, unknown>>): Record<string, unknown> => {
335
- const out: Record<string, unknown> = { ...(rows[HEAD] ?? {}) };
336
- const beings: Record<string, unknown> = {};
337
- const bind: Record<string, unknown> = {};
338
- let any = Object.keys(out).length > 0;
339
- for (const [row, value] of Object.entries(rows)) {
340
- if (row === HEAD) continue;
341
- put(beings, row, value.cells);
342
- put(bind, row, value.bind);
343
- any = true;
110
+ kept(): Promise<boolean> {
111
+ return this.#partition.kept();
344
112
  }
345
- // Nothing kept is nothing handed back. Rows that say only that there are no
346
- // rows must come back as the empty memory they are, because `open` reads an
347
- // empty one as a ward's first breath and anything else as a partition
348
- // somebody wrote, which must then say which version wrote it.
349
- if (!any) return {};
350
- out.beings = beings;
351
- out.bind = bind;
352
- return out;
353
- };
113
+ }