nervur 0.19.2 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +156 -104
  2. package/dist/being/being.d.ts +8 -15
  3. package/dist/being/being.js +20 -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 +10 -0
  7. package/dist/being/faculty.js +74 -0
  8. package/dist/being/index.d.ts +5 -5
  9. package/dist/being/index.js +6 -5
  10. package/dist/being/kind.d.ts +4 -0
  11. package/dist/being/kind.js +28 -0
  12. package/dist/being/types.d.ts +35 -63
  13. package/dist/being/types.js +3 -57
  14. package/dist/being/words.d.ts +13 -0
  15. package/dist/being/words.js +14 -0
  16. package/dist/cli/command.d.ts +13 -0
  17. package/dist/cli/command.js +194 -0
  18. package/dist/cli/harbor.d.ts +21 -0
  19. package/dist/cli/harbor.js +141 -0
  20. package/dist/cli/main.d.ts +2 -0
  21. package/dist/cli/main.js +14 -0
  22. package/dist/contract/index.d.ts +39 -0
  23. package/dist/contract/index.js +33 -0
  24. package/dist/crypto/aes.d.ts +3 -0
  25. package/dist/crypto/aes.js +24 -0
  26. package/dist/crypto/bytes.d.ts +6 -0
  27. package/dist/crypto/bytes.js +33 -0
  28. package/dist/crypto/ed25519.d.ts +3 -0
  29. package/dist/crypto/ed25519.js +85 -0
  30. package/dist/crypto/hash.d.ts +2 -0
  31. package/dist/crypto/hash.js +11 -0
  32. package/dist/crypto/index.d.ts +7 -0
  33. package/dist/crypto/index.js +10 -0
  34. package/dist/crypto/json.d.ts +8 -0
  35. package/dist/crypto/json.js +250 -0
  36. package/dist/crypto/mlkem.d.ts +12 -0
  37. package/dist/crypto/mlkem.js +36 -0
  38. package/dist/crypto/subtle.d.ts +10 -0
  39. package/dist/crypto/subtle.js +29 -0
  40. package/dist/crypto/x25519.d.ts +2 -0
  41. package/dist/crypto/x25519.js +21 -0
  42. package/dist/folder/index.d.ts +14 -0
  43. package/dist/folder/index.js +87 -0
  44. package/dist/harbor/catalogue.d.ts +25 -0
  45. package/dist/harbor/catalogue.js +93 -0
  46. package/dist/harbor/dock.d.ts +29 -0
  47. package/dist/harbor/dock.js +122 -0
  48. package/dist/harbor/harbor.d.ts +31 -0
  49. package/dist/harbor/harbor.js +146 -0
  50. package/dist/harbor/index.d.ts +7 -10
  51. package/dist/harbor/index.js +9 -18
  52. package/dist/harbor/package.d.ts +10 -0
  53. package/dist/harbor/package.js +115 -0
  54. package/dist/harbor/registry.d.ts +9 -0
  55. package/dist/harbor/registry.js +78 -0
  56. package/dist/harbor/root-line.d.ts +9 -0
  57. package/dist/harbor/root-line.js +36 -0
  58. package/dist/harbor/terrain.d.ts +9 -0
  59. package/dist/harbor/terrain.js +2 -0
  60. package/dist/index.d.ts +7 -0
  61. package/dist/index.js +14 -0
  62. package/dist/pointer/bodies.d.ts +26 -0
  63. package/dist/pointer/bodies.js +64 -0
  64. package/dist/pointer/index.d.ts +2 -0
  65. package/dist/pointer/index.js +4 -0
  66. package/dist/pointer/world.d.ts +35 -0
  67. package/dist/pointer/world.js +85 -0
  68. package/dist/quo/door.d.ts +34 -0
  69. package/dist/quo/door.js +172 -0
  70. package/dist/quo/index.d.ts +8 -0
  71. package/dist/quo/index.js +11 -0
  72. package/dist/quo/invitation.d.ts +7 -0
  73. package/dist/quo/invitation.js +15 -0
  74. package/dist/quo/keys.d.ts +40 -0
  75. package/dist/quo/keys.js +79 -0
  76. package/dist/quo/payload.d.ts +15 -0
  77. package/dist/quo/payload.js +55 -0
  78. package/dist/quo/relations.d.ts +40 -0
  79. package/dist/quo/relations.js +33 -0
  80. package/dist/quo/reply.d.ts +13 -0
  81. package/dist/quo/reply.js +35 -0
  82. package/dist/quo/seal.d.ts +42 -0
  83. package/dist/quo/seal.js +78 -0
  84. package/dist/quo/standing.d.ts +39 -0
  85. package/dist/quo/standing.js +90 -0
  86. package/dist/tcp/frame.d.ts +29 -0
  87. package/dist/tcp/frame.js +74 -0
  88. package/dist/tcp/index.d.ts +18 -0
  89. package/dist/tcp/index.js +164 -0
  90. package/dist/ward/allowance.d.ts +13 -9
  91. package/dist/ward/allowance.js +28 -67
  92. package/dist/ward/cells.d.ts +6 -6
  93. package/dist/ward/cells.js +67 -179
  94. package/dist/ward/index.d.ts +6 -10
  95. package/dist/ward/index.js +7 -13
  96. package/dist/ward/partition.d.ts +44 -60
  97. package/dist/ward/partition.js +56 -285
  98. package/dist/ward/pilot.d.ts +8 -0
  99. package/dist/ward/pilot.js +50 -0
  100. package/dist/ward/stance.d.ts +27 -20
  101. package/dist/ward/stance.js +174 -406
  102. package/dist/ward/ward-being.d.ts +35 -0
  103. package/dist/ward/ward-being.js +82 -0
  104. package/dist/ward/ward.d.ts +45 -11
  105. package/dist/ward/ward.js +239 -357
  106. package/package.json +24 -31
  107. package/src/being/being.ts +35 -78
  108. package/src/being/digest.ts +7 -35
  109. package/src/being/faculty.ts +73 -0
  110. package/src/being/index.ts +6 -6
  111. package/src/being/kind.ts +30 -0
  112. package/src/being/types.ts +50 -146
  113. package/src/being/words.ts +31 -0
  114. package/src/cli/command.ts +188 -0
  115. package/src/cli/harbor.ts +137 -0
  116. package/src/cli/main.ts +13 -0
  117. package/src/contract/index.ts +73 -0
  118. package/src/crypto/aes.ts +26 -0
  119. package/src/crypto/bytes.ts +37 -0
  120. package/src/crypto/ed25519.ts +84 -0
  121. package/src/crypto/hash.ts +14 -0
  122. package/src/crypto/index.ts +10 -0
  123. package/src/crypto/json.ts +241 -0
  124. package/src/crypto/mlkem.ts +38 -0
  125. package/src/crypto/subtle.ts +33 -0
  126. package/src/crypto/x25519.ts +21 -0
  127. package/src/folder/index.ts +92 -0
  128. package/src/harbor/catalogue.ts +110 -0
  129. package/src/harbor/dock.ts +137 -0
  130. package/src/harbor/harbor.ts +180 -0
  131. package/src/harbor/index.ts +9 -19
  132. package/src/harbor/package.ts +131 -0
  133. package/src/harbor/registry.ts +73 -0
  134. package/src/harbor/root-line.ts +36 -0
  135. package/src/harbor/terrain.ts +15 -0
  136. package/src/index.ts +20 -0
  137. package/src/pointer/bodies.ts +65 -0
  138. package/src/pointer/index.ts +4 -0
  139. package/src/pointer/world.ts +94 -0
  140. package/src/quo/door.ts +178 -0
  141. package/src/quo/index.ts +11 -0
  142. package/src/quo/invitation.ts +15 -0
  143. package/src/quo/keys.ts +91 -0
  144. package/src/quo/payload.ts +61 -0
  145. package/src/quo/relations.ts +62 -0
  146. package/src/quo/reply.ts +38 -0
  147. package/src/quo/seal.ts +101 -0
  148. package/src/quo/standing.ts +111 -0
  149. package/src/stand/main.ts +20 -0
  150. package/src/stand/stand.ts +220 -0
  151. package/src/tcp/frame.ts +78 -0
  152. package/src/tcp/index.ts +173 -0
  153. package/src/ward/allowance.ts +37 -75
  154. package/src/ward/cells.ts +63 -176
  155. package/src/ward/index.ts +7 -17
  156. package/src/ward/partition.ts +86 -326
  157. package/src/ward/pilot.ts +50 -0
  158. package/src/ward/stance.ts +186 -420
  159. package/src/ward/ward-being.ts +103 -0
  160. package/src/ward/ward.ts +256 -359
  161. package/dist/being/lent.d.ts +0 -32
  162. package/dist/being/lent.js +0 -72
  163. package/dist/being/silence.d.ts +0 -12
  164. package/dist/being/silence.js +0 -41
  165. package/dist/conformance/assert.d.ts +0 -11
  166. package/dist/conformance/assert.js +0 -106
  167. package/dist/conformance/beings.d.ts +0 -199
  168. package/dist/conformance/beings.js +0 -188
  169. package/dist/conformance/estate.d.ts +0 -5
  170. package/dist/conformance/estate.js +0 -388
  171. package/dist/conformance/index.d.ts +0 -80
  172. package/dist/conformance/index.js +0 -819
  173. package/dist/conformance/reach.d.ts +0 -10
  174. package/dist/conformance/reach.js +0 -72
  175. package/dist/conformance/store.d.ts +0 -5
  176. package/dist/conformance/store.js +0 -113
  177. package/dist/harbor/box.d.ts +0 -121
  178. package/dist/harbor/box.js +0 -121
  179. package/dist/harbor/core.d.ts +0 -55
  180. package/dist/harbor/core.js +0 -662
  181. package/dist/harbor/dial.d.ts +0 -9
  182. package/dist/harbor/dial.js +0 -81
  183. package/dist/harbor/memory.d.ts +0 -29
  184. package/dist/harbor/memory.js +0 -104
  185. package/dist/harbor/reach.d.ts +0 -36
  186. package/dist/harbor/reach.js +0 -199
  187. package/dist/harbor/store.d.ts +0 -35
  188. package/dist/harbor/store.js +0 -62
  189. package/dist/vector/cases.d.ts +0 -42
  190. package/dist/vector/cases.js +0 -223
  191. package/dist/vector/index.d.ts +0 -6
  192. package/dist/vector/index.js +0 -8
  193. package/dist/vector/stand.d.ts +0 -9
  194. package/dist/vector/stand.js +0 -77
  195. package/dist/vector/world.d.ts +0 -144
  196. package/dist/vector/world.js +0 -209
  197. package/dist/ward/arithmetic.d.ts +0 -31
  198. package/dist/ward/arithmetic.js +0 -249
  199. package/dist/ward/door.d.ts +0 -18
  200. package/dist/ward/door.js +0 -186
  201. package/dist/ward/ground.d.ts +0 -29
  202. package/dist/ward/ground.js +0 -56
  203. package/dist/ward/heirs.d.ts +0 -13
  204. package/dist/ward/heirs.js +0 -117
  205. package/dist/ward/json.d.ts +0 -2
  206. package/dist/ward/json.js +0 -163
  207. package/dist/ward/owner.d.ts +0 -13
  208. package/dist/ward/owner.js +0 -220
  209. package/dist/ward/seal.d.ts +0 -52
  210. package/dist/ward/seal.js +0 -150
  211. package/src/being/lent.ts +0 -72
  212. package/src/being/silence.ts +0 -46
  213. package/src/conformance/assert.ts +0 -100
  214. package/src/conformance/beings.ts +0 -188
  215. package/src/conformance/estate.ts +0 -412
  216. package/src/conformance/index.ts +0 -965
  217. package/src/conformance/reach.ts +0 -83
  218. package/src/conformance/store.ts +0 -125
  219. package/src/harbor/box.ts +0 -131
  220. package/src/harbor/core.ts +0 -699
  221. package/src/harbor/dial.ts +0 -112
  222. package/src/harbor/memory.ts +0 -123
  223. package/src/harbor/reach.ts +0 -221
  224. package/src/harbor/store.ts +0 -91
  225. package/src/vector/cases.ts +0 -257
  226. package/src/vector/index.ts +0 -11
  227. package/src/vector/stand.ts +0 -76
  228. package/src/vector/world.ts +0 -232
  229. package/src/ward/arithmetic.ts +0 -251
  230. package/src/ward/door.ts +0 -186
  231. package/src/ward/ground.ts +0 -142
  232. package/src/ward/heirs.ts +0 -116
  233. package/src/ward/json.ts +0 -144
  234. package/src/ward/owner.ts +0 -214
  235. package/src/ward/seal.ts +0 -178
