@quo-systems/quo 0.2.15 → 0.2.17
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 +128 -0
- package/README.md +38 -26
- package/dist/conformance/store.js +36 -10
- package/dist/harbor/core.js +12 -9
- package/dist/harbor/index.d.ts +2 -1
- package/dist/harbor/index.js +4 -1
- package/dist/harbor/memory.d.ts +1 -0
- package/dist/harbor/memory.js +7 -1
- package/dist/harbor/store.d.ts +10 -3
- package/dist/harbor/store.js +34 -5
- package/dist/ward/arithmetic.d.ts +4 -0
- package/dist/ward/arithmetic.js +82 -12
- package/dist/ward/ground.d.ts +1 -1
- package/dist/ward/partition.d.ts +4 -0
- package/dist/ward/partition.js +53 -0
- package/dist/ward/seal.js +1 -1
- package/dist/ward/ward.js +37 -23
- package/package.json +7 -7
- package/{SPEC.md → protocol/SPEC.md} +16 -4
- package/protocol/vectors/door.json +345 -0
- package/quo-kit.md +98 -15
- package/src/conformance/store.ts +37 -10
- package/src/harbor/core.ts +17 -10
- package/src/harbor/index.ts +4 -1
- package/src/harbor/memory.ts +7 -1
- package/src/harbor/store.ts +39 -7
- package/src/ward/arithmetic.ts +83 -14
- package/src/ward/ground.ts +11 -4
- package/src/ward/partition.ts +53 -0
- package/src/ward/seal.ts +1 -1
- package/src/ward/ward.ts +37 -23
- /package/{vectors → protocol/vectors}/arithmetic.json +0 -0
- /package/{vectors → protocol/vectors}/framing.json +0 -0
- /package/{vectors → protocol/vectors}/wire.json +0 -0
package/dist/ward/arithmetic.js
CHANGED
|
@@ -86,27 +86,97 @@ const key32 = (value, what) => {
|
|
|
86
86
|
return value;
|
|
87
87
|
};
|
|
88
88
|
const pkcs8 = (prefix, value, what) => concat([prefix, key32(value, what)]);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
// Importing a key is the most expensive thing on the path of an ask, and most
|
|
90
|
+
// of the imports are the same key again: a ward signs every reply with the one
|
|
91
|
+
// key, opens every ask with the one padlock, and verifies a relation under the
|
|
92
|
+
// key it verified it under last time. Measured over a round trip, fifteen of
|
|
93
|
+
// the twenty-five imports were bytes already imported once.
|
|
94
|
+
//
|
|
95
|
+
// So an imported key is kept, by the bytes it was imported from. A CryptoKey
|
|
96
|
+
// cannot be changed once it exists, so handing the same one out twice is
|
|
97
|
+
// handing out what a second import would have built. Nothing here is a
|
|
98
|
+
// decision a peer can see: two wards that cache differently, or not at all,
|
|
99
|
+
// speak the same bytes.
|
|
100
|
+
//
|
|
101
|
+
// It is bounded, and that is not a detail. A relation mints a fresh key on
|
|
102
|
+
// every ask, so a ward that talked all day would otherwise hold a key for
|
|
103
|
+
// every ask it ever made. Past the bound the least recently used goes, which
|
|
104
|
+
// is the key of a relation that has fallen quiet, and importing it again
|
|
105
|
+
// costs what it cost the first time.
|
|
106
|
+
//
|
|
107
|
+
// The secret keys in here are the ones the partition already holds in this
|
|
108
|
+
// process, as seeds. The cache is another shape of what the ward is already
|
|
109
|
+
// standing on, and never a second place a secret comes from.
|
|
110
|
+
const KEYS = 512;
|
|
111
|
+
const imported = new Map();
|
|
112
|
+
const keep = (id, make) => {
|
|
113
|
+
const had = imported.get(id);
|
|
114
|
+
if (had !== undefined) {
|
|
115
|
+
imported.delete(id); // and set again below: the most recently used goes last
|
|
116
|
+
imported.set(id, had);
|
|
117
|
+
return had;
|
|
118
|
+
}
|
|
119
|
+
const made = make();
|
|
120
|
+
// A key that would not import is not kept: the next call asks subtle again
|
|
121
|
+
// and hears the same refusal, rather than reading one this cache remembered.
|
|
122
|
+
// Node takes any thirty-two bytes as a public key and finds out at verify,
|
|
123
|
+
// so nothing here reaches this line; a terrain that checks the point at the
|
|
124
|
+
// import does, and a refusal it remembered would be a relation killed for
|
|
125
|
+
// good by one bad arrival.
|
|
126
|
+
made.catch(() => imported.delete(id));
|
|
127
|
+
imported.set(id, made);
|
|
128
|
+
// One in, at most one out: a map keeps what was put in the order it was put,
|
|
129
|
+
// so the first key it names is the one used longest ago.
|
|
130
|
+
if (imported.size > KEYS)
|
|
131
|
+
imported.delete(imported.keys().next().value);
|
|
132
|
+
return made;
|
|
133
|
+
};
|
|
134
|
+
// How many imported keys are held, and the bound they are held under. Nothing
|
|
135
|
+
// in the ward reads either: they are here to be looked at, and for the suite
|
|
136
|
+
// that holds the bound to what it says.
|
|
137
|
+
export const heldKeys = () => ({ held: imported.size, bound: KEYS });
|
|
138
|
+
const secretKey = (alg, prefix, value, what, uses) => {
|
|
139
|
+
const bytes = pkcs8(prefix, value, what);
|
|
140
|
+
return keep(`${alg.name}|${uses.join('+')}|${hex(bytes)}`, () => subtle().importKey('pkcs8', bytes, alg, true, uses));
|
|
141
|
+
};
|
|
142
|
+
const publicKey = (alg, value, what, uses) => {
|
|
143
|
+
const bytes = key32(value, what);
|
|
144
|
+
return keep(`${alg.name}|${uses.join('+')}|pk|${hex(bytes)}`, () => subtle().importKey('raw', bytes, alg, true, uses));
|
|
145
|
+
};
|
|
146
|
+
// Subtle exports the public half of a private key only through a JWK, where
|
|
147
|
+
// `x` is the 32 raw bytes in base64url. The answer is a fact about the key and
|
|
148
|
+
// never changes, so it is kept beside the key it was read from and goes when
|
|
149
|
+
// the key does.
|
|
150
|
+
const publics = new WeakMap();
|
|
151
|
+
function rawPublic(secret) {
|
|
152
|
+
const had = publics.get(secret);
|
|
153
|
+
if (had !== undefined)
|
|
154
|
+
return had;
|
|
155
|
+
const read = (async () => {
|
|
156
|
+
const jwk = await subtle().exportKey('jwk', secret);
|
|
157
|
+
const binary = atob(jwk.x.replaceAll('-', '+').replaceAll('_', '/'));
|
|
158
|
+
const out = new Uint8Array(binary.length);
|
|
159
|
+
for (let at = 0; at < binary.length; at += 1)
|
|
160
|
+
out[at] = binary.charCodeAt(at);
|
|
161
|
+
return out;
|
|
162
|
+
})();
|
|
163
|
+
publics.set(secret, read);
|
|
164
|
+
return read;
|
|
99
165
|
}
|
|
100
166
|
export async function sha256(...parts) {
|
|
101
167
|
return new Uint8Array(await subtle().digest('SHA-256', concat(parts)));
|
|
102
168
|
}
|
|
169
|
+
// Both halves are copies. The seed and the public key are kept behind the two
|
|
170
|
+
// caches above, and a pair is handed to whoever asked for it: what she does
|
|
171
|
+
// with the bytes in her hand is hers, and must not reach what the next caller
|
|
172
|
+
// is given.
|
|
103
173
|
export async function signingPair(seed) {
|
|
104
174
|
const secret = await secretKey(ED, ED_SECRET, seed, 'seed', ['sign']);
|
|
105
|
-
return { secret: Uint8Array.from(seed), pk: await rawPublic(secret) };
|
|
175
|
+
return { secret: Uint8Array.from(seed), pk: Uint8Array.from(await rawPublic(secret)) };
|
|
106
176
|
}
|
|
107
177
|
export async function sealingPair(seed) {
|
|
108
178
|
const secret = await secretKey(X, X_SECRET, seed, 'seed', ['deriveBits']);
|
|
109
|
-
return { secret: Uint8Array.from(seed), pk: await rawPublic(secret) };
|
|
179
|
+
return { secret: Uint8Array.from(seed), pk: Uint8Array.from(await rawPublic(secret)) };
|
|
110
180
|
}
|
|
111
181
|
export async function sign(message, secret) {
|
|
112
182
|
const key = await secretKey(ED, ED_SECRET, secret, 'secret', ['sign']);
|
package/dist/ward/ground.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type Ground = {
|
|
|
4
4
|
seed: string | Uint8Array;
|
|
5
5
|
memory: Record<string, unknown>;
|
|
6
6
|
instantiate(className: string, stance: Stance): BeingLike | null;
|
|
7
|
-
wrote?: () => void;
|
|
7
|
+
wrote?: (row: string) => void;
|
|
8
8
|
carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | undefined>;
|
|
9
9
|
random(n: number): Uint8Array;
|
|
10
10
|
lend?: Lend;
|
package/dist/ward/partition.d.ts
CHANGED
|
@@ -58,3 +58,7 @@ export declare const put: <T>(rec: Record<string, T>, key: string, value: T) =>
|
|
|
58
58
|
export declare const drop: (rec: Record<string, unknown>, key: string) => void;
|
|
59
59
|
export declare const emptyCells: () => Cells;
|
|
60
60
|
export declare const emptyBind: () => Bind;
|
|
61
|
+
export declare const HEAD = "";
|
|
62
|
+
export declare const rowOf: (p: Partition, row: string) => Record<string, unknown> | undefined;
|
|
63
|
+
export declare const rowsIn: (p: Record<string, unknown>) => string[];
|
|
64
|
+
export declare const fromRows: (rows: Record<string, Record<string, unknown>>) => Record<string, unknown>;
|
package/dist/ward/partition.js
CHANGED
|
@@ -167,3 +167,56 @@ export const drop = (rec, key) => {
|
|
|
167
167
|
};
|
|
168
168
|
export const emptyCells = () => ({ standings: {}, occupants: {} });
|
|
169
169
|
export const emptyBind = () => ({ standings: {}, occupants: {}, knocks: {}, answered: {}, minted: [] });
|
|
170
|
+
// ---- the partition in rows, for a store that keeps it
|
|
171
|
+
//
|
|
172
|
+
// A ward hands a harbor one object, so a being who writes one cell has every
|
|
173
|
+
// other being in her ward written down beside her. What a being holds is the
|
|
174
|
+
// one thing here that grows without limit, so she is a row: her cells and her
|
|
175
|
+
// bind table, under her key. Everything else is one head row, and every part
|
|
176
|
+
// of it is bounded already, one record per occupant and two lists that drop
|
|
177
|
+
// their oldest.
|
|
178
|
+
//
|
|
179
|
+
// The split is here, where the shape is, and not in the harbor, which keeps
|
|
180
|
+
// what it is given and reads none of it. A store is handed the rows that
|
|
181
|
+
// moved; what it does with that is its own, and one that writes every row is
|
|
182
|
+
// as correct as one that writes the few.
|
|
183
|
+
export const HEAD = ''; // the row that is not a being: no being's key is empty
|
|
184
|
+
// One row's value: a being, or the head, which is everything the partition
|
|
185
|
+
// holds that is not filed under a being. The head is read as what is left
|
|
186
|
+
// rather than as a list of names, so a store keeps whatever it was handed and
|
|
187
|
+
// hands the same thing back, and a field this file grows later needs no line
|
|
188
|
+
// in a store to survive a round trip.
|
|
189
|
+
//
|
|
190
|
+
// Undefined where the key names nobody, which is a row the store drops.
|
|
191
|
+
export const rowOf = (p, row) => {
|
|
192
|
+
const all = p;
|
|
193
|
+
if (row === HEAD)
|
|
194
|
+
return Object.fromEntries(Object.entries(all).filter(([k]) => k !== 'beings' && k !== 'bind'));
|
|
195
|
+
const cells = at(p.beings ?? {}, row);
|
|
196
|
+
return cells === undefined ? undefined : { cells, bind: at(p.bind ?? {}, row) ?? emptyBind() };
|
|
197
|
+
};
|
|
198
|
+
export const rowsIn = (p) => [HEAD, ...Object.keys(p.beings ?? {})];
|
|
199
|
+
// The partition back from its rows. A head that is absent is a partition
|
|
200
|
+
// nobody has written, and `open` makes an empty one of it.
|
|
201
|
+
export const fromRows = (rows) => {
|
|
202
|
+
const out = { ...(rows[HEAD] ?? {}) };
|
|
203
|
+
const beings = {};
|
|
204
|
+
const bind = {};
|
|
205
|
+
let any = Object.keys(out).length > 0;
|
|
206
|
+
for (const [row, value] of Object.entries(rows)) {
|
|
207
|
+
if (row === HEAD)
|
|
208
|
+
continue;
|
|
209
|
+
put(beings, row, value.cells);
|
|
210
|
+
put(bind, row, value.bind);
|
|
211
|
+
any = true;
|
|
212
|
+
}
|
|
213
|
+
// Nothing kept is nothing handed back. Rows that say only that there are no
|
|
214
|
+
// rows must come back as the empty memory they are, because `open` reads an
|
|
215
|
+
// empty one as a ward's first breath and anything else as a partition
|
|
216
|
+
// somebody wrote, which must then say which version wrote it.
|
|
217
|
+
if (!any)
|
|
218
|
+
return {};
|
|
219
|
+
out.beings = beings;
|
|
220
|
+
out.bind = bind;
|
|
221
|
+
return out;
|
|
222
|
+
};
|
package/dist/ward/seal.js
CHANGED
|
@@ -26,7 +26,7 @@ export const SIZE = 1024 * 1024;
|
|
|
26
26
|
// designs and no separation at all: one secret would be doing two jobs with
|
|
27
27
|
// nothing said about it, and a second kit would have to reproduce a
|
|
28
28
|
// construction nobody named. HKDF-SHA-256 under a label is the separation
|
|
29
|
-
// said out loud, and it is what `vectors/framing.json` pins.
|
|
29
|
+
// said out loud, and it is what `protocol/vectors/framing.json` pins.
|
|
30
30
|
//
|
|
31
31
|
// Bytes are key material and text is not. A seed handed in as bytes of the
|
|
32
32
|
// key length is taken as it stands, which is what a harbor mints; anything
|
package/dist/ward/ward.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// leaves, opens every one that arrives.
|
|
7
7
|
import { silence, isSilence, unreached, isWord, word } from '../being/silence.js';
|
|
8
8
|
import { OWNER } from '../being/types.js';
|
|
9
|
-
import { at, drop, open, put, emptyBind, emptyCells, MINTED } from './partition.js';
|
|
9
|
+
import { at, drop, open, put, emptyBind, emptyCells, HEAD, MINTED } from './partition.js';
|
|
10
10
|
import { Heirs } from './heirs.js';
|
|
11
11
|
import { makeDoor } from './door.js';
|
|
12
12
|
import { buildStance } from './stance.js';
|
|
@@ -38,7 +38,13 @@ class Self {
|
|
|
38
38
|
this.key = key;
|
|
39
39
|
this.pk = key.pk;
|
|
40
40
|
this.p = open(ground.memory); // the partition: the ward's own state, and every being's row
|
|
41
|
-
|
|
41
|
+
// Opening is itself a write: a memory nobody has written comes back with
|
|
42
|
+
// a version stamped on it and the ward's own tables under it. A ward that
|
|
43
|
+
// said nothing here would have every being of it kept and the head that
|
|
44
|
+
// says which version they are written under kept by nobody, so the next
|
|
45
|
+
// boot would read a partition of no version and refuse it.
|
|
46
|
+
this.#wrote(HEAD);
|
|
47
|
+
this.heirs = new Heirs(this.p, () => this.#wrote(HEAD));
|
|
42
48
|
this.door = makeDoor(key, this.heirs, this.doors, () => this.p.public, (n) => ground.random(n)); // pointer one
|
|
43
49
|
this.#boot(this.pk, () => this); // the ward's ward is itself
|
|
44
50
|
// a restart is silent: every being in the cells is constructed again, unasked.
|
|
@@ -93,15 +99,18 @@ class Self {
|
|
|
93
99
|
unboot: (key) => this.#unboot(key),
|
|
94
100
|
setPublic: (key) => {
|
|
95
101
|
this.p.public = key;
|
|
96
|
-
this.#wrote();
|
|
102
|
+
this.#wrote(HEAD);
|
|
97
103
|
},
|
|
98
104
|
}, asker, method, args);
|
|
99
105
|
}
|
|
100
106
|
// ---- ward functions. on the object. no stance reaches them.
|
|
101
|
-
// The partition was written
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
// The partition was written, and which row of it: a being by her key, or
|
|
108
|
+
// the head. The harbor is told, and nothing more: what it does with the
|
|
109
|
+
// word is its own, and the ward never learns. Every write says its row,
|
|
110
|
+
// because a harbor told only that something moved must keep all of it, and
|
|
111
|
+
// a being's row is the one part of a partition that grows without limit.
|
|
112
|
+
#wrote(row) {
|
|
113
|
+
this.g.wrote?.(row);
|
|
105
114
|
}
|
|
106
115
|
// The rows no door holds this run: beings whose class threw at birth or
|
|
107
116
|
// is not the harbor's to give. Key to class.
|
|
@@ -139,7 +148,7 @@ class Self {
|
|
|
139
148
|
if (!door)
|
|
140
149
|
return null;
|
|
141
150
|
unguarded(door.cells).class = className; // so a restart finds her. the ward's key, written behind her guard
|
|
142
|
-
this.#wrote();
|
|
151
|
+
this.#wrote(key);
|
|
143
152
|
return door;
|
|
144
153
|
}
|
|
145
154
|
// The inverse of boot, and the only way a being leaves a ward. Her
|
|
@@ -177,7 +186,8 @@ class Self {
|
|
|
177
186
|
drop(this.p.beings, key);
|
|
178
187
|
drop(this.p.bind, key);
|
|
179
188
|
this.doors.delete(key);
|
|
180
|
-
this.#wrote();
|
|
189
|
+
this.#wrote(key); // she is gone, and her row goes with her
|
|
190
|
+
this.#wrote(HEAD); // her heirs were closed, and the public being may have been her
|
|
181
191
|
return [...occupants, ...standings];
|
|
182
192
|
}
|
|
183
193
|
// Nothing is written until there is somebody to write it for: a class the
|
|
@@ -188,17 +198,17 @@ class Self {
|
|
|
188
198
|
// makers: the ground's `instantiate` for a being of the ward, and the ward
|
|
189
199
|
// itself, which is the first being in its own map and is already made.
|
|
190
200
|
#boot(key, make) {
|
|
191
|
-
const cells = guardCells(at(this.p.beings, key) ?? emptyCells(), () => this.#wrote());
|
|
201
|
+
const cells = guardCells(at(this.p.beings, key) ?? emptyCells(), () => this.#wrote(key));
|
|
192
202
|
const bind = at(this.p.bind, key) ?? emptyBind();
|
|
193
203
|
const stance = buildStance({
|
|
194
204
|
pk: this.pk,
|
|
195
205
|
// This stance, not merely this key: unboot and boot again under the
|
|
196
206
|
// same key makes a new being, and the old stance is not hers.
|
|
197
207
|
live: () => this.doors.get(key)?.stance === stance,
|
|
198
|
-
mintKey: (b) => this.#mintKey(b),
|
|
208
|
+
mintKey: (b) => this.#mintKey(b, key),
|
|
199
209
|
openHeir: (heir, being, id) => this.heirs.open(heir, being, id),
|
|
200
210
|
closeHeir: (heir) => this.heirs.close(heir),
|
|
201
|
-
send: (b, keys, method, args, wanted) => this.#send(b, keys, method, args, wanted),
|
|
211
|
+
send: (b, keys, method, args, wanted) => this.#send(b, keys, method, args, wanted, key),
|
|
202
212
|
// A throw at birth is null to her, not a throw in her method: she asked
|
|
203
213
|
// for a being and got none, and her own answer is still hers to give.
|
|
204
214
|
instantiate: (className, k) => {
|
|
@@ -215,7 +225,7 @@ class Self {
|
|
|
215
225
|
// names there are, which ward may ask for one, and what becomes of one
|
|
216
226
|
// she did not take, is the harbor's and no word of the ward.
|
|
217
227
|
lend: async (name, take) => (await this.g.lend?.(name, take)) ?? false,
|
|
218
|
-
wrote: () => this.#wrote(),
|
|
228
|
+
wrote: () => this.#wrote(key),
|
|
219
229
|
}, key, cells, bind);
|
|
220
230
|
const being = make(stance);
|
|
221
231
|
if (!being || typeof being.answer !== 'function')
|
|
@@ -224,10 +234,10 @@ class Self {
|
|
|
224
234
|
put(this.p.bind, key, bind);
|
|
225
235
|
const door = { key, cells, bind, stance, being };
|
|
226
236
|
this.doors.set(key, door);
|
|
227
|
-
this.#wrote();
|
|
237
|
+
this.#wrote(key);
|
|
228
238
|
return door;
|
|
229
239
|
}
|
|
230
|
-
async #mintKey(bind) {
|
|
240
|
+
async #mintKey(bind, row) {
|
|
231
241
|
const k = await beingKey(this.g.random(32));
|
|
232
242
|
// The last few she minted, and no more. A relation rotates on every ask,
|
|
233
243
|
// so a list of all of them is a partition that grows for as long as she
|
|
@@ -236,7 +246,7 @@ class Self {
|
|
|
236
246
|
bind.minted.push(k.pk);
|
|
237
247
|
if (bind.minted.length > MINTED)
|
|
238
248
|
bind.minted.splice(0, bind.minted.length - MINTED);
|
|
239
|
-
this.#wrote();
|
|
249
|
+
this.#wrote(row);
|
|
240
250
|
return k;
|
|
241
251
|
}
|
|
242
252
|
// One send for every door, the ward's own included. Mine: never leaves.
|
|
@@ -244,7 +254,7 @@ class Self {
|
|
|
244
254
|
// next, seals to the far ward, opens the reply with the ephemeral secret,
|
|
245
255
|
// and rotates to the announced key once the far door has answered under
|
|
246
256
|
// the current one.
|
|
247
|
-
async #send(bind, keys, method, args, wanted) {
|
|
257
|
+
async #send(bind, keys, method, args, wanted, row) {
|
|
248
258
|
// What she asked for, held to what this ward allows. Asking for nothing is
|
|
249
259
|
// the default, and asking for more than the ceiling is the ceiling: budget
|
|
250
260
|
// is granted by a ward, never minted by a being.
|
|
@@ -255,7 +265,7 @@ class Self {
|
|
|
255
265
|
// to rotate to. A standing on her signs with one key for life. Every other
|
|
256
266
|
// ask announces its next: there is no send that does not.
|
|
257
267
|
if (keys.next === null && keys.heir !== null)
|
|
258
|
-
keys.next = (await this.#mintKey(bind)).seed;
|
|
268
|
+
keys.next = (await this.#mintKey(bind, row)).seed;
|
|
259
269
|
const next = keys.next === null ? null : (await beingKey(unhex(keys.next))).pk;
|
|
260
270
|
// Her count for this relation, one higher every call and never reused. The
|
|
261
271
|
// far door honours each number once. One relation sends one at a time, so
|
|
@@ -279,10 +289,14 @@ class Self {
|
|
|
279
289
|
return unreached();
|
|
280
290
|
}
|
|
281
291
|
const { bytes, ephemeral } = sealed;
|
|
282
|
-
// The wait is bounded, and
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
292
|
+
// The wait is bounded here, and again around the lane in the stance. Two
|
|
293
|
+
// bounds and not one, because they end two different things: the stance's
|
|
294
|
+
// ends the wait a being is held in, and this one ends the occupancy of the
|
|
295
|
+
// relation's lane. A being holds three answers and a wait that does not
|
|
296
|
+
// end is none of them, and a lane nobody ever leaves is a relation the
|
|
297
|
+
// next ask never reaches. Only a bound on the wire breaks the second, and
|
|
298
|
+
// taking it out would leave one quiet far side holding the lane for good.
|
|
299
|
+
// What comes back late is not read.
|
|
286
300
|
//
|
|
287
301
|
// A wait that ran out is `late`, never unreached. Unreached promises
|
|
288
302
|
// nothing was delivered and is safe to retry; a bound that expired knows
|
|
@@ -303,7 +317,7 @@ class Self {
|
|
|
303
317
|
keys.current = keys.next; // the far door holds `next` as announced. move to it.
|
|
304
318
|
keys.next = null;
|
|
305
319
|
}
|
|
306
|
-
this.#wrote(); // the count moved, and the keys may have
|
|
320
|
+
this.#wrote(row); // the count moved, and the keys may have
|
|
307
321
|
return reply;
|
|
308
322
|
}
|
|
309
323
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quo-systems/quo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"description": "Quo: an object asks another object and gets an answer, without knowing where it is. Being, Ward, Harbor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"quo",
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
"types": "./dist/conformance/index.d.ts",
|
|
36
36
|
"default": "./dist/conformance/index.js"
|
|
37
37
|
},
|
|
38
|
-
"./
|
|
38
|
+
"./protocol/*": "./protocol/*",
|
|
39
39
|
"./package.json": "./package.json"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json && cp ../../papers/SPEC.md SPEC.md && cp ../../papers/quo-kit.md quo-kit.md",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
42
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && cp ../../papers/SPEC.md protocol/SPEC.md && cp ../../papers/quo-kit.md quo-kit.md && cp ../../papers/GETTING_STARTED.md GETTING_STARTED.md",
|
|
43
|
+
"check": "node --test \"test/*.test.ts\"",
|
|
44
|
+
"deep": "node --test \"test/terrain/*.test.ts\"",
|
|
45
45
|
"prepublishOnly": "test \"$QUO_GATED\" = 1 || { echo 'publish from the root, gated once: npm run release:quo' >&2; exit 1; }"
|
|
46
46
|
},
|
|
47
47
|
"publishConfig": {
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"files": [
|
|
51
51
|
"dist",
|
|
52
52
|
"src",
|
|
53
|
-
"
|
|
54
|
-
"SPEC.md",
|
|
53
|
+
"protocol",
|
|
55
54
|
"quo-kit.md",
|
|
55
|
+
"GETTING_STARTED.md",
|
|
56
56
|
"README.md",
|
|
57
57
|
"LICENSE",
|
|
58
58
|
"NOTICE"
|
|
@@ -710,6 +710,12 @@ spent, so bytes leave and the door's silence comes back: after take the
|
|
|
710
710
|
invitation names the standing and nothing else, so it is exactly as alive as
|
|
711
711
|
the standing is.
|
|
712
712
|
|
|
713
|
+
A lane is released by the bound and by nothing else. An ask that never comes
|
|
714
|
+
back holds its relation's lane, and every later ask on that relation waits
|
|
715
|
+
behind it, so the bound ends an occupancy and not only a wait. A kit that
|
|
716
|
+
bounds where a being is waiting, and not where the lane is held, has a
|
|
717
|
+
relation that one quiet far side closes for good.
|
|
718
|
+
|
|
713
719
|
### The public being
|
|
714
720
|
|
|
715
721
|
A ward may have one public being, and no more. She is an ordinary being,
|
|
@@ -898,7 +904,8 @@ the default would leave her only the narrowing half, and would give the ward
|
|
|
898
904
|
no way to allow one long piece of work without making every ask that
|
|
899
905
|
patient.
|
|
900
906
|
|
|
901
|
-
The sender's wait has an end, and
|
|
907
|
+
The sender's wait has an end, and time is the only thing the ward measures:
|
|
908
|
+
no count of doors, no count of bytes, no count of tries. The
|
|
902
909
|
bound covers the whole of an ask, from the moment she calls: a relation that
|
|
903
910
|
comes back round on itself is stopped at its own lane, before a byte is
|
|
904
911
|
sealed, and a bound that watched only the wire would never see it. This is
|
|
@@ -1045,9 +1052,14 @@ in pieces, by a being who knows how her own work divides.
|
|
|
1045
1052
|
|
|
1046
1053
|
The hand to a kit in another language is two things, and they are of two
|
|
1047
1054
|
kinds. `vectors/` is the byte-level hand: fixed inputs and outputs for
|
|
1048
|
-
everything a stranger can observe,
|
|
1049
|
-
the
|
|
1050
|
-
|
|
1055
|
+
everything a stranger can observe, in four areas. `arithmetic.json` is the
|
|
1056
|
+
primitives the seal rests on. `framing.json` is the ward pk, the digest, the
|
|
1057
|
+
signed body, the sealed shapes, the invitation and the knock.
|
|
1058
|
+
`wire.json` is the frames on a socket and the one request a door takes.
|
|
1059
|
+
`door.json` is the door's thirteen cases, each one an arrival: the bytes that
|
|
1060
|
+
come in, the bytes that go out, what those bytes open to where a hand holds
|
|
1061
|
+
the lid, and the partition's digest before and after, so `nothing written`
|
|
1062
|
+
is a value a kit checks and never a sentence it reads. A kit reproduces them
|
|
1051
1063
|
or it is not this protocol. The conformance suite is the behavioural hand,
|
|
1052
1064
|
and it is a checklist and not a harness: one ward is one runtime and one
|
|
1053
1065
|
language, so the beings a suite is shown with run only in the ward its kit
|