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.
Files changed (111) hide show
  1. package/GETTING_STARTED.md +138 -0
  2. package/NOTICE +1 -1
  3. package/README.md +108 -20
  4. package/dist/being/being.d.ts +24 -0
  5. package/dist/being/being.js +109 -0
  6. package/dist/being/digest.d.ts +3 -0
  7. package/dist/being/digest.js +37 -0
  8. package/dist/being/index.d.ts +5 -0
  9. package/dist/being/index.js +6 -0
  10. package/dist/being/silence.d.ts +12 -0
  11. package/dist/being/silence.js +41 -0
  12. package/dist/being/types.d.ts +89 -0
  13. package/dist/being/types.js +58 -0
  14. package/dist/conformance/assert.d.ts +11 -0
  15. package/dist/conformance/assert.js +106 -0
  16. package/dist/conformance/beings.d.ts +198 -0
  17. package/dist/conformance/beings.js +183 -0
  18. package/dist/conformance/estate.d.ts +5 -0
  19. package/dist/conformance/estate.js +388 -0
  20. package/dist/conformance/index.d.ts +81 -0
  21. package/dist/conformance/index.js +816 -0
  22. package/dist/conformance/reach.d.ts +10 -0
  23. package/dist/conformance/reach.js +72 -0
  24. package/dist/conformance/store.d.ts +5 -0
  25. package/dist/conformance/store.js +123 -0
  26. package/dist/harbor/core.d.ts +53 -0
  27. package/dist/harbor/core.js +621 -0
  28. package/dist/harbor/dial.d.ts +9 -0
  29. package/dist/harbor/dial.js +81 -0
  30. package/dist/harbor/index.d.ts +8 -0
  31. package/dist/harbor/index.js +16 -0
  32. package/dist/harbor/memory.d.ts +25 -0
  33. package/dist/harbor/memory.js +72 -0
  34. package/dist/harbor/reach.d.ts +36 -0
  35. package/dist/harbor/reach.js +191 -0
  36. package/dist/harbor/store.d.ts +40 -0
  37. package/dist/harbor/store.js +70 -0
  38. package/dist/vector/cases.d.ts +41 -0
  39. package/dist/vector/cases.js +195 -0
  40. package/dist/vector/index.d.ts +6 -0
  41. package/dist/vector/index.js +8 -0
  42. package/dist/vector/stand.d.ts +9 -0
  43. package/dist/vector/stand.js +76 -0
  44. package/dist/vector/world.d.ts +143 -0
  45. package/dist/vector/world.js +198 -0
  46. package/dist/ward/allowance.d.ts +10 -0
  47. package/dist/ward/allowance.js +71 -0
  48. package/dist/ward/arithmetic.d.ts +31 -0
  49. package/dist/ward/arithmetic.js +245 -0
  50. package/dist/ward/cells.d.ts +7 -0
  51. package/dist/ward/cells.js +186 -0
  52. package/dist/ward/door.d.ts +18 -0
  53. package/dist/ward/door.js +186 -0
  54. package/dist/ward/ground.d.ts +23 -0
  55. package/dist/ward/ground.js +38 -0
  56. package/dist/ward/heirs.d.ts +13 -0
  57. package/dist/ward/heirs.js +115 -0
  58. package/dist/ward/index.d.ts +9 -0
  59. package/dist/ward/index.js +13 -0
  60. package/dist/ward/owner.d.ts +13 -0
  61. package/dist/ward/owner.js +220 -0
  62. package/dist/ward/partition.d.ts +65 -0
  63. package/dist/ward/partition.js +295 -0
  64. package/dist/ward/seal.d.ts +52 -0
  65. package/dist/ward/seal.js +145 -0
  66. package/dist/ward/stance.d.ts +25 -0
  67. package/dist/ward/stance.js +413 -0
  68. package/dist/ward/ward.d.ts +12 -0
  69. package/dist/ward/ward.js +361 -0
  70. package/package.json +37 -15
  71. package/protocol/SPEC.md +1843 -0
  72. package/protocol/vectors/arithmetic.json +117 -0
  73. package/protocol/vectors/door.json +345 -0
  74. package/protocol/vectors/framing.json +92 -0
  75. package/protocol/vectors/wire.json +48 -0
  76. package/quo-kit.md +652 -0
  77. package/src/being/being.ts +123 -0
  78. package/src/being/digest.ts +46 -0
  79. package/src/being/index.ts +7 -0
  80. package/src/being/silence.ts +46 -0
  81. package/src/being/types.ts +170 -0
  82. package/src/conformance/assert.ts +100 -0
  83. package/src/conformance/beings.ts +183 -0
  84. package/src/conformance/estate.ts +412 -0
  85. package/src/conformance/index.ts +962 -0
  86. package/src/conformance/reach.ts +83 -0
  87. package/src/conformance/store.ts +136 -0
  88. package/src/harbor/core.ts +660 -0
  89. package/src/harbor/dial.ts +112 -0
  90. package/src/harbor/index.ts +17 -0
  91. package/src/harbor/memory.ts +83 -0
  92. package/src/harbor/reach.ts +215 -0
  93. package/src/harbor/store.ts +101 -0
  94. package/src/vector/cases.ts +229 -0
  95. package/src/vector/index.ts +11 -0
  96. package/src/vector/stand.ts +75 -0
  97. package/src/vector/world.ts +220 -0
  98. package/src/ward/allowance.ts +85 -0
  99. package/src/ward/arithmetic.ts +247 -0
  100. package/src/ward/cells.ts +190 -0
  101. package/src/ward/door.ts +186 -0
  102. package/src/ward/ground.ts +160 -0
  103. package/src/ward/heirs.ts +114 -0
  104. package/src/ward/index.ts +17 -0
  105. package/src/ward/owner.ts +214 -0
  106. package/src/ward/partition.ts +353 -0
  107. package/src/ward/seal.ts +174 -0
  108. package/src/ward/stance.ts +433 -0
  109. package/src/ward/ward.ts +378 -0
  110. package/index.js +0 -17
  111. package/nervur.js +0 -5
