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