@quo-systems/quo 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +9 -6
  2. package/SPEC.md +235 -137
  3. package/dist/being/being.d.ts +22 -0
  4. package/dist/being/being.js +78 -0
  5. package/dist/being/digest.d.ts +3 -0
  6. package/dist/being/digest.js +19 -0
  7. package/dist/being/index.d.ts +5 -0
  8. package/dist/being/index.js +6 -0
  9. package/dist/being/silence.d.ts +10 -0
  10. package/dist/being/silence.js +29 -0
  11. package/dist/being/types.d.ts +84 -0
  12. package/dist/being/types.js +16 -0
  13. package/dist/conformance/assert.d.ts +11 -0
  14. package/dist/conformance/assert.js +72 -0
  15. package/dist/conformance/beings.d.ts +114 -0
  16. package/dist/conformance/beings.js +126 -0
  17. package/dist/conformance/estate.d.ts +5 -0
  18. package/dist/conformance/estate.js +316 -0
  19. package/dist/conformance/index.d.ts +65 -0
  20. package/dist/conformance/index.js +448 -0
  21. package/dist/conformance/reach.d.ts +10 -0
  22. package/dist/conformance/reach.js +72 -0
  23. package/dist/conformance/store.d.ts +5 -0
  24. package/dist/conformance/store.js +97 -0
  25. package/dist/harbor/core.d.ts +46 -0
  26. package/dist/harbor/core.js +218 -0
  27. package/dist/harbor/dial.d.ts +8 -0
  28. package/dist/harbor/dial.js +45 -0
  29. package/dist/harbor/index.d.ts +6 -0
  30. package/dist/harbor/index.js +10 -0
  31. package/dist/harbor/memory.d.ts +24 -0
  32. package/dist/harbor/memory.js +68 -0
  33. package/dist/harbor/reach.d.ts +36 -0
  34. package/dist/harbor/reach.js +166 -0
  35. package/dist/harbor/store.d.ts +33 -0
  36. package/dist/harbor/store.js +41 -0
  37. package/dist/ward/allowance.d.ts +10 -0
  38. package/dist/ward/allowance.js +60 -0
  39. package/dist/ward/arithmetic.d.ts +26 -0
  40. package/dist/ward/arithmetic.js +159 -0
  41. package/dist/ward/cells.d.ts +3 -0
  42. package/dist/ward/cells.js +79 -0
  43. package/dist/ward/door.d.ts +16 -0
  44. package/dist/ward/door.js +127 -0
  45. package/dist/ward/ground.d.ts +15 -0
  46. package/dist/ward/ground.js +1 -0
  47. package/dist/ward/heirs.d.ts +14 -0
  48. package/dist/ward/heirs.js +102 -0
  49. package/dist/ward/index.d.ts +8 -0
  50. package/dist/ward/index.js +10 -0
  51. package/dist/ward/owner.d.ts +32 -0
  52. package/dist/ward/owner.js +116 -0
  53. package/dist/ward/partition.d.ts +57 -0
  54. package/dist/ward/partition.js +64 -0
  55. package/dist/ward/seal.d.ts +50 -0
  56. package/dist/ward/seal.js +109 -0
  57. package/dist/ward/stance.d.ts +19 -0
  58. package/dist/ward/stance.js +259 -0
  59. package/dist/ward/ward.d.ts +2 -0
  60. package/dist/ward/ward.js +197 -0
  61. package/package.json +21 -29
  62. package/src/being/index.ts +1 -1
  63. package/src/being/silence.ts +26 -8
  64. package/src/being/types.ts +13 -3
  65. package/src/conformance/beings.ts +5 -5
  66. package/src/conformance/estate.ts +11 -6
  67. package/src/conformance/index.ts +14 -12
  68. package/src/harbor/core.ts +56 -6
  69. package/src/harbor/dial.ts +1 -1
  70. package/src/harbor/memory.ts +6 -1
  71. package/src/ward/door.ts +54 -32
  72. package/src/ward/ground.ts +6 -2
  73. package/src/ward/heirs.ts +20 -5
  74. package/src/ward/owner.ts +4 -3
  75. package/src/ward/partition.ts +10 -0
  76. package/src/ward/seal.ts +8 -5
  77. package/src/ward/stance.ts +20 -14
  78. package/src/ward/ward.ts +9 -9
  79. package/vectors/framing.json +7 -1