@@ -0,0 +1,58 @@
1
+ // The ids the ward speaks under, and no being may mint. An asker id is hers:
2
+ // she chooses the strings, and the ward has no business in her namespace. But
3
+ // the ward must sometimes stand at her door itself — its describe asks every
4
+ // being under OWNER — and an id it shares with one of her occupants is two
5
+ // different parties wearing one name. She could not tell them apart, and the
6
+ // ward would shape its describe through an occupant's gate.
7
+ //
8
+ // So the ward's words are shouted, and refused at the mint. A word can only
9
+ // be reserved before anyone has used it: a partition already holding an
10
+ // occupant of that name could never have it taken back. PUBLIC guards nothing
11
+ // today — a public asker is {} and carries no id at all — and that is exactly
12
+ // why it is claimed now, while claiming it is free.
13
+ export const OWNER = 'OWNER';
14
+ export const PUBLIC = 'PUBLIC';
15
+ export const RESERVED_IDS = [OWNER, PUBLIC];
16
+ // An invitation as one object of values, which is what it already is. A being
17
+ // who hands one on sends it as args, and args are values; this is the one
18
+ // place that is spelled, so nobody spells it with a cast. Absent stays absent:
19
+ // an invitation to a public being carries no heir and no secret, and a key
20
+ // present and undefined is not the same object once JSON has been through it.
21
+ export const invitationArgs = (inv) => ({
22
+ ward: inv.ward,
23
+ ...(inv.heir !== undefined ? { heir: inv.heir } : {}),
24
+ ...(inv.secret !== undefined ? { secret: inv.secret } : {}),
25
+ });
26
+ // Whether a value that arrived is an invitation. The shape is the spec's, so
27
+ // the reading of it is too, and it is one reading: a form that
28
+ // asks a guest for one, a faculty handed one to wake her maker by, and a
29
+ // shell that finds one in a link all ask the same question, and a kinder
30
+ // answer in one of them is a value that fails at a door instead of at the
31
+ // edge it came in by. A ward pk is a hundred and twenty-eight lowercase hex,
32
+ // and a heir comes with its secret or neither comes: an invitation to a
33
+ // public being carries no heir, and a heir with no secret opens nothing.
34
+ export const isInvitation = (v) => {
35
+ if (v === null || typeof v !== 'object' || Array.isArray(v))
36
+ return false;
37
+ const o = v;
38
+ if (typeof o.ward !== 'string' || !/^[0-9a-f]{128}$/.test(o.ward))
39
+ return false;
40
+ const heir = typeof o.heir === 'string', secret = typeof o.secret === 'string';
41
+ return (heir && secret) || (!heir && !secret && !('heir' in o) && !('secret' in o));
42
+ };
43
+ // Whether what came back from an empty ask is a blueprint. Every describe on
44
+ // the far side of a door is somebody else's code, so nothing may be written
45
+ // into a standing's record as a blueprint without being read as one first: a
46
+ // side walks `asks` by name, and a list of anything else, or no list at all,
47
+ // is a side that breaks on a far ward's answer. The asks are checked to the
48
+ // depth a side reads them, a name and an input each, and no further: what a
49
+ // far being puts beside those is hers.
50
+ export const isBlueprint = (v) => {
51
+ if (v === null || typeof v !== 'object' || Array.isArray(v))
52
+ return false;
53
+ const { asks, notes } = v;
54
+ if (!Array.isArray(asks) || notes === undefined)
55
+ return false;
56
+ return asks.every((a) => a !== null && typeof a === 'object' && !Array.isArray(a) && typeof a.name === 'string' && a.input !== null && typeof a.input === 'object' && !Array.isArray(a.input));
57
+ };
58
+ export const WORD_KEY = Symbol.for('quo.word');
@@ -0,0 +1,11 @@
1
+ export declare class Failed extends Error {
2
+ }
3
+ export declare function same(a: unknown, b: unknown): boolean;
4
+ export type Assert = {
5
+ ok(value: unknown, message?: string): asserts value;
6
+ equal(actual: unknown, expected: unknown, message?: string): void;
7
+ notEqual(actual: unknown, expected: unknown, message?: string): void;
8
+ deepEqual(actual: unknown, expected: unknown, message?: string): void;
9
+ match(actual: string, re: RegExp, message?: string): void;
10
+ };
11
+ export declare const assert: Assert;
@@ -0,0 +1,106 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The five assertions the conformance suite makes, and no more. Node has all
3
+ // five in `node:assert/strict`, and naming that module would tie the suite to
4
+ // one terrain -- the suite is the ward's truth, and the ward's truth has to be
5
+ // checkable wherever a ward runs. So they are written here, in the language
6
+ // alone, and `test/assert.test.ts` holds them to Node's own behaviour.
7
+ export class Failed extends Error {
8
+ }
9
+ const show = (v) => {
10
+ if (typeof v === 'symbol')
11
+ return v.toString();
12
+ if (typeof v === 'bigint')
13
+ return `${v}n`;
14
+ if (v === undefined)
15
+ return 'undefined';
16
+ if (typeof v === 'function')
17
+ return `[function ${v.name}]`;
18
+ // A ward's word is a frozen object under one symbol key, and JSON shows it
19
+ // as `{}`. A red estate chapter saying `{} is not {}` names nothing, so a
20
+ // value whose own keys are all symbols is shown by its keys.
21
+ if (v !== null && typeof v === 'object') {
22
+ const keys = Object.keys(v);
23
+ const syms = Object.getOwnPropertySymbols(v).filter((s) => Object.getOwnPropertyDescriptor(v, s).enumerable);
24
+ if (keys.length === 0 && syms.length > 0)
25
+ return `{ ${syms.map((s) => `${s.toString()}: ${show(v[s])}`).join(', ')} }`;
26
+ }
27
+ try {
28
+ return JSON.stringify(v) ?? Object.prototype.toString.call(v);
29
+ }
30
+ catch {
31
+ return Object.prototype.toString.call(v); // a cycle, or a null prototype
32
+ }
33
+ };
34
+ const fail = (why, message) => {
35
+ throw new Failed(message ? `${message}: ${why}` : why);
36
+ };
37
+ // Structural, and strict about shape the way `node:assert/strict` is: a value
38
+ // is not equal to one of another kind, and an array is not equal to an object
39
+ // that happens to hold the same keys.
40
+ // The four kinds this compares, and every other object is refused rather than
41
+ // guessed at. A Date, a RegExp, a boxed primitive and a class instance each
42
+ // carry state no key walk can see, so two of them with the same keys are
43
+ // equal here and unequal in node, which is a wrong yes: the one answer a
44
+ // suite must never give. A refusal is a red test that says so.
45
+ const KNOWN = [Object.prototype, null, Array.prototype, Uint8Array.prototype];
46
+ // What node compares: own enumerable keys, strings then enumerable symbols.
47
+ // Not `Reflect.ownKeys`, which also lists what a definition hid.
48
+ const keysOf = (v) => [...Object.keys(v), ...Object.getOwnPropertySymbols(v).filter((s) => Object.getOwnPropertyDescriptor(v, s).enumerable)];
49
+ export function same(a, b) {
50
+ if (Object.is(a, b))
51
+ return true;
52
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
53
+ return false;
54
+ const proto = Object.getPrototypeOf(a);
55
+ if (proto !== Object.getPrototypeOf(b))
56
+ return false;
57
+ if (!KNOWN.includes(proto))
58
+ return false; // a Date, a RegExp, a boxed number, a class of hers
59
+ if (Array.isArray(a) !== Array.isArray(b))
60
+ return false;
61
+ if (a instanceof Uint8Array && b instanceof Uint8Array)
62
+ return a.length === b.length && a.every((x, i) => x === b[i]);
63
+ // An array is its indices and whatever else was hung on it, and a hole is
64
+ // not a present undefined: `[, 1]` and `[undefined, 1]` differ in node and
65
+ // must differ here. `every` skips holes, so the loop is written out.
66
+ if (Array.isArray(a) && Array.isArray(b)) {
67
+ if (a.length !== b.length)
68
+ return false;
69
+ for (let i = 0; i < a.length; i += 1) {
70
+ if (i in a !== i in b)
71
+ return false;
72
+ if (i in a && !same(a[i], b[i]))
73
+ return false;
74
+ }
75
+ const extra = (v) => keysOf(v).filter((k) => typeof k !== 'string' || !/^(?:0|[1-9]\d*)$/.test(k));
76
+ const xa = extra(a), xb = extra(b);
77
+ const at = (v, k) => v[k];
78
+ return xa.length === xb.length && xa.every((k) => xb.includes(k) && same(at(a, k), at(b, k)));
79
+ }
80
+ const ka = keysOf(a), kb = keysOf(b);
81
+ if (ka.length !== kb.length)
82
+ return false;
83
+ return ka.every((k) => kb.includes(k) && same(a[k], b[k]));
84
+ }
85
+ export const assert = {
86
+ ok(value, message) {
87
+ if (!value)
88
+ fail(`${show(value)} is not truthy`, message);
89
+ },
90
+ equal(actual, expected, message) {
91
+ if (!Object.is(actual, expected))
92
+ fail(`${show(actual)} !== ${show(expected)}`, message);
93
+ },
94
+ notEqual(actual, expected, message) {
95
+ if (Object.is(actual, expected))
96
+ fail(`${show(actual)} === ${show(expected)}`, message);
97
+ },
98
+ deepEqual(actual, expected, message) {
99
+ if (!same(actual, expected))
100
+ fail(`${show(actual)} is not ${show(expected)}`, message);
101
+ },
102
+ match(actual, re, message) {
103
+ if (typeof actual !== 'string' || !re.test(actual))
104
+ fail(`${show(actual)} does not match ${String(re)}`, message);
105
+ },
106
+ };
@@ -0,0 +1,198 @@
1
+ import { Being } from '../being/being.ts';
2
+ import type { Asker, Blueprint, Invitation, Json, JsonObject, OccupantRecord, Reply, Stance } from '../being/types.ts';
3
+ export declare class Printer extends Being {
4
+ static cells: {
5
+ jobs: JsonObject[];
6
+ asks: JsonObject[];
7
+ };
8
+ static asks: {
9
+ print: {
10
+ input: {
11
+ type: string;
12
+ };
13
+ };
14
+ };
15
+ describe(): Blueprint;
16
+ print({ doc }: JsonObject, asker: Asker): {
17
+ printed: string | number | boolean | Json[] | {
18
+ [key: string]: Json;
19
+ } | null;
20
+ };
21
+ }
22
+ export declare class Shop extends Being {
23
+ static cells: {
24
+ printerId: string | null;
25
+ sales: JsonObject[];
26
+ minted: number;
27
+ };
28
+ static asks: {
29
+ hello: {
30
+ input: {
31
+ type: string;
32
+ };
33
+ };
34
+ buy: {
35
+ input: {
36
+ type: string;
37
+ properties: {
38
+ item: {
39
+ type: string;
40
+ };
41
+ };
42
+ };
43
+ };
44
+ refund: {
45
+ input: {
46
+ type: string;
47
+ };
48
+ for: (rec: OccupantRecord | undefined) => boolean;
49
+ };
50
+ };
51
+ answer(asker: Asker, method?: string, args?: JsonObject): Promise<Reply>;
52
+ invite(tier?: string): Promise<Invitation>;
53
+ keepPrinter(invitation: Invitation): Promise<boolean>;
54
+ hello({ invitation }: JsonObject, asker: Asker): Promise<{
55
+ welcome: boolean;
56
+ }>;
57
+ buy({ item }: JsonObject, asker: Asker): Promise<{
58
+ error: string;
59
+ retry?: undefined;
60
+ ok?: undefined;
61
+ receipt?: undefined;
62
+ } | {
63
+ error: string;
64
+ retry: boolean;
65
+ ok?: undefined;
66
+ receipt?: undefined;
67
+ } | {
68
+ error?: undefined;
69
+ retry?: undefined;
70
+ ok: boolean;
71
+ receipt: Json;
72
+ }>;
73
+ refund(): {
74
+ ok: boolean;
75
+ };
76
+ }
77
+ export declare class Customer extends Being {
78
+ static cells: {
79
+ heard: JsonObject[];
80
+ };
81
+ static asks: {
82
+ hi: {
83
+ input: {
84
+ type: string;
85
+ };
86
+ };
87
+ };
88
+ join(invitation: Invitation): Promise<string | number | boolean | typeof import("../being/silence.ts").silence | Json[] | import("../being/types.ts").Word<import("../being/types.ts").WordName> | {
89
+ [key: string]: Json;
90
+ } | null>;
91
+ buy(item: string): Promise<import("../being/types.ts").Answer>;
92
+ learn(): Promise<import("../being/types.ts").Answer>;
93
+ hi(_args: JsonObject, asker: Asker): {
94
+ heard: string;
95
+ };
96
+ }
97
+ export declare class Echo {
98
+ s: Stance;
99
+ constructor(stance: Stance);
100
+ answer(asker: Asker, method?: string, args?: JsonObject): Reply;
101
+ }
102
+ export declare class Maker extends Being {
103
+ static cells: {
104
+ made: string[];
105
+ };
106
+ static asks: {
107
+ open: {
108
+ input: {
109
+ type: string;
110
+ properties: {
111
+ class: {
112
+ type: string;
113
+ };
114
+ key: {
115
+ type: string;
116
+ };
117
+ id: {
118
+ type: string;
119
+ };
120
+ };
121
+ required: string[];
122
+ };
123
+ };
124
+ say: {
125
+ input: {
126
+ type: string;
127
+ properties: {
128
+ id: {
129
+ type: string;
130
+ };
131
+ };
132
+ required: string[];
133
+ };
134
+ };
135
+ borrow: {
136
+ input: {
137
+ type: string;
138
+ properties: {
139
+ name: {
140
+ type: string;
141
+ };
142
+ id: {
143
+ type: string;
144
+ };
145
+ };
146
+ required: string[];
147
+ };
148
+ };
149
+ };
150
+ open({ class: className, key, id }: JsonObject): Promise<{
151
+ made: string | null;
152
+ }>;
153
+ say({ id }: JsonObject): Promise<{
154
+ said: Json;
155
+ }>;
156
+ borrow({ name, id }: JsonObject): Promise<{
157
+ lent: string | null;
158
+ }>;
159
+ }
160
+ export declare class Member extends Being {
161
+ static cells: {
162
+ heard: JsonObject[];
163
+ };
164
+ static asks: {
165
+ ping: {
166
+ input: {
167
+ type: string;
168
+ };
169
+ };
170
+ relay: {
171
+ input: {
172
+ type: string;
173
+ properties: {
174
+ id: {
175
+ type: string;
176
+ };
177
+ method: {
178
+ type: string;
179
+ };
180
+ time: {
181
+ type: string;
182
+ };
183
+ };
184
+ required: string[];
185
+ };
186
+ };
187
+ };
188
+ ping(_args: JsonObject, asker: Asker): {
189
+ pong: string | null;
190
+ };
191
+ relay({ id, method, args, time }: JsonObject): Promise<{
192
+ said: string;
193
+ out: null;
194
+ } | {
195
+ said: null;
196
+ out: Json;
197
+ }>;
198
+ }
@@ -0,0 +1,183 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The fixture beings. Fixed, so that the ward is what varies. A printer, a
3
+ // shop, a customer on the base class, a maker, a member, and one raw being
4
+ // with no base at all.
5
+ import { Being } from '../being/being.js';
6
+ import { answered, isSilence, isUnreached, isWord, wordOf } from '../being/silence.js';
7
+ import { invitationArgs } from '../being/types.js';
8
+ // A printer. Invites whom she is told to, prints for her occupants. Her
9
+ // blueprint lives in her cells so a test can change her shape.
10
+ export class Printer extends Being {
11
+ static cells = { jobs: [], asks: [{ name: 'print', input: { type: 'object' } }] };
12
+ static asks = { print: { input: { type: 'object' } } };
13
+ describe() {
14
+ // Her own cells, which a test writes to change her shape. Cells are Json
15
+ // to the ward, so the asks are named for what she keeps in them.
16
+ return { asks: this.cells.asks, notes: { model: 'LX-2' } };
17
+ }
18
+ print({ doc }, asker) {
19
+ this.cells.jobs.push({ by: asker.id ?? null, doc: doc ?? null });
20
+ return { printed: doc ?? null };
21
+ }
22
+ }
23
+ // A shop. Invites customers in her own time. Keeps a printer as a standing.
24
+ // Takes a gold customer back if the customer offers a way. Counts every ask
25
+ // per occupant, by wrapping answer.
26
+ export class Shop extends Being {
27
+ static cells = { printerId: null, sales: [], minted: 0 };
28
+ static asks = {
29
+ hello: { input: { type: 'object' } },
30
+ buy: { input: { type: 'object', properties: { item: { type: 'string' } } } },
31
+ refund: { input: { type: 'object' }, for: (rec) => rec?.notes.tier === 'gold' },
32
+ };
33
+ async answer(asker, method, args) {
34
+ const rec = this.occupant(asker);
35
+ if (rec && method !== undefined)
36
+ rec.notes.asks = (Number(rec.notes.asks) || 0) + 1;
37
+ return super.answer(asker, method, args);
38
+ }
39
+ // Outside any ask. She mints, her ward seals, she hands it out by mail.
40
+ async invite(tier = 'plain') {
41
+ const id = `cust${++this.cells.minted}`;
42
+ const inv = (await super.invite(id));
43
+ this.cells.occupants[id].notes.tier = tier;
44
+ return inv;
45
+ }
46
+ async keepPrinter(invitation) {
47
+ const bp = await this.knock(invitation);
48
+ if (!answered(bp))
49
+ return false;
50
+ this.cells.printerId = await this.take('prn', invitation);
51
+ return true;
52
+ }
53
+ async hello({ invitation }, asker) {
54
+ const rec = this.occupant(asker);
55
+ if (rec?.notes.tier === 'gold' && invitation) {
56
+ const back = await this.knock(invitation, 'hi');
57
+ if (answered(back))
58
+ await this.take(`vip-${asker.id}`, invitation);
59
+ }
60
+ return { welcome: true };
61
+ }
62
+ async buy({ item }, asker) {
63
+ if (typeof item !== 'string')
64
+ return { error: 'no such item' }; // args are hers to check
65
+ const id = this.cells.printerId;
66
+ const printer = id ? this.standings[id] : undefined;
67
+ if (!printer)
68
+ return { error: 'no printer' };
69
+ const out = await printer.ask('print', { doc: `receipt for ${item}` });
70
+ if (isUnreached(out))
71
+ return { error: 'printer unreachable', retry: true };
72
+ if (!answered(out))
73
+ return { error: 'printer refused' };
74
+ const st = this.cells.standings[id];
75
+ if (st.seen !== st.digest)
76
+ this.occupant(asker).notes.printerChanged = true;
77
+ this.cells.sales.push({ item: item ?? null, by: asker.id ?? null });
78
+ return { ok: true, receipt: out.printed };
79
+ }
80
+ refund() {
81
+ return { ok: true };
82
+ }
83
+ }
84
+ // A customer. Consumes an invitation, and only then decides to keep the
85
+ // shop. Offers the shop a way back so the shop can reach her.
86
+ export class Customer extends Being {
87
+ static cells = { heard: [] };
88
+ static asks = { hi: { input: { type: 'object' } } };
89
+ async join(invitation) {
90
+ // A way back for the shop, if she can mint one. She may not: two joins on
91
+ // one invitation race, and the second finds the id taken. Then she offers
92
+ // nothing and knocks all the same, which is what the shop is asked to
93
+ // survive. The old shape said `!` here and was wrong about it.
94
+ const mine = await this.invite('shop-back');
95
+ const out = await this.knock(invitation, 'hello', { invitation: mine === null ? null : invitationArgs(mine) });
96
+ if (!answered(out))
97
+ return out; // nothing was born
98
+ await this.take('shop', invitation); // now, and only now
99
+ return out;
100
+ }
101
+ buy(item) {
102
+ return this.standings.shop.ask('buy', { item });
103
+ }
104
+ learn() {
105
+ return this.standings.shop.ask();
106
+ }
107
+ hi(_args, asker) {
108
+ this.cells.heard.push({ from: asker.id ?? null, method: 'hi' });
109
+ return { heard: 'hi' };
110
+ }
111
+ }
112
+ // The raw shape. No base class, no import from the kit. A being all the same.
113
+ export class Echo {
114
+ s;
115
+ constructor(stance) {
116
+ this.s = stance;
117
+ }
118
+ answer(asker, method, args = {}) {
119
+ if (method === undefined)
120
+ return { asks: [{ name: 'echo', input: {} }], notes: {} };
121
+ return { from: asker.id ?? null, ...args };
122
+ }
123
+ }
124
+ // A member of an estate. Nothing scripted: she invites whom she is told to,
125
+ // knocks where she is told to, and answers `ping` with the asker's own name.
126
+ // The estate chapter drives her, and what it drives is the graph, not her.
127
+ // A being who makes beings. `boot` is on every stance, so it is the ward's
128
+ // promise and not this kit's, and a second kit that never wired it hands its
129
+ // beings a call that answers null for ever. She says what came of it: the key
130
+ // back, or null for a key already taken, a class the harbor does not hold, or
131
+ // a constructor that threw. With an id she also asks for a way back, and then
132
+ // she holds a standing to what she made like any other.
133
+ export class Maker extends Being {
134
+ static cells = { made: [] };
135
+ static asks = {
136
+ open: { input: { type: 'object', properties: { class: { type: 'string' }, key: { type: 'string' }, id: { type: 'string' } }, required: ['class', 'key'] } },
137
+ say: { input: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
138
+ borrow: { input: { type: 'object', properties: { name: { type: 'string' }, id: { type: 'string' } }, required: ['name', 'id'] } },
139
+ };
140
+ async open({ class: className, key, id }) {
141
+ const made = await this.boot(className, key, id);
142
+ if (made !== null)
143
+ this.cells.made.push(made);
144
+ return { made };
145
+ }
146
+ async say({ id }) {
147
+ const standing = this.standings[id];
148
+ return { said: standing === undefined ? null : (await standing.ask('ping')) };
149
+ }
150
+ // Making's other half: a standing at something the box already has, by the
151
+ // name the box knows it under. She is handed the id and never the value.
152
+ async borrow({ name, id }) {
153
+ return { lent: await this.lend(name, id) };
154
+ }
155
+ }
156
+ export class Member extends Being {
157
+ static cells = { heard: [] };
158
+ static asks = {
159
+ ping: { input: { type: 'object' } },
160
+ relay: { input: { type: 'object', properties: { id: { type: 'string' }, method: { type: 'string' }, time: { type: 'number' } }, required: ['id'] } },
161
+ };
162
+ ping(_args, asker) {
163
+ this.cells.heard.push({ from: asker.id ?? null });
164
+ return { pong: asker.id ?? null };
165
+ }
166
+ // She asks on one of her standings from inside her own answer, and says
167
+ // what came back as a word rather than as the word itself, since a word is
168
+ // her ward's value and never a thing that crosses. `time` is what she is
169
+ // willing to wait, which is the whole of what the third argument is for.
170
+ // Two members holding each other and relaying make a cycle, and a cycle is
171
+ // how a suite that owns no clock can see a wait end.
172
+ async relay({ id, method, args, time }) {
173
+ const standing = this.standings[id];
174
+ if (standing === undefined)
175
+ return { said: 'no standing', out: null };
176
+ const out = await standing.ask(method, (args ?? {}), time === undefined ? undefined : { time: time });
177
+ if (isSilence(out))
178
+ return { said: 'silence', out: null };
179
+ if (isWord(out))
180
+ return { said: wordOf(out), out: null };
181
+ return { said: null, out: out };
182
+ }
183
+ }
@@ -0,0 +1,5 @@
1
+ import type { Runner, World } from './index.ts';
2
+ export declare function estate(label: string, make: () => Promise<World>, { canDown, test }: {
3
+ canDown: boolean;
4
+ test: Runner;
5
+ }): void;