nervur 0.19.2 → 0.20.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 (205) hide show
  1. package/README.md +66 -101
  2. package/dist/being/being.d.ts +7 -15
  3. package/dist/being/being.js +16 -67
  4. package/dist/being/digest.d.ts +1 -1
  5. package/dist/being/digest.js +7 -27
  6. package/dist/being/faculty.d.ts +9 -0
  7. package/dist/being/faculty.js +68 -0
  8. package/dist/being/index.d.ts +4 -5
  9. package/dist/being/index.js +5 -5
  10. package/dist/being/types.d.ts +35 -62
  11. package/dist/being/types.js +3 -57
  12. package/dist/being/words.d.ts +13 -0
  13. package/dist/being/words.js +14 -0
  14. package/dist/contract/index.d.ts +26 -0
  15. package/dist/contract/index.js +19 -0
  16. package/dist/crypto/aes.d.ts +3 -0
  17. package/dist/crypto/aes.js +24 -0
  18. package/dist/crypto/bytes.d.ts +6 -0
  19. package/dist/crypto/bytes.js +33 -0
  20. package/dist/crypto/ed25519.d.ts +3 -0
  21. package/dist/crypto/ed25519.js +85 -0
  22. package/dist/crypto/hash.d.ts +2 -0
  23. package/dist/crypto/hash.js +11 -0
  24. package/dist/crypto/index.d.ts +7 -0
  25. package/dist/crypto/index.js +10 -0
  26. package/dist/crypto/json.d.ts +8 -0
  27. package/dist/crypto/json.js +250 -0
  28. package/dist/crypto/mlkem.d.ts +12 -0
  29. package/dist/crypto/mlkem.js +36 -0
  30. package/dist/crypto/subtle.d.ts +10 -0
  31. package/dist/crypto/subtle.js +29 -0
  32. package/dist/crypto/x25519.d.ts +2 -0
  33. package/dist/crypto/x25519.js +21 -0
  34. package/dist/folder/index.d.ts +13 -0
  35. package/dist/folder/index.js +125 -0
  36. package/dist/harbor/dock.d.ts +19 -0
  37. package/dist/harbor/dock.js +92 -0
  38. package/dist/harbor/harbor.d.ts +22 -0
  39. package/dist/harbor/harbor.js +84 -0
  40. package/dist/harbor/index.d.ts +4 -10
  41. package/dist/harbor/index.js +5 -18
  42. package/dist/harbor/registry.d.ts +7 -0
  43. package/dist/harbor/registry.js +13 -0
  44. package/dist/harbor/terrain.d.ts +8 -0
  45. package/dist/harbor/terrain.js +2 -0
  46. package/dist/index.d.ts +7 -0
  47. package/dist/index.js +14 -0
  48. package/dist/pointer/bodies.d.ts +20 -0
  49. package/dist/pointer/bodies.js +45 -0
  50. package/dist/pointer/index.d.ts +2 -0
  51. package/dist/pointer/index.js +4 -0
  52. package/dist/pointer/world.d.ts +34 -0
  53. package/dist/pointer/world.js +76 -0
  54. package/dist/quo/door.d.ts +34 -0
  55. package/dist/quo/door.js +172 -0
  56. package/dist/quo/index.d.ts +8 -0
  57. package/dist/quo/index.js +11 -0
  58. package/dist/quo/invitation.d.ts +7 -0
  59. package/dist/quo/invitation.js +15 -0
  60. package/dist/quo/keys.d.ts +40 -0
  61. package/dist/quo/keys.js +79 -0
  62. package/dist/quo/payload.d.ts +15 -0
  63. package/dist/quo/payload.js +55 -0
  64. package/dist/quo/relations.d.ts +40 -0
  65. package/dist/quo/relations.js +33 -0
  66. package/dist/quo/reply.d.ts +13 -0
  67. package/dist/quo/reply.js +35 -0
  68. package/dist/quo/seal.d.ts +42 -0
  69. package/dist/quo/seal.js +78 -0
  70. package/dist/quo/standing.d.ts +39 -0
  71. package/dist/quo/standing.js +90 -0
  72. package/dist/ward/allowance.d.ts +13 -9
  73. package/dist/ward/allowance.js +28 -67
  74. package/dist/ward/cells.d.ts +6 -6
  75. package/dist/ward/cells.js +67 -179
  76. package/dist/ward/index.d.ts +5 -10
  77. package/dist/ward/index.js +6 -13
  78. package/dist/ward/partition.d.ts +44 -60
  79. package/dist/ward/partition.js +56 -285
  80. package/dist/ward/stance.d.ts +29 -20
  81. package/dist/ward/stance.js +173 -406
  82. package/dist/ward/ward-being.d.ts +33 -0
  83. package/dist/ward/ward-being.js +76 -0
  84. package/dist/ward/ward.d.ts +43 -11
  85. package/dist/ward/ward.js +215 -363
  86. package/package.json +19 -33
  87. package/src/being/being.ts +31 -78
  88. package/src/being/digest.ts +7 -35
  89. package/src/being/faculty.ts +66 -0
  90. package/src/being/index.ts +5 -6
  91. package/src/being/types.ts +47 -145
  92. package/src/being/words.ts +31 -0
  93. package/src/contract/index.ts +44 -0
  94. package/src/crypto/aes.ts +26 -0
  95. package/src/crypto/bytes.ts +37 -0
  96. package/src/crypto/ed25519.ts +84 -0
  97. package/src/crypto/hash.ts +14 -0
  98. package/src/crypto/index.ts +10 -0
  99. package/src/crypto/json.ts +241 -0
  100. package/src/crypto/mlkem.ts +38 -0
  101. package/src/crypto/subtle.ts +33 -0
  102. package/src/crypto/x25519.ts +21 -0
  103. package/src/folder/index.ts +134 -0
  104. package/src/harbor/dock.ts +101 -0
  105. package/src/harbor/harbor.ts +105 -0
  106. package/src/harbor/index.ts +5 -19
  107. package/src/harbor/registry.ts +20 -0
  108. package/src/harbor/terrain.ts +13 -0
  109. package/src/index.ts +20 -0
  110. package/src/pointer/bodies.ts +47 -0
  111. package/src/pointer/index.ts +4 -0
  112. package/src/pointer/world.ts +91 -0
  113. package/src/quo/door.ts +178 -0
  114. package/src/quo/index.ts +11 -0
  115. package/src/quo/invitation.ts +15 -0
  116. package/src/quo/keys.ts +91 -0
  117. package/src/quo/payload.ts +61 -0
  118. package/src/quo/relations.ts +62 -0
  119. package/src/quo/reply.ts +38 -0
  120. package/src/quo/seal.ts +101 -0
  121. package/src/quo/standing.ts +111 -0
  122. package/src/stand/main.ts +19 -0
  123. package/src/stand/stand.ts +178 -0
  124. package/src/ward/allowance.ts +37 -75
  125. package/src/ward/cells.ts +63 -176
  126. package/src/ward/index.ts +6 -17
  127. package/src/ward/partition.ts +86 -326
  128. package/src/ward/stance.ts +185 -420
  129. package/src/ward/ward-being.ts +97 -0
  130. package/src/ward/ward.ts +229 -363
  131. package/dist/being/lent.d.ts +0 -32
  132. package/dist/being/lent.js +0 -72
  133. package/dist/being/silence.d.ts +0 -12
  134. package/dist/being/silence.js +0 -41
  135. package/dist/conformance/assert.d.ts +0 -11
  136. package/dist/conformance/assert.js +0 -106
  137. package/dist/conformance/beings.d.ts +0 -199
  138. package/dist/conformance/beings.js +0 -188
  139. package/dist/conformance/estate.d.ts +0 -5
  140. package/dist/conformance/estate.js +0 -388
  141. package/dist/conformance/index.d.ts +0 -80
  142. package/dist/conformance/index.js +0 -819
  143. package/dist/conformance/reach.d.ts +0 -10
  144. package/dist/conformance/reach.js +0 -72
  145. package/dist/conformance/store.d.ts +0 -5
  146. package/dist/conformance/store.js +0 -113
  147. package/dist/harbor/box.d.ts +0 -121
  148. package/dist/harbor/box.js +0 -121
  149. package/dist/harbor/core.d.ts +0 -55
  150. package/dist/harbor/core.js +0 -662
  151. package/dist/harbor/dial.d.ts +0 -9
  152. package/dist/harbor/dial.js +0 -81
  153. package/dist/harbor/memory.d.ts +0 -29
  154. package/dist/harbor/memory.js +0 -104
  155. package/dist/harbor/reach.d.ts +0 -36
  156. package/dist/harbor/reach.js +0 -199
  157. package/dist/harbor/store.d.ts +0 -35
  158. package/dist/harbor/store.js +0 -62
  159. package/dist/vector/cases.d.ts +0 -42
  160. package/dist/vector/cases.js +0 -223
  161. package/dist/vector/index.d.ts +0 -6
  162. package/dist/vector/index.js +0 -8
  163. package/dist/vector/stand.d.ts +0 -9
  164. package/dist/vector/stand.js +0 -77
  165. package/dist/vector/world.d.ts +0 -144
  166. package/dist/vector/world.js +0 -209
  167. package/dist/ward/arithmetic.d.ts +0 -31
  168. package/dist/ward/arithmetic.js +0 -249
  169. package/dist/ward/door.d.ts +0 -18
  170. package/dist/ward/door.js +0 -186
  171. package/dist/ward/ground.d.ts +0 -29
  172. package/dist/ward/ground.js +0 -56
  173. package/dist/ward/heirs.d.ts +0 -13
  174. package/dist/ward/heirs.js +0 -117
  175. package/dist/ward/json.d.ts +0 -2
  176. package/dist/ward/json.js +0 -163
  177. package/dist/ward/owner.d.ts +0 -13
  178. package/dist/ward/owner.js +0 -220
  179. package/dist/ward/seal.d.ts +0 -52
  180. package/dist/ward/seal.js +0 -150
  181. package/src/being/lent.ts +0 -72
  182. package/src/being/silence.ts +0 -46
  183. package/src/conformance/assert.ts +0 -100
  184. package/src/conformance/beings.ts +0 -188
  185. package/src/conformance/estate.ts +0 -412
  186. package/src/conformance/index.ts +0 -965
  187. package/src/conformance/reach.ts +0 -83
  188. package/src/conformance/store.ts +0 -125
  189. package/src/harbor/box.ts +0 -131
  190. package/src/harbor/core.ts +0 -699
  191. package/src/harbor/dial.ts +0 -112
  192. package/src/harbor/memory.ts +0 -123
  193. package/src/harbor/reach.ts +0 -221
  194. package/src/harbor/store.ts +0 -91
  195. package/src/vector/cases.ts +0 -257
  196. package/src/vector/index.ts +0 -11
  197. package/src/vector/stand.ts +0 -76
  198. package/src/vector/world.ts +0 -232
  199. package/src/ward/arithmetic.ts +0 -251
  200. package/src/ward/door.ts +0 -186
  201. package/src/ward/ground.ts +0 -142
  202. package/src/ward/heirs.ts +0 -116
  203. package/src/ward/json.ts +0 -144
  204. package/src/ward/owner.ts +0 -214
  205. package/src/ward/seal.ts +0 -178