@@ -1,10 +1,10 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // The one stance builder. Used for every being the ward boots. Nothing
3
3
  // outer is in what she holds: ids in, values out, keys in the bind table.
4
- import { silence, isSilence, isUnreached } from '../being/silence.ts';
4
+ import { silence, isSilence, isUnreached, isWord, word } from '../being/silence.ts';
5
5
  import { digest } from '../being/digest.ts';
6
6
  import { RESERVED_IDS } from '../being/types.ts';
7
- import type { Answer, Cells, Invitation, JsonObject, Silence, Stance, Standing, Standings, Unreached, Wanted } from '../being/types.ts';
7
+ import type { Answer, Cells, Invitation, JsonObject, Silence, Stance, Standing, Standings, Wanted, Word } from '../being/types.ts';
8
8
  import { at, put, drop, type Bind, type StandingKeys } from './partition.ts';
9
9
  import { allow, within, LATE } from './allowance.ts';
10
10
  import { isWardPk, type ReplyPayload } from './seal.ts';
@@ -17,7 +17,7 @@ export type Inside = {
17
17
  closeHeir(heir: string): void;
18
18
  // one send for every destination. the far door's reply, silence, or unreached.
19
19
  // signs as keys.current, announces keys.next (minting one if asked), and rotates keys on an answer.
20
- send(bind: Bind, keys: StandingKeys, announce: boolean, method: string | undefined, args: JsonObject, wanted: Wanted | undefined): Promise<ReplyPayload | Silence | Unreached>;
20
+ send(bind: Bind, keys: StandingKeys, announce: boolean, method: string | undefined, args: JsonObject, wanted: Wanted | undefined): Promise<ReplyPayload | Silence | Word>;
21
21
  };
22
22
 
23
23
  // An invitation as it travels: the far ward's pk, the heir pk, the heir's secret seed. Or just the ward, for its public being.
@@ -84,13 +84,14 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
84
84
  const out = await within(allow(wanted).time, work(() => !late), () => {
85
85
  late = true;
86
86
  });
87
- return out === LATE ? silence : out;
87
+ return out === LATE ? word('late') : out;
88
88
  };
89
89
 
90
90
  const send = async (keys: StandingKeys, method: string | undefined, args: JsonObject, wanted: Wanted | undefined, rec?: Cells['standings'][string]): Promise<Answer> => {
91
91
  const r = await inside.send(bind, keys, true, method, args, wanted);
92
- if (isSilence(r) || isUnreached(r)) return r;
92
+ if (isSilence(r) || isWord(r)) return r;
93
93
  if ('silence' in r) return silence;
94
+ if ('quo' in r) return word(r.quo); // the far door's word for her, sealed to her lid
94
95
  if (rec && method !== undefined && r.seen !== null) rec.seen = r.seen; // the digest rode along
95
96
  return r.object;
96
97
  };
@@ -122,7 +123,7 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
122
123
  k.current = own.current;
123
124
  k.next = own.next;
124
125
  if (isUnreached(r)) return r; // still nothing. she knows no more than before.
