@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.
- package/CONTRIBUTING.md +44 -0
- package/LICENSE +22 -0
- package/README.md +97 -0
- package/TARGET.bound +361 -0
- package/dist/cli/accept.d.ts +23 -0
- package/dist/cli/accept.js +423 -0
- package/dist/cli/args.d.ts +10 -0
- package/dist/cli/args.js +34 -0
- package/dist/cli/host.d.ts +1 -0
- package/dist/cli/host.js +32 -0
- package/dist/cli/names.d.ts +25 -0
- package/dist/cli/names.js +12 -0
- package/dist/cli/observe.d.ts +1 -0
- package/dist/cli/observe.js +6 -0
- package/dist/cli/owner.d.ts +35 -0
- package/dist/cli/owner.js +119 -0
- package/dist/cli/pass.d.ts +6 -0
- package/dist/cli/pass.js +309 -0
- package/dist/cli/place.d.ts +74 -0
- package/dist/cli/place.js +290 -0
- package/dist/cli/takes.d.ts +30 -0
- package/dist/cli/takes.js +108 -0
- package/dist/cli/verb.d.ts +2 -0
- package/dist/cli/verb.js +478 -0
- package/dist/fold/ceilings.d.ts +24 -0
- package/dist/fold/ceilings.js +129 -0
- package/dist/fold/claims.d.ts +25 -0
- package/dist/fold/claims.js +107 -0
- package/dist/fold/closed.d.ts +15 -0
- package/dist/fold/closed.js +168 -0
- package/dist/fold/configures.d.ts +4 -0
- package/dist/fold/configures.js +22 -0
- package/dist/fold/digests.d.ts +29 -0
- package/dist/fold/digests.js +211 -0
- package/dist/fold/evidence.d.ts +32 -0
- package/dist/fold/evidence.js +22 -0
- package/dist/fold/fold-worker.d.ts +1 -0
- package/dist/fold/fold-worker.js +13 -0
- package/dist/fold/kept.d.ts +29 -0
- package/dist/fold/kept.js +142 -0
- package/dist/fold/keys.d.ts +17 -0
- package/dist/fold/keys.js +84 -0
- package/dist/fold/laid.d.ts +17 -0
- package/dist/fold/laid.js +90 -0
- package/dist/fold/nos.d.ts +8 -0
- package/dist/fold/nos.js +48 -0
- package/dist/fold/object.d.ts +17 -0
- package/dist/fold/object.js +38 -0
- package/dist/fold/observed.d.ts +33 -0
- package/dist/fold/observed.js +444 -0
- package/dist/fold/opened.d.ts +5 -0
- package/dist/fold/opened.js +33 -0
- package/dist/fold/own.d.ts +24 -0
- package/dist/fold/own.js +215 -0
- package/dist/fold/paid.d.ts +11 -0
- package/dist/fold/paid.js +92 -0
- package/dist/fold/place-inputs.d.ts +17 -0
- package/dist/fold/place-inputs.js +70 -0
- package/dist/fold/places.d.ts +20 -0
- package/dist/fold/places.js +84 -0
- package/dist/fold/rank.d.ts +3 -0
- package/dist/fold/rank.js +9 -0
- package/dist/fold/reach.d.ts +13 -0
- package/dist/fold/reach.js +65 -0
- package/dist/fold/readers.d.ts +44 -0
- package/dist/fold/readers.js +132 -0
- package/dist/fold/receipts.d.ts +8 -0
- package/dist/fold/receipts.js +47 -0
- package/dist/fold/region.d.ts +72 -0
- package/dist/fold/region.js +286 -0
- package/dist/fold/release-policy.d.ts +67 -0
- package/dist/fold/release-policy.js +103 -0
- package/dist/fold/resolved.d.ts +18 -0
- package/dist/fold/resolved.js +123 -0
- package/dist/fold/roles.d.ts +17 -0
- package/dist/fold/roles.js +59 -0
- package/dist/fold/second.d.ts +18 -0
- package/dist/fold/second.js +68 -0
- package/dist/fold/shards.d.ts +29 -0
- package/dist/fold/shards.js +100 -0
- package/dist/fold/signed.d.ts +18 -0
- package/dist/fold/signed.js +75 -0
- package/dist/fold/signers.d.ts +24 -0
- package/dist/fold/signers.js +58 -0
- package/dist/fold/stranger.d.ts +3 -0
- package/dist/fold/stranger.js +24 -0
- package/dist/fold/views.d.ts +27 -0
- package/dist/fold/views.js +66 -0
- package/dist/fold/wire.d.ts +19 -0
- package/dist/fold/wire.js +41 -0
- package/dist/fold/zoom.d.ts +16 -0
- package/dist/fold/zoom.js +80 -0
- package/dist/host/adapters/keys/ed25519.d.ts +7 -0
- package/dist/host/adapters/keys/ed25519.js +30 -0
- package/dist/host/archive.d.ts +11 -0
- package/dist/host/archive.js +152 -0
- package/dist/host/capsule-process.d.ts +20 -0
- package/dist/host/capsule-process.js +54 -0
- package/dist/host/capsule.d.ts +13 -0
- package/dist/host/capsule.js +149 -0
- package/dist/host/cell-receipts.d.ts +2 -0
- package/dist/host/cell-receipts.js +124 -0
- package/dist/host/digest.d.ts +3 -0
- package/dist/host/digest.js +15 -0
- package/dist/host/hash.d.ts +2 -0
- package/dist/host/hash.js +3 -0
- package/dist/host/io.d.ts +9 -0
- package/dist/host/io.js +9 -0
- package/dist/host/located.d.ts +4 -0
- package/dist/host/located.js +105 -0
- package/dist/host/objects.d.ts +5 -0
- package/dist/host/objects.js +18 -0
- package/dist/host/ports/store.d.ts +2 -0
- package/dist/host/ports/store.js +18 -0
- package/dist/host/provide.d.ts +23 -0
- package/dist/host/provide.js +81 -0
- package/dist/host/release.d.ts +12 -0
- package/dist/host/release.js +213 -0
- package/dist/host/selected-capsules.d.ts +8 -0
- package/dist/host/selected-capsules.js +19 -0
- package/dist/host/selected-topos.d.ts +17 -0
- package/dist/host/selected-topos.js +61 -0
- package/dist/host/source-content.d.ts +10 -0
- package/dist/host/source-content.js +94 -0
- package/dist/host/timing.d.ts +2 -0
- package/dist/host/timing.js +7 -0
- package/dist/judge/late.d.ts +7 -0
- package/dist/judge/late.js +33 -0
- package/dist/judge/take.d.ts +57 -0
- package/dist/judge/take.js +75 -0
- package/dist/judge/widening.d.ts +1 -0
- package/dist/judge/widening.js +25 -0
- package/dist/land/act.d.ts +4 -0
- package/dist/land/act.js +209 -0
- package/dist/land/alias.d.ts +10 -0
- package/dist/land/alias.js +41 -0
- package/dist/land/held.d.ts +7 -0
- package/dist/land/held.js +23 -0
- package/dist/land/inert.d.ts +26 -0
- package/dist/land/inert.js +33 -0
- package/dist/land/leaving.d.ts +14 -0
- package/dist/land/leaving.js +51 -0
- package/dist/land/ledger.d.ts +28 -0
- package/dist/land/ledger.js +148 -0
- package/dist/land/owned.d.ts +23 -0
- package/dist/land/owned.js +65 -0
- package/dist/land/sign.d.ts +16 -0
- package/dist/land/sign.js +30 -0
- package/dist/land/vouched.d.ts +21 -0
- package/dist/land/vouched.js +112 -0
- package/dist/land/withdraw.d.ts +14 -0
- package/dist/land/withdraw.js +24 -0
- package/dist/observe/files.d.ts +14 -0
- package/dist/observe/files.js +68 -0
- package/dist/observe/identity.d.ts +19 -0
- package/dist/observe/identity.js +61 -0
- package/dist/observe/match.d.ts +1 -0
- package/dist/observe/match.js +8 -0
- package/dist/observe/regions.d.ts +29 -0
- package/dist/observe/regions.js +200 -0
- package/dist/observe/run.d.ts +30 -0
- package/dist/observe/run.js +34 -0
- package/dist/observe/runner.d.ts +16 -0
- package/dist/observe/runner.js +83 -0
- package/dist/render/capsule.d.ts +24 -0
- package/dist/render/capsule.js +30 -0
- package/dist/render/field.d.ts +9 -0
- package/dist/render/field.js +46 -0
- package/dist/render/names.d.ts +3 -0
- package/dist/render/names.js +4 -0
- package/dist/render/open.d.ts +27 -0
- package/dist/render/open.js +61 -0
- package/dist/render/page.d.ts +3 -0
- package/dist/render/page.js +7 -0
- package/dist/render/receipts.d.ts +3 -0
- package/dist/render/receipts.js +46 -0
- package/dist/render/shape.d.ts +16 -0
- package/dist/render/shape.js +74 -0
- package/dist/render/target.d.ts +18 -0
- package/dist/render/target.js +52 -0
- package/dist/render/view.d.ts +22 -0
- package/dist/render/view.js +191 -0
- package/docs/readme-assets/atlas.svg +169 -0
- package/docs/usage.md +53 -0
- package/package.json +48 -0
- package/receipts.bound +59 -0
package/dist/fold/own.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { timing } from "../host/timing.js";
|
|
2
|
+
import { basename, existsSync, relative, resolve } from "../host/io.js";
|
|
3
|
+
import { EXTENSION, matches } from '@lapxo/topos/wire';
|
|
4
|
+
import { fieldOf, isWire } from "./claims.js";
|
|
5
|
+
import { wordsOf } from "./wire.js";
|
|
6
|
+
import { inheritedRoles, stepsOutside } from "./roles.js";
|
|
7
|
+
import { writerFor } from "./signers.js";
|
|
8
|
+
import { isCeiling } from "./configures.js";
|
|
9
|
+
import { keyOf } from "./keys.js";
|
|
10
|
+
import { VERB_NAMES } from "../cli/names.js";
|
|
11
|
+
import { actSeconds, idleCount, receiptsSeen } from "./closed.js";
|
|
12
|
+
import { foldsElsewhere } from "./kept.js";
|
|
13
|
+
import { ledgerLines } from "../land/ledger.js";
|
|
14
|
+
import { vouchedCoordinates } from "../land/vouched.js";
|
|
15
|
+
import { placedReadings, readerLines, readerModules } from "./observed.js";
|
|
16
|
+
import { storeDirs } from "../host/ports/store.js";
|
|
17
|
+
import { entriesIn, observeText } from "../observe/files.js";
|
|
18
|
+
import { placesOf, viewsOf } from "./views.js";
|
|
19
|
+
import { declaredBy } from "../observe/regions.js";
|
|
20
|
+
import { REGION_NAMES } from "../render/names.js";
|
|
21
|
+
import { ownLock, ownRoot } from "../observe/runner.js";
|
|
22
|
+
import { namingTheirPlace, zoomedOutsideThePlace } from "./zoom.js";
|
|
23
|
+
import { resetsOnEqualEmits, withoutVectors } from "../observe/identity.js";
|
|
24
|
+
import { instrumentCoordinates } from "./digests.js";
|
|
25
|
+
import { bugsWithoutANo } from "./nos.js";
|
|
26
|
+
import { writtenIn } from "./signed.js";
|
|
27
|
+
import { laidDiffers } from "./laid.js";
|
|
28
|
+
import { heldBytes } from "../land/held.js";
|
|
29
|
+
import { capsuleAt, placesIn, worldAt } from "./places.js";
|
|
30
|
+
const entered = { foreign: 0 };
|
|
31
|
+
/** Host and instrument measurements. Domain contracts require their selected readers; missing readings are not fabricated zeroes. */
|
|
32
|
+
function shipped(root, store, standing, under) {
|
|
33
|
+
const shape = [...viewsOf(standing).values()].flatMap((view) => ('regions' in view && view.shape ? [view.shape] : []))
|
|
34
|
+
.find((coordinate) => coordinate.endsWith(EXTENSION));
|
|
35
|
+
if (shape === undefined)
|
|
36
|
+
return 0;
|
|
37
|
+
const outside = stepsOutside(store);
|
|
38
|
+
const places = under ? [under] : ['', ...entriesIn(resolve(root)).filter((entry) => entry.dir && !outside.has(entry.name)).map((entry) => `${entry.name}/`)];
|
|
39
|
+
const ships = () => wordsOf(standing, 'ships');
|
|
40
|
+
entered.foreign = 0;
|
|
41
|
+
return Math.max(0, ...places.map((place) => (ships().includes(shape) ? (observeText(resolve(root, place, shape)) ?? '').split('\n').filter(isWire).length : 0)));
|
|
42
|
+
}
|
|
43
|
+
function unrenderedIn(root, store, standing, observed) {
|
|
44
|
+
const views = [...viewsOf(standing).values()].flatMap((view) => ('regions' in view && view.shape ? [view] : []));
|
|
45
|
+
const own = new Set(REGION_NAMES);
|
|
46
|
+
const missing = new Set();
|
|
47
|
+
for (const place of ['', ...placesIn(root).map((one) => `${one}/`)]) {
|
|
48
|
+
const declared = declaredBy({ root, store, standing, observed, ...(place ? { under: place } : {}) });
|
|
49
|
+
const here = views.filter((view) => ((at) => (at.length ? at.includes(place) : observeText(resolve(root, place, view.shape)) !== undefined))(placesOf(standing, view)));
|
|
50
|
+
for (const region of here.flatMap((view) => view.regions))
|
|
51
|
+
if (!own.has(region.name) && !declared.some((glob) => matches(glob, region.name)))
|
|
52
|
+
missing.add(`${place}${region.name}`);
|
|
53
|
+
}
|
|
54
|
+
return missing.size;
|
|
55
|
+
}
|
|
56
|
+
function removedByHand(root, store, under) {
|
|
57
|
+
return [...vouchedCoordinates(store).keys()].filter((coordinate) => (!under || coordinate.startsWith(under)) && !existsSync(resolve(root, coordinate))).length;
|
|
58
|
+
}
|
|
59
|
+
function readersOverFirstRun(store) {
|
|
60
|
+
const runs = new Map();
|
|
61
|
+
for (const line of readerLines(store)) {
|
|
62
|
+
const scope = fieldOf(line, 'scope');
|
|
63
|
+
if (!scope.startsWith('cost/reader/') || !/^place:[0-9a-f]{64}$/.test(fieldOf(line, 'at')))
|
|
64
|
+
continue;
|
|
65
|
+
const key = `${scope} ${fieldOf(line, 'by')} ${fieldOf(line, 'at')}`;
|
|
66
|
+
runs.set(key, [...(runs.get(key) ?? []), Number(fieldOf(line, 'value').split('..')[0])]);
|
|
67
|
+
}
|
|
68
|
+
return [...runs.values()].filter((ms) => ms.length > 1 && ms[ms.length - 1] > ms[0]).length;
|
|
69
|
+
}
|
|
70
|
+
function attestBeat(store, standing) {
|
|
71
|
+
const head = keyOf(store, 'attest');
|
|
72
|
+
if (head === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
return middleBeat(store, standing, `${head}/beat`, 'folded', head);
|
|
75
|
+
}
|
|
76
|
+
function twiceWritten(observed) {
|
|
77
|
+
const places = new Map();
|
|
78
|
+
for (const reading of placedReadings(observed)) {
|
|
79
|
+
if (!reading.question.startsWith('concept/'))
|
|
80
|
+
continue;
|
|
81
|
+
places.set(reading.question, (places.get(reading.question) ?? new Set()).add(reading.place));
|
|
82
|
+
}
|
|
83
|
+
return [...places.values()].filter((held) => held.size > 1).length;
|
|
84
|
+
}
|
|
85
|
+
function counted(store, standing, scope, at) {
|
|
86
|
+
const folder = writerFor(standing, 'fold');
|
|
87
|
+
if (folder === undefined)
|
|
88
|
+
return 0;
|
|
89
|
+
return ledgerLines(store, folder)
|
|
90
|
+
.filter((line) => fieldOf(line, 'scope') === scope && /place:(folded|kept)$/.test(fieldOf(line, 'at')))
|
|
91
|
+
.slice(-10)
|
|
92
|
+
.filter((line) => fieldOf(line, 'at') === `place:${at}`).length;
|
|
93
|
+
}
|
|
94
|
+
function middleBeat(store, standing, scope, at, signer) {
|
|
95
|
+
const folder = signer ?? writerFor(standing, 'fold');
|
|
96
|
+
if (folder === undefined)
|
|
97
|
+
return undefined;
|
|
98
|
+
const ms = ledgerLines(store, folder)
|
|
99
|
+
.filter((line) => fieldOf(line, 'scope') === scope && fieldOf(line, 'at') === `place:${at}`)
|
|
100
|
+
.map((line) => Number(fieldOf(line, 'value').split('..')[0]))
|
|
101
|
+
.filter((one) => Number.isFinite(one))
|
|
102
|
+
.slice(-10)
|
|
103
|
+
.sort((a, b) => a - b);
|
|
104
|
+
return ms[Math.floor(ms.length / 2)];
|
|
105
|
+
}
|
|
106
|
+
function alphabetsInCode(observed, standing, under) {
|
|
107
|
+
const signed = standing
|
|
108
|
+
.filter((line) => fieldOf(line, 'form') === 'alphabet')
|
|
109
|
+
.map((line) => fieldOf(line, 'value'))
|
|
110
|
+
.filter((value) => value !== 'withdraw' && !value.startsWith('not:'))
|
|
111
|
+
.map((value) => new Set(value.split('|').filter(Boolean)))
|
|
112
|
+
.filter((alphabet) => alphabet.size > 1);
|
|
113
|
+
const crossing = placedReadings(observed)
|
|
114
|
+
.filter((reading) => reading.question === 'alphabet' && reading.measure === 'id' && (!under || reading.place.startsWith(under)))
|
|
115
|
+
.filter((reading) => signed.some((alphabet) => reading.value.split('|').every((member) => alphabet.has(member))));
|
|
116
|
+
return new Set(crossing.map((reading) => `${reading.place} ${reading.value}`)).size;
|
|
117
|
+
}
|
|
118
|
+
const ceilingsNamed = (standing, name) => standing.filter((line) => isCeiling(line) && (fieldOf(line, 'measure') === name || fieldOf(line, 'scope').split('/').pop() === name));
|
|
119
|
+
const ownFrom = (line, name, value) => {
|
|
120
|
+
if (value === undefined)
|
|
121
|
+
return {};
|
|
122
|
+
const scope = fieldOf(line ?? '', 'scope');
|
|
123
|
+
return { [name]: value, ...(scope && scope !== name ? { [scope]: value } : {}) };
|
|
124
|
+
};
|
|
125
|
+
const exporting = (observed, under, names) => new Set(placedReadings(observed).filter((one) => one.place.startsWith(under) && names.some((name) => one.question === `export/${name}`)).map((one) => one.place)).size;
|
|
126
|
+
export const outsideTheirCapsule = (root, standing, lines) => {
|
|
127
|
+
const family = wordsOf(standing, 'families').find((one) => one === 'reader' || one === 'readers');
|
|
128
|
+
return lines.filter((line) => (family === undefined || fieldOf(line, 'scope').startsWith(`${family}/`))
|
|
129
|
+
&& fieldOf(line, 'measure') === 'reader' && fieldOf(line, 'value') !== 'withdraw'
|
|
130
|
+
&& ((head) => !fieldOf(line, 'value').includes('/') || (capsuleAt(root, head) && !worldAt(root, head)))(fieldOf(line, 'value').split('/')[0] ?? ''));
|
|
131
|
+
};
|
|
132
|
+
const TREE = new Set(['readers-over-their-first-run', 'stale-capsules', 'accept-line',
|
|
133
|
+
'regions-described', 'walks-prune-by-role',
|
|
134
|
+
'ledger-region-verdicts-served-from-a-kept-fold', 'kept-folds-read-under-another-instrument', 'accepts-that-refolded',
|
|
135
|
+
'readers-without-vectors', 'lines-that-name-their-place', 'memo-resets-on-unchanged-extension', 'status-for-no-effect', 'accept-ms',
|
|
136
|
+
'bodies-written-twice', 'world-lines-outside-their-capsule']);
|
|
137
|
+
const placeOnly = (readings, under) => Object.fromEntries(Object.entries(readings).flatMap(([measure, value]) => (value === undefined || (under && TREE.has(measure)) ? [] : [[measure, value]])));
|
|
138
|
+
/**
|
|
139
|
+
* What the instrument reads of itself rather than through a reader: how many programs its source opens, how many
|
|
140
|
+
* signed lines stand unadmitted, how many stores it keeps, which of the modules it runs are no source, how many samples
|
|
141
|
+
* nothing names, how many alphabets are spelled both in code and in the lock, and how many regions it writes that no
|
|
142
|
+
* line of the lock describes, how many bodies are written twice anywhere in the tree rather than once and used by
|
|
143
|
+
* digest, and whether the version a package carries is the one the lock names. Inside one package, only what lies
|
|
144
|
+
* there. A cost is read now and never read back from the fold it was taken with: a beat is a line of the ledger.
|
|
145
|
+
*/
|
|
146
|
+
export function costReadings(store, standing, under) {
|
|
147
|
+
return placeOnly({
|
|
148
|
+
'beat-kept': middleBeat(store, standing, `beat/${under ?? '.'}`, 'kept'),
|
|
149
|
+
'accept-line': middleBeat(store, standing, 'beat/accept', 'line'),
|
|
150
|
+
'accept-ms': middleBeat(store, standing, 'beat/accept', 'total'),
|
|
151
|
+
'accepts-that-refolded': counted(store, standing, 'beat/accept', 'folded'),
|
|
152
|
+
'test-kept': under ? middleBeat(store, standing, `beat/${under}`, 'kept') : undefined,
|
|
153
|
+
'programs-place': under ? middleBeat(store, standing, `beat/${under}`, 'command-kept') : undefined,
|
|
154
|
+
...(keyOf(store, 'attest') === undefined ? {} : { [`${keyOf(store, 'attest')}-ms`]: under ? undefined : attestBeat(store, standing) }),
|
|
155
|
+
}, under);
|
|
156
|
+
}
|
|
157
|
+
export function ownReadings(input) {
|
|
158
|
+
const { root, store, entry, roleOf, standing, observed, under } = input;
|
|
159
|
+
const spent = [];
|
|
160
|
+
const took = timing(spent);
|
|
161
|
+
const instrument = under === undefined || under === `${basename(ownRoot())}/`;
|
|
162
|
+
const tree = (fn) => (under ? undefined : fn());
|
|
163
|
+
const imported = tree(() => took('imports', () => [...instrumentCoordinates(store, resolve(root, entry), standing, root, ownLock()).keys()]
|
|
164
|
+
.filter((coordinate) => capsuleAt(root, relative(root, coordinate).split('/')[0] ?? '')).length));
|
|
165
|
+
const twice = tree(() => took('bodies', () => twiceWritten(observed)));
|
|
166
|
+
const local = {
|
|
167
|
+
'orphan-samples': input.orphans,
|
|
168
|
+
'removed-by-hand': took('removed', () => removedByHand(root, store, under)),
|
|
169
|
+
'readers-over-their-first-run': tree(() => took('readers', () => readersOverFirstRun(store))),
|
|
170
|
+
'alphabet-in-code': took('alphabets', () => alphabetsInCode(observed, standing, under)),
|
|
171
|
+
'stale-capsules': tree(() => took('capsules', () => inheritedRoles(store).stale.length)),
|
|
172
|
+
'beat-kept': middleBeat(store, standing, `beat/${under ?? '.'}`, 'kept'),
|
|
173
|
+
'accept-line': tree(() => middleBeat(store, standing, 'beat/accept', 'line')),
|
|
174
|
+
'accept-ms': tree(() => middleBeat(store, standing, 'beat/accept', 'total')),
|
|
175
|
+
'test-kept': under ? middleBeat(store, standing, `beat/${under}`, 'kept') : undefined,
|
|
176
|
+
'programs-place': under ? middleBeat(store, standing, `beat/${under}`, 'command-kept') : undefined,
|
|
177
|
+
'regions-described': tree(() => took('regions', () => unrenderedIn(root, store, standing, observed))),
|
|
178
|
+
'readers-without-vectors': tree(() => took('vectors', () => withoutVectors(root, readerModules(store), standing))),
|
|
179
|
+
'lines-that-name-their-place': tree(() => took('zoom', () => namingTheirPlace(root))),
|
|
180
|
+
...(instrument ? {
|
|
181
|
+
'bugs-without-a-no': took('nos', () => bugsWithoutANo(root, store, standing, basename(ownRoot()), 'bugs-without-a-no')),
|
|
182
|
+
'zoom-rewrites-outside-the-place': took('zoomed', () => zoomedOutsideThePlace(root, standing)),
|
|
183
|
+
'own-lines-written-in': took('applied', () => writtenIn(root, basename(ownRoot()))),
|
|
184
|
+
'laid-digest-differs': took('laid', () => laidDiffers(store, ownRoot(), ownLock(), writerFor(standing, 'fold'))),
|
|
185
|
+
} : {}),
|
|
186
|
+
'memo-resets-on-unchanged-extension': tree(() => took('resets', () => resetsOnEqualEmits(readerLines(store)))),
|
|
187
|
+
...ownFrom(ceilingsNamed(standing, 'capsules-imported').find((line) => fieldOf(line, 'form') === 'interval'), 'capsules-imported', imported),
|
|
188
|
+
...((n) => ({ bytes: n, 'store/bytes': n }))(took('store', () => heldBytes(root, store, standing))),
|
|
189
|
+
'walks-prune-by-role': entered.foreign,
|
|
190
|
+
...ownFrom(ceilingsNamed(standing, 'bodies-written-twice').find((line) => fieldOf(line, 'form') === 'interval'), 'bodies-written-twice', twice),
|
|
191
|
+
...Object.fromEntries(standing.filter((line) => isCeiling(line) && (fieldOf(line, 'measure') === 'render-modules' || fieldOf(line, 'scope').split('/').pop() === 'render-modules'))
|
|
192
|
+
.flatMap((line) => ((place) => place === undefined ? [] : [[fieldOf(line, 'scope'), tree(() => exporting(observed, `${place}/`, ['render']))]])(fieldOf(line, 'scope').split('/').find((step) => capsuleAt(root, step))))),
|
|
193
|
+
...Object.fromEntries(standing.filter((line) => isCeiling(line) && (fieldOf(line, 'measure') === 'reader-modules' || fieldOf(line, 'scope').split('/').pop() === 'reader-modules'))
|
|
194
|
+
.flatMap((line) => ((place) => place === undefined ? [] : [[fieldOf(line, 'scope'), tree(() => exporting(observed, `${place}/`, ['observe', 'run']))]])(fieldOf(line, 'scope').split('/').find((step) => capsuleAt(root, step))))),
|
|
195
|
+
...ownFrom(ceilingsNamed(standing, 'world-lines-outside-their-capsule').find((line) => fieldOf(line, 'form') === 'interval'), 'world-lines-outside-their-capsule', tree(() => outsideTheirCapsule(root, standing, standing).length)),
|
|
196
|
+
idle: idleCount(),
|
|
197
|
+
s: actSeconds(),
|
|
198
|
+
'cli-verbs': VERB_NAMES.length,
|
|
199
|
+
'walk-read': receiptsSeen().count,
|
|
200
|
+
'walk-ms': receiptsSeen().ms,
|
|
201
|
+
'kept-folds-read-under-another-instrument': tree(() => foldsElsewhere()),
|
|
202
|
+
'accepts-that-refolded': tree(() => counted(store, standing, 'beat/accept', 'folded')),
|
|
203
|
+
...(keyOf(store, 'attest') === undefined ? {} : { [`${keyOf(store, 'attest')}-ms`]: tree(() => attestBeat(store, standing)) }),
|
|
204
|
+
ship: took('ship', () => shipped(root, store, standing, under)),
|
|
205
|
+
};
|
|
206
|
+
process.stderr.write(`OWN ${spent.join(' ms · ')} ms\n`);
|
|
207
|
+
if (under)
|
|
208
|
+
return placeOnly(local, under);
|
|
209
|
+
return {
|
|
210
|
+
...placeOnly(local),
|
|
211
|
+
unadmitted: input.unadmitted,
|
|
212
|
+
dirs: storeDirs(store),
|
|
213
|
+
'artifacts-in-loop': [entry, ...readerModules(store)].filter((coordinate) => roleOf(coordinate) !== 'source').length,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { state } from '@lapxo/obligations/views/field';
|
|
2
|
+
import type { PlaceFold } from '../cli/place.ts';
|
|
3
|
+
/** Where a case lies: a demand is judged only over a cone that holds one, by a take or read from a landed cone alike. */
|
|
4
|
+
export declare const holdsCases: (location: string) => boolean;
|
|
5
|
+
export declare function paidOf(store: string, demands: readonly string[], observed: readonly string[], views: string): {
|
|
6
|
+
readonly paid: readonly string[];
|
|
7
|
+
readonly missing: readonly string[];
|
|
8
|
+
readonly answered: ReadonlySet<string>;
|
|
9
|
+
};
|
|
10
|
+
export type Verdict = ReturnType<typeof state>;
|
|
11
|
+
export declare const verdictsFor: (fold: PlaceFold) => Readonly<Record<string, Verdict>>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { answers, point, region, within } from '@lapxo/obligations/field';
|
|
2
|
+
import { alphabetForm } from '@lapxo/obligations/forms';
|
|
3
|
+
import { cell, mark, observe, state } from '@lapxo/obligations/views/field';
|
|
4
|
+
import { fieldOf } from "./claims.js";
|
|
5
|
+
import { boundOf, regionsNamed } from "./region.js";
|
|
6
|
+
import { wordFrom } from "./wire.js";
|
|
7
|
+
import { lockStanding } from "./keys.js";
|
|
8
|
+
import { ledgerLines, storeAt } from "../land/ledger.js";
|
|
9
|
+
import { observeText } from "../observe/files.js";
|
|
10
|
+
import { memoized } from "./digests.js";
|
|
11
|
+
/** Where a case lies: a demand is judged only over a cone that holds one, by a take or read from a landed cone alike. */
|
|
12
|
+
export const holdsCases = (location) => {
|
|
13
|
+
const steps = location.split('/');
|
|
14
|
+
return steps.includes('yes') || steps.includes('no');
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* A cone answers for a region it covers as much as for one it lies inside: the same region read at two resolutions
|
|
18
|
+
* (L09). A fact about a region a demand never asked about pays nothing, a demand every asked coordinate of which was
|
|
19
|
+
* judged is paid even when the take that judged it reached further, and an answer pays only what its take judged.
|
|
20
|
+
*/
|
|
21
|
+
function pays(demand, answer, views) {
|
|
22
|
+
if (answer.value !== (fieldOf(demand, 'value') || 'present'))
|
|
23
|
+
return false;
|
|
24
|
+
const needs = fieldOf(demand, 'needs').split('|').filter(Boolean);
|
|
25
|
+
if (answer.read && !fieldOf(demand, 'scope').startsWith(views) && !needs.some(holdsCases))
|
|
26
|
+
return false;
|
|
27
|
+
const asked = needs.map((need) => region(need));
|
|
28
|
+
if (answer.judged && !asked.every((where) => answer.judged?.some((judged) => within(where, region(judged)))))
|
|
29
|
+
return false;
|
|
30
|
+
const moved = answer.cone.split('|').filter(Boolean).map((coordinate) => point(coordinate));
|
|
31
|
+
if (!moved.length)
|
|
32
|
+
return true;
|
|
33
|
+
return answers(moved, asked) || (asked.length > 0 && asked.every((where) => moved.some((coordinate) => within(coordinate, where))));
|
|
34
|
+
}
|
|
35
|
+
function judgedBy(store, scope, at) {
|
|
36
|
+
if (!at.startsWith('receipt:sha256:'))
|
|
37
|
+
return undefined;
|
|
38
|
+
const held = memoized(store, `judged ${at}`, () => {
|
|
39
|
+
const text = observeText(storeAt(store, 'cas', 'envelopes', `${at.replace(/^receipt:sha256:/, '')}.json`));
|
|
40
|
+
const got = text === undefined ? {} : JSON.parse(text);
|
|
41
|
+
return JSON.stringify(Array.isArray(got.cone) ? [got.scope ?? '', ...got.cone] : []);
|
|
42
|
+
});
|
|
43
|
+
const [said, ...cone] = JSON.parse(held);
|
|
44
|
+
return said === scope ? cone : undefined;
|
|
45
|
+
}
|
|
46
|
+
export function paidOf(store, demands, observed, views) {
|
|
47
|
+
const wanted = new Set(demands.map((line) => fieldOf(line, 'scope')));
|
|
48
|
+
const seen = [...ledgerLines(store, 'judge'), ...observed].filter((line) => wanted.has(fieldOf(line, 'scope')));
|
|
49
|
+
const cones = new Map();
|
|
50
|
+
for (const line of seen)
|
|
51
|
+
if (fieldOf(line, 'measure') === 'cone')
|
|
52
|
+
cones.set(`${fieldOf(line, 'scope')} ${fieldOf(line, 'at')}`, fieldOf(line, 'value'));
|
|
53
|
+
const answered = new Map();
|
|
54
|
+
for (const line of seen) {
|
|
55
|
+
const scope = fieldOf(line, 'scope');
|
|
56
|
+
if (!scope || fieldOf(line, 'measure') === 'cone')
|
|
57
|
+
continue;
|
|
58
|
+
const judged = judgedBy(store, scope, fieldOf(line, 'at'));
|
|
59
|
+
(answered.get(scope) ?? answered.set(scope, []).get(scope)).push({
|
|
60
|
+
value: fieldOf(line, 'value'), cone: cones.get(`${scope} ${fieldOf(line, 'at')}`) ?? '', read: fieldOf(line, 'at').startsWith('cone:'), ...(judged ? { judged } : {}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const paid = demands.filter((line) => (answered.get(fieldOf(line, 'scope')) ?? []).some((answer) => pays(line, answer, views)));
|
|
64
|
+
return { paid, missing: demands.filter((line) => !paid.includes(line)), answered: new Set(answered.keys()) };
|
|
65
|
+
}
|
|
66
|
+
const HELD = { tokens: ['absent', 'present'] };
|
|
67
|
+
const heldAs = (holds) => alphabetForm.parse(HELD, { want: [holds ? 'present' : 'absent'] });
|
|
68
|
+
/**
|
|
69
|
+
* The judge's verdict on each demand, as the object judges a cell: the demand is the floor its place requires, and the
|
|
70
|
+
* two heads are the origins that speak on it — the take, holding it or not where it answered or paid it, and the second
|
|
71
|
+
* head, holding what the take holds where it agrees, the other where the demand's region stands apart, and silent where
|
|
72
|
+
* its word is stale, grey or missing. Both holding it is FREE, one alone REQUIRED, their disagreeing CONFLICT, and
|
|
73
|
+
* neither admitting it FORBIDDEN.
|
|
74
|
+
*/
|
|
75
|
+
function verdictsOf(demands, paid, answered, second, standing) {
|
|
76
|
+
const named = regionsNamed(standing);
|
|
77
|
+
const apart = new Set((second.apart ?? []).map((one) => (one === '.' ? '' : one)));
|
|
78
|
+
const heard = second.verdict === 'agrees' || second.verdict === 'forks';
|
|
79
|
+
return Object.fromEntries(demands.map((line) => {
|
|
80
|
+
const scope = fieldOf(line, 'scope');
|
|
81
|
+
const holds = paid.includes(line);
|
|
82
|
+
const spoke = holds || answered.has(scope);
|
|
83
|
+
const differs = second.verdict === 'forks' && (second.apart === undefined || apart.has(boundOf(named, line).region));
|
|
84
|
+
let one = { ...cell(scope), marks: [mark({ pole: 'floor', reach: 'local', span: heldAs(true) })] };
|
|
85
|
+
if (spoke)
|
|
86
|
+
one = observe(one, { origin: 'take', span: heldAs(holds), id: 'take' });
|
|
87
|
+
if (spoke && heard)
|
|
88
|
+
one = observe(one, { origin: 'second', span: heldAs(differs ? !holds : holds), id: 'second' });
|
|
89
|
+
return [scope, state(alphabetForm.lattice(HELD), one)];
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
export const verdictsFor = (fold) => verdictsOf(fold.demands, fold.paid, paidOf(fold.store, fold.demands, fold.observed, `${wordFrom(fold.standing, 'families', 'view', lockStanding(fold.store))}/`).answered, fold.second, fold.standing);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type View } from '../fold/views.ts';
|
|
2
|
+
import type { PlaceFold } from '../cli/place.ts';
|
|
3
|
+
/**
|
|
4
|
+
* A place seen from inside it: the wire and the keys it is read with, the regions it is written in, and every line of
|
|
5
|
+
* the place — its manifest, its build, its pages, its prose, its own ceilings and every line a view of it reads. Nothing
|
|
6
|
+
* of the tree's history, and nothing the instrument keeps for itself, crosses; a need outside the place is left behind.
|
|
7
|
+
* A capsule renders a place from these lines, in the tree as in the place published alone, so both write one coordinate; the
|
|
8
|
+
* lock a place is rendered with is these lines as target lines its first land signs at epoch one, the bootstrap first.
|
|
9
|
+
* A view is the place's own: a line of it that names coordinates is taken when one of them is a coordinate of the place, and a
|
|
10
|
+
* line that names none where its view names this place or no place at all, or at a world that holds no line of a view
|
|
11
|
+
* the views/world list names. A line another published place leads is taken only by its needs.
|
|
12
|
+
*/
|
|
13
|
+
export declare function takenFor(held: readonly string[], under: string, world?: {
|
|
14
|
+
readonly own: readonly string[];
|
|
15
|
+
readonly tree: ReadonlyMap<string, View>;
|
|
16
|
+
}): (line: string) => boolean;
|
|
17
|
+
export declare function placeLines(fold: Pick<PlaceFold, 'root' | 'standing' | 'under'>): readonly string[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { canonical, matches } from '@lapxo/topos/wire';
|
|
2
|
+
import { fieldOf, fieldsOf } from "../fold/claims.js";
|
|
3
|
+
import { lockStanding } from "../fold/keys.js";
|
|
4
|
+
import { worldAt } from "../fold/places.js";
|
|
5
|
+
import { defaulted, placesOf, viewsOf } from "../fold/views.js";
|
|
6
|
+
import { zoomed } from "../fold/zoom.js";
|
|
7
|
+
import { ownLockOf } from "../fold/signed.js";
|
|
8
|
+
import { wordFrom, wireLine } from "../fold/wire.js";
|
|
9
|
+
import { sourced } from "../land/vouched.js";
|
|
10
|
+
import { nameOf } from "../render/page.js";
|
|
11
|
+
/** Ephemeral view input, never a signed source record or publication artifact. */
|
|
12
|
+
const unsigned = (line) => {
|
|
13
|
+
const fields = fieldsOf(line);
|
|
14
|
+
delete fields['sig'];
|
|
15
|
+
delete fields['epoch'];
|
|
16
|
+
return { ...fields, by: 'target' };
|
|
17
|
+
};
|
|
18
|
+
import { ownLock, ownStore } from "../observe/runner.js";
|
|
19
|
+
/**
|
|
20
|
+
* A place seen from inside it: the wire and the keys it is read with, the regions it is written in, and every line of
|
|
21
|
+
* the place — its manifest, its build, its pages, its prose, its own ceilings and every line a view of it reads. Nothing
|
|
22
|
+
* of the tree's history, and nothing the instrument keeps for itself, crosses; a need outside the place is left behind.
|
|
23
|
+
* A capsule renders a place from these lines, in the tree as in the place published alone, so both write one coordinate; the
|
|
24
|
+
* lock a place is rendered with is these lines as target lines its first land signs at epoch one, the bootstrap first.
|
|
25
|
+
* A view is the place's own: a line of it that names coordinates is taken when one of them is a coordinate of the place, and a
|
|
26
|
+
* line that names none where its view names this place or no place at all, or at a world that holds no line of a view
|
|
27
|
+
* the views/world list names. A line another published place leads is taken only by its needs.
|
|
28
|
+
*/
|
|
29
|
+
export function takenFor(held, under, world) {
|
|
30
|
+
const views = new Map([...viewsOf(held).values()].flatMap((view) => ('regions' in view ? [[view.name, view]] : [])));
|
|
31
|
+
const inside = (need) => need.startsWith(under);
|
|
32
|
+
const crosses = ownLock().filter((line) => fieldOf(line, 'scope') === 'publish/takes').flatMap((line) => fieldOf(line, 'value').split('|').filter(Boolean));
|
|
33
|
+
const kept = new Set([sourced(held), wordFrom(held, 'families', 'reader', lockStanding(ownStore())), 'publish']);
|
|
34
|
+
const places = new Set(held.map((line) => fieldOf(line, 'scope').split('/')).filter((steps) => steps.length === 3 && steps[0] === 'publish' && steps[2] === 'bootstrap').map((steps) => steps[1]));
|
|
35
|
+
return (line) => {
|
|
36
|
+
const scope = fieldOf(line, 'scope');
|
|
37
|
+
const needs = fieldOf(line, 'needs').split('|').filter(Boolean);
|
|
38
|
+
if (crosses.some((glob) => matches(glob, scope)))
|
|
39
|
+
return true;
|
|
40
|
+
if (scope.startsWith(under))
|
|
41
|
+
return true;
|
|
42
|
+
if (scope.startsWith('view/')) {
|
|
43
|
+
const view = views.get(scope.slice('view/'.length));
|
|
44
|
+
if (view === undefined || view.shape === '')
|
|
45
|
+
return false;
|
|
46
|
+
const listed = wireLine(held, 'views/world');
|
|
47
|
+
if ((listed === undefined ? [] : fieldOf(listed, 'value').split('|').filter(Boolean)).includes(view.name))
|
|
48
|
+
return world !== undefined && defaulted(held, view, world.own, world.tree);
|
|
49
|
+
return (world !== undefined && defaulted(held, view, world.own, world.tree)) || (needs.length ? needs.some(inside) : placesOf(held, view).includes(under) || !placesOf(held, view).length);
|
|
50
|
+
}
|
|
51
|
+
const first = scope.split('/')[0] ?? '';
|
|
52
|
+
if (kept.has(first))
|
|
53
|
+
return false;
|
|
54
|
+
return (!places.has(first) && scope.includes(`/${under}`)) || (needs.length > 0 && needs.every(inside));
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function placeLines(fold) {
|
|
58
|
+
const place = nameOf(fold);
|
|
59
|
+
const under = `${place}/`;
|
|
60
|
+
const held = fold.standing.filter((line) => fieldOf(line, 'value') !== 'withdraw');
|
|
61
|
+
const own = ownLockOf(fold.root, place);
|
|
62
|
+
const taken = takenFor(held, under, worldAt(fold.root, place) ? { own, tree: viewsOf(ownLockOf(fold.root, '')) } : undefined);
|
|
63
|
+
const rooted = (line) => canonical(unsigned(zoomed(line, place)));
|
|
64
|
+
const key = (line) => ['scope', 'role', 'measure'].map((field) => fieldOf(line, field)).join(' ');
|
|
65
|
+
const mine = own.filter((line) => !fieldOf(line, 'scope').startsWith('keys/')).map((line) => canonical(unsigned(line)));
|
|
66
|
+
const ruled = new Set(mine.map(key));
|
|
67
|
+
const merged = [...mine, ...held.filter(taken).map(rooted).filter((line) => !ruled.has(key(line)))];
|
|
68
|
+
const scopes = new Set(merged.map((line) => fieldOf(line, 'scope')));
|
|
69
|
+
return [...new Set(merged.filter((line) => !fieldOf(line, 'condition') || scopes.has(fieldOf(line, 'condition'))))];
|
|
70
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type Spoken = {
|
|
2
|
+
readonly place: string;
|
|
3
|
+
readonly line: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const placesIn: (root: string) => readonly string[];
|
|
6
|
+
/** A directory with no own lock is founded by the root: a line of its region family names the directory's coordinates, and a covered batch that names the place lands its first own lock. */
|
|
7
|
+
export declare const foundableIn: (root: string, standing: readonly string[], family: string, name: string) => boolean;
|
|
8
|
+
export declare const capsuleAt: (root: string, name: string) => boolean;
|
|
9
|
+
/** A world is a place whose own lock names capsule/key. A view named by wire/views/world resolves into these and no other place. */
|
|
10
|
+
export declare const worldAt: (root: string, name: string) => boolean;
|
|
11
|
+
export declare const worldsIn: (root: string) => readonly string[];
|
|
12
|
+
export declare function kindsOf(root: string, place: string): readonly string[];
|
|
13
|
+
type ClassDefault = {
|
|
14
|
+
readonly classes: readonly string[];
|
|
15
|
+
readonly line: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function classDefaults(root: string): readonly ClassDefault[];
|
|
18
|
+
export declare const fromPlace: (place: string, places: readonly string[]) => (path: string) => string;
|
|
19
|
+
export declare function spoken(root: string): readonly Spoken[];
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { join } from "../host/io.js";
|
|
2
|
+
import { CAPSULE, canonical, LOCK, matches, parse } from '@lapxo/topos/wire';
|
|
3
|
+
import { fieldOf } from "./claims.js";
|
|
4
|
+
import { isCeiling } from "./configures.js";
|
|
5
|
+
import { entriesIn, observeText } from "../observe/files.js";
|
|
6
|
+
import { ownLock } from "../observe/runner.js";
|
|
7
|
+
import { said } from "./wire.js";
|
|
8
|
+
import { ownLockOf } from "./signed.js";
|
|
9
|
+
export const placesIn = (root) => entriesIn(root)
|
|
10
|
+
.filter((entry) => entry.dir && observeText(join(root, entry.name, LOCK)) !== undefined).map((entry) => entry.name).sort();
|
|
11
|
+
/** A directory with no own lock is founded by the root: a line of its region family names the directory's coordinates, and a covered batch that names the place lands its first own lock. */
|
|
12
|
+
export const foundableIn = (root, standing, family, name) => !placesIn(root).includes(name)
|
|
13
|
+
&& (entriesIn(root).find((entry) => entry.name === name)?.dir ?? false)
|
|
14
|
+
&& standing.some((line) => fieldOf(line, 'scope') === `${family}/${name}` && fieldOf(line, 'measure') === 'coordinates' && fieldOf(line, 'value').split('|').includes(`${name}/**`));
|
|
15
|
+
export const capsuleAt = (root, name) => [LOCK, CAPSULE]
|
|
16
|
+
.some((lock) => (observeText(join(root, name, lock)) ?? '').split('\n').some((line) => fieldOf(line, 'scope').startsWith('capsule/')));
|
|
17
|
+
/** A world is a place whose own lock names capsule/key. A view named by wire/views/world resolves into these and no other place. */
|
|
18
|
+
export const worldAt = (root, name) => [LOCK, CAPSULE]
|
|
19
|
+
.some((lock) => (observeText(join(root, name, lock)) ?? '').split('\n').some((line) => fieldOf(line, 'scope') === 'capsule/key' && fieldOf(line, 'value') !== 'withdraw'));
|
|
20
|
+
export const worldsIn = (root) => placesIn(root).filter((name) => worldAt(root, name));
|
|
21
|
+
export function kindsOf(root, place) {
|
|
22
|
+
const lock = ownLockOf(root, place);
|
|
23
|
+
const kinds = [];
|
|
24
|
+
if (worldAt(root, place))
|
|
25
|
+
kinds.push('world');
|
|
26
|
+
const saidKind = lock.find((line) => fieldOf(line, 'scope') === 'place/kind' && fieldOf(line, 'value') !== 'withdraw');
|
|
27
|
+
if (saidKind !== undefined)
|
|
28
|
+
kinds.push(...fieldOf(saidKind, 'value').split('|').filter(Boolean));
|
|
29
|
+
return [...new Set(kinds)];
|
|
30
|
+
}
|
|
31
|
+
export function classDefaults(root) {
|
|
32
|
+
return placesIn(root).flatMap((place) => {
|
|
33
|
+
const lock = ownLockOf(root, place);
|
|
34
|
+
return lock.filter((line) => fieldOf(line, 'scope').startsWith('class/') && fieldOf(line, 'value') !== 'withdraw')
|
|
35
|
+
.flatMap((line) => {
|
|
36
|
+
const kind = fieldOf(line, 'scope').slice('class/'.length);
|
|
37
|
+
const named = new Set(fieldOf(line, 'value').split('|').filter(Boolean));
|
|
38
|
+
return lock.filter((one) => named.has(fieldOf(one, 'scope')) && isCeiling(one))
|
|
39
|
+
.map((audit) => ({ classes: kind === '' ? [] : [kind], line: audit }));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export const fromPlace = (place, places) => (path) => (path === './' ? `${place}/` : places.some((one) => path.startsWith(`${one}/`)) ? path : `${place}/${path}`);
|
|
44
|
+
/**
|
|
45
|
+
* What each place's own lock says for the tree, in the tree's names: the families bound's own lock lists, every need
|
|
46
|
+
* and every reader's module read from the place, and the scopes it lists under a place written with the place after
|
|
47
|
+
* the steps its glob names. A view the place writes and no need names is the place's own coordinate.
|
|
48
|
+
*/
|
|
49
|
+
const heard = new Map();
|
|
50
|
+
export function spoken(root) {
|
|
51
|
+
const places = placesIn(root);
|
|
52
|
+
const parts = [ownLock(), ...places.map((place) => ownLockOf(root, place))];
|
|
53
|
+
const last = heard.get(root);
|
|
54
|
+
if (last !== undefined && last.parts.length === parts.length && last.parts.every((part, i) => part === parts[i]))
|
|
55
|
+
return last.got;
|
|
56
|
+
const got = speakingOf(root, places);
|
|
57
|
+
heard.set(root, { parts, got });
|
|
58
|
+
return got;
|
|
59
|
+
}
|
|
60
|
+
function speakingOf(root, places) {
|
|
61
|
+
const speaks = said(ownLock(), 'place/speaks');
|
|
62
|
+
const under = said(ownLock(), 'place/speaks-under');
|
|
63
|
+
if (!speaks.length && !under.length)
|
|
64
|
+
return [];
|
|
65
|
+
return places.flatMap((place) => ownLockOf(root, place).flatMap((line) => {
|
|
66
|
+
const got = parse(line);
|
|
67
|
+
const scope = fieldOf(line, 'scope');
|
|
68
|
+
const steps = scope.split('/');
|
|
69
|
+
const cut = under.flatMap((glob) => steps.map((_, i) => i + 1).filter((i) => i < steps.length && matches(glob, steps.slice(0, i).join('/')))).sort((a, b) => a - b)[0];
|
|
70
|
+
if (got.kind !== 'fact' || (cut === undefined && !speaks.some((glob) => matches(glob, scope))))
|
|
71
|
+
return [];
|
|
72
|
+
const fields = { ...got.value.fields };
|
|
73
|
+
const path = fromPlace(place, places);
|
|
74
|
+
if (cut !== undefined && steps[cut] !== place)
|
|
75
|
+
fields['scope'] = [...steps.slice(0, cut), place, ...steps.slice(cut)].join('/');
|
|
76
|
+
if (fields['needs'])
|
|
77
|
+
fields['needs'] = fields['needs'].split('|').filter(Boolean).map(path).join('|');
|
|
78
|
+
else if (fields['shape'] && fields['role'] === 'demands')
|
|
79
|
+
fields['needs'] = path(fields['shape']);
|
|
80
|
+
if (fields['measure'] === 'reader' && fields['value'])
|
|
81
|
+
fields['value'] = path(fields['value']);
|
|
82
|
+
return [{ place, line: canonical(fields) }];
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { fieldOf } from "./claims.js";
|
|
2
|
+
/** What rests on a signed line: the rank of a signature is the lines that would move with it and nothing else. */
|
|
3
|
+
export function rankLines(fold) {
|
|
4
|
+
const on = new Map();
|
|
5
|
+
for (const line of fold.standing)
|
|
6
|
+
for (const name of fieldOf(line, 'restsOn').split('|').filter(Boolean))
|
|
7
|
+
on.set(name, (on.get(name) ?? 0) + 1);
|
|
8
|
+
return [...on].sort((a, b) => b[1] - a[1]).map(([scope, n]) => `RANK ${scope} · ${n} lines rest on it`);
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Capsule } from '../host/capsule.ts';
|
|
2
|
+
export declare const placedAt: (places: readonly string[], line: string) => string | undefined;
|
|
3
|
+
export declare function touchedBy(root: string, places: readonly string[], lines: readonly string[], standing: readonly string[]): readonly string[];
|
|
4
|
+
/** A selected world can execute only its explicit capsule offers. Dependency aliases are host projections, not selections. */
|
|
5
|
+
export declare const selectionLines: (fold: {
|
|
6
|
+
readonly root: string;
|
|
7
|
+
readonly standing: readonly string[];
|
|
8
|
+
readonly under?: string;
|
|
9
|
+
}) => readonly string[];
|
|
10
|
+
export declare function capsulesFor(standing: readonly string[], place: string, root?: string): readonly {
|
|
11
|
+
readonly capsule: Capsule;
|
|
12
|
+
readonly offer: string;
|
|
13
|
+
}[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { selectedCapsules } from "../host/selected-capsules.js";
|
|
2
|
+
import { basename } from "../host/io.js";
|
|
3
|
+
import { matches } from '@lapxo/topos/wire';
|
|
4
|
+
import { fieldOf } from "./claims.js";
|
|
5
|
+
import { spoken, worldsIn } from "./places.js";
|
|
6
|
+
import { wordOf, wordsOf } from "./wire.js";
|
|
7
|
+
import { ownLock, ownRoot } from "../observe/runner.js";
|
|
8
|
+
import { ownLockOf } from "./signed.js";
|
|
9
|
+
const ROOT = '.';
|
|
10
|
+
const WORLD = '\0';
|
|
11
|
+
/**
|
|
12
|
+
* Who reads a line: the place its scope's first step names, or the one place its needs lie in but bound; every place for
|
|
13
|
+
* a line an own lock speaks, the published place bound is measured over, a line publish/takes carries into every place,
|
|
14
|
+
* a view, and a line whose needs are bound's own modules; the one place a later step of its scope names; and the root
|
|
15
|
+
* alone, where the places join, for every other line: no place's pages read it.
|
|
16
|
+
*/
|
|
17
|
+
function ownerIn(root, places, standing) {
|
|
18
|
+
const own = basename(ownRoot());
|
|
19
|
+
const words = new Set(spoken(root).map((one) => one.line));
|
|
20
|
+
const views = `${wordOf(standing, 'families', 'view')}/`;
|
|
21
|
+
const takes = ownLock().filter((line) => fieldOf(line, 'scope') === 'publish/takes').flatMap((line) => fieldOf(line, 'value').split('|').filter(Boolean));
|
|
22
|
+
return (line) => {
|
|
23
|
+
const scope = fieldOf(line, 'scope');
|
|
24
|
+
if (words.has(line))
|
|
25
|
+
return undefined;
|
|
26
|
+
const steps = scope.split('/');
|
|
27
|
+
if (places.includes(steps[0] ?? ''))
|
|
28
|
+
return steps[0];
|
|
29
|
+
const heads = new Set(fieldOf(line, 'needs').split('|').filter(Boolean).map((need) => need.split('/')[0] ?? ''));
|
|
30
|
+
const head = [...heads][0] ?? '';
|
|
31
|
+
if (heads.size === 1 && head !== own && places.includes(head))
|
|
32
|
+
return head;
|
|
33
|
+
if (scope.startsWith(views))
|
|
34
|
+
return wordsOf(standing, 'views/world').includes(scope.slice(views.length)) ? WORLD : undefined;
|
|
35
|
+
if (takes.some((glob) => matches(glob, scope)))
|
|
36
|
+
return undefined;
|
|
37
|
+
const named = places.filter((one) => steps.slice(1).includes(one));
|
|
38
|
+
if (named.length)
|
|
39
|
+
return named.length === 1 ? named[0] : undefined;
|
|
40
|
+
return heads.size && [...heads].every((one) => one === own) ? undefined : ROOT;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export const placedAt = (places, line) => {
|
|
44
|
+
const [first = '', next = ''] = fieldOf(line, 'scope').split('/');
|
|
45
|
+
return places.includes(first) ? first : places.includes(next) ? next : undefined;
|
|
46
|
+
};
|
|
47
|
+
export function touchedBy(root, places, lines, standing) {
|
|
48
|
+
const owners = lines.map(ownerIn(root, places, standing));
|
|
49
|
+
if (owners.some((one) => one === undefined))
|
|
50
|
+
return places;
|
|
51
|
+
const worlds = owners.some((one) => one === WORLD) ? worldsIn(root) : [];
|
|
52
|
+
return [...new Set([...owners.filter((one) => one !== undefined && one !== ROOT && one !== WORLD), ...worlds])].sort();
|
|
53
|
+
}
|
|
54
|
+
/** A selected world can execute only its explicit capsule offers. Dependency aliases are host projections, not selections. */
|
|
55
|
+
export const selectionLines = (fold) => {
|
|
56
|
+
const local = fold.under ? ownLockOf(fold.root, fold.under.replace(/\/$/, '')).filter((line) => fieldOf(line, 'scope').startsWith('uses/')) : [];
|
|
57
|
+
const qualified = fold.standing.filter((line) => fieldOf(line, 'scope').startsWith(`${fold.under ?? ''}uses/`));
|
|
58
|
+
return [...new Set([...local, ...qualified])].filter((line) => fieldOf(line, 'value') !== 'withdraw');
|
|
59
|
+
};
|
|
60
|
+
export function capsulesFor(standing, place, root) {
|
|
61
|
+
const prefix = place ? `${place}/` : '';
|
|
62
|
+
const pins = root === undefined ? standing.filter(line => fieldOf(line, 'scope').startsWith(`${prefix}uses/`) && fieldOf(line, 'value') !== 'withdraw')
|
|
63
|
+
: selectionLines({ root, standing, ...(prefix ? { under: prefix } : {}) });
|
|
64
|
+
return selectedCapsules(pins);
|
|
65
|
+
}
|