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.
- package/README.md +156 -104
- package/dist/being/being.d.ts +8 -15
- package/dist/being/being.js +20 -67
- package/dist/being/digest.d.ts +1 -1
- package/dist/being/digest.js +7 -27
- package/dist/being/faculty.d.ts +10 -0
- package/dist/being/faculty.js +74 -0
- package/dist/being/index.d.ts +5 -5
- package/dist/being/index.js +6 -5
- package/dist/being/kind.d.ts +4 -0
- package/dist/being/kind.js +28 -0
- package/dist/being/types.d.ts +35 -63
- package/dist/being/types.js +3 -57
- package/dist/being/words.d.ts +13 -0
- package/dist/being/words.js +14 -0
- package/dist/cli/command.d.ts +13 -0
- package/dist/cli/command.js +194 -0
- package/dist/cli/harbor.d.ts +21 -0
- package/dist/cli/harbor.js +141 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +14 -0
- package/dist/contract/index.d.ts +39 -0
- package/dist/contract/index.js +33 -0
- package/dist/crypto/aes.d.ts +3 -0
- package/dist/crypto/aes.js +24 -0
- package/dist/crypto/bytes.d.ts +6 -0
- package/dist/crypto/bytes.js +33 -0
- package/dist/crypto/ed25519.d.ts +3 -0
- package/dist/crypto/ed25519.js +85 -0
- package/dist/crypto/hash.d.ts +2 -0
- package/dist/crypto/hash.js +11 -0
- package/dist/crypto/index.d.ts +7 -0
- package/dist/crypto/index.js +10 -0
- package/dist/crypto/json.d.ts +8 -0
- package/dist/crypto/json.js +250 -0
- package/dist/crypto/mlkem.d.ts +12 -0
- package/dist/crypto/mlkem.js +36 -0
- package/dist/crypto/subtle.d.ts +10 -0
- package/dist/crypto/subtle.js +29 -0
- package/dist/crypto/x25519.d.ts +2 -0
- package/dist/crypto/x25519.js +21 -0
- package/dist/folder/index.d.ts +14 -0
- package/dist/folder/index.js +87 -0
- package/dist/harbor/catalogue.d.ts +25 -0
- package/dist/harbor/catalogue.js +93 -0
- package/dist/harbor/dock.d.ts +29 -0
- package/dist/harbor/dock.js +122 -0
- package/dist/harbor/harbor.d.ts +31 -0
- package/dist/harbor/harbor.js +146 -0
- package/dist/harbor/index.d.ts +7 -10
- package/dist/harbor/index.js +9 -18
- package/dist/harbor/package.d.ts +10 -0
- package/dist/harbor/package.js +115 -0
- package/dist/harbor/registry.d.ts +9 -0
- package/dist/harbor/registry.js +78 -0
- package/dist/harbor/root-line.d.ts +9 -0
- package/dist/harbor/root-line.js +36 -0
- package/dist/harbor/terrain.d.ts +9 -0
- package/dist/harbor/terrain.js +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/pointer/bodies.d.ts +26 -0
- package/dist/pointer/bodies.js +64 -0
- package/dist/pointer/index.d.ts +2 -0
- package/dist/pointer/index.js +4 -0
- package/dist/pointer/world.d.ts +35 -0
- package/dist/pointer/world.js +85 -0
- package/dist/quo/door.d.ts +34 -0
- package/dist/quo/door.js +172 -0
- package/dist/quo/index.d.ts +8 -0
- package/dist/quo/index.js +11 -0
- package/dist/quo/invitation.d.ts +7 -0
- package/dist/quo/invitation.js +15 -0
- package/dist/quo/keys.d.ts +40 -0
- package/dist/quo/keys.js +79 -0
- package/dist/quo/payload.d.ts +15 -0
- package/dist/quo/payload.js +55 -0
- package/dist/quo/relations.d.ts +40 -0
- package/dist/quo/relations.js +33 -0
- package/dist/quo/reply.d.ts +13 -0
- package/dist/quo/reply.js +35 -0
- package/dist/quo/seal.d.ts +42 -0
- package/dist/quo/seal.js +78 -0
- package/dist/quo/standing.d.ts +39 -0
- package/dist/quo/standing.js +90 -0
- package/dist/tcp/frame.d.ts +29 -0
- package/dist/tcp/frame.js +74 -0
- package/dist/tcp/index.d.ts +18 -0
- package/dist/tcp/index.js +164 -0
- package/dist/ward/allowance.d.ts +13 -9
- package/dist/ward/allowance.js +28 -67
- package/dist/ward/cells.d.ts +6 -6
- package/dist/ward/cells.js +67 -179
- package/dist/ward/index.d.ts +6 -10
- package/dist/ward/index.js +7 -13
- package/dist/ward/partition.d.ts +44 -60
- package/dist/ward/partition.js +56 -285
- package/dist/ward/pilot.d.ts +8 -0
- package/dist/ward/pilot.js +50 -0
- package/dist/ward/stance.d.ts +27 -20
- package/dist/ward/stance.js +174 -406
- package/dist/ward/ward-being.d.ts +35 -0
- package/dist/ward/ward-being.js +82 -0
- package/dist/ward/ward.d.ts +45 -11
- package/dist/ward/ward.js +239 -357
- package/package.json +24 -31
- package/src/being/being.ts +35 -78
- package/src/being/digest.ts +7 -35
- package/src/being/faculty.ts +73 -0
- package/src/being/index.ts +6 -6
- package/src/being/kind.ts +30 -0
- package/src/being/types.ts +50 -146
- package/src/being/words.ts +31 -0
- package/src/cli/command.ts +188 -0
- package/src/cli/harbor.ts +137 -0
- package/src/cli/main.ts +13 -0
- package/src/contract/index.ts +73 -0
- package/src/crypto/aes.ts +26 -0
- package/src/crypto/bytes.ts +37 -0
- package/src/crypto/ed25519.ts +84 -0
- package/src/crypto/hash.ts +14 -0
- package/src/crypto/index.ts +10 -0
- package/src/crypto/json.ts +241 -0
- package/src/crypto/mlkem.ts +38 -0
- package/src/crypto/subtle.ts +33 -0
- package/src/crypto/x25519.ts +21 -0
- package/src/folder/index.ts +92 -0
- package/src/harbor/catalogue.ts +110 -0
- package/src/harbor/dock.ts +137 -0
- package/src/harbor/harbor.ts +180 -0
- package/src/harbor/index.ts +9 -19
- package/src/harbor/package.ts +131 -0
- package/src/harbor/registry.ts +73 -0
- package/src/harbor/root-line.ts +36 -0
- package/src/harbor/terrain.ts +15 -0
- package/src/index.ts +20 -0
- package/src/pointer/bodies.ts +65 -0
- package/src/pointer/index.ts +4 -0
- package/src/pointer/world.ts +94 -0
- package/src/quo/door.ts +178 -0
- package/src/quo/index.ts +11 -0
- package/src/quo/invitation.ts +15 -0
- package/src/quo/keys.ts +91 -0
- package/src/quo/payload.ts +61 -0
- package/src/quo/relations.ts +62 -0
- package/src/quo/reply.ts +38 -0
- package/src/quo/seal.ts +101 -0
- package/src/quo/standing.ts +111 -0
- package/src/stand/main.ts +20 -0
- package/src/stand/stand.ts +220 -0
- package/src/tcp/frame.ts +78 -0
- package/src/tcp/index.ts +173 -0
- package/src/ward/allowance.ts +37 -75
- package/src/ward/cells.ts +63 -176
- package/src/ward/index.ts +7 -17
- package/src/ward/partition.ts +86 -326
- package/src/ward/pilot.ts +50 -0
- package/src/ward/stance.ts +186 -420
- package/src/ward/ward-being.ts +103 -0
- package/src/ward/ward.ts +256 -359
- package/dist/being/lent.d.ts +0 -32
- package/dist/being/lent.js +0 -72
- package/dist/being/silence.d.ts +0 -12
- package/dist/being/silence.js +0 -41
- package/dist/conformance/assert.d.ts +0 -11
- package/dist/conformance/assert.js +0 -106
- package/dist/conformance/beings.d.ts +0 -199
- package/dist/conformance/beings.js +0 -188
- package/dist/conformance/estate.d.ts +0 -5
- package/dist/conformance/estate.js +0 -388
- package/dist/conformance/index.d.ts +0 -80
- package/dist/conformance/index.js +0 -819
- package/dist/conformance/reach.d.ts +0 -10
- package/dist/conformance/reach.js +0 -72
- package/dist/conformance/store.d.ts +0 -5
- package/dist/conformance/store.js +0 -113
- package/dist/harbor/box.d.ts +0 -121
- package/dist/harbor/box.js +0 -121
- package/dist/harbor/core.d.ts +0 -55
- package/dist/harbor/core.js +0 -662
- package/dist/harbor/dial.d.ts +0 -9
- package/dist/harbor/dial.js +0 -81
- package/dist/harbor/memory.d.ts +0 -29
- package/dist/harbor/memory.js +0 -104
- package/dist/harbor/reach.d.ts +0 -36
- package/dist/harbor/reach.js +0 -199
- package/dist/harbor/store.d.ts +0 -35
- package/dist/harbor/store.js +0 -62
- package/dist/vector/cases.d.ts +0 -42
- package/dist/vector/cases.js +0 -223
- package/dist/vector/index.d.ts +0 -6
- package/dist/vector/index.js +0 -8
- package/dist/vector/stand.d.ts +0 -9
- package/dist/vector/stand.js +0 -77
- package/dist/vector/world.d.ts +0 -144
- package/dist/vector/world.js +0 -209
- package/dist/ward/arithmetic.d.ts +0 -31
- package/dist/ward/arithmetic.js +0 -249
- package/dist/ward/door.d.ts +0 -18
- package/dist/ward/door.js +0 -186
- package/dist/ward/ground.d.ts +0 -29
- package/dist/ward/ground.js +0 -56
- package/dist/ward/heirs.d.ts +0 -13
- package/dist/ward/heirs.js +0 -117
- package/dist/ward/json.d.ts +0 -2
- package/dist/ward/json.js +0 -163
- package/dist/ward/owner.d.ts +0 -13
- package/dist/ward/owner.js +0 -220
- package/dist/ward/seal.d.ts +0 -52
- package/dist/ward/seal.js +0 -150
- package/src/being/lent.ts +0 -72
- package/src/being/silence.ts +0 -46
- package/src/conformance/assert.ts +0 -100
- package/src/conformance/beings.ts +0 -188
- package/src/conformance/estate.ts +0 -412
- package/src/conformance/index.ts +0 -965
- package/src/conformance/reach.ts +0 -83
- package/src/conformance/store.ts +0 -125
- package/src/harbor/box.ts +0 -131
- package/src/harbor/core.ts +0 -699
- package/src/harbor/dial.ts +0 -112
- package/src/harbor/memory.ts +0 -123
- package/src/harbor/reach.ts +0 -221
- package/src/harbor/store.ts +0 -91
- package/src/vector/cases.ts +0 -257
- package/src/vector/index.ts +0 -11
- package/src/vector/stand.ts +0 -76
- package/src/vector/world.ts +0 -232
- package/src/ward/arithmetic.ts +0 -251
- package/src/ward/door.ts +0 -186
- package/src/ward/ground.ts +0 -142
- package/src/ward/heirs.ts +0 -116
- package/src/ward/json.ts +0 -144
- package/src/ward/owner.ts +0 -214
- 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
|
+
}
|
package/dist/harbor/index.d.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export
|
|
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';
|
package/dist/harbor/index.js
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
|
|
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
|
+
}
|