125
- if (!isSilence(r) && !('silence' in r)) {
126
+ if (!isSilence(r) && !isWord(r) && 'object' in r) {
126
127
  k.spoke = true; // it was heard the first time, and this is the answer
127
128
  bind.answered[name] = true;
128
129
  return r.object;
@@ -144,8 +145,13 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
144
145
  k.next = keys.next;
145
146
  }
146
147
  if (isUnreached(r)) return r; // nothing came back. she does not know whether it was heard.
147
- k.spoke = true;
148
- if (isSilence(r) || 'silence' in r) return silence;
148
+ // The heir is spent once the door honoured it, whatever she then said: an
149
+ // object, silence, or that she threw. A refusal wrote nothing, and says
150
+ // so by its word, so the heir still speaks and the next knock is as it.
151
+ k.spoke = !(!isSilence(r) && !isWord(r) && 'quo' in r && r.quo !== 'threw');
152
+ if (isSilence(r) || isWord(r)) return r;
153
+ if ('silence' in r) return silence;
154
+ if ('quo' in r) return word(r.quo);
149
155
  bind.answered[name] = true;
150
156
  return r.object;
151
157
  };
@@ -158,14 +164,14 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
158
164
 
159
165
  const calls = {
160
166
  knock: async (inv: Invitation, method?: string, args: JsonObject = {}, wanted?: Wanted): Promise<Answer> => {
161
- if (!valid(inv)) return silence;
167
+ if (!valid(inv)) return word('invitation'); // S1. nothing is sent
162
168
  // She may knock again, and after take that knock is an ask: the relation
163
169
  // has one home, so it answers on the standing's lane, under the
164
170
  // standing's keys and its count. A second lane here would speak for the
165
171
  // same relation, and the two would refuse each other.
166
172
  const id = takenAs(nameOf(inv));
167
- if (id !== undefined) return bounded(wanted, (live) => lane(`ask:${id}`, () => (live() ? asking(id, bind.standings[id], cells.standings[id], method, args, wanted) : Promise.resolve(silence))));
168
- return bounded(wanted, (live) => lane(`knock:${nameOf(inv)}`, () => (live() ? knocking(inv, method, args, wanted) : Promise.resolve(silence))));
173
+ if (id !== undefined) return bounded(wanted, (live) => lane(`ask:${id}`, () => (live() ? asking(id, bind.standings[id], cells.standings[id], method, args, wanted) : Promise.resolve(word('late')))));
174
+ return bounded(wanted, (live) => lane(`knock:${nameOf(inv)}`, () => (live() ? knocking(inv, method, args, wanted) : Promise.resolve(word('late')))));
169
175
  },
170
176
  // Take consumes. Until now the relation lived in the knock record, under
171
177
  // the ward and the heir; from now it lives in the standing, under her id.
@@ -214,15 +220,15 @@ export function buildStance(inside: Inside, key: string, cells: Cells, bind: Bin
214
220
  // the next line. Waiting for the lane is not a reason to lose it.
215
221
  ask: (method?: string, args: JsonObject = {}, wanted?: Wanted): Promise<Answer> => {
216
222
  const keys = at(bind.standings, id);
217
- if (!keys) return Promise.resolve(silence as Answer); // she dropped it between one line and the next
223
+ if (!keys) return Promise.resolve(word('dropped') as Answer); // S2. she dropped it between one line and the next
218
224
  const rec = at(cells.standings, id);
219
- return bounded(wanted, (live) => lane(`ask:${id}`, () => (live() ? asking(id, keys, rec, method, args, wanted) : Promise.resolve(silence))));
225
+ return bounded(wanted, (live) => lane(`ask:${id}`, () => (live() ? asking(id, keys, rec, method, args, wanted) : Promise.resolve(word('late')))));
220
226
  },
221
227
  });
222
228
 
