@lapxo/bound 0.1.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 (186) hide show
  1. package/CONTRIBUTING.md +44 -0
  2. package/LICENSE +22 -0
  3. package/README.md +97 -0
  4. package/TARGET.bound +361 -0
  5. package/dist/cli/accept.d.ts +23 -0
  6. package/dist/cli/accept.js +423 -0
  7. package/dist/cli/args.d.ts +10 -0
  8. package/dist/cli/args.js +34 -0
  9. package/dist/cli/host.d.ts +1 -0
  10. package/dist/cli/host.js +32 -0
  11. package/dist/cli/names.d.ts +25 -0
  12. package/dist/cli/names.js +12 -0
  13. package/dist/cli/observe.d.ts +1 -0
  14. package/dist/cli/observe.js +6 -0
  15. package/dist/cli/owner.d.ts +35 -0
  16. package/dist/cli/owner.js +119 -0
  17. package/dist/cli/pass.d.ts +6 -0
  18. package/dist/cli/pass.js +309 -0
  19. package/dist/cli/place.d.ts +74 -0
  20. package/dist/cli/place.js +290 -0
  21. package/dist/cli/takes.d.ts +30 -0
  22. package/dist/cli/takes.js +108 -0
  23. package/dist/cli/verb.d.ts +2 -0
  24. package/dist/cli/verb.js +478 -0
  25. package/dist/fold/ceilings.d.ts +24 -0
  26. package/dist/fold/ceilings.js +129 -0
  27. package/dist/fold/claims.d.ts +25 -0
  28. package/dist/fold/claims.js +107 -0
  29. package/dist/fold/closed.d.ts +15 -0
  30. package/dist/fold/closed.js +168 -0
  31. package/dist/fold/configures.d.ts +4 -0
  32. package/dist/fold/configures.js +22 -0
  33. package/dist/fold/digests.d.ts +29 -0
  34. package/dist/fold/digests.js +211 -0
  35. package/dist/fold/evidence.d.ts +32 -0
  36. package/dist/fold/evidence.js +22 -0
  37. package/dist/fold/fold-worker.d.ts +1 -0
  38. package/dist/fold/fold-worker.js +13 -0
  39. package/dist/fold/kept.d.ts +29 -0
  40. package/dist/fold/kept.js +142 -0
  41. package/dist/fold/keys.d.ts +17 -0
  42. package/dist/fold/keys.js +84 -0
  43. package/dist/fold/laid.d.ts +17 -0
  44. package/dist/fold/laid.js +90 -0
  45. package/dist/fold/nos.d.ts +8 -0
  46. package/dist/fold/nos.js +48 -0
  47. package/dist/fold/object.d.ts +17 -0
  48. package/dist/fold/object.js +38 -0
  49. package/dist/fold/observed.d.ts +33 -0
  50. package/dist/fold/observed.js +444 -0
  51. package/dist/fold/opened.d.ts +5 -0
  52. package/dist/fold/opened.js +33 -0
  53. package/dist/fold/own.d.ts +24 -0
  54. package/dist/fold/own.js +215 -0
  55. package/dist/fold/paid.d.ts +11 -0
  56. package/dist/fold/paid.js +92 -0
  57. package/dist/fold/place-inputs.d.ts +17 -0
  58. package/dist/fold/place-inputs.js +70 -0
  59. package/dist/fold/places.d.ts +20 -0
  60. package/dist/fold/places.js +84 -0
  61. package/dist/fold/rank.d.ts +3 -0
  62. package/dist/fold/rank.js +9 -0
  63. package/dist/fold/reach.d.ts +13 -0
  64. package/dist/fold/reach.js +65 -0
  65. package/dist/fold/readers.d.ts +44 -0
  66. package/dist/fold/readers.js +132 -0
  67. package/dist/fold/receipts.d.ts +8 -0
  68. package/dist/fold/receipts.js +47 -0
  69. package/dist/fold/region.d.ts +72 -0
  70. package/dist/fold/region.js +286 -0
  71. package/dist/fold/release-policy.d.ts +67 -0
  72. package/dist/fold/release-policy.js +103 -0
  73. package/dist/fold/resolved.d.ts +18 -0
  74. package/dist/fold/resolved.js +123 -0
  75. package/dist/fold/roles.d.ts +17 -0
  76. package/dist/fold/roles.js +59 -0
  77. package/dist/fold/second.d.ts +18 -0
  78. package/dist/fold/second.js +68 -0
  79. package/dist/fold/shards.d.ts +29 -0
  80. package/dist/fold/shards.js +100 -0
  81. package/dist/fold/signed.d.ts +18 -0
  82. package/dist/fold/signed.js +75 -0
  83. package/dist/fold/signers.d.ts +24 -0
  84. package/dist/fold/signers.js +58 -0
  85. package/dist/fold/stranger.d.ts +3 -0
  86. package/dist/fold/stranger.js +24 -0
  87. package/dist/fold/views.d.ts +27 -0
  88. package/dist/fold/views.js +66 -0
  89. package/dist/fold/wire.d.ts +19 -0
  90. package/dist/fold/wire.js +41 -0
  91. package/dist/fold/zoom.d.ts +16 -0
  92. package/dist/fold/zoom.js +80 -0
  93. package/dist/host/adapters/keys/ed25519.d.ts +7 -0
  94. package/dist/host/adapters/keys/ed25519.js +30 -0
  95. package/dist/host/archive.d.ts +11 -0
  96. package/dist/host/archive.js +152 -0
  97. package/dist/host/capsule-process.d.ts +20 -0
  98. package/dist/host/capsule-process.js +54 -0
  99. package/dist/host/capsule.d.ts +13 -0
  100. package/dist/host/capsule.js +149 -0
  101. package/dist/host/cell-receipts.d.ts +2 -0
  102. package/dist/host/cell-receipts.js +124 -0
  103. package/dist/host/digest.d.ts +3 -0
  104. package/dist/host/digest.js +15 -0
  105. package/dist/host/hash.d.ts +2 -0
  106. package/dist/host/hash.js +3 -0
  107. package/dist/host/io.d.ts +9 -0
  108. package/dist/host/io.js +9 -0
  109. package/dist/host/located.d.ts +4 -0
  110. package/dist/host/located.js +105 -0
  111. package/dist/host/objects.d.ts +5 -0
  112. package/dist/host/objects.js +18 -0
  113. package/dist/host/ports/store.d.ts +2 -0
  114. package/dist/host/ports/store.js +18 -0
  115. package/dist/host/provide.d.ts +23 -0
  116. package/dist/host/provide.js +81 -0
  117. package/dist/host/release.d.ts +12 -0
  118. package/dist/host/release.js +213 -0
  119. package/dist/host/selected-capsules.d.ts +8 -0
  120. package/dist/host/selected-capsules.js +19 -0
  121. package/dist/host/selected-topos.d.ts +17 -0
  122. package/dist/host/selected-topos.js +61 -0
  123. package/dist/host/source-content.d.ts +10 -0
  124. package/dist/host/source-content.js +94 -0
  125. package/dist/host/timing.d.ts +2 -0
  126. package/dist/host/timing.js +7 -0
  127. package/dist/judge/late.d.ts +7 -0
  128. package/dist/judge/late.js +33 -0
  129. package/dist/judge/take.d.ts +57 -0
  130. package/dist/judge/take.js +75 -0
  131. package/dist/judge/widening.d.ts +1 -0
  132. package/dist/judge/widening.js +25 -0
  133. package/dist/land/act.d.ts +4 -0
  134. package/dist/land/act.js +209 -0
  135. package/dist/land/alias.d.ts +10 -0
  136. package/dist/land/alias.js +41 -0
  137. package/dist/land/held.d.ts +7 -0
  138. package/dist/land/held.js +23 -0
  139. package/dist/land/inert.d.ts +26 -0
  140. package/dist/land/inert.js +33 -0
  141. package/dist/land/leaving.d.ts +14 -0
  142. package/dist/land/leaving.js +51 -0
  143. package/dist/land/ledger.d.ts +28 -0
  144. package/dist/land/ledger.js +148 -0
  145. package/dist/land/owned.d.ts +23 -0
  146. package/dist/land/owned.js +65 -0
  147. package/dist/land/sign.d.ts +16 -0
  148. package/dist/land/sign.js +30 -0
  149. package/dist/land/vouched.d.ts +21 -0
  150. package/dist/land/vouched.js +112 -0
  151. package/dist/land/withdraw.d.ts +14 -0
  152. package/dist/land/withdraw.js +24 -0
  153. package/dist/observe/files.d.ts +14 -0
  154. package/dist/observe/files.js +68 -0
  155. package/dist/observe/identity.d.ts +19 -0
  156. package/dist/observe/identity.js +61 -0
  157. package/dist/observe/match.d.ts +1 -0
  158. package/dist/observe/match.js +8 -0
  159. package/dist/observe/regions.d.ts +29 -0
  160. package/dist/observe/regions.js +200 -0
  161. package/dist/observe/run.d.ts +30 -0
  162. package/dist/observe/run.js +34 -0
  163. package/dist/observe/runner.d.ts +16 -0
  164. package/dist/observe/runner.js +83 -0
  165. package/dist/render/capsule.d.ts +24 -0
  166. package/dist/render/capsule.js +30 -0
  167. package/dist/render/field.d.ts +9 -0
  168. package/dist/render/field.js +46 -0
  169. package/dist/render/names.d.ts +3 -0
  170. package/dist/render/names.js +4 -0
  171. package/dist/render/open.d.ts +27 -0
  172. package/dist/render/open.js +61 -0
  173. package/dist/render/page.d.ts +3 -0
  174. package/dist/render/page.js +7 -0
  175. package/dist/render/receipts.d.ts +3 -0
  176. package/dist/render/receipts.js +46 -0
  177. package/dist/render/shape.d.ts +16 -0
  178. package/dist/render/shape.js +74 -0
  179. package/dist/render/target.d.ts +18 -0
  180. package/dist/render/target.js +52 -0
  181. package/dist/render/view.d.ts +22 -0
  182. package/dist/render/view.js +191 -0
  183. package/docs/readme-assets/atlas.svg +169 -0
  184. package/docs/usage.md +53 -0
  185. package/package.json +48 -0
  186. package/receipts.bound +59 -0