@@ -1,117 +0,0 @@
1
- import { GONE } from './partition.js';
2
- // How wide the span is, is the spec's and never a ward's. Wider is more
3
- // forgiving of a rough road, and that is exactly why it is not a ward's to
4
- // choose: a peer tells two doors apart by being refused at one and answered
5
- // at the other, which is one relation dying over one number.
6
- const SPAN = 64;
7
- export class Heirs {
8
- #p;
9
- #wrote;
10
- constructor(p, wrote = () => { }) {
11
- this.#p = p;
12
- this.#wrote = wrote;
13
- }
14
- open(heir, being, id) {
15
- this.#p.heirs[heir] = { being, id, current: heir, announced: null, fresh: true, mark: 0, spent: [] };
16
- this.#wrote();
17
- }
18
- // The id was removed. The heir is forgotten, and its last keys are kept
19
- // apart, bounded, so that whoever still holds them hears `removed` at the
20
- // door, once they have signed as themselves, and nobody else hears a thing.
21
- close(heir) {
22
- const h = this.#p.heirs[heir];
23
- if (!h)
24
- return;
25
- delete this.#p.heirs[heir];
26
- this.#p.gone[heir] = { current: h.current, announced: h.announced };
27
- const keys = Object.keys(this.#p.gone);
28
- for (const old of keys.slice(0, Math.max(0, keys.length - GONE)))
29
- delete this.#p.gone[old];
30
- this.#wrote();
31
- }
32
- // May `by` speak for a relation she removed? True only for the keys the
33
- // door held when the id went, which nobody but their holder has.
34
- gone(heir, by) {
35
- const g = this.#p.gone[heir];
36
- return !!g && (by === g.current || by === g.announced);
37
- }
38
- // May `by` speak for this heir? Returns the record if so, null if not.
39
- // Does not write: the caller verifies the signature first, then settles.
40
- admits(heir, by) {
41
- const h = this.#p.heirs[heir];
42
- if (!h)
43
- return null;
44
- if (h.fresh)
45
- return by === h.current ? h : null;
46
- return by === h.current || by === h.announced ? h : null;
47
- }
48
- // Once only. A number above the mark is honoured and moves it; a number
49
- // inside the span is honoured once and never again; a number at or below
50
- // the span is refused, because a door that remembered every number ever
51
- // seen would be a door with unbounded memory. Counting starts where
52
- // strangers must agree: the first legal number is one.
53
- //
54
- // The number rides inside the signed body, so bytes caught on the road
55
- // carry the number they were sent under and are refused as themselves. A
56
- // caller who means to ask again asks again, under the next number, and is
57
- // heard: retry and fire-and-forget stay hers to build. Only the accident
58
- // and the interception are refused.
59
- //
60
- // Writes, so the caller settles it only once the signature has checked out.
61
- // The same bytes twice are one honoured call and one silence.
62
- spend(h, seq) {
63
- if (!Number.isSafeInteger(seq) || seq < 1)
64
- return false;
65
- if (seq > h.mark) {
66
- if (h.mark > 0)
67
- h.spent.push(h.mark);
68
- h.mark = seq;
69
- h.spent = h.spent.filter((past) => past > seq - SPAN);
70
- return true;
71
- }
72
- if (seq === h.mark || seq <= h.mark - SPAN || h.spent.includes(seq))
73
- return false;
74
- h.spent.push(seq);
75
- return true;
76
- }
77
- // The signature checked out. Spend the number and settle the keys, or do
78
- // neither: a call that binds nothing must not burn a number on its way to
79
- // being refused, or a stranger who cannot be heard would still leave a mark
80
- // behind her. Every write below this line is one that is going to hold.
81
- // This is where a knock is judged and where `unannounced` is said. settle
82
- // below is the writing alone, and it judges nothing: one owner for the
83
- // question, one for the answer.
84
- honour(h, by, next, seq) {
85
- if (h.fresh && (next === null || next === h.current))
86
- return 'unannounced'; // a knock without a key of her own binds nothing, and the heir is not a key of her own
87
- if (!this.spend(h, seq))
88
- return 'repeated';
89
- this.settle(h, by, next);
90
- this.#wrote(); // the number and the keys moved together
91
- return true;
92
- }
93
- // The signature checked out and honour has judged. Settle the keys: a fresh
94
- // heir rotates at once to what it announced, which honour has already held
95
- // to being something other than itself, so that the heir dies as it speaks;
96
- // a current key replaces its announcement; an announced key becomes current.
97
- settle(h, by, next) {
98
- if (h.fresh) {
99
- if (next === null)
100
- return; // honour judged this. the compiler has not read it, so the narrowing stays.
101
- h.current = next;
102
- h.announced = null;
103
- h.fresh = false;
104
- return;
105
- }
106
- if (by === h.announced)
107
- h.current = by;
108
- // A send that announced nothing leaves the spare standing. This kit
109
- // announces on every send but a public one, which holds no heir and
110
- // reaches none of this; a kit that skips one is a kit whose own next is
111
- // still the key it announced last time, and forgetting it here would meet
112
- // that key with silence and kill a healthy relation. Nothing is dropped
113
- // that was vouched for until something replaces it.
114
- if (next !== null)
115
- h.announced = next;
116
- }
117
- }
@@ -1,2 +0,0 @@
1
- export declare function numberFault(literal: string): string | null;
2
- export declare function readJson(input: Uint8Array | string): unknown;
package/dist/ward/json.js DELETED
@@ -1,163 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // The one reader of JSON that crossed an edge. A runtime's own parser is
3
- // where two kits stop agreeing: it keeps the last of two keys of one name,
4
- // rounds a whole number no double holds, reads a number past the doubles as
5
- // infinity, and mends bytes that are not UTF-8 into a replacement character.
6
- // Each of those is text two kits read two ways, so each is refused here, where
7
- // it is read, with a throw the caller already turns into what bytes that said
8
- // nothing are.
9
- //
10
- // What a value is beyond its text, a lone surrogate, a key named `__proto__`,
11
- // a nesting past the bound, is the value rule's, and `cellFault` in cells.ts
12
- // is its one reader. This file holds the rules that live only in the text.
13
- const utf8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
14
- const fail = (at, why) => {
15
- throw new Error(`JSON at ${at}: ${why}`);
16
- };
17
- // A number text, held to the Values chapter. A text naming a whole number
18
- // stands only if that number is exactly a double, or if the text is how
19
- // ECMAScript writes the double it reads as. A text past the largest double,
20
- // or naming a value other than zero that rounds to zero, is refused, and so
21
- // is minus zero in any spelling.
22
- export function numberFault(literal) {
23
- const m = /^(-?)(0|[1-9]\d*)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(literal);
24
- if (!m)
25
- return 'not a number';
26
- const [, sign, whole, frac = '', exp = '0'] = m;
27
- const v = Number(literal);
28
- if (!Number.isFinite(v))
29
- return 'past the largest double';
30
- let digits = (whole + frac).replace(/^0+/, '');
31
- if (v === 0) {
32
- if (digits !== '')
33
- return 'a value other than zero that rounds to zero';
34
- return sign ? 'minus zero' : null;
35
- }
36
- // The exact value is digits times ten to e. A finite double bounds e from
37
- // above, so the power below is never larger than the double's own width.
38
- let e = BigInt(exp) - BigInt(frac.length);
39
- const trimmed = digits.replace(/0+$/, '');
40
- e += BigInt(digits.length - trimmed.length);
41
- digits = trimmed;
42
- if (e < 0n)
43
- return null; // a fraction: the double nearest it, as every reader takes it
44
- const exact = BigInt(digits) * 10n ** e;
45
- if (exact === (v < 0 ? -BigInt(v) : BigInt(v)))
46
- return null;
47
- return literal === String(v) ? null : 'a whole number that is not exactly a double';
48
- }
49
- // Bytes to a value, or a throw.
50
- export function readJson(input) {
51
- const s = typeof input === 'string' ? input : utf8.decode(input);
52
- let at = 0;
53
- const space = () => {
54
- for (;;) {
55
- const c = s.charCodeAt(at);
56
- if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d)
57
- at += 1;
58
- else
59
- return;
60
- }
61
- };
62
- const string = () => {
63
- const start = at;
64
- at += 1;
65
- for (;;) {
66
- const c = s.charCodeAt(at);
67
- if (Number.isNaN(c))
68
- fail(start, 'an unterminated string');
69
- if (c === 0x22)
70
- break;
71
- if (c < 0x20)
72
- fail(at, 'a control character in a string');
73
- at += c === 0x5c ? 2 : 1;
74
- }
75
- at += 1;
76
- // The escapes are JSON's own, and the runtime reads them the one way.
77
- return JSON.parse(s.slice(start, at));
78
- };
79
- const value = () => {
80
- space();
81
- const c = s[at];
82
- if (c === '{') {
83
- at += 1;
84
- const out = {};
85
- const keys = new Set();
86
- space();
87
- if (s[at] === '}') {
88
- at += 1;
89
- return out;
90
- }
91
- for (;;) {
92
- space();
93
- if (s[at] !== '"')
94
- fail(at, 'a key that is not a string');
95
- const where = at;
96
- const k = string();
97
- if (keys.has(k))
98
- fail(where, `two keys named ${JSON.stringify(k)}`);
99
- keys.add(k);
100
- space();
101
- if (s[at] !== ':')
102
- fail(at, 'a key with no colon');
103
- at += 1;
104
- // Defined, never assigned: a key named `__proto__` is an own key here
105
- // as it is in JSON.parse, and the value rule refuses it after.
106
- Object.defineProperty(out, k, { value: value(), enumerable: true, writable: true, configurable: true });
107
- space();
108
- if (s[at] === ',')
109
- at += 1;
110
- else if (s[at] === '}') {
111
- at += 1;
112
- return out;
113
- }
114
- else
115
- fail(at, 'an object not closed');
116
- }
117
- }
118
- if (c === '[') {
119
- at += 1;
120
- const out = [];
121
- space();
122
- if (s[at] === ']') {
123
- at += 1;
124
- return out;
125
- }
126
- for (;;) {
127
- out.push(value());
128
- space();
129
- if (s[at] === ',')
130
- at += 1;
131
- else if (s[at] === ']') {
132
- at += 1;
133
- return out;
134
- }
135
- else
136
- fail(at, 'a list not closed');
137
- }
138
- }
139
- if (c === '"')
140
- return string();
141
- for (const [word, v] of [['true', true], ['false', false], ['null', null]]) {
142
- if (s.startsWith(word, at)) {
143
- at += word.length;
144
- return v;
145
- }
146
- }
147
- const m = /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
148
- m.lastIndex = at;
149
- const hit = m.exec(s);
150
- if (!hit)
151
- return fail(at, 'no value');
152
- const why = numberFault(hit[0]);
153
- if (why !== null)
154
- fail(at, `${hit[0]} is ${why}`);
155
- at += hit[0].length;
156
- return Number(hit[0]);
157
- };
158
- const out = value();
159
- space();
160
- if (at !== s.length)
161
- fail(at, 'text after the value');
162
- return out;
163
- }
@@ -1,13 +0,0 @@
1
- import type { Ask, Asker, Json, JsonObject } from '../being/types.ts';
2
- import type { Resident } from './ward.ts';
3
- export type OwnerSide = {
4
- pk: string;
5
- doors: Map<string, Resident>;
6
- absent(): Record<string, string | null>;
7
- publicKey(): string | null;
8
- instantiate(className: string, key: string): Resident | 'threw' | null;
9
- unboot(key: string): string[] | null;
10
- setPublic(key: string | null): void;
11
- };
12
- export declare const OWNER_ASKS: Ask[];
13
- export declare function ownerAnswer(w: OwnerSide, asker: Asker, method: string | undefined, args: JsonObject): Promise<Json>;
@@ -1,220 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // The owner's asks. The root owner holds the ask pointer; every other owner
3
- // is an occupant of the ward itself, invited by the root. The ward is a
4
- // being to her owner: the empty ask is her describe, and it carries her asks
5
- // with a description and an input each, so a side renders them as it
6
- // renders any being's. Seven asks: boot, public, invite, knock, remove and
7
- // unboot, which is boot's inverse and takes her relations with her, and ask,
8
- // which reaches into a being and asks her. The knock is a being's knock made
9
- // for her, and the standing is written into her cells under the id the owner
10
- // gave; remove takes a relation out of her by id, the mirror of it.
11
- import { isSilence, isWord, wordOf } from '../being/silence.js';
12
- import { OWNER } from '../being/types.js';
13
- import { put } from './partition.js';
14
- import { seen, answered } from './door.js';
15
- import { within, allow, DEFAULT, LATE } from './allowance.js';
16
- const str = { type: 'string' };
17
- export const OWNER_ASKS = [
18
- { name: 'boot', description: 'boot a being by class name, under a key the owner chooses', input: { type: 'object', properties: { key: str, class: str }, required: ['key', 'class'] } },
19
- { name: 'public', description: 'mark a booted being as the one public being of the ward, reached by anyone at the bare pk; null takes the mark off and leaves her booted', input: { type: 'object', properties: { key: str }, required: ['key'] } },
20
- { name: 'invite', description: 'mint an invitation on a being of the ward, under the id she will know the occupant by, with the notes she will read on it; on the ward pk it mints an owner, and only the root may', input: { type: 'object', properties: { being: str, id: str, notes: { type: 'object' } }, required: ['being', 'id'] } },
21
- {
22
- name: 'knock',
23
- description: 'knock for a being of the ward with an invitation, and take the standing under id if answered; being is a key booted, or { boot: class, key } to boot her first',
24
- input: { type: 'object', properties: { being: { description: 'a key, or { boot, key }' }, id: str, invitation: { type: 'object' }, method: str, args: { type: 'object' }, wanted: { type: 'object', properties: { time: { type: 'number' } } } }, required: ['being', 'id', 'invitation'] },
25
- },
26
- { name: 'remove', description: 'take a relation out of a being of the ward by id, occupant or standing; on the ward pk it unseats an owner, and only the root may', input: { type: 'object', properties: { being: str, id: str }, required: ['being', 'id'] } },
27
- { name: 'unboot', description: 'take a being out of the ward, with every relation she holds; her occupants hear removed, and the ward itself is refused', input: { type: 'object', properties: { being: str }, required: ['being'] } },
28
- {
29
- name: 'ask',
30
- description: 'ask a being of the ward, as the owner; with no being it is the public being asked as nobody, which is what a stranger would hear',
31
- input: { type: 'object', properties: { being: str, method: str, args: { type: 'object' }, wanted: { type: 'object', properties: { time: { type: 'number' } } } } },
32
- },
33
- ];
34
- export async function ownerAnswer(w, asker, method, args) {
35
- // Every name the owner gives is a string, or it is nothing. Coerced instead
36
- // of checked, an object passed where a name belongs becomes the string
37
- // "[object Object]", and two owners who each got the shape wrong would be
38
- // handed one being. Not `word`: a word in this tree is what a ward says when
39
- // no object came back, and this file imports those.
40
- const named = (v) => (typeof v === 'string' ? v : null);
41
- if (method === undefined)
42
- return describe(w);
43
- if (method === 'boot') {
44
- const key = named(args.key), className = named(args.class);
45
- if (key === null || className === null)
46
- return { error: 'no such class, or key taken' };
47
- const door = w.instantiate(className, key);
48
- if (door === 'threw')
49
- return { error: 'threw at birth' };
50
- return door ? { booted: door.key } : { error: 'no such class, or key taken' };
51
- }
52
- if (method === 'public') {
53
- // A ward may have one public being, and no more. Marking a second would
54
- // leave the first holding every relation she had, reachable by nobody at
55
- // the bare pk, and told by nobody that she had been replaced. One absent
56
- // this run is reachable by nobody already, so the mark may move off her.
57
- // Null takes the mark off: the being stays and keeps every relation she
58
- // holds, and the ward answers arrivals for no heir with silence, as one
59
- // that never had a public being does. Retiring her is not destroying her.
60
- if (args.key === null) {
61
- w.setPublic(null);
62
- return { public: null };
63
- }
64
- const key = named(args.key);
65
- if (key === null || key === w.pk || !w.doors.has(key))
66
- return { error: 'no such being' };
67
- const standing = w.publicKey();
68
- if (standing !== null && standing !== key && w.doors.has(standing))
69
- return { error: 'a ward has one public being' };
70
- w.setPublic(key);
71
- return { public: key };
72
- }
73
- if (method === 'invite') {
74
- // On the ward itself, only the root may invite: an occupant of the ward
75
- // is an owner, and ownership is minted by the ask pointer alone. An owner
76
- // at the door asking to invite on the ward is refused, so that a carried
77
- // key can pilot but never hand piloting on.
78
- const being = named(args.being), id = named(args.id);
79
- if (being === null || id === null)
80
- return { error: 'no such being' };
81
- if (being === w.pk && asker.id !== OWNER)
82
- return { error: 'no such being' };
83
- // The notes are the terms the owner mints under, and the being reads them
84
- // on her gate. Placing an occupant is the owner's, and saying what it may
85
- // do is part of placing it; the work behind the gate is still hers alone.
86
- const notes = args.notes;
87
- const door = w.doors.get(being);
88
- if (!door)
89
- return { error: 'no such being' };
90
- return await door.stance.occupants.invite(id, notes !== null && typeof notes === 'object' && !Array.isArray(notes) ? (notes) : undefined);
91
- }
92
- if (method === 'knock') {
93
- const b = args.being;
94
- let door = typeof b === 'string' ? w.doors.get(b) : undefined;
95
- if (!door && b && typeof b === 'object') {
96
- // new or existing: the owner names a being of theirs, and a key already
97
- // booted is a being of theirs, not a class that failed to instantiate.
98
- const key = named(b.key), className = named(b.boot);
99
- if (key === null || className === null)
100
- return { error: 'no such being' };
101
- const made = w.instantiate(className, key);
102
- if (made === 'threw')
103
- return { error: 'threw at birth' };
104
- // Made or already there, the door is the one under the key she named:
105
- // instantiate boots under that key or refuses, and never another.
106
- door = w.doors.get(key);
107
- }
108
- const id = named(args.id);
109
- // Never for the ward itself, for the reason invite refuses it: the ward
110
- // is a being to nobody outside, and holds no relation of its own. A
111
- // standing written into its cells would be the owner's reach dressed as
112
- // a being's, and no ask of the ward's could ever use it.
113
- if (!door || id === null || door.key === w.pk)
114
- return { error: 'no such being' };
115
- const inv = args.invitation;
116
- // The owner is a caller like any other and may say what this knock may
117
- // spend. Saying nothing is the ward's default, as it is for a being.
118
- const out = await door.stance.standings.knock(inv, args.method, args.args ?? {}, args.wanted);
119
- if (isSilence(out))
120
- return { error: 'silence' };
121
- if (isWord(out))
122
- return { error: wordOf(out) }; // the owner hears objects: the word is the error's name
123
- return { taken: await door.stance.standings.take(id, inv), answer: out };
124
- }
125
- if (method === 'remove') {
126
- // The mirror of knock: a relation out of a being, by id, occupant or
127
- // standing, since the two share one namespace. On the ward itself the
128
- // id is an owner, and only the root may unseat one, for the reason only
129
- // the root may invite one: ownership moves by the root alone.
130
- const being = named(args.being), id = named(args.id);
131
- if (being === null || id === null)
132
- return { error: 'no such being' };
133
- if (being === w.pk && asker.id !== OWNER)
134
- return { error: 'no such being' };
135
- const door = w.doors.get(being);
136
- if (!door)
137
- return { error: 'no such being' };
138
- if (Object.hasOwn(door.cells.occupants, id))
139
- door.stance.occupants.remove(id);
140
- else if (Object.hasOwn(door.cells.standings, id))
141
- door.stance.standings.remove(id);
142
- else
143
- return { error: 'nothing to remove' };
144
- return { removed: id };
145
- }
146
- if (method === 'unboot') {
147
- // The inverse of boot, and the only way a being leaves a ward. Her
148
- // relations go with her, so an occupant of hers hears `removed` from the
149
- // keys the door kept, exactly as it would had the owner removed each id
150
- // by hand; the owner hears which ids those were. The ward itself is
151
- // refused, for the reason knock is: it is a being to nobody outside, and
152
- // unbooting it would be a ward deleting itself from inside its own map,
153
- // leaving its owners bound to a door that is gone.
154
- const being = named(args.being);
155
- if (being === null || being === w.pk)
156
- return { error: 'no such being' };
157
- const removed = w.unboot(being);
158
- return removed === null ? { error: 'no such being' } : { unbooted: being, removed };
159
- }
160
- if (method === 'ask') {
161
- // The owner's other power: to reach into a being and ask her. It is
162
- // strictly less than unboot, which takes her out with every relation she
163
- // holds, and it is the third asker of the ward-to-being edge finally
164
- // filled in by the ward rather than minted by whoever holds the pointer.
165
- // Bounded, judged and named here, so no side outside the ward ever holds
166
- // a being's answer and writes the door's discipline again beside it.
167
- const being = args.being === undefined ? null : named(args.being);
168
- if (args.being !== undefined && being === null)
169
- return { error: 'no such being' };
170
- // A method is a name or it is the empty ask. Coerced instead of checked,
171
- // a shape passed where a name belongs becomes a string she never declared,
172
- // and she would answer `unknown ask` to something nobody asked.
173
- if (args.method !== undefined && typeof args.method !== 'string')
174
- return { error: 'an ask is named by a word' };
175
- // No being named is the in-process twin of bytes for nobody: the public
176
- // being, asked as nobody, unbound, so a throw of hers is silence. Her
177
- // insides are not a stranger's to read, and a device serving her to
178
- // strangers must hear what a stranger hears and not one word more.
179
- const key = being ?? w.publicKey();
180
- const door = key === null ? undefined : w.doors.get(key);
181
- if (!door || door.key === w.pk)
182
- return { error: 'no such being' };
183
- const bound = being !== null;
184
- // The owner is a caller like any other: wanted says what this ask may
185
- // spend, and saying nothing is the ward's default. A wait that ran out is
186
- // late, the same word a being would hear, said as the object an owner hears.
187
- const out = await within(allow(args.wanted).time, answered(door, bound ? { id: OWNER } : {}, args.method, args.args ?? {}, bound));
188
- if (out === LATE)
189
- return { error: 'late' };
190
- if ('quo' in out)
191
- return { error: out.quo };
192
- if (!('object' in out))
193
- return { error: 'silence' };
194
- return out.object;
195
- }
196
- return { error: 'unknown ask' };
197
- }
198
- // The ward's describe for its owner: its beings, their classes, a digest each,
199
- // as she would describe herself to the owner. A being absent this run is
200
- // listed too, with a null digest and absent true, so the owner can see her
201
- // and take her out.
202
- async function describe(w) {
203
- const beings = {};
204
- // Every being is asked at once and each is bounded on her own. One being
205
- // whose describe never settles is one null digest, not a ward that never
206
- // answers its owner: the harbor learns the pk by this very ask at boot, so
207
- // an unbounded wait here is a ward no restart can bring back.
208
- const asked = [...w.doors].filter(([key]) => key !== w.pk);
209
- const digests = await Promise.all(asked.map(async ([, door]) => (await within(DEFAULT.time, seen(door, { id: OWNER })))));
210
- for (const [i, [key, door]] of asked.entries()) {
211
- const d = digests[i];
212
- put(beings, key, { class: door.cells.class ?? null, public: w.publicKey() === key, digest: d === LATE ? null : d });
213
- }
214
- for (const [key, cls] of Object.entries(w.absent()))
215
- put(beings, key, { class: cls, public: w.publicKey() === key, digest: null, absent: true });
216
- return {
217
- asks: OWNER_ASKS,
218
- notes: { pk: w.pk, beings },
219
- };
220
- }
@@ -1,52 +0,0 @@
1
- import type { DoorWord, Json, JsonObject } from '../being/types.ts';
2
- export type WardKey = {
3
- sign: Uint8Array;
4
- padlock: Uint8Array;
5
- signPk: Uint8Array;
6
- padlockPk: Uint8Array;
7
- pk: string;
8
- };
9
- export declare const SIZE: number;
10
- export declare function wardKey(seed: string | Uint8Array): Promise<WardKey>;
11
- export declare const wardSignPk: (pk: string) => Uint8Array;
12
- export declare const wardPadlock: (pk: string) => Uint8Array;
13
- export declare const isWardPk: (pk: unknown) => pk is string;
14
- export declare const isHex: (v: unknown) => v is string;
15
- export declare function beingKey(seed: Uint8Array): Promise<{
16
- seed: string;
17
- pk: string;
18
- }>;
19
- export type AskPayload = {
20
- by: string;
21
- next: string | null;
22
- seq: number;
23
- time: number;
24
- hops?: number;
25
- method?: string;
26
- args: JsonObject;
27
- };
28
- export type ReplyPayload = {
29
- object: Json;
30
- seen: string | null;
31
- } | {
32
- silence: true;
33
- } | {
34
- quo: DoorWord;
35
- };
36
- export declare function sealAsk(to: string | null, payload: AskPayload, signer: Uint8Array, padlock: Uint8Array, seed: Uint8Array): Promise<{
37
- bytes: Uint8Array;
38
- ephemeral: Uint8Array;
39
- }>;
40
- export declare function openAsk(bytes: Uint8Array, padlockSecret: Uint8Array): Promise<{
41
- to: string | null;
42
- payload: AskPayload;
43
- body: Uint8Array;
44
- signature: Uint8Array;
45
- ephemeralPk: Uint8Array;
46
- } | null>;
47
- export declare const verifyAsk: (a: {
48
- body: Uint8Array;
49
- signature: Uint8Array;
50
- }, pk: string) => Promise<boolean>;
51
- export declare function sealReply(reply: ReplyPayload, ephemeralPk: Uint8Array, wardSign: Uint8Array, seed: Uint8Array): Promise<Uint8Array>;
52
- export declare function openReply(bytes: unknown, ephemeralSecret: Uint8Array, signPk: Uint8Array): Promise<ReplyPayload | null>;