223
229
  const asking = async (id: string, keys: StandingKeys, rec: Cells['standings'][string] | undefined, method: string | undefined, args: JsonObject, wanted: Wanted | undefined): Promise<Answer> => {
224
230
  const out = await send(keys, method, args, wanted, rec);
225
- if (method === undefined && !isSilence(out) && !isUnreached(out)) {
231
+ if (method === undefined && !isSilence(out) && !isWord(out)) {
226
232
  const here = at(cells.standings, id);
227
233
  if (here) {
228
234
  here.blueprint = out as never;
package/src/ward/ward.ts CHANGED
@@ -4,9 +4,9 @@
4
4
  // the first being in its own map, and every being it later instantiates gets
5
5
  // a stance built by the same code. Mints every key, seals every ask that
6
6
  // leaves, opens every one that arrives.
7
- import { silence, isSilence, unreached, isUnreached } from '../being/silence.ts';
7
+ import { silence, isSilence, unreached, isWord, word } from '../being/silence.ts';
8
8
  import { OWNER } from '../being/types.ts';
9
- import type { Asker, BeingLike, Cells, JsonObject, Reply, Stance, Wanted } from '../being/types.ts';
9
+ import type { Asker, BeingLike, Cells, JsonObject, Reply, Stance, Wanted, Word } from '../being/types.ts';
10
10
  import type { Ground, WardPointers } from './ground.ts';
11
11
  import { at, open, put, emptyBind, emptyCells, type Bind, type Partition, type StandingKeys } from './partition.ts';
12
12
  import { Heirs } from './heirs.ts';
@@ -72,7 +72,7 @@ class Self implements BeingLike {
72
72
  this.ask = async (method, args = {}) => {
73
73
  try {
74
74
  const out = await this.answer({ id: OWNER }, method, args as JsonObject);
75
- return isSilence(out) || isUnreached(out) ? silence : out;
75
+ return isSilence(out) || isWord(out) ? silence : out;
76
76
  } catch {
77
77
  return silence;
78
78
  }
@@ -151,7 +151,7 @@ class Self implements BeingLike {
151
151
  // next, seals to the far ward, opens the reply with the ephemeral secret,
152
152
  // and rotates to the announced key once the far door has answered under
153
153
  // the current one.
154
- async #send(bind: Bind, keys: StandingKeys, announce: boolean, method: string | undefined, args: JsonObject, wanted?: Wanted): Promise<ReplyPayload | typeof silence | ReturnType<typeof unreached>> {
154
+ async #send(bind: Bind, keys: StandingKeys, announce: boolean, method: string | undefined, args: JsonObject, wanted?: Wanted): Promise<ReplyPayload | typeof silence | Word> {
155
155
  // What she asked for, held to what this ward allows. Asking for nothing is
156
156
  // the default, and asking for more than the ceiling is the ceiling: budget
157
157
  // is granted by a ward, never minted by a being.
@@ -185,22 +185,22 @@ class Self implements BeingLike {
185
185
  // relation that comes back round holds a lane the answer needs, and only a
186
186
  // bound on the wait can break that. What comes back late is not read.
187
187
  //
188
- // A wait that ran out is silence, never unreached. Unreached promises
188
+ // A wait that ran out is `late`, never unreached. Unreached promises
189
189
  // nothing was delivered and is safe to retry; a bound that expired knows
190
190
  // no such thing, because the far door may have heard and be working still.
191
191
  //
192
192
  // A harbor answers bytes or nothing. One that throws instead has answered
193
193
  // nothing in a louder voice, and is read as nothing: no door was reached.
194
- const carried = (keys.ward === this.pk ? this.door(new Uint8Array(bytes)) : this.g.carry(keys.ward, bytes)).then(
194
+ const carried = (keys.ward === this.pk ? this.door(new Uint8Array(bytes)).then((r) => r.bytes) : this.g.carry(keys.ward, bytes)).then(
195
195
  (b) => b,
196
196
  () => undefined,
197
197
  );
198
198
  const out = await within(budget.time, carried);
199
- if (out === LATE) return silence;
199
+ if (out === LATE) return word('late');
200
200
  if (out === undefined) return unreached(); // nothing came back. no door was reached.
201
201
  const reply = await openReply(out, ephemeral, wardSignPk(keys.ward));
202
- if (!reply) return silence; // did not open, or not from that ward
203
- if (!('silence' in reply) && keys.next !== null) {
202
+ if (!reply) return silence; // did not open, or not from that ward: not Quo's bytes
203
+ if ('object' in reply && keys.next !== null) {
204
204
  keys.current = keys.next; // the far door holds `next` as announced. move to it.
205
205
  keys.next = null;
206
206
  }
@@ -2,7 +2,7 @@
2
2
  "corpus": "@quo-systems/quo",
3
3
  "encoding": "hex",
4
4
  "area": "framing",
5
- "note": "The formats a second kit must reproduce byte for byte: the ward pk, the digest, the signed ask body, the sealed ask, the sealed reply, the invitation and the knock. Every random input is fixed here, so every output is fixed. The ask body is JSON.stringify of the payload, which is not canonical: these vectors pin the key order sealAsk writes, to, by, next, seq, time, method, args; hops is in no vector, because nothing sets it. A kit that emits another order still interoperates, because a door verifies the bytes it received, but it will not reproduce these vectors. An invitation is JSON too, ward, heir, secret, and a heir secret is the seed of a being key. A knock is an ask whose by is the heir pk, signed with the heir secret, whose next is the knocker's own key, and whose to is the heir; its answer is an ordinary reply, and the heir is spent by it. The ward here is the one from the ward pk vector, and the heir secret is the first entropy it draws, so the invitation it mints and the knock its door binds are these bytes.",
5
+ "note": "The formats a second kit must reproduce byte for byte: the ward pk, the digest, the signed ask body, the sealed ask, the sealed reply, the invitation and the knock. Every random input is fixed here, so every output is fixed. The ask body is JSON.stringify of the payload, which is not canonical: these vectors pin the key order sealAsk writes, to, by, next, seq, time, method, args; hops is in no vector, because nothing sets it. A kit that emits another order still interoperates, because a door verifies the bytes it received, but it will not reproduce these vectors. An invitation is JSON too, ward, heir, secret, and a heir secret is the seed of a being key. A knock is an ask whose by is the heir pk, signed with the heir secret, whose next is the knocker's own key, and whose to is the heir; its answer is an ordinary reply, and the heir is spent by it. A reply is one of three shapes: an object with its digest, silence, or the ward's word to a key the door has bound, { quo: word }, where word is one of removed, absent, unannounced, repeated, threw. The ward here is the one from the ward pk vector, and the heir secret is the first entropy it draws, so the invitation it mints and the knock its door binds are these bytes.",
6
6
  "vectors": [
7
7
  {
8
8
  "name": "a ward pk from a fixed seed",
@@ -53,6 +53,12 @@
53
53
  "replySeed": "4444444444444444444444444444444444444444444444444444444444444444",
54
54
  "bytes": "ff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b76ec93e97a657e002d9135a7394f3ec33bcd723c9d8947909f4cc659d8dfeed743f5b64bfa87b4ac79074219c1032037de444400b509f31559331b7995f9dfbadbe7fb0f6d46db2257db70f248b7739b3baa2ca79b07b66b29ea680faf79340f2309163c9e3c65fcdb33297c419d1cd8517647"
55
55
  },
56
+ {
57
+ "name": "a sealed word reply: the ward's word to a key it has bound",
58
+ "reply": "{\"quo\":\"removed\"}",
59
+ "replySeed": "4444444444444444444444444444444444444444444444444444444444444444",
60
+ "bytes": "ff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b76ec8dfe7f2227567dce23ea274378db64eb74efed5c91135d62d89db62db7d8ffbd54cf3e82e3272d0b26d59130020755b442a16340798d3eec60bc3a3875020da9276d5e72d712ac3dad7ef287d3c153a0588bf4951b9d3540b1142bd54875a3"
61
+ },
56
62
  {
57
63
  "name": "an invitation for a heir: the ward pk, the heir pk, the heir secret, and nothing else",
58
64
  "wardSeed": "1111111111111111111111111111111111111111111111111111111111111111",