@@ -0,0 +1,478 @@
1
+ #!/usr/bin/env node
2
+ import { resolveSources } from "../host/source-content.js";
3
+ import { cellsWithReceipts } from "../host/cell-receipts.js";
4
+ import { releasePolicyLines } from "../fold/release-policy.js";
5
+ import { admittedObjects } from "../host/objects.js";
6
+ import { objectFold } from "../fold/object.js";
7
+ import { OBJECT_VIEW } from '@lapxo/topos/wire';
8
+ import { toposStanding } from '@lapxo/topos/standing';
9
+ import { wireAt, parse as parseWire } from '@lapxo/topos/wire';
10
+ import { ownLock } from "../observe/runner.js";
11
+ import { signedOwnerLines } from "./owner.js";
12
+ import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'node:fs';
13
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
14
+ import { eraOf, signaturesOf } from "../fold/digests.js";
15
+ import { fullDigest as bootstrapDigest } from "../host/digest.js";
16
+ import { canonical } from '@lapxo/topos/wire';
17
+ import { EXTENSION, LOCK, fieldOf, foldClaims, isWire, selfName } from "../fold/claims.js";
18
+ import { observedClaims } from "../fold/observed.js";
19
+ import { rolesOf } from "../fold/roles.js";
20
+ import { authorityFor, releaseSigner, rootSigner, writerFor } from "../fold/signers.js";
21
+ import { land, ledgerLines, storeOf } from "../land/ledger.js";
22
+ import { lockLines, releaseOf } from "../fold/keys.js";
23
+ import { signConsentLine } from "../land/sign.js";
24
+ import { admittedIn, ownLockOf, tellOwn } from "../fold/signed.js";
25
+ import { hasVouchedTree, landCoordinates, coordinatesOf, locationsOf, vouching, sourced } from "../land/vouched.js";
26
+ import { observeText } from "../observe/files.js";
27
+ import { askWhy, unrendered, viewOf } from "../render/view.js";
28
+ import { openCards } from "../render/open.js";
29
+ import { foldPlace, ownCeilings, standingOf } from "./place.js";
30
+ import { foldPoints, movedPoints } from "../fold/kept.js";
31
+ import { placesOf, sharedShapes, viewsOf } from "../fold/views.js";
32
+ import { placesIn } from "../fold/places.js";
33
+ import { isCeiling } from "../fold/configures.js";
34
+ import { readingsIn } from "../land/inert.js";
35
+ import { pass } from "./pass.js";
36
+ import { underTheLock, underTheRegions } from "../land/act.js";
37
+ import { ownerOf, renderInto, signedDemands, targetFile } from "./owner.js";
38
+ import { claimLinesIn, entryOf, namedPlaces, positionalsOf, valueOf } from "./args.js";
39
+ import { EXIT, FLAGS, NAMED_VIEWS, helpLines } from "./names.js";
40
+ import { accept, signedBatch } from "./accept.js";
41
+ /** The first land of a lock: TARGET.bound into an empty owner ledger, then the sources that lock names. */
42
+ function firstLand(root, args) {
43
+ const started = Date.now();
44
+ const store = storeOf(root);
45
+ const today = claimLinesIn(targetFile(root));
46
+ const proposed = observeText(targetFile(root));
47
+ let owner;
48
+ let keyId;
49
+ let born;
50
+ let verified = 0;
51
+ let anchored;
52
+ try {
53
+ if (!today.length)
54
+ throw new Error('REFUSE·bootstrap the proposed lock holds no claims');
55
+ const config = eraOf(today);
56
+ const digestLine = today.find((line) => ['wire/digest-algorithms', 'audit/wire/digest-algorithms'].includes(fieldOf(line, 'scope')) && fieldOf(line, 'value') !== 'withdraw');
57
+ const algorithm = fieldOf(digestLine ?? '', 'value').split('|').filter(Boolean)[0]?.split(':')[0] ?? '';
58
+ if (!algorithm)
59
+ throw new Error('REFUSE·bootstrap the proposed lock names no digest algorithm');
60
+ owner = writerFor(today, 'authorize') ?? '';
61
+ if (!owner)
62
+ throw new Error('REFUSE·bootstrap the proposed lock declares no root authorizer');
63
+ keyId = valueOf(args, FLAGS.key) ?? owner;
64
+ if (ledgerLines(store, owner).length)
65
+ throw new Error('REFUSE·bootstrap the owner ledger already holds claims; the first land happens once');
66
+ const keyFile = valueOf(args, FLAGS.keyFile);
67
+ if (keyFile !== undefined) {
68
+ const pem = observeText(keyFile);
69
+ if (pem === undefined)
70
+ throw new Error('REFUSE·bootstrap the requested signing key is unreadable');
71
+ const signed = [];
72
+ for (const line of today) {
73
+ if (/\bsig=/.test(line)) {
74
+ signed.push(line);
75
+ continue;
76
+ }
77
+ const got = signConsentLine(line, pem, { epoch: 1, by: keyId, algorithm: config.era });
78
+ if (got.kind === 'refuse')
79
+ throw new Error(got.why);
80
+ signed.push(got.line);
81
+ }
82
+ today.splice(0, today.length, ...signed);
83
+ }
84
+ const said = (measure) => {
85
+ const values = new Set(today.filter((line) => fieldOf(line, 'scope') === `keys/${owner}` && fieldOf(line, 'measure') === measure && fieldOf(line, 'value') !== 'withdraw').map((line) => fieldOf(line, 'value')));
86
+ if (values.size !== 1)
87
+ throw new Error(`REFUSE·bootstrap root key ${owner} must declare one ${measure}`);
88
+ return [...values][0];
89
+ };
90
+ if (said('class') !== 'authorize')
91
+ throw new Error('REFUSE·bootstrap the root key must authorize');
92
+ const publicKey = said('public-key');
93
+ const coverage = said('coverage').split('|').filter(Boolean);
94
+ if (!publicKey || !coverage.length)
95
+ throw new Error('REFUSE·bootstrap the root key must declare its public key and coverage');
96
+ const authority = authorityFor(today, { ...releaseSigner(today), id: owner, publicKey, coverage }, config.admitted);
97
+ for (const line of today) {
98
+ const verdict = authority.of(line);
99
+ if (verdict.kind !== 'admitted')
100
+ throw new Error(`REFUSE·bootstrap ${fieldOf(line, 'scope')} is not admitted: ${verdict.why ?? verdict.kind}`);
101
+ verified += 1;
102
+ }
103
+ // Source receipts require admitted instrument coordinates before the root commit.
104
+ vouching(today);
105
+ sourced(today);
106
+ born = bootstrapDigest(today.map((line) => line.replace(/ sig="[^"]*"/, '')).sort(), algorithm);
107
+ anchored = { [owner]: { id: owner, publicKey, coverage } };
108
+ }
109
+ catch (error) {
110
+ process.stderr.write(`${selfName()}: ${error instanceof Error ? error.message : String(error)}\n`);
111
+ return EXIT.refuse;
112
+ }
113
+ // Preflight above performs no writes. Only an admitted batch may establish its root of trust.
114
+ return underTheLock(store, 'bootstrap', () => {
115
+ if (ledgerLines(store, owner).length || observeText(targetFile(root)) !== proposed) {
116
+ process.stderr.write(`${selfName()}: REFUSE·bootstrap the proposed lock or owner ledger changed before commit\n`);
117
+ return EXIT.refuse;
118
+ }
119
+ const landed = land(store, owner, today);
120
+ if (keyId !== owner)
121
+ land(store, keyId, today);
122
+ const file = join(store, `${selfName()}.keys`);
123
+ mkdirSync(dirname(file), { recursive: true });
124
+ writeFileSync(file, `${JSON.stringify(anchored, null, 2)}\n`);
125
+ const signedLines = today.filter((line) => /\bsig=/.test(line));
126
+ const failing = signedLines.length - verified;
127
+ land(store, 'land', [canonical({
128
+ scope: 'bootstrap', role: 'writes', form: 'alphabet', measure: 'status', value: 'present', by: 'land', at: `bootstrap:${born}`,
129
+ about: 'the lines this clone was born from: one land per clone, and a second is a fork of the same cell',
130
+ })]);
131
+ const rendered = renderInto(root, store);
132
+ process.stdout.write(`FACT landed ${landed.appended} unique lines from ${today.length} in TARGET`
133
+ + ` · ${failing} of ${signedLines.length} signed lines fail admission by declared authority\n`
134
+ + `TARGET ${rendered.standing} claims · ${rendered.superseded.length} superseded · ${Date.now() - started} ms\n`);
135
+ for (const line of rendered.superseded)
136
+ process.stdout.write(`SUPERSEDED ${line}\n`);
137
+ if (!hasVouchedTree(store)) {
138
+ const locations = new Set();
139
+ const refused = new Set();
140
+ for (const line of signedDemands(store)) {
141
+ const got = locationsOf(fieldOf(line, 'needs'));
142
+ for (const l of got.locations)
143
+ locations.add(l);
144
+ for (const r of got.refused)
145
+ refused.add(r);
146
+ }
147
+ const coordinates = new Map();
148
+ const roleOf = rolesOf(store);
149
+ for (const location of locations)
150
+ for (const [path, digest] of coordinatesOf(root, location, roleOf))
151
+ coordinates.set(path, digest);
152
+ const vouched = landCoordinates(store, coordinates, [], 'origin:land');
153
+ process.stdout.write(`FACT vouched · ${vouched} sources under ${locations.size} locations · ${refused.size} needs refused as globs\n`);
154
+ }
155
+ return EXIT.closed;
156
+ });
157
+ }
158
+ /** What one fold cost, where it was taken and whether its points had already been folded: a beat of the instrument. */
159
+ function beat(root, fold, ms) {
160
+ const folder = writerFor(fold.standing, 'fold');
161
+ const seconds = (n) => `${(n / 1000).toFixed(n < 100 ? 2 : 1)} s`;
162
+ process.stderr.write(`BEAT fold ${fold.key.slice(0, 8)} ${fold.kept ? 'kept' : 'folded'} ${seconds(ms)}`
163
+ + `${fold.beats.kept ? ` · ${seconds(fold.beats.kept)} kept` : ''}${fold.beats.folded ? ` · ${seconds(fold.beats.folded)} folded` : ''}\n`);
164
+ if (folder === undefined)
165
+ return;
166
+ if (!fold.kept) {
167
+ const moved = movedPoints(storeOf(root), foldPoints(storeOf(root), fold.standing, fold.under, folder, fold.instrument), fold.key);
168
+ const named = moved === undefined ? 'no fold was kept before this one'
169
+ : `${moved.gone.length} points gone · ${moved.came.length} came · ${[...moved.gone, ...moved.came].slice(0, 3).map((one) => one.split(' ')[0]).join(' ') || 'none'}`;
170
+ process.stderr.write(`KEPT fold ${fold.key.slice(0, 8)} miss · ${named}\n`);
171
+ }
172
+ land(storeOf(root), folder, [canonical({
173
+ scope: `beat/${fold.under ?? '.'}`, role: 'writes', form: 'interval', measure: 'ms',
174
+ value: `${ms}..${ms}`, by: folder, at: `place:${fold.kept ? 'kept' : 'folded'}`,
175
+ })]);
176
+ }
177
+ /**
178
+ * A view of a place, rendered from its fold; a shaped one writes the coordinate its shape names. With a batch named, it is the view of the tree
179
+ * as if the batch had landed: the batch is signed and folded, never landed, and nothing is written — `--as rank <batch>`
180
+ * is what a batch would move, read before land.
181
+ */
182
+ async function view(root, under, asked, told = [], coord, key, includePolicy = true) {
183
+ const [name = asked, depth] = asked.split('@');
184
+ if (name === NAMED_VIEWS.help) {
185
+ process.stdout.write(`${helpLines().join('\n')}\n`);
186
+ return EXIT.closed;
187
+ }
188
+ if (!lockLines(storeOf(root)).length) {
189
+ process.stderr.write(`${selfName()}: the owner ledger is empty; land it before rendering a view\n`);
190
+ return EXIT.refuse;
191
+ }
192
+ const at = Date.now();
193
+ const place = under?.replace(/\/$/, '');
194
+ const released = releaseOf(storeOf(root));
195
+ const authority = authorityFor([...released ?? lockLines(storeOf(root)), ...told], released ? releaseSigner(released) : rootSigner(root), signaturesOf(storeOf(root), told).admitted);
196
+ const ledger = told.filter((line) => authority.of(line).kind === 'admitted');
197
+ const rest = told.filter((line) => !ledger.includes(line));
198
+ if (place && rest.length)
199
+ tellOwn(place, rest);
200
+ const own = place ? rest.filter((line) => admittedIn(root, place, line)) : [];
201
+ const fold = told.length
202
+ ? foldPlace(root, entryOf(root), under, false, { standing: standingOf(root, under, ledger), observed: observedClaims(root, storeOf(root), { wait: false }) })
203
+ : foldPlace(root, entryOf(root), under, false);
204
+ if (coord)
205
+ askWhy(fold, coord);
206
+ if (name === NAMED_VIEWS.lines) {
207
+ if (!told.length)
208
+ beat(root, fold, Date.now() - at);
209
+ const text = fold.standing.filter(isWire).join('\n');
210
+ process.stdout.write(text ? `${text}\n` : '');
211
+ return EXIT.closed;
212
+ }
213
+ const views = viewsOf(fold.standing);
214
+ const cellsView = [...views.entries()].find(([, one]) => !one.shape && one.regions.some((region) => region.name === 'cells'));
215
+ if (cellsView && name === cellsView[0]) {
216
+ const history = signedOwnerLines(storeOf(root));
217
+ const { objectHistory } = await import('@lapxo/topos/wire');
218
+ const parsed = objectHistory(history);
219
+ if (parsed.kind !== 'fact')
220
+ throw Error(`REFUSE·wire ${parsed.why}`);
221
+ if (parsed.value.objects.length) {
222
+ await cellsWithReceipts(root, history, text => process.stdout.write(text + '\n'));
223
+ if (includePolicy)
224
+ for (const line of releasePolicyLines(fold.release))
225
+ process.stdout.write(line + '\n');
226
+ if (!told.length)
227
+ beat(root, fold, Date.now() - at);
228
+ return EXIT.closed;
229
+ }
230
+ if (!told.length)
231
+ beat(root, fold, Date.now() - at);
232
+ const cells = fold.open ?? [];
233
+ const mine = key ? cells.filter((cell) => cell.origins.includes(key)) : cells;
234
+ const cards = openCards(mine);
235
+ if (key)
236
+ process.stdout.write(`KEY ${key}\n`);
237
+ if (cards.length)
238
+ process.stdout.write(`${cards.join('\n')}\n`);
239
+ return EXIT.closed;
240
+ }
241
+ if (!told.length)
242
+ beat(root, fold, Date.now() - at);
243
+ if (name === 'rank') {
244
+ const asked = place ? [place] : placesIn(root);
245
+ const reds = asked.flatMap((one) => sharedShapes(ownLockOf(root, one)).map((said) => `${one} ${said}`));
246
+ if (reds.length) {
247
+ process.stderr.write(`${selfName()}: REFUSE·rank ${reds.join(' · ')}: two views naming the same shape in one place\n`);
248
+ return EXIT.refuse;
249
+ }
250
+ }
251
+ const signed = views.get(name);
252
+ if (!signed) {
253
+ process.stderr.write(`${selfName()}: no view/${name} is signed; the lock signs ${[...views.keys()].sort().map((v) => `view/${v}`).join(' · ') || 'no view'}\n`);
254
+ return EXIT.usage;
255
+ }
256
+ const missing = unrendered(fold, signed);
257
+ if (missing.length) {
258
+ process.stderr.write(`${selfName()}: REFUSE·view view/${name} asks regions nothing renders: ${missing.join(' ')} · none is the instrument's own and no capsule ${under ?? 'the root'} can run declares it\n`);
259
+ return EXIT.refuse;
260
+ }
261
+ const bound = placesOf(fold.standing, signed);
262
+ if (bound.length && !bound.includes(under ?? '')) {
263
+ process.stderr.write(`${selfName()}: view/${name} writes ${bound.map((place) => `${place}${signed.shape}`).join(' · ')}, and nothing of ${under ?? 'this place'}\n`);
264
+ return EXIT.usage;
265
+ }
266
+ const folder = writerFor(fold.standing, 'fold');
267
+ if (folder !== undefined && !told.length) {
268
+ const spent = Date.now() - at;
269
+ land(storeOf(root), folder, [canonical({
270
+ scope: `beat/${under ?? '.'}`, role: 'writes', form: 'interval', measure: 'ms',
271
+ value: `${spent}..${spent}`, by: folder, at: `place:command-${fold.kept ? 'kept' : 'folded'}`,
272
+ })]);
273
+ }
274
+ const shown = (fold) => {
275
+ const text = viewOf(fold, Number.isFinite(Number(depth)) && depth !== undefined ? { ...signed, regions: signed.regions.map((one) => (one.called ? { ...one, at: Number(depth) } : one)) } : signed);
276
+ if (process.argv.includes(FLAGS.check)) {
277
+ const coordinate = join(root, under ?? '', signed.shape || '');
278
+ const held = signed.shape ? observeText(coordinate) : undefined;
279
+ const same = held !== undefined && held === text;
280
+ if (same) {
281
+ process.stdout.write(`SAME ${signed.shape || `view/${name}`}\n`);
282
+ return EXIT.closed;
283
+ }
284
+ const heldLines = (held ?? '').split('\n');
285
+ const next = text.split('\n');
286
+ const found = heldLines.findIndex((line, i) => line !== next[i]);
287
+ const at = found < 0 ? Math.min(heldLines.length, next.length) : found;
288
+ const line = (found < 0 ? next[at] : heldLines[at]) ?? next[at] ?? '';
289
+ process.stdout.write(`DIFFERS ${signed.shape || `view/${name}`}${held === undefined ? ' · this place holds no such region' : ` · line ${at + 1} · ${line}`}\n`);
290
+ return EXIT.refuse;
291
+ }
292
+ if (!signed.shape || told.length) {
293
+ if (told.length) {
294
+ process.stdout.write(`TOLD ${told.length} lines folded as if landed · ${ledger.length} in the ledger · ${own.length} in ${place ?? 'the root'}'s own lock`
295
+ + `${rest.length - own.length ? ` · ${rest.length - own.length} admitted nowhere` : ''} · the batch lands nothing and nothing is written\n`);
296
+ }
297
+ process.stdout.write(text);
298
+ if (depth === '1')
299
+ for (const line of readingsIn(told.length ? told.filter(isCeiling) : [...fold.standing.filter(isCeiling), ...ownCeilings(under)], fold))
300
+ process.stdout.write(`${line}\n`);
301
+ return EXIT.closed;
302
+ }
303
+ const coordinate = join(root, under ?? '', signed.shape);
304
+ return underTheRegions(storeOf(root), [under ?? ''], `view/${name}`, () => {
305
+ mkdirSync(dirname(coordinate), { recursive: true });
306
+ writeFileSync(coordinate, text);
307
+ process.stdout.write(`RENDERED ${relative(root, coordinate).split(sep).join('/')} · view/${name} · ${text.split('\n').length - 1} lines\n`);
308
+ return EXIT.closed;
309
+ });
310
+ };
311
+ return shown(fold);
312
+ }
313
+ async function signVerb(root, rest) {
314
+ const batch = await signedBatch(root, rest, positionalsOf(rest));
315
+ if (batch.kind === 'exit')
316
+ return batch.code;
317
+ if (batch.kind === 'once') {
318
+ process.stderr.write(`ONCE ${batch.proposed.length} lines · every one already landed: nothing to sign\n`);
319
+ return EXIT.closed;
320
+ }
321
+ for (const place of namedPlaces(rest))
322
+ process.stdout.write(`# place ${place}\n`);
323
+ for (const line of batch.signed)
324
+ process.stdout.write(`${line}\n`);
325
+ process.stderr.write(`SIGNED ${batch.signed.length} lines · epoch ${batch.epoch}${namedPlaces(rest).length ? ` · own lines for ${namedPlaces(rest).join(', ')}` : ''} · they land together, before the lock moves\n`);
326
+ return EXIT.closed;
327
+ }
328
+ async function landVerb(root, rest) {
329
+ const lock = resolve(targetFile(root));
330
+ const batch = positionalsOf(rest).filter((one) => {
331
+ const at = resolve(one);
332
+ return existsSync(at) && !lstatSync(at).isDirectory() && at !== lock;
333
+ });
334
+ if (!batch.length) {
335
+ const store = storeOf(root);
336
+ if (existsSync(join(store, 'ledger')) && ledgerLines(store, ownerOf(store)).length) {
337
+ process.stderr.write(`${selfName()}: REFUSE·bootstrap the owner ledger already holds claims; provide an explicit signed batch to change it\n`);
338
+ return EXIT.refuse;
339
+ }
340
+ return firstLand(root, rest);
341
+ }
342
+ const die = (why) => {
343
+ process.stderr.write(`${selfName()}: ${why.startsWith('REFUSE·') ? why : `REFUSE·crash ${why}`}\n`);
344
+ process.exitCode = EXIT.refuse;
345
+ };
346
+ process.once('SIGINT', () => die('SIGINT'));
347
+ process.once('SIGHUP', () => die('SIGHUP'));
348
+ process.once('uncaughtException', (error) => die(error instanceof Error ? error.message : String(error)));
349
+ process.once('unhandledRejection', (error) => die(error instanceof Error ? error.message : String(error)));
350
+ try {
351
+ return await accept(root, rest);
352
+ }
353
+ catch (error) {
354
+ const message = error instanceof Error ? error.message : String(error);
355
+ process.stderr.write(`${selfName()}: ${message.startsWith('REFUSE·') ? message : `REFUSE·crash ${message}`}\n`);
356
+ return EXIT.refuse;
357
+ }
358
+ }
359
+ function foldFile(at) {
360
+ const lines = claimLinesIn(at);
361
+ const folded = foldClaims(lines);
362
+ const fields = folded.standing.flatMap(line => { const got = parseWire(line); return got.kind === 'fact' ? [got.value.fields] : []; });
363
+ const isTopos = fields.some(f => f.scope?.startsWith('region/') && f.measure === 'reads');
364
+ if (isTopos && folded.forks.length)
365
+ throw Error('REFUSE·topos incompatible live claims in declared fold');
366
+ const selected = isTopos ? toposStanding(folded.standing) : undefined;
367
+ if (selected !== undefined) {
368
+ const wire = wireAt(ownLock().flatMap(line => { const got = parseWire(line); return got.kind === 'fact' ? [got.value.fields] : []; }), Number.MAX_SAFE_INTEGER);
369
+ const algorithm = wire?.digests.values().next().value;
370
+ if (!algorithm)
371
+ throw Error('REFUSE·topos instrument names no digest algorithm');
372
+ process.stdout.write(selected.split('\n').filter(Boolean).map(line => 'TOPOS ' + line).join('\n') + '\n');
373
+ process.stdout.write('PIN ' + bootstrapDigest([selected], algorithm) + '\n');
374
+ }
375
+ for (const line of folded.standing)
376
+ process.stdout.write(`${line}\n`);
377
+ for (const fork of folded.forks)
378
+ process.stdout.write(`FORK ${fork.key}${fork.state ? ` · ${fork.state}` : ''}\n`);
379
+ process.stdout.write(`FACT ${folded.standing.length} standing · ${folded.forks.length} forks · ${basename(at)}\n`);
380
+ return EXIT.closed;
381
+ }
382
+ function foldVerb(_root, rest) {
383
+ const files = positionalsOf(rest).filter((one) => existsSync(resolve(one)) && !lstatSync(resolve(one)).isDirectory() && one.endsWith(EXTENSION));
384
+ const places = positionalsOf(rest).filter((one) => existsSync(resolve(one)) && lstatSync(resolve(one)).isDirectory());
385
+ if (files.length === 1 && !places.length && valueOf(rest, FLAGS.as) === undefined) {
386
+ const at = resolve(files[0]);
387
+ if (!at.endsWith(`/${LOCK}`) && at !== LOCK)
388
+ return foldFile(at);
389
+ return foldAt(dirname(at), undefined, rest);
390
+ }
391
+ const under = places[0];
392
+ const tree = files.length === 1 ? dirname(resolve(files[0])) : resolve('.');
393
+ const place = under ? relative(tree, resolve(under)).split(sep).join('/') : '';
394
+ return foldAt(tree, place ? `${place}/` : undefined, rest);
395
+ }
396
+ async function foldAt(root, under, argv) {
397
+ const as = valueOf(argv, FLAGS.as);
398
+ if (as !== NAMED_VIEWS.help && signedOwnerLines(storeOf(root)).length)
399
+ await resolveSources(standingOf(root, under).standing);
400
+ if (as === OBJECT_VIEW) {
401
+ const inputs = positionalsOf(argv).filter(one => existsSync(resolve(one)) && !lstatSync(resolve(one)).isDirectory());
402
+ if (inputs.length > 1)
403
+ throw Error('REFUSE·object one history snapshot per fold');
404
+ const history = inputs.length ? claimLinesIn(inputs[0]) : signedOwnerLines(storeOf(root));
405
+ const held = await admittedObjects(root, history, text => process.stdout.write(text + '\n'));
406
+ const cells = objectFold(held.records, held.context);
407
+ for (const c of cells)
408
+ process.stdout.write('CELL ' + JSON.stringify(c) + '\n');
409
+ process.stdout.write(`FACT object fold ${cells.length} cells\n`);
410
+ return EXIT.closed;
411
+ }
412
+ const key = valueOf(argv, FLAGS.key);
413
+ if (as === undefined) {
414
+ const exit = await pass(root, entryOf(root), under, argv.includes(FLAGS.check));
415
+ if (exit !== 0)
416
+ return exit;
417
+ const history = signedOwnerLines(storeOf(root));
418
+ const { objectHistory } = await import('@lapxo/topos/wire');
419
+ const parsed = objectHistory(history);
420
+ if (parsed.kind !== 'fact')
421
+ throw Error(`REFUSE·wire ${parsed.why}`);
422
+ if (parsed.value.objects.length) {
423
+ const signedCells = [...viewsOf(history).values()].find(one => !one.shape && one.regions.some(region => region.name === 'cells'));
424
+ if (!signedCells)
425
+ throw Error('REFUSE·view object cells require a signed cells view');
426
+ return view(root, under, signedCells.name, [], undefined, undefined, false);
427
+ }
428
+ return exit;
429
+ }
430
+ const named = positionalsOf(argv).filter((one) => existsSync(resolve(one)) && !lstatSync(resolve(one)).isDirectory());
431
+ const coord = positionalsOf(argv).find((one) => one !== under?.replace(/\/$/, '') && !one.startsWith('-') && !(existsSync(resolve(one)) && lstatSync(resolve(one)).isDirectory()));
432
+ if ((as === 'why' || as === NAMED_VIEWS.because || as.split('@')[0] === 'why') && coord !== undefined)
433
+ return view(root, under, as === NAMED_VIEWS.because ? 'why' : as, [], coord, key);
434
+ if (!named.length)
435
+ return view(root, under, as, [], undefined, key);
436
+ const batch = await signedBatch(root, argv, named);
437
+ if (batch.kind === 'exit')
438
+ return batch.code;
439
+ if (batch.kind === 'once')
440
+ process.stdout.write(`ONCE ${batch.proposed.length} lines · every one already landed: the view folds the tree as it stands\n`);
441
+ return view(root, under, as, batch.kind === 'signed' ? batch.signed : [], undefined, key);
442
+ }
443
+ const VERBS = {
444
+ fold: (root, rest) => foldVerb(root, rest),
445
+ land: (root, rest) => landVerb(root, rest),
446
+ sign: (root, rest) => signVerb(root, rest),
447
+ };
448
+ function main(argv) {
449
+ const verb = argv[0] ?? '';
450
+ const run = verb in VERBS ? VERBS[verb] : undefined;
451
+ const places = positionalsOf(run === undefined ? argv : argv.slice(1)).filter((one) => existsSync(resolve(one)) && lstatSync(resolve(one)).isDirectory());
452
+ if (run !== undefined)
453
+ return run(resolve(places[0] ?? '.'), argv.slice(1), argv);
454
+ const under = argv[0] ?? '';
455
+ if (under && !under.startsWith('-') && places.includes(under)) {
456
+ const root = resolve('.');
457
+ const place = relative(root, resolve(under)).split(sep).join('/');
458
+ return foldAt(root, place ? `${place}/` : undefined, argv);
459
+ }
460
+ process.stderr.write(`${selfName()}: ${helpLines().join(' · ')}\n`);
461
+ return EXIT.usage;
462
+ }
463
+ // The same refusal boundary covers synchronous operations and asynchronous processes.
464
+ const refused = (error) => {
465
+ const message = error instanceof Error ? error.message : String(error);
466
+ process.stderr.write(`${selfName()}: ${message.startsWith('REFUSE·') ? message : `REFUSE·crash ${message}`}\n`);
467
+ process.exitCode = EXIT.refuse;
468
+ };
469
+ try {
470
+ const ran = main(process.argv.slice(2));
471
+ if (ran instanceof Promise)
472
+ ran.then((code) => { process.exitCode = code; }).catch(refused);
473
+ else
474
+ process.exitCode = ran;
475
+ }
476
+ catch (error) {
477
+ refused(error);
478
+ }
@@ -0,0 +1,24 @@
1
+ type Side = 'short' | 'over';
2
+ export interface CeilingFold {
3
+ readonly read: readonly string[];
4
+ readonly over: readonly {
5
+ readonly ceiling: string;
6
+ readonly question: string;
7
+ readonly got: string;
8
+ readonly side: Side;
9
+ }[];
10
+ readonly unread: readonly string[];
11
+ readonly vacuous: readonly string[];
12
+ readonly readings: ReadonlyMap<string, readonly string[]>;
13
+ }
14
+ export declare const answersElsewhere: (ceiling: string, own: Readonly<Record<string, number>>, under?: string, places?: ReadonlySet<string>) => boolean;
15
+ export declare function foldCeilings(input: {
16
+ readonly standing: readonly string[];
17
+ readonly ceilings: readonly string[];
18
+ readonly observed: readonly string[];
19
+ readonly own: Readonly<Record<string, number>>;
20
+ readonly epoch: number;
21
+ readonly under?: string;
22
+ readonly places?: ReadonlySet<string>;
23
+ }): CeilingFold;
24
+ export {};
@@ -0,0 +1,129 @@
1
+ import { point, region, within } from '@lapxo/obligations/field';
2
+ import { fieldOf } from "./claims.js";
3
+ import { wordOf, wordsOf } from "./wire.js";
4
+ import { lockStanding } from "./keys.js";
5
+ import { ownLock, ownStore } from "../observe/runner.js";
6
+ import { ALPHABETS, idsOf, INTERVALS, spanOf } from '@lapxo/topos/forms';
7
+ import { placedReadings } from "./observed.js";
8
+ import { combine, matches } from '@lapxo/topos/wire';
9
+ function meeting(scope) {
10
+ const whole = region(scope);
11
+ const steps = scope.split('/');
12
+ const cuts = steps.slice(1).map((_, i) => ({ head: region(steps.slice(0, i + 1).join('/')), tail: region(steps.slice(i + 1).join('/')) }));
13
+ const globbed = steps.some((step) => step.includes('*') && step !== '*' && step !== '**');
14
+ return (reading) => within(reading.asked, whole)
15
+ || cuts.some(({ head, tail }) => (within(reading.asked, head) && within(reading.where, tail))
16
+ || (within(reading.where, head) && within(reading.asked, tail)))
17
+ || (globbed && steps.slice(1).some((_, i) => matches(steps.slice(0, i + 1).join('/'), reading.question) && matches(steps.slice(i + 1).join('/'), reading.where.at.join('/'))));
18
+ }
19
+ export const answersElsewhere = (ceiling, own, under, places) => {
20
+ const measure = fieldOf(ceiling, 'measure');
21
+ const step = fieldOf(ceiling, 'scope').split('/')[0] ?? '';
22
+ const here = under?.split('/')[0];
23
+ return here !== undefined && step !== here && (places?.has(step) ?? false) && (own[measure] ?? (measure === 'count' ? own[fieldOf(ceiling, 'scope')] : undefined)) !== undefined;
24
+ };
25
+ export function foldCeilings(input) {
26
+ const readings = new Map();
27
+ for (const got of placedReadings(input.observed)) {
28
+ const mine = readings.get(got.measure) ?? [];
29
+ mine.push({ question: got.question, measure: got.measure, value: got.value, asked: point(got.question), where: point(got.place) });
30
+ readings.set(got.measure, mine);
31
+ }
32
+ const unreadOf = (standing) => wordsOf(standing, 'states').includes('unread') ? wordOf(standing, 'states', 'unread') : undefined;
33
+ const UNREAD = unreadOf(input.standing) ?? unreadOf(ownLock()) ?? unreadOf(lockStanding(ownStore()));
34
+ const read = [];
35
+ const over = [];
36
+ const unmet = [];
37
+ const lines = new Map();
38
+ for (const one of readings.get('line') ?? []) {
39
+ lines.set(`${one.question} ${one.where.at.join('/')}`, new Map(one.value.split('|').filter(Boolean).map((pair) => [pair.slice(0, pair.lastIndexOf(':')), pair.slice(pair.lastIndexOf(':') + 1)])));
40
+ }
41
+ const said = new Map();
42
+ const bound = (ceiling, question, met, at) => {
43
+ const scope = fieldOf(ceiling, 'scope');
44
+ const want = fieldOf(ceiling, 'value');
45
+ const heard = (got) => void said.set(scope, [...(said.get(scope) ?? []), question === scope ? got : `${question} ${got}`]);
46
+ if (fieldOf(ceiling, 'form') === 'interval') {
47
+ const span = spanOf(want);
48
+ if (!span)
49
+ return;
50
+ const lows = met.map((value) => spanOf(value)?.lo ?? 0);
51
+ let total;
52
+ try {
53
+ total = combine(fieldOf(ceiling, 'measure'), lows);
54
+ }
55
+ catch {
56
+ over.push({ ceiling: scope, question, got: `no combination for ${fieldOf(ceiling, 'measure')}, read ${lows.length} times`, side: 'over' });
57
+ return;
58
+ }
59
+ const at = { lo: total, hi: total };
60
+ heard(String(total));
61
+ if (!INTERVALS.leq(at, { lo: span.lo, hi: Infinity }))
62
+ over.push({ ceiling: scope, question, got: String(total), side: 'short' });
63
+ if (!INTERVALS.leq(at, { lo: -Infinity, hi: span.hi }))
64
+ over.push({ ceiling: scope, question, got: String(total), side: 'over' });
65
+ return;
66
+ }
67
+ const joined = new Set(met.flatMap((value) => [...idsOf(value).values]));
68
+ heard([...joined].sort().join('|') || 'none');
69
+ const allowed = idsOf(want);
70
+ if (!ALPHABETS.leq({ polarity: 'permit', values: joined }, allowed)) {
71
+ const outside = [...joined].filter((id) => allowed.values.has(id) === (allowed.polarity === 'forbid')).sort();
72
+ const line = (id) => (at === undefined || lines.get(at)?.get(id) === undefined ? id : `${id}:${lines.get(at).get(id)}`);
73
+ over.push({ ceiling: scope, question, got: outside.map(line).join('|'), side: 'over' });
74
+ }
75
+ };
76
+ const own = (measure) => (measure === UNREAD ? undefined : input.own[measure]);
77
+ // A coordinate projected into this place is not a narrower question merely
78
+ // because its host prefix adds a segment.
79
+ const localScope = (scope) => input.under && scope.startsWith(input.under)
80
+ ? scope.slice(input.under.length) : scope;
81
+ const literal = (scope) => localScope(scope).split('/').filter((step) => !step.includes('*')).length;
82
+ const last = [];
83
+ for (const ceiling of input.ceilings) {
84
+ const measure = fieldOf(ceiling, 'measure');
85
+ if (measure === UNREAD) {
86
+ last.push(ceiling);
87
+ continue;
88
+ }
89
+ const mine = own(measure) ?? (measure === 'count' ? input.own[fieldOf(ceiling, 'scope')] : undefined);
90
+ if (answersElsewhere(ceiling, input.own, input.under, input.places))
91
+ continue;
92
+ if (mine !== undefined) {
93
+ read.push(ceiling);
94
+ bound(ceiling, fieldOf(ceiling, 'scope'), [`${mine}..${mine}`]);
95
+ continue;
96
+ }
97
+ const byQuestion = new Map();
98
+ const where = new Map();
99
+ const meets = meeting(fieldOf(ceiling, 'scope'));
100
+ const alone = measure === 'per-file' || fieldOf(ceiling, 'scope').endsWith('/*') || fieldOf(ceiling, 'form') === 'alphabet';
101
+ const narrower = spanOf(fieldOf(ceiling, 'value')) ? input.ceilings.filter((other) => fieldOf(other, 'measure') === measure && spanOf(fieldOf(other, 'value')) && literal(fieldOf(other, 'scope')) > literal(fieldOf(ceiling, 'scope'))).map((other) => meeting(fieldOf(other, 'scope'))) : [];
102
+ for (const reading of readings.get(measure) ?? []) {
103
+ if (!meets(reading) || narrower.some((one) => one(reading)))
104
+ continue;
105
+ const question = alone ? reading.where.at.join('/') : reading.question;
106
+ byQuestion.set(question, [...(byQuestion.get(question) ?? []), reading.value]);
107
+ where.set(question, `${reading.question} ${reading.where.at.join('/')}`);
108
+ }
109
+ if (!byQuestion.size) {
110
+ unmet.push(ceiling);
111
+ continue;
112
+ }
113
+ read.push(ceiling);
114
+ for (const [question, met] of byQuestion)
115
+ bound(ceiling, question, met, where.get(question));
116
+ }
117
+ for (const ceiling of last) {
118
+ read.push(ceiling);
119
+ bound(ceiling, fieldOf(ceiling, 'scope'), [`${unmet.length}..${unmet.length}`]);
120
+ }
121
+ const epochOf = (line) => Number(fieldOf(line, 'epoch')) || 0;
122
+ return {
123
+ read,
124
+ over,
125
+ unread: unmet.filter((line) => epochOf(line) > input.epoch - 3),
126
+ vacuous: unmet.filter((line) => epochOf(line) <= input.epoch - 3),
127
+ readings: said,
128
+ };
129
+ }
@@ -0,0 +1,25 @@
1
+ export { CAPSULE, EXTENSION, LOCK, RECEIPTS } from '@lapxo/topos/wire';
2
+ export declare const fieldOf: (line: string, name: string) => string;
3
+ export declare const fieldsOf: (line: string) => Record<string, string>;
4
+ export declare function selfName(): string;
5
+ export declare function isWire(line: string): boolean;
6
+ export declare const wireLinesOf: (text: string | undefined) => readonly string[];
7
+ export declare const isConfig: (line: string) => boolean;
8
+ export declare const sayingOf: (line: string) => string;
9
+ /** One key per claim: scope, role and measure; what a line says, its envelope set aside, is one however often it landed. */
10
+ export declare function claimKey(line: string): string;
11
+ export interface Fork {
12
+ readonly key: string;
13
+ readonly lines: readonly string[];
14
+ readonly state?: string;
15
+ }
16
+ export declare function foldClaims(lines: readonly string[], receipts?: readonly {
17
+ readonly scope: string;
18
+ readonly moved: readonly string[];
19
+ }[], _placed?: boolean): {
20
+ readonly standing: readonly string[];
21
+ readonly superseded: readonly string[];
22
+ readonly retracted: readonly string[];
23
+ readonly forks: readonly Fork[];
24
+ readonly vacuous: readonly string[];
25
+ };