@@ -0,0 +1,87 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // A harbor on a disk, for local proof: `nervur/folder`, an entry that
3
+ // names a platform.
4
+ //
5
+ // <root>/seed the harbor's seed, as hex
6
+ // <root>/<place>/<entry> one sealed entry of the harbor's memory
7
+ //
8
+ // Places and entries are the hex names the kit chose, so no name reaches
9
+ // outside its folder, and the disk shows no ward's name. A file is written
10
+ // whole, beside itself and then renamed over, so a crash leaves the last
11
+ // kept copy. The seed is a plain file: custody that seals it is a
12
+ // terrain's, in the dock.
13
+ import { mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
14
+ import { join } from 'node:path';
15
+ import { hex, isHex, unhex } from '../crypto/index.js';
16
+ import { Custody, Memory } from '../contract/index.js';
17
+ const missing = (e) => e.code === 'ENOENT';
18
+ const named = (name) => {
19
+ if (!isHex(name) || name === '')
20
+ throw new Error(`${name} is not a name the kit chose`);
21
+ return name;
22
+ };
23
+ const writeWhole = async (path, bytes) => {
24
+ await writeFile(`${path}.next`, bytes, { mode: 0o600 });
25
+ await rename(`${path}.next`, path);
26
+ };
27
+ export class FolderMemory extends Memory {
28
+ root;
29
+ #line = Promise.resolve();
30
+ constructor(root) {
31
+ super();
32
+ this.root = root;
33
+ }
34
+ // The entries as the disk holds them now. A file that is not an entry,
35
+ // one still being written among them, is not read.
36
+ async read(place) {
37
+ const dir = join(this.root, named(place));
38
+ const out = new Map();
39
+ const files = await readdir(dir, { withFileTypes: true }).catch((e) => (missing(e) ? [] : Promise.reject(e)));
40
+ for (const file of files) {
41
+ if (!file.isFile() || !isHex(file.name) || file.name === '')
42
+ continue;
43
+ out.set(file.name, new Uint8Array(await readFile(join(dir, file.name))));
44
+ }
45
+ return out;
46
+ }
47
+ // One keep at a time, each entry in the order given.
48
+ async write(place, entries) {
49
+ const dir = join(this.root, named(place));
50
+ const kept = this.#line.then(async () => {
51
+ await mkdir(dir, { recursive: true, mode: 0o700 });
52
+ for (const [name, bytes] of entries) {
53
+ const path = join(dir, named(name));
54
+ if (bytes === null)
55
+ await rm(path, { force: true });
56
+ else
57
+ await writeWhole(path, bytes);
58
+ }
59
+ });
60
+ this.#line = kept.catch(() => undefined);
61
+ await kept;
62
+ }
63
+ }
64
+ export class FolderCustody extends Custody {
65
+ root;
66
+ #entropy;
67
+ constructor(root, entropy) {
68
+ super();
69
+ this.root = root;
70
+ this.#entropy = entropy;
71
+ }
72
+ // The seed the folder holds, or a drawn one written there the first time.
73
+ async seed() {
74
+ const path = join(this.root, 'seed');
75
+ const kept = await readFile(path, 'utf8').catch((e) => (missing(e) ? undefined : Promise.reject(e)));
76
+ if (kept !== undefined) {
77
+ const text = kept.trim();
78
+ if (!isHex(text, 32))
79
+ throw new Error(`${path} is not a seed`);
80
+ return unhex(text);
81
+ }
82
+ const seed = this.#entropy.draw(32);
83
+ await mkdir(this.root, { recursive: true, mode: 0o700 });
84
+ await writeFile(path, `${hex(seed)}\n`, { mode: 0o600, flag: 'wx' });
85
+ return seed;
86
+ }
87
+ }
@@ -0,0 +1,25 @@
1
+ import { Faculty, type AskSpec, type BeingClass, type JsonObject, type Stance } from '../being/index.ts';
2
+ import type { Loaded } from '../contract/index.ts';
3
+ export declare const CATALOGUE = "catalogue";
4
+ export type ModuleRow = {
5
+ version: string;
6
+ from: string;
7
+ };
8
+ export interface Cataloguing {
9
+ load(from: string): Promise<Loaded | null>;
10
+ run(modules: readonly Loaded[]): string | null;
11
+ stands(kinds: readonly string[]): boolean;
12
+ kindsOf(module: string): string[];
13
+ }
14
+ export declare class Catalogue extends Faculty {
15
+ #private;
16
+ static readonly kind: string;
17
+ static cells: JsonObject;
18
+ static asks: Record<string, AskSpec>;
19
+ constructor(stance: Stance, harbor: Cataloguing);
20
+ wake(): Promise<void>;
21
+ add(args: JsonObject): Promise<JsonObject>;
22
+ remove(args: JsonObject): JsonObject;
23
+ modules(): JsonObject;
24
+ }
25
+ export declare const catalogueFor: (harbor: Cataloguing) => BeingClass;
@@ -0,0 +1,93 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The catalogue: the dock's first faculty, the code a harbor runs. Her
3
+ // cells name each module by the name it declares, its version, and where
4
+ // the terrain's loader finds it. The harbor wakes her before any other
5
+ // faculty, loads every module she names, and refuses to stand when one is
6
+ // missing.
7
+ //
8
+ // add { from } the module the loader finds there, run from now
9
+ // on; the same module again takes its new version
10
+ // remove { module } a module no being stands on, run no more
11
+ // modules what she names, each with the version running
12
+ //
13
+ // She is the kit's, of kind `org.nervur.catalogue`, and answers the root
14
+ // and an owner alone.
15
+ import { Faculty } from '../being/index.js';
16
+ import { ownerAsk } from '../ward/index.js';
17
+ export const CATALOGUE = 'catalogue';
18
+ const text = (v) => typeof v === 'string';
19
+ export class Catalogue extends Faculty {
20
+ static kind = 'org.nervur.catalogue';
21
+ static cells = { modules: {} };
22
+ static asks = {
23
+ add: ownerAsk('a module run from now on', { required: ['from'] }),
24
+ remove: ownerAsk('a module run no more', { required: ['module'] }),
25
+ modules: ownerAsk('the modules this harbor runs'),
26
+ };
27
+ #harbor;
28
+ #running = new Map();
29
+ constructor(stance, harbor) {
30
+ super(stance);
31
+ this.#harbor = harbor;
32
+ }
33
+ get #rows() {
34
+ return this.cells.modules;
35
+ }
36
+ // Every module her cells name, loaded and run, or a throw naming the one
37
+ // that cannot be.
38
+ async wake() {
39
+ for (const [module, row] of Object.entries(this.#rows)) {
40
+ const loaded = await this.#harbor.load(row.from);
41
+ if (loaded === null)
42
+ throw new Error(`module ${module} is not found at ${row.from}`);
43
+ if (loaded.module !== module)
44
+ throw new Error(`${row.from} is module ${loaded.module}, not ${module}`);
45
+ this.#running.set(module, loaded);
46
+ }
47
+ const refused = this.#harbor.run([...this.#running.values()]);
48
+ if (refused !== null)
49
+ throw new Error(refused);
50
+ }
51
+ async add(args) {
52
+ if (!text(args.from))
53
+ return { error: 'from is text' };
54
+ const loaded = await this.#harbor.load(args.from);
55
+ if (loaded === null)
56
+ return { error: 'no such module' };
57
+ const running = new Map(this.#running);
58
+ running.set(loaded.module, loaded);
59
+ const refused = this.#harbor.run([...running.values()]);
60
+ if (refused !== null)
61
+ return { error: refused };
62
+ this.#running.set(loaded.module, loaded);
63
+ this.#rows[loaded.module] = { version: loaded.version, from: loaded.from };
64
+ return { added: loaded.module, version: loaded.version };
65
+ }
66
+ remove(args) {
67
+ const module = args.module;
68
+ if (!text(module) || !this.#running.has(module))
69
+ return { error: 'no such module' };
70
+ if (this.#harbor.stands(this.#harbor.kindsOf(module)))
71
+ return { error: 'a being stands on this module' };
72
+ const running = new Map(this.#running);
73
+ running.delete(module);
74
+ this.#harbor.run([...running.values()]);
75
+ this.#running.delete(module);
76
+ delete this.#rows[module];
77
+ return { removed: module };
78
+ }
79
+ modules() {
80
+ const out = {};
81
+ for (const [module, row] of Object.entries(this.#rows))
82
+ out[module] = { version: row.version, running: this.#running.get(module)?.version ?? null, from: row.from };
83
+ return out;
84
+ }
85
+ }
86
+ // The catalogue class one harbor stands: a being is born of her class and
87
+ // her stance alone, so the class holds its harbor.
88
+ export const catalogueFor = (harbor) => class extends Catalogue {
89
+ static kind = Catalogue.kind;
90
+ constructor(stance) {
91
+ super(stance, harbor);
92
+ }
93
+ };
@@ -0,0 +1,29 @@
1
+ import { type AskSpec, type Invitation, type JsonObject } from '../being/index.ts';
2
+ import { WardBeing, type Lender, type WardBeingStance } from '../ward/index.ts';
3
+ export interface Hosting {
4
+ host(name: string, invitation: Invitation, seed?: string): Promise<string | null>;
5
+ route(pk: string, at: string | null): boolean;
6
+ keep(): Promise<boolean>;
7
+ }
8
+ export type Hosted = {
9
+ ward: string;
10
+ seed: string;
11
+ };
12
+ export declare const BOX = "box";
13
+ export declare class Dock extends WardBeing implements Lender {
14
+ #private;
15
+ static readonly kind: string;
16
+ static cells: JsonObject;
17
+ static asks: Record<string, AskSpec>;
18
+ constructor(stance: WardBeingStance, hosting: Hosting);
19
+ get hosted(): Hosted[];
20
+ get routes(): Record<string, string>;
21
+ route(args: JsonObject): JsonObject;
22
+ standCatalogue(): Promise<void>;
23
+ open(args: JsonObject): Promise<JsonObject>;
24
+ host(args: JsonObject): Promise<JsonObject>;
25
+ lend(args: JsonObject): Promise<JsonObject>;
26
+ retract(args: JsonObject): Promise<JsonObject>;
27
+ offer(contract: string): Promise<Invitation | null>;
28
+ withdraw(contract: string, heir: string): Promise<void>;
29
+ }
@@ -0,0 +1,122 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The dock: the box ward's ward-being. Beside the root's asks of every ward,
3
+ // she opens faculties, hosts wards, and lends each contract a faculty
4
+ // fulfils to the wards she hosts.
5
+ //
6
+ // open { key, class } a faculty stands in the box ward, and the
7
+ // dock holds a standing on her under her key
8
+ // host { ward, seed? } a hosted ward stands under the seed named,
9
+ // or a drawn one, holding a standing on the
10
+ // dock; the dock keeps its name and seed
11
+ // lend { contract } asked by a hosted ward: an offer from a
12
+ // faculty that fulfils the contract
13
+ // retract { contract, heir } an offer that was not taken
14
+ // route { ward, at } where a ward pk is reached, `host:port`, or
15
+ // null to forget it; the dock keeps it and
16
+ // the harbor hands it to its carrier
17
+ import { Faculty, KIT } from '../being/index.js';
18
+ import { CATALOGUE } from './catalogue.js';
19
+ import { readInvitation } from '../quo/index.js';
20
+ import { ownerAsk, WardBeing } from '../ward/index.js';
21
+ export const BOX = 'box';
22
+ const text = (v) => typeof v === 'string';
23
+ const hosted = (_, notes) => notes?.hosted === true;
24
+ export class Dock extends WardBeing {
25
+ static kind = 'org.nervur.dock';
26
+ static cells = { hosted: [], routes: {} };
27
+ static asks = {
28
+ ...WardBeing.asks,
29
+ open: ownerAsk('a faculty of this harbor', { required: ['key', 'class'] }),
30
+ host: ownerAsk('a ward this harbor hosts', { required: ['ward'] }),
31
+ route: ownerAsk('where a ward is reached, or null', { required: ['ward', 'at'] }),
32
+ lend: { description: 'an offer from a faculty fulfilling a contract', input: { type: 'object', required: ['contract'] }, for: hosted },
33
+ retract: { description: 'an offer that was not taken', input: { type: 'object', required: ['contract', 'heir'] }, for: hosted },
34
+ };
35
+ #hosting;
36
+ constructor(stance, hosting) {
37
+ super(stance);
38
+ this.#hosting = hosting;
39
+ }
40
+ get hosted() {
41
+ return this.cells.hosted.map(({ ward, seed }) => ({ ward, seed }));
42
+ }
43
+ get routes() {
44
+ return { ...this.cells.routes };
45
+ }
46
+ route(args) {
47
+ const { ward, at } = args;
48
+ if (!text(ward) || (at !== null && !text(at)) || !this.#hosting.route(ward, at))
49
+ return { error: 'not routed' };
50
+ const routes = this.cells.routes;
51
+ if (at === null)
52
+ delete routes[ward];
53
+ else
54
+ routes[ward] = at;
55
+ return { routed: ward };
56
+ }
57
+ // Genesis: the catalogue stands first, and is kept at once.
58
+ async standCatalogue() {
59
+ const kind = 'org.nervur.catalogue';
60
+ if ((await this.stance.boot(kind, CATALOGUE, CATALOGUE)) === null || !(await this.#hosting.keep()))
61
+ throw new Error('the catalogue did not stand');
62
+ }
63
+ async open(args) {
64
+ if (!text(args.key) || !text(args.class))
65
+ return { error: 'key and class are text' };
66
+ if (args.class.startsWith(KIT) || !Faculty.fulfils(this.steward.classOf(args.class)))
67
+ return { error: 'no such faculty' };
68
+ const opened = await this.stance.boot(args.class, args.key, args.key);
69
+ return opened === null ? { error: 'not opened' } : { opened };
70
+ }
71
+ async host(args) {
72
+ const name = args.ward;
73
+ if (!text(name) || name === BOX || this.hosted.some((h) => h.ward === name))
74
+ return { error: 'no such ward to host' };
75
+ if (args.seed !== undefined && !text(args.seed))
76
+ return { error: 'seed is text' };
77
+ const invitation = await this.stance.occupants.invite(name, { hosted: true });
78
+ if (invitation === null)
79
+ return { error: 'not hosted' };
80
+ const seed = await this.#hosting.host(name, invitation, args.seed);
81
+ if (seed === null) {
82
+ this.stance.occupants.remove(name);
83
+ return { error: 'not hosted' };
84
+ }
85
+ this.cells.hosted.push({ ward: name, seed });
86
+ return { hosted: name };
87
+ }
88
+ async lend(args) {
89
+ if (!text(args.contract))
90
+ return { error: 'contract is text' };
91
+ const invitation = await this.offer(args.contract);
92
+ return invitation ? { invitation } : { error: 'nothing lent' };
93
+ }
94
+ async retract(args) {
95
+ if (!text(args.contract) || !text(args.heir))
96
+ return { error: 'contract and heir are text' };
97
+ await this.withdraw(args.contract, args.heir);
98
+ return { retracted: args.heir };
99
+ }
100
+ // Lender: the first standing faculty that fulfils the contract offers.
101
+ async offer(contract) {
102
+ for (const key of this.#fulfilling(contract)) {
103
+ const out = await this.stance.standings.get(key)?.ask('offer');
104
+ const invitation = readInvitation(out?.invitation);
105
+ if (invitation)
106
+ return invitation;
107
+ }
108
+ return null;
109
+ }
110
+ async withdraw(contract, heir) {
111
+ for (const key of this.#fulfilling(contract))
112
+ await this.stance.standings.get(key)?.ask('retract', { heir });
113
+ }
114
+ #fulfilling(contract) {
115
+ const beings = this.steward.beings();
116
+ return this.stance.standings.ids().filter((key) => {
117
+ const being = beings[key];
118
+ const C = being && !being.absent ? this.steward.classOf(being.class) : undefined;
119
+ return Faculty.fulfils(C) && Faculty.contracts(C).includes(contract);
120
+ });
121
+ }
122
+ }
@@ -0,0 +1,31 @@
1
+ import type { Answer, BeingClass, Invitation, JsonObject } from '../being/index.ts';
2
+ import { Carrier, type Loaded } from '../contract/index.ts';
3
+ import { Ward, type Catalogue as Resolving } from '../ward/index.ts';
4
+ import { Catalogue, type Cataloguing } from './catalogue.ts';
5
+ import { Dock, type Hosting } from './dock.ts';
6
+ import { Registry } from './registry.ts';
7
+ import type { Terrain } from './terrain.ts';
8
+ export declare class Harbor extends Carrier implements Hosting, Cataloguing, Resolving {
9
+ #private;
10
+ readonly terrain: Terrain;
11
+ private constructor();
12
+ static open(terrain: Terrain): Promise<Harbor>;
13
+ get box(): Ward;
14
+ get dock(): Dock;
15
+ get catalogue(): Catalogue;
16
+ get registry(): Registry;
17
+ ward(name: string): Ward | undefined;
18
+ get names(): string[];
19
+ root(method?: string, args?: JsonObject): Promise<Answer>;
20
+ holds(pk: string): boolean;
21
+ arrive(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
22
+ carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
23
+ classOf(kind: string): BeingClass | undefined;
24
+ load(from: string): Promise<Loaded | null>;
25
+ run(modules: readonly Loaded[]): string | null;
26
+ stands(kinds: readonly string[]): boolean;
27
+ kindsOf(module: string): string[];
28
+ host(name: string, invitation: Invitation, seed?: string): Promise<string | null>;
29
+ keep(): Promise<boolean>;
30
+ route(pk: string, at: string | null): boolean;
31
+ }
@@ -0,0 +1,146 @@
1
+ import { Carrier } from '../contract/index.js';
2
+ import { hex, isHex, unhex } from '../crypto/index.js';
3
+ import { WardKey } from '../quo/index.js';
4
+ import { DOCK, Pilot, Ward } from '../ward/index.js';
5
+ import { Catalogue, CATALOGUE, catalogueFor } from './catalogue.js';
6
+ import { BOX, Dock } from './dock.js';
7
+ import { Package } from './package.js';
8
+ import { Registry } from './registry.js';
9
+ export class Harbor extends Carrier {
10
+ terrain;
11
+ #package;
12
+ #wards = new Map();
13
+ #kit;
14
+ #registry;
15
+ constructor(terrain, pack) {
16
+ super();
17
+ this.terrain = terrain;
18
+ this.#package = pack;
19
+ this.#kit = [catalogueFor(this), Pilot];
20
+ this.#registry = new Registry(this.#kit, []);
21
+ }
22
+ static async open(terrain) {
23
+ const seed = await terrain.custody.seed();
24
+ const harbor = new Harbor(terrain, await Package.open(terrain.memory, terrain.entropy, seed));
25
+ await harbor.#unpack(BOX, seed, undefined, (stance) => new Dock(stance, harbor));
26
+ if (!harbor.box.partition.being(CATALOGUE))
27
+ await harbor.dock.standCatalogue();
28
+ await harbor.catalogue.wake();
29
+ for (const [pk, at] of Object.entries(harbor.dock.routes))
30
+ terrain.carrier.route(pk, at);
31
+ for (const hosted of harbor.dock.hosted)
32
+ await harbor.#unpack(hosted.ward, unhex(hosted.seed));
33
+ return harbor;
34
+ }
35
+ get box() {
36
+ return this.#wards.get(BOX);
37
+ }
38
+ get dock() {
39
+ return this.box.wardBeing;
40
+ }
41
+ get catalogue() {
42
+ return this.box.being(CATALOGUE);
43
+ }
44
+ get registry() {
45
+ return this.#registry;
46
+ }
47
+ ward(name) {
48
+ return this.#wards.get(name);
49
+ }
50
+ get names() {
51
+ return [...this.#wards.keys()].filter((name) => name !== BOX);
52
+ }
53
+ // The unsealed ask on the box ward: what a command line carries.
54
+ root(method, args) {
55
+ return this.box.root(method, args);
56
+ }
57
+ holds(pk) {
58
+ return this.#door(pk) !== undefined;
59
+ }
60
+ // Bytes for one of this harbor's doors, or nothing.
61
+ async arrive(pk, bytes) {
62
+ const ward = this.#door(pk);
63
+ return ward ? (await ward.door.arrive(bytes)).bytes : null;
64
+ }
65
+ // Carrier: this harbor's own doors first, then the terrain's.
66
+ carry(pk, bytes) {
67
+ return this.holds(pk) ? this.arrive(pk, bytes) : this.terrain.carrier.carry(pk, bytes);
68
+ }
69
+ // Resolving: a kind, by the registry the catalogue runs.
70
+ classOf(kind) {
71
+ return this.#registry.classOf(kind);
72
+ }
73
+ // Cataloguing: code through the terrain, and the modules run.
74
+ load(from) {
75
+ return this.terrain.loader.load(from);
76
+ }
77
+ run(modules) {
78
+ try {
79
+ this.#registry = new Registry(this.#kit, modules);
80
+ }
81
+ catch (e) {
82
+ return e.message;
83
+ }
84
+ for (const ward of this.#wards.values())
85
+ ward.bearAnew();
86
+ return null;
87
+ }
88
+ stands(kinds) {
89
+ for (const ward of this.#wards.values()) {
90
+ for (const row of Object.values(ward.partition.beings))
91
+ if (kinds.includes(row.class))
92
+ return true;
93
+ }
94
+ return false;
95
+ }
96
+ kindsOf(module) {
97
+ return this.#registry.kindsOf(module);
98
+ }
99
+ // Hosting: a ward the dock hosts stands now, under the seed named or a
100
+ // drawn one, and the seed it stands under comes back. A ward whose key
101
+ // another ward of this harbor already has does not stand.
102
+ async host(name, invitation, seed) {
103
+ if (this.#wards.has(name))
104
+ return null;
105
+ const bytes = seed === undefined ? this.terrain.entropy.draw(32) : isHex(seed, 32) ? unhex(seed) : null;
106
+ if (bytes === null || this.holds((await WardKey.from(bytes)).pk))
107
+ return null;
108
+ try {
109
+ await this.#unpack(name, bytes, invitation);
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ if (this.#wards.get(name).wardBeing.stance.standings.get(DOCK))
115
+ return hex(bytes);
116
+ this.#wards.delete(name);
117
+ return null;
118
+ }
119
+ keep() {
120
+ return this.box.partition.kept();
121
+ }
122
+ // Hosting: the terrain's carrier takes a route, or does not dial.
123
+ route(pk, at) {
124
+ return this.terrain.carrier.route(pk, at);
125
+ }
126
+ async #unpack(name, seed, dock, root) {
127
+ const { entropy, clock } = this.terrain;
128
+ const ward = await Ward.unpack({
129
+ seed,
130
+ memory: await this.#package.rows(name),
131
+ catalogue: this,
132
+ entropy,
133
+ clock,
134
+ carrier: this,
135
+ ...(dock ? { dock } : {}),
136
+ ...(root ? { root } : {}),
137
+ });
138
+ this.#wards.set(name, ward);
139
+ }
140
+ #door(pk) {
141
+ for (const ward of this.#wards.values())
142
+ if (ward.pk === pk)
143
+ return ward;
144
+ return undefined;
145
+ }
146
+ }
@@ -1,10 +1,7 @@
1
- export { MemoryHarbor, type Booted, type WardFactory } from './memory.ts';
2
- export { Harbor, DEFAULT_CODE, type Hosted, type Bound, type Loader } from './core.ts';
3
- export { dial, type Dialer } from './dial.ts';
4
- export { MemoryStore, values, rowsOf, type Store, type Kept, type WardRecord } from './store.ts';
5
- export { HEAD, fromRows, rowsIn } from '../ward/partition.ts';
6
- export { request, Socket, SUITE, REFUSED, type Reach, type Carry, type Line, type Announce } from './reach.ts';
7
- export type { Ground, WardPointers, Memory } from '../ward/ground.ts';
8
- export { BOX, OFFER, RETRACT, volatile } from '../ward/ground.ts';
9
- export { Box, BOX_SEED, BOX_ASKS, stand, hold, offerTo, roll } from './box.ts';
10
- export { maker, entropy, learnPk } from '../ward/ground.ts';
1
+ export { Catalogue, CATALOGUE, type Cataloguing, type ModuleRow } from './catalogue.ts';
2
+ export { BOX, Dock, type Hosted, type Hosting } from './dock.ts';
3
+ export { Harbor } from './harbor.ts';
4
+ export { Package } from './package.ts';
5
+ export { readRootRequest, rootLine, type RootRequest } from './root-line.ts';
6
+ export { Registry } from './registry.ts';
7
+ export { Terrain } from './terrain.ts';
@@ -1,19 +1,10 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // nervur/harbor — the Ground contract and the harbors. The only
3
- // place in the package that may know a wire, and it knows one on the
4
- // standard surface alone: fetch and WebSocket. MemoryHarbor knows none: it
5
- // is one process talking to itself, where every ward assertion is made.
6
- export { MemoryHarbor } from './memory.js';
7
- export { Harbor, DEFAULT_CODE } from './core.js';
8
- export { dial } from './dial.js';
9
- export { MemoryStore, values, rowsOf } from './store.js';
10
- // What a store keeping a ward in rows needs: the name of the row that is not
11
- // a being, and the partition back from the rows it was cut into.
12
- export { HEAD, fromRows, rowsIn } from '../ward/partition.js';
13
- export { request, Socket, SUITE, REFUSED } from './reach.js';
14
- export { BOX, OFFER, RETRACT, volatile } from '../ward/ground.js';
15
- // The box as one being, and the acts a harbor runs to stand it.
16
- export { Box, BOX_SEED, BOX_ASKS, stand, hold, offerTo, roll } from './box.js';
17
- // What a harbor builds a ground out of. Convenience, never contract: a kit
18
- // writing its own harbor may write these three again.
19
- export { maker, entropy, learnPk } from '../ward/ground.js';
2
+ // The onion: a terrain, the package, the dock, the catalogue and its
3
+ // registry, and the harbor.
4
+ export { Catalogue, CATALOGUE } from './catalogue.js';
5
+ export { BOX, Dock } from './dock.js';
6
+ export { Harbor } from './harbor.js';
7
+ export { Package } from './package.js';
8
+ export { readRootRequest, rootLine } from './root-line.js';
9
+ export { Registry } from './registry.js';
10
+ export { Terrain } from './terrain.js';
@@ -0,0 +1,10 @@
1
+ import type { Entropy, Memory, Rows } from '../contract/index.ts';
2
+ export declare class Package {
3
+ #private;
4
+ readonly memory: Memory;
5
+ readonly entropy: Entropy;
6
+ private constructor();
7
+ static open(memory: Memory, entropy: Entropy, seed: Uint8Array): Promise<Package>;
8
+ derive(info: string[], length: number): Promise<Uint8Array>;
9
+ rows(name: string): Promise<Rows>;
10
+ }