@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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { selectionLines } from '../fold/reach.ts';
|
|
2
|
+
import type { Declaration } from '@lapxo/topos/capsule';
|
|
3
|
+
import type { Request } from '@lapxo/topos/contract';
|
|
4
|
+
import type { Capsule } from '../host/capsule.ts';
|
|
5
|
+
import type { PlaceFold } from '../cli/place.ts';
|
|
6
|
+
type Asking = {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly at: number;
|
|
9
|
+
};
|
|
10
|
+
type Placed = Pick<PlaceFold, 'root' | 'store' | 'standing' | 'observed'> & {
|
|
11
|
+
readonly under?: string;
|
|
12
|
+
readonly second?: PlaceFold['second'];
|
|
13
|
+
};
|
|
14
|
+
/** A declared selector keeps its contract token while the host binds only offered, concretely named regions. */
|
|
15
|
+
export declare function regionNames(reads: readonly string[], descriptions: readonly string[]): readonly string[];
|
|
16
|
+
export declare const describedReceipts: () => readonly string[];
|
|
17
|
+
/** Execution is supplied only by explicit capsule offers of the selected standing. */
|
|
18
|
+
export declare function capsulesOf(fold: Placed): readonly Capsule[];
|
|
19
|
+
export declare const declaredBy: (fold: Placed) => readonly string[];
|
|
20
|
+
type Ask = (declaration: Declaration, one: Asking, verb: 'render' | 'read', shape: string) => Request | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* What a region of a capsule is handed: of the reads its line declares, every read of the place's lines, and each region
|
|
23
|
+
* a line of the instrument's lock describes, with its lines and its receipts; a region no line describes is not handed,
|
|
24
|
+
* and a region handed less than it declares is refused by its capsule, which is every capsule's no. Nothing is handed
|
|
25
|
+
* as a file: the lines are the place as its published lock holds them, and each region is read once for the fold,
|
|
26
|
+
* however many views ask it.
|
|
27
|
+
*/
|
|
28
|
+
export declare const askingOf: (fold: Placed) => Ask;
|
|
29
|
+
export declare function capsuleReadingsOver(fold: Placed): readonly string[];
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { selectedCapsules } from "../host/selected-capsules.js";
|
|
2
|
+
import { selectionLines } from "../fold/reach.js";
|
|
3
|
+
export { selectionLines } from "../fold/reach.js";
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { basename, dirname, join, relative } from 'node:path';
|
|
6
|
+
import { CAPSULE, LOCK, canonical, matches, PROTOCOL } from '@lapxo/topos/wire';
|
|
7
|
+
import { handed, readsOf } from '@lapxo/topos/capsule';
|
|
8
|
+
import { STATES } from '@lapxo/obligations';
|
|
9
|
+
import { foldCeilings } from "../fold/ceilings.js";
|
|
10
|
+
import { fieldOf, wireLinesOf as linesOf } from "../fold/claims.js";
|
|
11
|
+
import { isCeiling } from "../fold/configures.js";
|
|
12
|
+
import { keyFor } from "../fold/keys.js";
|
|
13
|
+
import { casesHeld, placedReadings } from "../fold/observed.js";
|
|
14
|
+
import { verdictsFor } from "../fold/paid.js";
|
|
15
|
+
import { capsuleAt as isWorld, placesIn, spoken } from "../fold/places.js";
|
|
16
|
+
import { rolesOf } from "../fold/roles.js";
|
|
17
|
+
import { ownLockOf } from "../fold/signed.js";
|
|
18
|
+
import { wordOn } from "../fold/wire.js";
|
|
19
|
+
import { placesOf, viewsOf } from "../fold/views.js";
|
|
20
|
+
import { zoomed } from "../fold/zoom.js";
|
|
21
|
+
import { sourced } from "../land/vouched.js";
|
|
22
|
+
import { coordinatesUnder, observeText } from "./files.js";
|
|
23
|
+
import { ownLock } from "./runner.js";
|
|
24
|
+
import { nameOf } from "../render/page.js";
|
|
25
|
+
import { placeLines, takenFor } from "../fold/place-inputs.js";
|
|
26
|
+
const fact = (scope, value, form = 'alphabet', measure = 'fact') => canonical({ scope, role: 'writes', form, measure, value, by: 'fold', at: 'place:fold' });
|
|
27
|
+
const described = (local = []) => {
|
|
28
|
+
const lock = ownLock();
|
|
29
|
+
const measures = ['lines', 'receipts', 'reads', wordOn(lock, 'region-coordinate', lock), wordOn(lock, 'region-leaf', lock)];
|
|
30
|
+
const eligible = (line) => fieldOf(line, 'scope').startsWith('region/') && measures.includes(fieldOf(line, 'measure')) && fieldOf(line, 'value') !== 'withdraw';
|
|
31
|
+
const key = (line) => `${fieldOf(line, 'scope')} ${fieldOf(line, 'measure')}`;
|
|
32
|
+
const supplied = local.filter(eligible);
|
|
33
|
+
const overridden = new Set(supplied.map(key));
|
|
34
|
+
return [...lock.filter(eligible).filter((line) => !overridden.has(key(line))), ...supplied];
|
|
35
|
+
};
|
|
36
|
+
/** A declared selector keeps its contract token while the host binds only offered, concretely named regions. */
|
|
37
|
+
export function regionNames(reads, descriptions) {
|
|
38
|
+
const names = [...new Set(descriptions.map((line) => fieldOf(line, 'scope')).filter((scope) => scope.startsWith('region/') && !/[?*]/.test(scope)))];
|
|
39
|
+
return names.filter((scope) => reads.some((read) => read.startsWith('region/') && matches(read, scope))).map((scope) => scope.slice('region/'.length));
|
|
40
|
+
}
|
|
41
|
+
export const describedReceipts = () => described().filter((line) => fieldOf(line, 'measure') === 'receipts')
|
|
42
|
+
.flatMap((line) => fieldOf(line, 'value').split('|').filter(Boolean));
|
|
43
|
+
function factsOf(fold) {
|
|
44
|
+
const here = join(fold.root, fold.under ?? '');
|
|
45
|
+
const written = [...viewsOf(fold.standing).values()].flatMap((view) => ('regions' in view && view.shape && observeText(join(here, view.shape)) !== undefined ? [view.shape] : []));
|
|
46
|
+
return [fact('cases/held', String(casesHeld(fold.observed))), ...(fold.second ? [fact('second/verdict', fold.second.verdict)] : []),
|
|
47
|
+
...[...new Set(written)].map((shape) => fact(`${sourced(fold.standing)}/${shape}`, 'present')), ...('ceilings' in fold ? [...readingsOf(fold), ...verdictOf(fold), ...(released(fold) ? [] : askedOf(fold))] : []), ...coordinatesOf(fold)];
|
|
48
|
+
}
|
|
49
|
+
/** The place's own ceilings as the fold reads them, one line a ceiling at the scope the place speaks it: the bound stays its line's. */
|
|
50
|
+
function readingsOf(fold) {
|
|
51
|
+
const held = fold.standing.filter((line) => fieldOf(line, 'value') !== 'withdraw');
|
|
52
|
+
const ceilings = held.filter((line) => isCeiling(line) && (!fold.under || takenFor(held, `${nameOf(fold)}/`)(line)));
|
|
53
|
+
const read = foldCeilings({ standing: fold.standing, ceilings, observed: fold.observed, own: fold.own, epoch: fold.epoch }).readings;
|
|
54
|
+
const spans = (got) => got.map((one) => (one.split(' ').at(-1) ?? '').split('..').map(Number)).filter((span) => span.every(Number.isFinite));
|
|
55
|
+
return ceilings.flatMap((line) => ((got) => (got === undefined ? [] : [canonical({ scope: fieldOf(fold.under ? zoomed(line, nameOf(fold)) : line, 'scope'), role: 'reads', measure: fieldOf(line, 'measure'), by: 'fold', at: 'place:fold',
|
|
56
|
+
...(spans(got).length === got.length ? { form: 'interval', value: `${Math.min(...spans(got).map((span) => span[0]))}..${Math.max(...spans(got).map((span) => span.at(-1)))}` } : { form: 'alphabet', value: [...new Set(got)].join('|') }) })]))(read.get(fieldOf(line, 'scope'))));
|
|
57
|
+
}
|
|
58
|
+
const released = (fold) => fold.standing.some((line) => [`publish/${nameOf(fold)}/bootstrap`, 'publish/bootstrap'].includes(fieldOf(line, 'scope')) && fieldOf(line, 'value') !== 'withdraw');
|
|
59
|
+
const pinnersOf = (fold) => [...new Set(fold.standing.filter((line) => fieldOf(line, 'value') !== 'withdraw')
|
|
60
|
+
.flatMap((line) => ((steps) => (steps.length === 3 && steps[1] === 'uses' && steps[2] === nameOf(fold) ? [steps[0]] : []))(fieldOf(line, 'scope').split('/'))))];
|
|
61
|
+
const capsuleOf = (fold) => [CAPSULE, LOCK].map((file) => linesOf(observeText(join(fold.root, fold.under ?? '', file))))
|
|
62
|
+
.find((lines) => lines.some((line) => fieldOf(line, 'scope').startsWith('capsule/'))) ?? [];
|
|
63
|
+
const coordinatesOf = (fold) => ((here, role) => (!isWorld(fold.root, (fold.under ?? '').replace(/\/$/, '')) || (fold.under === undefined && !released(fold)) ? []
|
|
64
|
+
: coordinatesUnder(here, here, (dir) => dir === '' || (!basename(dir).startsWith('.') && role(relative(dirname(fold.store), join(here, dir))) !== 'foreign'))
|
|
65
|
+
.map((file) => fact(`coordinate/${file}`, neededBy(fold.standing, fold.under ?? '', file) ? 'needed' : 'present'))))(join(fold.root, fold.under ?? ''), rolesOf(fold.store));
|
|
66
|
+
const neededBy = (standing, under, file) => standing.some((line) => fieldOf(line, 'value') !== 'withdraw' && fieldOf(line, 'measure') !== 'reader'
|
|
67
|
+
&& fieldOf(line, 'needs').split('|').some((need) => !['', './', under].includes(need) && (need === `${under}${file}` || (need.endsWith('/') && `${under}${file}`.startsWith(need)) || matches(need, `${under}${file}`))));
|
|
68
|
+
function askedOf(fold) {
|
|
69
|
+
const at = (place) => ((lines) => ((views) => new Set(views.filter((view) => placesOf(lines, view).includes(`${place}/`) || (!placesOf(lines, view).length
|
|
70
|
+
&& observeText(join(fold.root, place, view.shape)) !== undefined && !views.some((other) => other.shape === view.shape && placesOf(lines, other).includes(`${place}/`)))).flatMap((view) => view.regions.map((one) => one.name))))([...viewsOf(lines).values()].flatMap((view) => ('regions' in view && view.shape ? [view] : []))))([...ownLockOf(fold.root, place, true), ...fold.standing]);
|
|
71
|
+
return fold.under ? [...new Set(capsuleOf(fold).filter((line) => fieldOf(line, 'scope').startsWith('region/') && fieldOf(line, 'role') === 'render').map((line) => fieldOf(line, 'scope').slice('region/'.length)))]
|
|
72
|
+
.map((name) => ((n) => fact(`asked/${name}`, `${n}..${n}`, 'interval', 'count'))(pinnersOf(fold).map(at).filter((names) => names.has(name)).length)) : [];
|
|
73
|
+
}
|
|
74
|
+
/** The place's verdict line as counts: what the fold lands, forks and refuses, each of the object's four states, and the ceilings it meets. */
|
|
75
|
+
function verdictOf(fold) {
|
|
76
|
+
const states = Object.values(verdictsFor(fold));
|
|
77
|
+
const named = (line) => ((cut) => [line.slice(0, cut < 0 ? line.length : cut).trim(), line.slice(cut < 0 ? line.length : cut + 3).trim() || line])(line.indexOf(' · '));
|
|
78
|
+
const here = (fold.under ?? '').replace(/\/$/, '');
|
|
79
|
+
const others = new Set(here ? placesIn(fold.root).filter((place) => place !== here) : []);
|
|
80
|
+
const mine = (scope) => {
|
|
81
|
+
if (!here)
|
|
82
|
+
return true;
|
|
83
|
+
if (scope === here || scope.startsWith(`${here}/`))
|
|
84
|
+
return true;
|
|
85
|
+
const head = scope.split('/')[0] ?? '';
|
|
86
|
+
return !others.has(head);
|
|
87
|
+
};
|
|
88
|
+
return [['claims', fold.claims], ['forks', fold.forks.length + (fold.second.verdict === 'forks' ? 1 : 0)], ['refused', fold.refused.length], ['vacuous', fold.vacuous], ['history', fold.history.length],
|
|
89
|
+
['met', fold.ceilings.read], ['short', fold.ceilings.short.length], ['over', fold.ceilings.over.length], ...STATES.map((one) => [one, states.filter((state) => state === one).length])].map(([name, n]) => fact(`verdict/${name}`, `${n}..${n}`, 'interval', 'count'))
|
|
90
|
+
.concat(fold.ceilings.short.filter((one) => mine(named(one)[0])).map((one) => (([scope, reading]) => fact(`unpaid/${scope}`, reading))(named(one))))
|
|
91
|
+
.concat(fold.ceilings.over.filter((one) => mine(named(one)[0])).map((one) => (([scope, reading]) => fact(`outside/${scope}`, reading))(named(one))))
|
|
92
|
+
.concat(fold.ceilings.unread.filter(mine).map((scope) => fact(`unpaid/${scope}`, 'unread')))
|
|
93
|
+
.concat(fold.missing.filter((line) => mine(fieldOf(line, 'scope'))).map((line) => fact(`unpaid/${fieldOf(line, 'scope')}`, 'unpaid')))
|
|
94
|
+
.concat([
|
|
95
|
+
fact('walk/read', `${fold.own['walk-read'] ?? 0}..${fold.own['walk-read'] ?? 0}`, 'interval', 'count'),
|
|
96
|
+
fact('walk/ms', `${fold.own['walk-ms'] ?? 0}..${fold.own['walk-ms'] ?? 0}`, 'interval', 'count'),
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
function regionOf(fold, name, lines, facts) {
|
|
100
|
+
const here = fold.under ?? '';
|
|
101
|
+
const said = (measure) => handed([`region/${name}`], lines).filter((fields) => fields['measure'] === measure)
|
|
102
|
+
.flatMap((fields) => (fields['value'] ?? '').split('|').filter(Boolean));
|
|
103
|
+
const globs = said('receipts');
|
|
104
|
+
const placed = placedReadings(fold.observed).filter((one) => one.place.startsWith(here) && globs.some((glob) => matches(glob, one.question)))
|
|
105
|
+
.map((one) => canonical({ scope: one.question, role: 'reads', form: 'alphabet', measure: one.measure, value: one.value, by: one.by, evidence: one.at, at: `place:${one.place.slice(here.length).replace(/^\.\/$/, '')}` }));
|
|
106
|
+
const role = rolesOf(fold.store);
|
|
107
|
+
const walked = coordinatesUnder(join(fold.root, here), fold.root, (dir) => dir === '' || (!basename(dir).startsWith('.') && role(relative(dirname(fold.store), join(fold.root, dir))) !== 'foreign'));
|
|
108
|
+
const named = wordOn(fold.standing, 'region-coordinate', ownLock());
|
|
109
|
+
const files = said(named).flatMap((glob) => walked.filter((file) => matches(glob, file.slice(here.length)) || matches(glob, file)));
|
|
110
|
+
const saidLines = files.flatMap((file) => {
|
|
111
|
+
if (basename(file) === 'TARGET.bound' || basename(file) === 'receipts.bound')
|
|
112
|
+
return [];
|
|
113
|
+
const lock = linesOf(observeText(join(fold.root, file)));
|
|
114
|
+
return lock.length ? [canonical({ scope: 'said/lines', role: 'reads', form: 'alphabet', measure: 'lines', value: lock.join('\n'), by: 'reader', at: `place:${file}` })] : [];
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
lines: said('lines').flatMap((coordinate) => linesOf(observeText(join(fold.root, here, coordinate)))),
|
|
118
|
+
receipts: [...placed, ...saidLines, ...(globs.length ? facts() : []).filter((line) => globs.some((glob) => matches(glob, fieldOf(line, 'scope'))))],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** A local selection belongs to its own lock; tree-qualified selections retain their explicit owner. */
|
|
122
|
+
const heldCapsules = new WeakMap();
|
|
123
|
+
/** Execution is supplied only by explicit capsule offers of the selected standing. */
|
|
124
|
+
export function capsulesOf(fold) {
|
|
125
|
+
const cached = heldCapsules.get(fold);
|
|
126
|
+
if (cached !== undefined)
|
|
127
|
+
return cached;
|
|
128
|
+
const got = selectedCapsules(selectionLines(fold)).map(one => one.capsule);
|
|
129
|
+
heldCapsules.set(fold, got);
|
|
130
|
+
return got;
|
|
131
|
+
}
|
|
132
|
+
export const declaredBy = (fold) => [...new Set(capsulesOf(fold).flatMap((capsule) => capsule.declaration.renders))];
|
|
133
|
+
const asks = new WeakMap();
|
|
134
|
+
/**
|
|
135
|
+
* What a region of a capsule is handed: of the reads its line declares, every read of the place's lines, and each region
|
|
136
|
+
* a line of the instrument's lock describes, with its lines and its receipts; a region no line describes is not handed,
|
|
137
|
+
* and a region handed less than it declares is refused by its capsule, which is every capsule's no. Nothing is handed
|
|
138
|
+
* as a file: the lines are the place as its published lock holds them, and each region is read once for the fold,
|
|
139
|
+
* however many views ask it.
|
|
140
|
+
*/
|
|
141
|
+
export const askingOf = (fold) => asks.get(fold) ?? asks.set(fold, handing(fold)).get(fold);
|
|
142
|
+
function handing(fold) {
|
|
143
|
+
const capsule = linesOf(observeText(join(fold.root, fold.under ?? '', CAPSULE))).filter((line) => fieldOf(line, 'scope').startsWith('region/'));
|
|
144
|
+
const seen = handed(['**'], fold.under ? placeLines(fold) : fold.standing);
|
|
145
|
+
const lines = described(seen.map((fields) => canonical(fields)));
|
|
146
|
+
let held;
|
|
147
|
+
const facts = () => (held ?? (held = factsOf(fold)));
|
|
148
|
+
const regions = new Map();
|
|
149
|
+
const region = (name) => regions.get(name) ?? regions.set(name, regionOf(fold, name, lines, facts)).get(name);
|
|
150
|
+
return (declaration, one, verb, shape) => {
|
|
151
|
+
const declared = readsOf(declaration, one.name);
|
|
152
|
+
if (declared === undefined)
|
|
153
|
+
return undefined;
|
|
154
|
+
const reads = declared.filter((read) => !read.startsWith('region/') || handed([read], lines).length > 0 || (matches(read, 'region/capsule') && capsule.length > 0));
|
|
155
|
+
const names = [...new Set([...regionNames(reads, lines), ...(capsule.length && reads.some((read) => matches(read, 'region/capsule')) ? ['capsule'] : [])])];
|
|
156
|
+
return { protocol: PROTOCOL, verb, rootScope: fold.under ?? '', files: [], region: one.name, at: one.at, shape, name: nameOf(fold) || basename(fold.root),
|
|
157
|
+
lines: seen
|
|
158
|
+
.filter((fields) => reads.some((read) => matches(read, fields['scope'] ?? ''))).map((fields) => canonical(fields)), reads,
|
|
159
|
+
regions: Object.fromEntries(names.map((name) => [name, name === 'capsule' && capsule.length ? { lines: capsule, receipts: [] } : region(name)])) };
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const read = new WeakMap();
|
|
163
|
+
function capsuleReadings(fold) {
|
|
164
|
+
const place = fold.under || './';
|
|
165
|
+
const known = read.get(fold.observed) ?? read.set(fold.observed, new Map()).get(fold.observed);
|
|
166
|
+
return known.get(place) ?? known.set(place, readingsAt(fold, place)).get(place);
|
|
167
|
+
}
|
|
168
|
+
function readingsAt(fold, place) {
|
|
169
|
+
const asking = askingOf(fold);
|
|
170
|
+
const speaker = keyFor(fold.store, 'read');
|
|
171
|
+
return capsulesOf(fold).flatMap((capsule) => {
|
|
172
|
+
const writing = new Set(capsule.lines.filter((line) => fieldOf(line, 'scope').startsWith('region/') && fieldOf(line, 'measure') === 'writes').map((line) => fieldOf(line, 'scope').slice('region/'.length)));
|
|
173
|
+
const receipts = Object.keys(capsule.declaration.writes).filter((name) => capsule.declaration.writes[name] === 'receipt' && !writing.has(name)).sort();
|
|
174
|
+
const requests = receipts.flatMap((name) => ((request) => (request === undefined ? [] : [request]))(asking(capsule.declaration, { name, at: 3 }, 'read', '')));
|
|
175
|
+
const answers = capsule.ask(requests);
|
|
176
|
+
const refused = requests.filter((_, i) => answers[i]?.kind !== 'fact');
|
|
177
|
+
if (refused.length)
|
|
178
|
+
throw new Error(`REFUSE·capsule ${capsule.digest} · ${place} · ${refused.length} of ${requests.length} regions: ${refused.map((one) => one.region).join(' ')} · ${answers[requests.indexOf(refused[0])]?.why ?? 'no answer'}`);
|
|
179
|
+
return requests.flatMap((request, i) => {
|
|
180
|
+
const said = answers[i];
|
|
181
|
+
if (said?.kind !== 'fact')
|
|
182
|
+
return [];
|
|
183
|
+
const digest = createHash('sha256').update(JSON.stringify(request)).digest('hex');
|
|
184
|
+
const at = `place:sha256:${digest}`;
|
|
185
|
+
const by = `${speaker}:${capsule.digest.slice(7, 19)}`;
|
|
186
|
+
const line = (row) => canonical({ scope: row.scope, role: row.role, form: row.bound.kind === 'interval' ? 'interval' : 'alphabet', measure: row.measure,
|
|
187
|
+
value: row.bound.kind === 'interval' ? `${row.bound.lo ?? 0}..${row.bound.hi ?? 0}` : (row.bound.values ?? []).join('|'), by, at });
|
|
188
|
+
return [canonical({ scope: place, role: 'writes', form: 'alphabet', measure: 'observed', value: digest.slice(0, 16), by, at }),
|
|
189
|
+
...(said.claims ?? []).map(line)];
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
export function capsuleReadingsOver(fold) {
|
|
194
|
+
if (fold.under)
|
|
195
|
+
return capsuleReadings(fold);
|
|
196
|
+
const said = spoken(fold.root);
|
|
197
|
+
const places = [...new Set(fold.standing.flatMap((line) => /^([^/]+)\/uses\//.exec(fieldOf(line, 'scope'))?.[1] ?? []))];
|
|
198
|
+
return [...capsuleReadings(fold), ...places.flatMap((place) => capsuleReadings({ ...fold, under: `${place}/`,
|
|
199
|
+
standing: [...new Set([...said.filter((one) => one.place === place).map((one) => one.line), ...fold.standing])] }))];
|
|
200
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type Kind = 'js' | 'process';
|
|
2
|
+
/** The kinds this host starts. A reader of any other kind is signed and stays potential: nothing here runs it. */
|
|
3
|
+
export declare const runs: (kind: string) => kind is Kind;
|
|
4
|
+
interface Row {
|
|
5
|
+
readonly scope: string;
|
|
6
|
+
readonly measure: string;
|
|
7
|
+
readonly role: string;
|
|
8
|
+
readonly bound: {
|
|
9
|
+
readonly kind: string;
|
|
10
|
+
readonly values?: readonly string[];
|
|
11
|
+
readonly lo?: number;
|
|
12
|
+
readonly hi?: number;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
interface Input {
|
|
16
|
+
readonly place: string;
|
|
17
|
+
readonly text?: string;
|
|
18
|
+
readonly held?: readonly (readonly [string, string])[];
|
|
19
|
+
readonly region?: string;
|
|
20
|
+
}
|
|
21
|
+
/** The host end of the contract, beside the command that started this process: a module runs in a process of its own. */
|
|
22
|
+
export declare const hostOf: () => string;
|
|
23
|
+
/**
|
|
24
|
+
* A reader runs as a process of its own, started by the runtime from the module its lock names, in the root its places
|
|
25
|
+
* are named from: the instrument loads no capsule into itself. It is asked through the one contract, each place with
|
|
26
|
+
* the file it is handed or alone when it reads the place by running it, as the region it was matched for when its
|
|
27
|
+
* capsule reads with several, and answers one list of claims per place.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runReader(module: string, root: string, inputs: readonly Input[]): readonly (readonly Row[])[];
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { PROTOCOL } from '@lapxo/topos/wire';
|
|
4
|
+
import { responsesOf } from '@lapxo/topos/contract';
|
|
5
|
+
import { processOf, readerOf } from "./runner.js";
|
|
6
|
+
/** The kinds this host starts. A reader of any other kind is signed and stays potential: nothing here runs it. */
|
|
7
|
+
export const runs = (kind) => kind === 'js' || kind === 'process';
|
|
8
|
+
/** The host end of the contract, beside the command that started this process: a module runs in a process of its own. */
|
|
9
|
+
export const hostOf = () => processOf('host');
|
|
10
|
+
/**
|
|
11
|
+
* A reader runs as a process of its own, started by the runtime from the module its lock names, in the root its places
|
|
12
|
+
* are named from: the instrument loads no capsule into itself. It is asked through the one contract, each place with
|
|
13
|
+
* the file it is handed or alone when it reads the place by running it, as the region it was matched for when its
|
|
14
|
+
* capsule reads with several, and answers one list of claims per place.
|
|
15
|
+
*/
|
|
16
|
+
export function runReader(module, root, inputs) {
|
|
17
|
+
if (!inputs.length)
|
|
18
|
+
return [];
|
|
19
|
+
const requests = inputs.map((input) => ({
|
|
20
|
+
protocol: PROTOCOL, verb: 'read', rootScope: input.place, files: input.text === undefined ? [] : [{ place: input.place, text: input.text }], ...(input.held ? { held: input.held } : {}),
|
|
21
|
+
...(input.region ? { region: input.region } : {}),
|
|
22
|
+
}));
|
|
23
|
+
const reader = readerOf(module);
|
|
24
|
+
if (!existsSync(reader.path))
|
|
25
|
+
throw new Error(`REFUSE·reader ${module} unavailable; its declared capability needs an admitted provider`);
|
|
26
|
+
const ran = spawnSync(process.execPath, [...reader.flags, hostOf(), reader.path, module], { cwd: root, input: JSON.stringify(requests), encoding: 'utf8', maxBuffer: 1 << 28 });
|
|
27
|
+
if (ran.error)
|
|
28
|
+
throw Object.assign(new Error(`${module}: reader process failed: ${ran.error.message}`), { cause: ran.error });
|
|
29
|
+
if (ran.status !== 0)
|
|
30
|
+
throw new Error(`${module}: reader exited ${ran.status ?? `with signal ${ran.signal}`}\n${ran.stderr ?? ''}${ran.stdout ?? ''}`);
|
|
31
|
+
if (ran.stderr)
|
|
32
|
+
process.stderr.write(ran.stderr);
|
|
33
|
+
return responsesOf(ran.stdout ?? '', inputs.length).map((answer) => (answer.claims ?? []));
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const ownRoot: () => string;
|
|
2
|
+
export declare const ownStore: () => string;
|
|
3
|
+
export declare const ownLock: () => readonly string[];
|
|
4
|
+
export declare const sourceOf: () => string;
|
|
5
|
+
export declare function processOf(name: string, folder?: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* A reader's code is what the bundler takes of it: its module and every file that module imports, wherever it lies, as
|
|
8
|
+
* one blob of the tree's store named by the digest of its bytes. The files the bundler read, at the stamps they had, key
|
|
9
|
+
* the blob, so it is built again only when one of them moved, and a helper a step above the module is one of them.
|
|
10
|
+
*/
|
|
11
|
+
export declare function bundleOf(module: string): string | undefined;
|
|
12
|
+
/** A reader run by a blob is a blob itself: its bundle, with everything it imports but the compiler. */
|
|
13
|
+
export declare function readerOf(module: string): {
|
|
14
|
+
readonly path: string;
|
|
15
|
+
readonly flags: readonly string[];
|
|
16
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { sha } from "../host/hash.js";
|
|
3
|
+
import { existsSync, mkdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { fieldOf } from "../fold/claims.js";
|
|
7
|
+
import { observeFile, observeText } from "./files.js";
|
|
8
|
+
import { ownLockOf } from "../fold/signed.js";
|
|
9
|
+
import { landBlob } from "../land/ledger.js";
|
|
10
|
+
import { EXTENSION, LOCK, publicLock } from '@lapxo/topos/wire';
|
|
11
|
+
import { foldClaims, isWire } from "../fold/claims.js";
|
|
12
|
+
const self = () => process.argv[1] ?? '';
|
|
13
|
+
const fromBlob = () => extname(existsSync(self()) ? realpathSync(self()) : self()) === '';
|
|
14
|
+
/**
|
|
15
|
+
* Where the instrument lies and what it is run from: from source or compiled modules, bound is two steps above this runner module; as a blob, the command is a blob of the tree's store, named by the digest bound's own lock names for
|
|
16
|
+
* it, and bound lies beside that store. Other processes use the same runtime directory and extension; only source readers receive source conditions. A name that resolves to no blob beside it is refused by name
|
|
17
|
+
* before anything is started, never handed to the runtime as the store's own directory.
|
|
18
|
+
*/
|
|
19
|
+
// Blob bundles have no module URL; source and compiled modules carry their own entry location.
|
|
20
|
+
const moduleAt = fromBlob() ? undefined : fileURLToPath(import.meta.url);
|
|
21
|
+
const runtimeAt = moduleAt === undefined ? '' : resolve(dirname(moduleAt), '..');
|
|
22
|
+
const runtimeExtension = moduleAt === undefined ? '' : extname(moduleAt);
|
|
23
|
+
export const ownRoot = () => (fromBlob() ? join(resolve(dirname(self()), '..', '..', '..'), 'bound') : resolve(runtimeAt, '..'));
|
|
24
|
+
export const ownStore = () => join(ownRoot(), '..', EXTENSION);
|
|
25
|
+
/** The colocated public artifact is read without an unrelated parent's authority store. */
|
|
26
|
+
// An act is judged by the instrument it started with, even when that act changes its colocated lock.
|
|
27
|
+
let instrumentLock;
|
|
28
|
+
export const ownLock = () => {
|
|
29
|
+
if (instrumentLock !== undefined)
|
|
30
|
+
return instrumentLock;
|
|
31
|
+
const lines = (observeText(join(ownRoot(), LOCK)) ?? '').split('\n').filter(isWire);
|
|
32
|
+
const published = publicLock(lines);
|
|
33
|
+
instrumentLock = published === undefined ? ownLockOf(join(ownRoot(), '..'), basename(ownRoot()), true) : foldClaims(published).standing;
|
|
34
|
+
return instrumentLock;
|
|
35
|
+
};
|
|
36
|
+
export const sourceOf = () => (fromBlob() ? join(ownRoot(), 'src', 'cli', 'verb.ts') : self());
|
|
37
|
+
export function processOf(name, folder) {
|
|
38
|
+
if (!fromBlob())
|
|
39
|
+
return join(runtimeAt, folder ?? 'cli', `${name}${runtimeExtension}`);
|
|
40
|
+
const named = ownLock().find((line) => fieldOf(line, 'scope') === `runner/${name}`);
|
|
41
|
+
const blob = fieldOf(named ?? '', 'value').replace(/^[^:]*:/, '');
|
|
42
|
+
if (!blob || !existsSync(join(dirname(self()), blob))) {
|
|
43
|
+
throw new Error(`${basename(ownRoot())}: REFUSE·alias runner/${name} resolves to no blob: ${named === undefined ? 'no signed line of the instrument\'s own lock names it' : `${fieldOf(named, 'value')} is not beside ${basename(self())}`}`);
|
|
44
|
+
}
|
|
45
|
+
return join(dirname(self()), blob);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A reader's code is what the bundler takes of it: its module and every file that module imports, wherever it lies, as
|
|
49
|
+
* one blob of the tree's store named by the digest of its bytes. The files the bundler read, at the stamps they had, key
|
|
50
|
+
* the blob, so it is built again only when one of them moved, and a helper a step above the module is one of them.
|
|
51
|
+
*/
|
|
52
|
+
export function bundleOf(module) {
|
|
53
|
+
const tree = join(ownRoot(), '..');
|
|
54
|
+
const named = fieldOf(ownLock().find((line) => fieldOf(line, 'scope') === 'host/bundler') ?? '', 'value');
|
|
55
|
+
const bundler = named && existsSync(join(tree, named)) ? join(tree, named) : '';
|
|
56
|
+
if (!bundler)
|
|
57
|
+
return undefined;
|
|
58
|
+
const store = join(tree, EXTENSION);
|
|
59
|
+
const stamp = (coordinate) => ((at) => (at === undefined ? '' : `${at.mtimeMs} ${at.size}`))(statSync(join(tree, coordinate), { throwIfNoEntry: false }));
|
|
60
|
+
const index = join(store, 'cas', 'built', sha(`bundle ${module}`));
|
|
61
|
+
const held = JSON.parse(observeText(index) ?? 'null');
|
|
62
|
+
if (held !== null && existsSync(join(store, 'cas', 'blobs', held.blob)) && held.inputs.every(([coordinate, was]) => stamp(coordinate) === was))
|
|
63
|
+
return held.blob;
|
|
64
|
+
const [meta, out] = [`${index}.${process.pid}.json`, `${index}.${process.pid}.js`];
|
|
65
|
+
mkdirSync(dirname(index), { recursive: true });
|
|
66
|
+
const built = spawnSync(bundler, [module, '--bundle', '--platform=node', '--format=cjs', '--external:typescript', '--log-level=error', `--metafile=${meta}`, `--outfile=${out}`], { cwd: tree });
|
|
67
|
+
const inputs = Object.keys(JSON.parse(observeText(meta) ?? '{}').inputs ?? {}).sort();
|
|
68
|
+
const bytes = observeFile(out);
|
|
69
|
+
rmSync(meta, { force: true });
|
|
70
|
+
rmSync(out, { force: true });
|
|
71
|
+
if (built.status !== 0 || bytes === undefined || !bytes.length || !inputs.length)
|
|
72
|
+
return undefined;
|
|
73
|
+
const hex = sha(bytes);
|
|
74
|
+
landBlob(store, `sha256:${hex}`, bytes);
|
|
75
|
+
writeFileSync(index, `${JSON.stringify({ blob: hex, inputs: inputs.map((coordinate) => [coordinate, stamp(coordinate)]) })}\n`);
|
|
76
|
+
return hex;
|
|
77
|
+
}
|
|
78
|
+
/** A reader run by a blob is a blob itself: its bundle, with everything it imports but the compiler. */
|
|
79
|
+
export function readerOf(module) {
|
|
80
|
+
const source = { path: module, flags: runtimeExtension === '.ts' ? ['--disable-warning=ExperimentalWarning', '--disable-warning=MODULE_TYPELESS_PACKAGE_JSON'] : [] };
|
|
81
|
+
const hex = fromBlob() ? bundleOf(module) : undefined;
|
|
82
|
+
return hex === undefined ? source : { path: join(ownStore(), 'cas', 'blobs', hex), flags: [] };
|
|
83
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { PlaceFold } from '../cli/place.ts';
|
|
2
|
+
type Asking = {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly at: number;
|
|
5
|
+
};
|
|
6
|
+
type Viewed = {
|
|
7
|
+
readonly asking: readonly Asking[];
|
|
8
|
+
readonly shape: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const capsuleRegions: (fold: PlaceFold, asking: readonly Asking[], shape?: string) => ReadonlyMap<string, readonly string[]>;
|
|
11
|
+
/**
|
|
12
|
+
* A region this instrument does not render is rendered by a capsule: one the place uses by the digest of its lock and
|
|
13
|
+
* its bytes, or else one the instrument carries. What the views of one fold ask of each capsule is asked at once; a
|
|
14
|
+
* region one capsule answered is not asked of another, and one it refused or never answered is named and asked of the
|
|
15
|
+
* next only when no region was offered. A refusal from an offered region stops the request; it never selects another implementation.
|
|
16
|
+
*/
|
|
17
|
+
export declare const capsuleViews: (fold: PlaceFold, views: readonly Viewed[]) => readonly ReadonlyMap<string, readonly string[]>[];
|
|
18
|
+
type Answer = {
|
|
19
|
+
readonly lines: readonly string[];
|
|
20
|
+
readonly by: string;
|
|
21
|
+
};
|
|
22
|
+
/** What each capsule answered a view's regions with, the capsule by its digest, and whether the place's pin chose it. */
|
|
23
|
+
export declare function capsuleAnswers(fold: PlaceFold, views: readonly Viewed[]): readonly ReadonlyMap<string, Answer>[];
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { matches } from '@lapxo/topos/wire';
|
|
2
|
+
import { askingOf, capsulesOf } from "../observe/regions.js";
|
|
3
|
+
export const capsuleRegions = (fold, asking, shape = '') => capsuleViews(fold, [{ asking, shape }])[0] ?? new Map();
|
|
4
|
+
/**
|
|
5
|
+
* A region this instrument does not render is rendered by a capsule: one the place uses by the digest of its lock and
|
|
6
|
+
* its bytes, or else one the instrument carries. What the views of one fold ask of each capsule is asked at once; a
|
|
7
|
+
* region one capsule answered is not asked of another, and one it refused or never answered is named and asked of the
|
|
8
|
+
* next only when no region was offered. A refusal from an offered region stops the request; it never selects another implementation.
|
|
9
|
+
*/
|
|
10
|
+
export const capsuleViews = (fold, views) => capsuleAnswers(fold, views).map((answers) => new Map([...answers].map(([name, answer]) => [name, answer.lines])));
|
|
11
|
+
/** What each capsule answered a view's regions with, the capsule by its digest, and whether the place's pin chose it. */
|
|
12
|
+
export function capsuleAnswers(fold, views) {
|
|
13
|
+
const outs = views.map(() => new Map());
|
|
14
|
+
if (!views.some((view) => view.asking.length))
|
|
15
|
+
return outs;
|
|
16
|
+
const asking = askingOf(fold);
|
|
17
|
+
for (const capsule of capsulesOf(fold)) {
|
|
18
|
+
const asked = views.flatMap((view, i) => view.asking.filter((one) => capsule.declaration.renders.some((glob) => matches(glob, one.name)) && !outs[i].has(one.name))
|
|
19
|
+
.flatMap((one) => ((request) => (request === undefined ? [] : [{ i, one, request }]))(asking(capsule.declaration, one, 'render', view.shape))));
|
|
20
|
+
const answers = capsule.ask(asked.map((one) => one.request));
|
|
21
|
+
asked.forEach(({ i, one, request }, at) => {
|
|
22
|
+
const said = answers[at];
|
|
23
|
+
if (said?.kind === 'fact')
|
|
24
|
+
outs[i].set(one.name, { lines: ((got) => got.lines ?? got.leaf ?? [])(said), by: capsule.digest });
|
|
25
|
+
else
|
|
26
|
+
throw new Error(`REFUSE·capsule ${capsule.digest} · ${request.rootScope}${one.name} · ${said?.why ?? 'no answer'}`);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return outs;
|
|
30
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PlaceFold } from '../cli/place.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The field of a place at resolution N: every bound placed at the place or at none, in the state its poles give, and
|
|
4
|
+
* every coordinate cut to its first N steps in the worst state it holds. Interval poles with no value in common
|
|
5
|
+
* conflict, as two signers on one key do; an alphabet that permits none is a ceiling like any other. A value read
|
|
6
|
+
* outside the poles is forbidden; a bound nothing read, a reader that refused and a demand unpaid are required; the
|
|
7
|
+
* rest is free. It is read off the region summaries and the landed readings, and no reader runs.
|
|
8
|
+
*/
|
|
9
|
+
export declare function field(fold: PlaceFold, at: number): readonly string[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { fieldOf } from "../fold/claims.js";
|
|
2
|
+
import { placesIn } from "../fold/places.js";
|
|
3
|
+
import { placedAt } from "../fold/reach.js";
|
|
4
|
+
import { polesOf, rankOf, unpackRegions } from "../fold/region.js";
|
|
5
|
+
import { INTERVALS } from '@lapxo/topos/forms';
|
|
6
|
+
const WORST = ['CONFLICT', 'FORBIDDEN', 'REQUIRED', 'FREE'];
|
|
7
|
+
const empty = (ceiling) => ceiling !== undefined && 'lo' in ceiling && !INTERVALS.inhabited(ceiling);
|
|
8
|
+
/**
|
|
9
|
+
* The field of a place at resolution N: every bound placed at the place or at none, in the state its poles give, and
|
|
10
|
+
* every coordinate cut to its first N steps in the worst state it holds. Interval poles with no value in common
|
|
11
|
+
* conflict, as two signers on one key do; an alphabet that permits none is a ceiling like any other. A value read
|
|
12
|
+
* outside the poles is forbidden; a bound nothing read, a reader that refused and a demand unpaid are required; the
|
|
13
|
+
* rest is free. It is read off the region summaries and the landed readings, and no reader runs.
|
|
14
|
+
*/
|
|
15
|
+
export function field(fold, at) {
|
|
16
|
+
if (fold.regions === undefined)
|
|
17
|
+
return ['GREY field · this fold carries no region summaries; it is read again at the next fold'];
|
|
18
|
+
const regions = unpackRegions(fold.regions);
|
|
19
|
+
const places = placesIn(fold.root);
|
|
20
|
+
const place = fold.under?.replace(/\/$/, '');
|
|
21
|
+
const here = (line) => place === undefined || [place, undefined].includes(placedAt(places, line));
|
|
22
|
+
const own = { ...fold.own, unread: fold.ceilings.unread.length + fold.ceilings.vacuous.length };
|
|
23
|
+
const poles = new Map();
|
|
24
|
+
const states = [
|
|
25
|
+
...regions.bounds.filter((bound) => here(bound.line)).map((bound) => {
|
|
26
|
+
const ceiling = (poles.get(bound.region) ?? poles.set(bound.region, polesOf(regions, bound.region)).get(bound.region)).get(`${bound.question} ${bound.measure}`)?.ceiling;
|
|
27
|
+
const got = rankOf(regions, bound, own);
|
|
28
|
+
return [fieldOf(bound.line, 'scope'), empty(ceiling) ? 'CONFLICT' : got.bars || got.sums ? 'FORBIDDEN' : got.met ? 'FREE' : 'REQUIRED'];
|
|
29
|
+
}),
|
|
30
|
+
...fold.missing.filter(here).map((line) => [fieldOf(line, 'scope'), 'REQUIRED']),
|
|
31
|
+
...fold.forks.flatMap((fork) => fork.lines.slice(0, 1)).filter(here).map((line) => [fieldOf(line, 'scope'), 'CONFLICT']),
|
|
32
|
+
];
|
|
33
|
+
const cells = new Map();
|
|
34
|
+
for (const [scope, state] of states) {
|
|
35
|
+
const cell = scope.split('/').slice(0, Math.max(1, at)).join('/');
|
|
36
|
+
const held = cells.get(cell);
|
|
37
|
+
cells.set(cell, { state: held !== undefined && WORST.indexOf(held.state) < WORST.indexOf(state) ? held.state : state, bounds: (held?.bounds ?? 0) + 1 });
|
|
38
|
+
}
|
|
39
|
+
const ordered = [...cells].sort((a, b) => WORST.indexOf(a[1].state) - WORST.indexOf(b[1].state) || a[0].localeCompare(b[0]));
|
|
40
|
+
return [
|
|
41
|
+
'## field', '', '```',
|
|
42
|
+
`FIELD @${at} · ${cells.size} cells · ${states.length} bounds · ${WORST.map((state) => `${state} ${ordered.filter(([, one]) => one.state === state).length}`).join(' · ')}`,
|
|
43
|
+
...ordered.map(([cell, one]) => `FIELD ${cell} · ${one.state} · ${one.bounds} bounds`),
|
|
44
|
+
'```',
|
|
45
|
+
];
|
|
46
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** The regions bound renders itself, by name: a view that asks any other is answered by a capsule that declares it, or refused whole. */
|
|
2
|
+
export declare const REGION_NAMES: readonly ["receipts", "help", "lines", "red", "forks", "programs", "why", "sign", "take", "history", "field"];
|
|
3
|
+
export type RegionName = (typeof REGION_NAMES)[number];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The regions bound renders itself, by name: a view that asks any other is answered by a capsule that declares it, or refused whole. */
|
|
2
|
+
export const REGION_NAMES = [
|
|
3
|
+
'receipts', 'help', 'lines', 'red', 'forks', 'programs', 'why', 'sign', 'take', 'history', 'field',
|
|
4
|
+
];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface OpenCell {
|
|
2
|
+
readonly scope: string;
|
|
3
|
+
readonly ceiling: string;
|
|
4
|
+
readonly bits: number | null;
|
|
5
|
+
readonly freedom: string;
|
|
6
|
+
readonly states: readonly string[];
|
|
7
|
+
readonly origins: readonly string[];
|
|
8
|
+
readonly rank: number;
|
|
9
|
+
readonly cone: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* What is open, as cells, taken once when the tree is folded and read by the view rather than taken again at every
|
|
13
|
+
* render. A line of the lock with more than one bit still free is a question this tree has not answered; a cell with
|
|
14
|
+
* one legal state is applied and not open; a cell a reading has already answered is applied whatever its width; a
|
|
15
|
+
* cell standing on one origin with a closer already named is asked and not open. A cell says of itself that it is a
|
|
16
|
+
* decision: a ceiling in force is not a question because it is wide, and a lock with no decision in it is a lock with
|
|
17
|
+
* nothing open, which the render says by saying nothing. What each state would be worth is rendered beside it, and a quantity this instrument cannot yet take is
|
|
18
|
+
* rendered grey with the name of what would take it, never as a number it invented.
|
|
19
|
+
*/
|
|
20
|
+
export declare function openCells(input: {
|
|
21
|
+
readonly store: string;
|
|
22
|
+
readonly standing: readonly string[];
|
|
23
|
+
readonly observed: readonly string[];
|
|
24
|
+
readonly grey: readonly string[];
|
|
25
|
+
readonly epoch: number;
|
|
26
|
+
}): readonly OpenCell[];
|
|
27
|
+
export declare const openCards: (open: readonly OpenCell[]) => readonly string[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { fieldOf } from "../fold/claims.js";
|
|
2
|
+
import { alphabet } from '@lapxo/topos/wire';
|
|
3
|
+
import { declaredConfig as declared } from '@lapxo/topos/forms';
|
|
4
|
+
import { bytesDigest } from "../fold/digests.js";
|
|
5
|
+
/**
|
|
6
|
+
* What is open, as cells, taken once when the tree is folded and read by the view rather than taken again at every
|
|
7
|
+
* render. A line of the lock with more than one bit still free is a question this tree has not answered; a cell with
|
|
8
|
+
* one legal state is applied and not open; a cell a reading has already answered is applied whatever its width; a
|
|
9
|
+
* cell standing on one origin with a closer already named is asked and not open. A cell says of itself that it is a
|
|
10
|
+
* decision: a ceiling in force is not a question because it is wide, and a lock with no decision in it is a lock with
|
|
11
|
+
* nothing open, which the render says by saying nothing. What each state would be worth is rendered beside it, and a quantity this instrument cannot yet take is
|
|
12
|
+
* rendered grey with the name of what would take it, never as a number it invented.
|
|
13
|
+
*/
|
|
14
|
+
export function openCells(input) {
|
|
15
|
+
const { store, standing, observed } = input;
|
|
16
|
+
const rests = new Map();
|
|
17
|
+
const asked = new Set();
|
|
18
|
+
for (const line of standing) {
|
|
19
|
+
for (const name of fieldOf(line, 'restsOn').split('|').filter(Boolean))
|
|
20
|
+
rests.set(name, (rests.get(name) ?? 0) + 1);
|
|
21
|
+
for (const need of fieldOf(line, 'needs').split('|').filter(Boolean))
|
|
22
|
+
asked.add(need);
|
|
23
|
+
}
|
|
24
|
+
const by = new Map();
|
|
25
|
+
for (const line of [...standing, ...observed]) {
|
|
26
|
+
const at = fieldOf(line, 'scope');
|
|
27
|
+
by.set(at, (by.get(at) ?? new Set()).add(fieldOf(line, 'by')));
|
|
28
|
+
}
|
|
29
|
+
const grey = new Set(input.grey.map((line) => fieldOf(line, 'scope')));
|
|
30
|
+
const answered = new Set(observed.map((line) => fieldOf(line, 'scope')));
|
|
31
|
+
const asDecision = standing.filter((line) => fieldOf(line, 'shape') === 'decision' && fieldOf(line, 'value') !== 'withdraw');
|
|
32
|
+
const held = asDecision.length ? asDecision : [];
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const line of held) {
|
|
35
|
+
const scope = fieldOf(line, 'scope');
|
|
36
|
+
const value = fieldOf(line, 'value');
|
|
37
|
+
const freedom = declared(line);
|
|
38
|
+
const bits = freedom.bits;
|
|
39
|
+
if (freedom.kind === 'inactive' || (bits !== null && bits < 1))
|
|
40
|
+
continue;
|
|
41
|
+
if (answered.has(scope) || (grey.has(scope) && asked.has(scope)))
|
|
42
|
+
continue;
|
|
43
|
+
const needs = fieldOf(line, 'needs');
|
|
44
|
+
out.push({
|
|
45
|
+
scope,
|
|
46
|
+
ceiling: value,
|
|
47
|
+
bits,
|
|
48
|
+
freedom: freedom.kind,
|
|
49
|
+
states: freedom.kind === 'finite' && freedom.form === 'alphabet' ? [...new Set(alphabet(value).members)] : [],
|
|
50
|
+
origins: [...(by.get(scope) ?? new Set())].sort(),
|
|
51
|
+
rank: rests.get(scope) ?? 0,
|
|
52
|
+
cone: needs ? bytesDigest(store, new TextEncoder().encode(needs)).replace(/^[^:]*:/, '').slice(0, 8) : '',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return out.sort((a, b) => b.rank - a.rank || (b.bits ?? 0) - (a.bits ?? 0) || a.scope.localeCompare(b.scope));
|
|
56
|
+
}
|
|
57
|
+
export const openCards = (open) => open.flatMap((cell) => [
|
|
58
|
+
`OPEN ${cell.scope} · cone ${cell.cone || 'grey · a demand would name it'} · ceiling ${cell.ceiling}`
|
|
59
|
+
+ ` · origins ${cell.origins.length} ${cell.origins.join(' ')} · ${cell.bits === null ? `unknown freedom (${cell.freedom})` : `${cell.bits.toFixed(1)} bits`}`,
|
|
60
|
+
...cell.states.map((state) => `CANDIDATE ${state} · rank ${cell.rank}`),
|
|
61
|
+
]);
|