@lapxo/bound 0.1.1 → 0.1.3
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/README.md +5 -5
- package/TARGET.bound +13 -18
- package/dist/cli/accept.js +112 -71
- package/dist/cli/owner.js +3 -0
- package/dist/cli/verb.js +48 -10
- package/dist/fold/configures.js +4 -1
- package/dist/fold/digests.js +7 -1
- package/dist/fold/kept.js +20 -5
- package/dist/fold/observed.d.ts +1 -0
- package/dist/fold/observed.js +22 -5
- package/dist/fold/reach.d.ts +5 -0
- package/dist/fold/reach.js +20 -2
- package/dist/fold/region-declarations.d.ts +2 -0
- package/dist/fold/region-declarations.js +13 -0
- package/dist/fold/resolved.js +9 -1
- package/dist/fold/second.js +3 -0
- package/dist/host/capsule.js +8 -2
- package/dist/host/content-needed.d.ts +5 -0
- package/dist/host/content-needed.js +8 -0
- package/dist/host/read-only.d.ts +3 -0
- package/dist/host/read-only.js +9 -0
- package/dist/host/release.d.ts +2 -0
- package/dist/host/release.js +14 -6
- package/dist/host/selected-capsules.d.ts +1 -1
- package/dist/host/selected-capsules.js +8 -4
- package/dist/host/selected-topos.d.ts +1 -1
- package/dist/host/selected-topos.js +5 -10
- package/dist/host/source-content.d.ts +4 -2
- package/dist/host/source-content.js +28 -15
- package/dist/land/leaving.js +4 -0
- package/dist/land/ledger.js +3 -0
- package/dist/observe/regions.js +4 -4
- package/dist/observe/run.js +5 -1
- package/dist/render/view.js +2 -1
- package/package.json +1 -1
- package/receipts.bound +88 -86
package/dist/cli/verb.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { withoutEffects } from "../host/read-only.js";
|
|
3
|
+
import { resolveSources, resolveRequested } from "../host/source-content.js";
|
|
3
4
|
import { cellsWithReceipts } from "../host/cell-receipts.js";
|
|
5
|
+
import { renderCells } from '@lapxo/topos/cells-view';
|
|
4
6
|
import { releasePolicyLines } from "../fold/release-policy.js";
|
|
5
7
|
import { admittedObjects } from "../host/objects.js";
|
|
6
8
|
import { objectFold } from "../fold/object.js";
|
|
@@ -15,7 +17,7 @@ import { eraOf, signaturesOf } from "../fold/digests.js";
|
|
|
15
17
|
import { fullDigest as bootstrapDigest } from "../host/digest.js";
|
|
16
18
|
import { canonical } from '@lapxo/topos/wire';
|
|
17
19
|
import { EXTENSION, LOCK, fieldOf, foldClaims, isWire, selfName } from "../fold/claims.js";
|
|
18
|
-
import { observedClaims } from "../fold/observed.js";
|
|
20
|
+
import { observedClaims, readerRefusals } from "../fold/observed.js";
|
|
19
21
|
import { rolesOf } from "../fold/roles.js";
|
|
20
22
|
import { authorityFor, releaseSigner, rootSigner, writerFor } from "../fold/signers.js";
|
|
21
23
|
import { land, ledgerLines, storeOf } from "../land/ledger.js";
|
|
@@ -189,10 +191,31 @@ async function view(root, under, asked, told = [], coord, key, includePolicy = t
|
|
|
189
191
|
const rest = told.filter((line) => !ledger.includes(line));
|
|
190
192
|
if (place && rest.length)
|
|
191
193
|
tellOwn(place, rest);
|
|
194
|
+
if (name === NAMED_VIEWS.lines && (told.length || depth === undefined)) {
|
|
195
|
+
const text = standingOf(root, under, ledger).standing.filter(isWire).join('\n');
|
|
196
|
+
process.stdout.write(text ? `${text}\n` : '');
|
|
197
|
+
return EXIT.closed;
|
|
198
|
+
}
|
|
199
|
+
if (told.length) {
|
|
200
|
+
const selected = viewsOf(standingOf(root, under, ledger).standing).get(name);
|
|
201
|
+
if (selected && !selected.shape && selected.regions.some(region => region.name === 'cells')) {
|
|
202
|
+
const history = [...signedOwnerLines(storeOf(root)), ...ledger];
|
|
203
|
+
const held = await admittedObjects(root, history);
|
|
204
|
+
process.stdout.write(`TOLD ${told.length} lines folded as if landed · nothing written\n`);
|
|
205
|
+
for (const line of renderCells(objectFold(held.records, held.context)))
|
|
206
|
+
process.stdout.write(line + '\n');
|
|
207
|
+
return EXIT.closed;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
192
210
|
const own = place ? rest.filter((line) => admittedIn(root, place, line)) : [];
|
|
193
211
|
const fold = told.length
|
|
194
|
-
? foldPlace(root, entryOf(root), under, false, { standing: standingOf(root, under, ledger), observed: observedClaims(root, storeOf(root), { wait: false }) })
|
|
195
|
-
: foldPlace(root, entryOf(root), under,
|
|
212
|
+
? foldPlace(root, entryOf(root), under, false, { standing: standingOf(root, under, ledger), observed: observedClaims(root, storeOf(root), { wait: false }), free: true })
|
|
213
|
+
: foldPlace(root, entryOf(root), under, true);
|
|
214
|
+
if (!told.length && readerRefusals().length) {
|
|
215
|
+
for (const why of readerRefusals())
|
|
216
|
+
process.stderr.write(`${selfName()}: ${why}\n`);
|
|
217
|
+
return EXIT.refuse;
|
|
218
|
+
}
|
|
196
219
|
if (coord)
|
|
197
220
|
askWhy(fold, coord);
|
|
198
221
|
if (name === NAMED_VIEWS.lines) {
|
|
@@ -200,6 +223,9 @@ async function view(root, under, asked, told = [], coord, key, includePolicy = t
|
|
|
200
223
|
beat(root, fold, Date.now() - at);
|
|
201
224
|
const text = fold.standing.filter(isWire).join('\n');
|
|
202
225
|
process.stdout.write(text ? `${text}\n` : '');
|
|
226
|
+
if (depth === '1')
|
|
227
|
+
for (const line of readingsIn([...fold.standing.filter(isCeiling), ...ownCeilings(under)], fold))
|
|
228
|
+
process.stdout.write(`${line}\n`);
|
|
203
229
|
return EXIT.closed;
|
|
204
230
|
}
|
|
205
231
|
const views = viewsOf(fold.standing);
|
|
@@ -371,7 +397,7 @@ function foldFile(at) {
|
|
|
371
397
|
process.stdout.write(`FACT ${folded.standing.length} standing · ${folded.forks.length} forks · ${basename(at)}\n`);
|
|
372
398
|
return EXIT.closed;
|
|
373
399
|
}
|
|
374
|
-
function foldVerb(
|
|
400
|
+
function foldVerb(root, rest) {
|
|
375
401
|
const files = positionalsOf(rest).filter((one) => existsSync(resolve(one)) && !lstatSync(resolve(one)).isDirectory() && one.endsWith(EXTENSION));
|
|
376
402
|
const places = positionalsOf(rest).filter((one) => existsSync(resolve(one)) && lstatSync(resolve(one)).isDirectory());
|
|
377
403
|
if (files.length === 1 && !places.length && valueOf(rest, FLAGS.as) === undefined) {
|
|
@@ -381,14 +407,24 @@ function foldVerb(_root, rest) {
|
|
|
381
407
|
return foldAt(dirname(at), undefined, rest);
|
|
382
408
|
}
|
|
383
409
|
const under = places[0];
|
|
384
|
-
|
|
410
|
+
// A prospective lot is data, not the place whose authority admits it.
|
|
411
|
+
const tree = files.length ? root : resolve('.');
|
|
385
412
|
const place = under ? relative(tree, resolve(under)).split(sep).join('/') : '';
|
|
386
413
|
return foldAt(tree, place ? `${place}/` : undefined, rest);
|
|
387
414
|
}
|
|
388
415
|
async function foldAt(root, under, argv) {
|
|
389
416
|
const as = valueOf(argv, FLAGS.as);
|
|
390
|
-
|
|
391
|
-
|
|
417
|
+
const prospective = as !== undefined && positionalsOf(argv).some(one => existsSync(resolve(one)) && !lstatSync(resolve(one)).isDirectory());
|
|
418
|
+
if (as === NAMED_VIEWS.help || prospective || argv.includes(FLAGS.check))
|
|
419
|
+
return foldResolved(root, under, argv);
|
|
420
|
+
return resolveRequested(digest => resolveSources(standingOf(root, under).standing, [digest]), () => foldResolved(root, under, argv));
|
|
421
|
+
}
|
|
422
|
+
async function foldResolved(root, under, argv) {
|
|
423
|
+
const as = valueOf(argv, FLAGS.as);
|
|
424
|
+
const inputs = positionalsOf(argv).filter(one => existsSync(resolve(one)) && !lstatSync(resolve(one)).isDirectory());
|
|
425
|
+
// Public content is authenticated by its digest, independently of owner history.
|
|
426
|
+
// A named view does not request mounting every release of the instrument.
|
|
427
|
+
const receiptCheck = as === undefined && argv.includes(FLAGS.check);
|
|
392
428
|
if (as === OBJECT_VIEW) {
|
|
393
429
|
const inputs = positionalsOf(argv).filter(one => existsSync(resolve(one)) && !lstatSync(resolve(one)).isDirectory());
|
|
394
430
|
if (inputs.length > 1)
|
|
@@ -404,6 +440,8 @@ async function foldAt(root, under, argv) {
|
|
|
404
440
|
const key = valueOf(argv, FLAGS.key);
|
|
405
441
|
if (as === undefined) {
|
|
406
442
|
const exit = await pass(root, entryOf(root), under, argv.includes(FLAGS.check));
|
|
443
|
+
if (receiptCheck)
|
|
444
|
+
return exit;
|
|
407
445
|
if (exit !== 0)
|
|
408
446
|
return exit;
|
|
409
447
|
const history = signedOwnerLines(storeOf(root));
|
|
@@ -419,7 +457,7 @@ async function foldAt(root, under, argv) {
|
|
|
419
457
|
}
|
|
420
458
|
return exit;
|
|
421
459
|
}
|
|
422
|
-
const named =
|
|
460
|
+
const named = inputs;
|
|
423
461
|
const coord = positionalsOf(argv).find((one) => one !== under?.replace(/\/$/, '') && !one.startsWith('-') && !(existsSync(resolve(one)) && lstatSync(resolve(one)).isDirectory()));
|
|
424
462
|
if ((as === 'why' || as === NAMED_VIEWS.because || as.split('@')[0] === 'why') && coord !== undefined)
|
|
425
463
|
return view(root, under, as === NAMED_VIEWS.because ? 'why' : as, [], coord, key);
|
|
@@ -430,7 +468,7 @@ async function foldAt(root, under, argv) {
|
|
|
430
468
|
return batch.code;
|
|
431
469
|
if (batch.kind === 'once')
|
|
432
470
|
process.stdout.write(`ONCE ${batch.proposed.length} lines · every one already landed: the view folds the tree as it stands\n`);
|
|
433
|
-
return view(root, under, as, batch.kind === 'signed' ? batch.signed :
|
|
471
|
+
return withoutEffects(() => view(root, under, as, batch.kind === 'signed' ? batch.signed : batch.proposed, undefined, key));
|
|
434
472
|
}
|
|
435
473
|
const VERBS = {
|
|
436
474
|
fold: (root, rest) => foldVerb(root, rest),
|
package/dist/fold/configures.js
CHANGED
|
@@ -3,6 +3,9 @@ import { fieldOf, isConfig } from "./claims.js";
|
|
|
3
3
|
import { isReaderLock } from "./observed.js";
|
|
4
4
|
import { isRoleLock } from "./roles.js";
|
|
5
5
|
import { isSignerLock } from "./signers.js";
|
|
6
|
+
import { isReleaseBinding } from "../host/release.js";
|
|
7
|
+
import { isRegionDeclaration } from "./region-declarations.js";
|
|
8
|
+
import { ownLock } from "../observe/runner.js";
|
|
6
9
|
function isEffectLock(line) {
|
|
7
10
|
const got = parse(line);
|
|
8
11
|
return got.kind === 'fact' && got.value.fields['measure'] === 'effect';
|
|
@@ -10,7 +13,7 @@ function isEffectLock(line) {
|
|
|
10
13
|
/** A lock line the instrument itself consumes — an effect it runs, a reader it loads, a role it walks by, a signer it admits, the wire it reads by. */
|
|
11
14
|
function configures(line) {
|
|
12
15
|
const got = parse(line);
|
|
13
|
-
return isEffectLock(line) || isReaderLock(line) || isRoleLock(line) || isSignerLock(line) || (got.kind === 'fact' && isWireClaim(got.value.fields));
|
|
16
|
+
return isReleaseBinding(line) || isEffectLock(line) || isReaderLock(line) || isRoleLock(line) || isSignerLock(line) || (got.kind === 'fact' && isWireClaim(got.value.fields)) || isRegionDeclaration(line, ownLock());
|
|
14
17
|
}
|
|
15
18
|
/** Every reads line that configures nothing is a ceiling: a bound some reading has to meet. */
|
|
16
19
|
export function isCeiling(line) {
|
package/dist/fold/digests.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readOnly } from "../host/read-only.js";
|
|
1
2
|
import { appendFileSync, dirname, join, mkdirSync, readdirSync, relative, resolve } from "../host/io.js";
|
|
2
3
|
import { bytesDigest as bytesWith, fullDigest as fullWith } from "../host/digest.js";
|
|
3
4
|
import { coordinatesUnder, observeFile, observeText, stampOf } from "../observe/files.js";
|
|
@@ -116,6 +117,8 @@ export function coordinateDigest(store, at) {
|
|
|
116
117
|
return held;
|
|
117
118
|
const digest = bytesDigest(store, observeFile(at) ?? new Uint8Array());
|
|
118
119
|
memo.set(key, digest);
|
|
120
|
+
if (readOnly())
|
|
121
|
+
return digest;
|
|
119
122
|
mkdirSync(join(store, 'cas'), { recursive: true });
|
|
120
123
|
appendFileSync(join(store, 'cas', `stamps${EXTENSION}`), `${key}\t${digest}\n`);
|
|
121
124
|
return digest;
|
|
@@ -127,6 +130,8 @@ export function memoized(store, key, compute) {
|
|
|
127
130
|
return held;
|
|
128
131
|
const value = compute();
|
|
129
132
|
memo.set(key, value);
|
|
133
|
+
if (readOnly())
|
|
134
|
+
return value;
|
|
130
135
|
mkdirSync(join(store, 'cas'), { recursive: true });
|
|
131
136
|
appendFileSync(join(store, 'cas', `stamps${EXTENSION}`), `${key}\t${value}\n`);
|
|
132
137
|
return value;
|
|
@@ -141,7 +146,8 @@ const importsOf = (store, at, digest) => {
|
|
|
141
146
|
const found = [...new TextDecoder().decode(observeFile(at) ?? new Uint8Array()).matchAll(/(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s*)['"]([^'"]*)['"]/g)].map((m) => m[1] ?? '');
|
|
142
147
|
imported.set(digest, found);
|
|
143
148
|
memo.set(key, found.join('|'));
|
|
144
|
-
|
|
149
|
+
if (!readOnly())
|
|
150
|
+
appendFileSync(join(store, 'cas', `stamps${EXTENSION}`), `${key}\t${found.join('|')}\n`);
|
|
145
151
|
return found;
|
|
146
152
|
};
|
|
147
153
|
/**
|
package/dist/fold/kept.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { readOnly } from "../host/read-only.js";
|
|
1
2
|
import { createHash, dirname, existsSync, join, mkdirSync, readdirSync, renameSync, writeFileSync } from "../host/io.js";
|
|
2
3
|
import { bytesDigest, coordinateDigest } from "./digests.js";
|
|
3
4
|
import { viewsOf } from "./views.js";
|
|
4
|
-
import { landMemo, ledgerLines, memoAt } from "../land/ledger.js";
|
|
5
|
-
import { observeText } from "../observe/files.js";
|
|
5
|
+
import { landMemo, ledgerLines, memoAt, shardFile, writerFile } from "../land/ledger.js";
|
|
6
|
+
import { coordinatesUnder, observeFile, observeText } from "../observe/files.js";
|
|
6
7
|
import { EXTENSION, fieldOf } from "./claims.js";
|
|
7
|
-
import { keyFor, lockSigners } from "./keys.js";
|
|
8
|
+
import { keyFor, keyOf, lockSigners } from "./keys.js";
|
|
8
9
|
import { sourceOf, vouching } from "../land/vouched.js";
|
|
9
10
|
import { wordsOf } from "./wire.js";
|
|
10
11
|
import { ownLock } from "../observe/runner.js";
|
|
@@ -45,6 +46,7 @@ export function foldPoints(store, standing, under, keeper, instrument = '', obse
|
|
|
45
46
|
return [
|
|
46
47
|
`place ${under ?? '.'}`,
|
|
47
48
|
`instrument ${instrument}`,
|
|
49
|
+
`reading context ${readingContext(store)}`,
|
|
48
50
|
...written,
|
|
49
51
|
...lined,
|
|
50
52
|
...[...new Set(coordinates)].sort(),
|
|
@@ -93,6 +95,14 @@ export const foldsElsewhere = () => elsewhere.folds;
|
|
|
93
95
|
const joined = (lines) => lines.join('\n');
|
|
94
96
|
const split = (text) => (text === '' ? [] : text.split('\n'));
|
|
95
97
|
const linesOf = (value) => value === undefined ? [] : typeof value === 'string' ? split(value) : value;
|
|
98
|
+
/** Operational reader history guards file-fold reuse, never cell-region identity. */
|
|
99
|
+
function readingContext(store) {
|
|
100
|
+
const speaker = keyOf(store, 'read');
|
|
101
|
+
const files = speaker === undefined ? [] : [writerFile(store, speaker), ...coordinatesUnder(dirname(shardFile(store, speaker, '')), store)
|
|
102
|
+
.filter(file => file.endsWith(EXTENSION)).map(file => join(store, file))];
|
|
103
|
+
return bytesDigest(store, text(files.filter(file => existsSync(file)).sort()
|
|
104
|
+
.map(file => bytesDigest(store, observeFile(file) ?? new Uint8Array())).join('\n')));
|
|
105
|
+
}
|
|
96
106
|
export function keptPlace(store, under) {
|
|
97
107
|
const key = observeText(memoAt(store, lastOf([`place ${under ?? '.'}`])))?.trim();
|
|
98
108
|
if (key === undefined || key === '')
|
|
@@ -101,6 +111,8 @@ export function keptPlace(store, under) {
|
|
|
101
111
|
if (held === undefined)
|
|
102
112
|
return undefined;
|
|
103
113
|
const blob = JSON.parse(held);
|
|
114
|
+
if (blob.readingContext !== readingContext(store))
|
|
115
|
+
return undefined;
|
|
104
116
|
const fold = blob.fold;
|
|
105
117
|
return {
|
|
106
118
|
...fold,
|
|
@@ -118,8 +130,11 @@ export function keptPlace(store, under) {
|
|
|
118
130
|
};
|
|
119
131
|
}
|
|
120
132
|
export const keptRender = (store, key, name) => observeText(memoAt(store, `render/${key}/${name}`));
|
|
121
|
-
export const keepRender = (store, _signer, key, name, _epoch, text_) => {
|
|
133
|
+
export const keepRender = (store, _signer, key, name, _epoch, text_) => { if (!readOnly())
|
|
134
|
+
landMemo(store, `render/${key}/${name}`, text(text_)); };
|
|
122
135
|
export function keepFold(store, key, fold, points, instrument = '') {
|
|
136
|
+
if (readOnly())
|
|
137
|
+
return;
|
|
123
138
|
const packed = {
|
|
124
139
|
...fold,
|
|
125
140
|
standing: joined(fold.standing),
|
|
@@ -134,7 +149,7 @@ export function keepFold(store, key, fold, points, instrument = '') {
|
|
|
134
149
|
signed: [...fold.signed],
|
|
135
150
|
kept: false,
|
|
136
151
|
};
|
|
137
|
-
landMemo(store, `fold/${key}`, text(JSON.stringify({ fold: packed, points, instrument })));
|
|
152
|
+
landMemo(store, `fold/${key}`, text(JSON.stringify({ fold: packed, points, instrument, readingContext: readingContext(store) })));
|
|
138
153
|
const at = memoAt(store, lastOf(points));
|
|
139
154
|
mkdirSync(dirname(at), { recursive: true });
|
|
140
155
|
writeFileSync(`${at}.${process.pid}.part`, `${key}\n`);
|
package/dist/fold/observed.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare const readersBehind: () => readonly {
|
|
|
15
15
|
readonly module: string;
|
|
16
16
|
readonly places: number;
|
|
17
17
|
}[];
|
|
18
|
+
export declare const readerRefusals: () => readonly string[];
|
|
18
19
|
export declare function readerLines(store: string): readonly string[];
|
|
19
20
|
/**
|
|
20
21
|
* A reader never blocks a fold that did not ask to wait: what it read before is served at once, and a reader whose
|
package/dist/fold/observed.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readOnly } from "../host/read-only.js";
|
|
1
2
|
import { basename, dirname, existsSync, mkdirSync, resolve, rmSync, spawn, statSync, writeFileSync } from "../host/io.js";
|
|
2
3
|
import { bundleOf, processOf } from "../observe/runner.js";
|
|
3
4
|
import { canonical, parse, PROTOCOL } from '@lapxo/topos/wire';
|
|
@@ -15,7 +16,7 @@ import { capsuleReadersOf, handedAt, lookOf, offersOf } from "./readers.js";
|
|
|
15
16
|
import { runReader, runs } from "../observe/run.js";
|
|
16
17
|
import { LEVEL, OBSERVED, keepObservation, keyOf, legacyOf, observation, observed as observedAt, opened, pointer, readerOf, regionIn, setAside, sha, trimmed } from "./shards.js";
|
|
17
18
|
import { saidDigest } from "./kept.js";
|
|
18
|
-
import { carriedIn } from "./resolved.js";
|
|
19
|
+
import { carriedIn, carriedFrom } from "./resolved.js";
|
|
19
20
|
export const cased = (question) => /^(?:vector|sample)\//.test(question);
|
|
20
21
|
export function isReaderLock(line) {
|
|
21
22
|
const got = parse(line);
|
|
@@ -87,6 +88,8 @@ function takeReadings(observed) {
|
|
|
87
88
|
}
|
|
88
89
|
const behind = [];
|
|
89
90
|
export const readersBehind = () => behind;
|
|
91
|
+
const refused = [];
|
|
92
|
+
export const readerRefusals = () => refused;
|
|
90
93
|
const alive = (pid) => { try {
|
|
91
94
|
return process.kill(pid, 0);
|
|
92
95
|
}
|
|
@@ -170,6 +173,14 @@ export function readerReaches(reach, where) {
|
|
|
170
173
|
return where.some((one) => reached(trimmed(one)));
|
|
171
174
|
}
|
|
172
175
|
export function observedClaims(root, store, options = {}) {
|
|
176
|
+
refused.length = 0;
|
|
177
|
+
if (readOnly()) {
|
|
178
|
+
const carried = (observeText(resolve(root, 'receipts.bound')) ?? '').split('\n').filter(isWire);
|
|
179
|
+
const evidence = carriedFrom(store, carried);
|
|
180
|
+
if (evidence === undefined)
|
|
181
|
+
throw Error('REFUSE·preview native receipt bytes unavailable; fold the declared inputs first');
|
|
182
|
+
return evidence;
|
|
183
|
+
}
|
|
173
184
|
const wait = options.wait ?? true;
|
|
174
185
|
const standing = lockStanding(store);
|
|
175
186
|
const lines = readersOf(store);
|
|
@@ -255,7 +266,7 @@ export function observedClaims(root, store, options = {}) {
|
|
|
255
266
|
const module = resolve(root, reader.module);
|
|
256
267
|
const asked = reader.capsule === undefined ? undefined : capsuleAt(reader.capsule.digest, reader.capsule.entry);
|
|
257
268
|
const source = reader.capsule === undefined ? observeFile(module) : asked === undefined ? undefined : new Uint8Array();
|
|
258
|
-
const bundle = source && reader.capsule === undefined ? bundleOf(module) ??
|
|
269
|
+
const bundle = source && reader.capsule === undefined ? bundleOf(module) ?? bytesDigest(store, source) : 'none';
|
|
259
270
|
const offered = offersOf(root, store, reader, roleOf, look, (where) => reached(trimmed(where)));
|
|
260
271
|
const vectors = reader.capsule === undefined ? observeText(resolve(root, vectorsOf(reader.module, standing))) : undefined;
|
|
261
272
|
const kept = whole && legacy === undefined && answered === undefined && source ? sha([
|
|
@@ -351,8 +362,10 @@ export function observedClaims(root, store, options = {}) {
|
|
|
351
362
|
}
|
|
352
363
|
return [];
|
|
353
364
|
};
|
|
354
|
-
if (due.length && !source)
|
|
365
|
+
if (due.length && !source) {
|
|
366
|
+
refused.push(`REFUSE·reader ${reader.module} unavailable · ${due.length} readings remain due`);
|
|
355
367
|
return false;
|
|
368
|
+
}
|
|
356
369
|
if (due.length && !wait) {
|
|
357
370
|
for (const plan of plans) {
|
|
358
371
|
if (plan.said !== undefined) {
|
|
@@ -397,11 +410,15 @@ export function observedClaims(root, store, options = {}) {
|
|
|
397
410
|
: runReader(module, root, due.map((d) => ({ place: d.place, ...(reader.kind === 'js' ? { text: new TextDecoder().decode(d.bytes) } : {}), held: heldOf(d.place, d.region) })));
|
|
398
411
|
}
|
|
399
412
|
catch (error) {
|
|
400
|
-
|
|
413
|
+
const why = `REFUSE·reader ${reader.module} · ${error instanceof Error ? error.message : String(error)}`;
|
|
414
|
+
refused.push(why);
|
|
415
|
+
process.stderr.write(`${why}\n`);
|
|
401
416
|
return false;
|
|
402
417
|
}
|
|
403
418
|
if (rows.every((mine) => !mine.length)) {
|
|
404
|
-
|
|
419
|
+
const why = `REFUSE·reader ${reader.module} · answered none of ${due.length} places · nothing landed, they stay due`;
|
|
420
|
+
refused.push(why);
|
|
421
|
+
process.stderr.write(`${why}\n`);
|
|
405
422
|
return false;
|
|
406
423
|
}
|
|
407
424
|
ran += due.length;
|
package/dist/fold/reach.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ export declare const selectionLines: (fold: {
|
|
|
7
7
|
readonly standing: readonly string[];
|
|
8
8
|
readonly under?: string;
|
|
9
9
|
}) => readonly string[];
|
|
10
|
+
export declare const projectionLines: (fold: {
|
|
11
|
+
readonly root: string;
|
|
12
|
+
readonly standing: readonly string[];
|
|
13
|
+
readonly under?: string;
|
|
14
|
+
}) => readonly string[];
|
|
10
15
|
export declare function capsulesFor(standing: readonly string[], place: string, root?: string): readonly {
|
|
11
16
|
readonly capsule: Capsule;
|
|
12
17
|
readonly offer: string;
|
package/dist/fold/reach.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { selectedCapsules } from "../host/selected-capsules.js";
|
|
2
2
|
import { basename } from "../host/io.js";
|
|
3
|
-
import { matches } from '@lapxo/topos/wire';
|
|
3
|
+
import { canonical, matches, parse } from '@lapxo/topos/wire';
|
|
4
4
|
import { fieldOf } from "./claims.js";
|
|
5
5
|
import { spoken, worldsIn } from "./places.js";
|
|
6
6
|
import { wordOf, wordsOf } from "./wire.js";
|
|
@@ -57,9 +57,27 @@ export const selectionLines = (fold) => {
|
|
|
57
57
|
const qualified = fold.standing.filter((line) => fieldOf(line, 'scope').startsWith(`${fold.under ?? ''}uses/`));
|
|
58
58
|
return [...new Set([...local, ...qualified])].filter((line) => fieldOf(line, 'value') !== 'withdraw');
|
|
59
59
|
};
|
|
60
|
+
/** Host bindings belong to the selected place, never to another sibling's lock. */
|
|
61
|
+
const relativeProjections = (standing, prefix) => standing.filter(line => fieldOf(line, 'scope').startsWith(`${prefix}dep/`)).map(line => {
|
|
62
|
+
const got = parse(line, { preserveKeys: true });
|
|
63
|
+
if (got.kind !== 'fact')
|
|
64
|
+
throw Error(`REFUSE·wire ${got.why}`);
|
|
65
|
+
// An unsigned host projection of admitted fields, never a newly signed claim.
|
|
66
|
+
const { sig, by, epoch, expires, repo, ...fields } = got.value.fields;
|
|
67
|
+
return canonical({ ...fields, scope: fields.scope.slice(prefix.length) }, got.value.version);
|
|
68
|
+
});
|
|
69
|
+
export const projectionLines = (fold) => {
|
|
70
|
+
const place = fold.under?.replace(/\/$/, '') ?? '';
|
|
71
|
+
const prefix = place ? `${place}/` : '';
|
|
72
|
+
const local = ownLockOf(fold.root, place).filter(line => fieldOf(line, 'scope').startsWith('dep/'));
|
|
73
|
+
const qualified = relativeProjections(fold.standing, prefix);
|
|
74
|
+
return [...new Set([...ownLock(), ...local, ...qualified])];
|
|
75
|
+
};
|
|
60
76
|
export function capsulesFor(standing, place, root) {
|
|
61
77
|
const prefix = place ? `${place}/` : '';
|
|
62
78
|
const pins = root === undefined ? standing.filter(line => fieldOf(line, 'scope').startsWith(`${prefix}uses/`) && fieldOf(line, 'value') !== 'withdraw')
|
|
63
79
|
: selectionLines({ root, standing, ...(prefix ? { under: prefix } : {}) });
|
|
64
|
-
|
|
80
|
+
const host = root === undefined ? [...ownLock(), ...relativeProjections(standing, prefix)]
|
|
81
|
+
: projectionLines({ root, standing, ...(prefix ? { under: prefix } : {}) });
|
|
82
|
+
return selectedCapsules(pins, undefined, undefined, host);
|
|
65
83
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { fieldOf, isConfig } from "./claims.js";
|
|
2
|
+
import { wordsOf } from "./wire.js";
|
|
3
|
+
/** Region input selectors consumed by the host, rather than measured ceilings. */
|
|
4
|
+
export function isRegionDeclaration(line, wire) {
|
|
5
|
+
if (!isConfig(line) || !fieldOf(line, 'scope').startsWith('region/') || fieldOf(line, 'value') === 'withdraw')
|
|
6
|
+
return false;
|
|
7
|
+
const named = ['region-coordinate', 'region-leaf'].flatMap(name => {
|
|
8
|
+
const words = wordsOf(wire, name);
|
|
9
|
+
return words.length === 1 ? words : [];
|
|
10
|
+
});
|
|
11
|
+
const measures = ['lines', 'receipts', 'reads', ...named];
|
|
12
|
+
return measures.includes(fieldOf(line, 'measure'));
|
|
13
|
+
}
|
package/dist/fold/resolved.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readOnly } from "../host/read-only.js";
|
|
1
2
|
import { createHash, dirname, existsSync, join, linkSync, mkdirSync, readdirSync, renameSync, writeFileSync } from "../host/io.js";
|
|
2
3
|
import { canonical, parse } from '@lapxo/topos/wire';
|
|
3
4
|
import { RECEIPTS, fieldOf, isWire } from "./claims.js";
|
|
@@ -11,6 +12,10 @@ import { sourced } from "../land/vouched.js";
|
|
|
11
12
|
import { entriesIn, observeFile, observeText } from "../observe/files.js";
|
|
12
13
|
export const bytesOnly = (line) => {
|
|
13
14
|
const got = parse(line);
|
|
15
|
+
// Authenticated evidence is delivered verbatim. A shortened observation is
|
|
16
|
+
// a different signed message, even when it describes the same input.
|
|
17
|
+
if (got.kind === 'fact' && got.value.fields.sig)
|
|
18
|
+
return line;
|
|
14
19
|
return got.kind === 'fact' && fieldOf(line, 'measure') === 'observed' ? canonical({ ...got.value.fields, value: fieldOf(line, 'at').replace(/^.*:/, '').slice(0, 16) }) : line;
|
|
15
20
|
};
|
|
16
21
|
const held = new WeakMap();
|
|
@@ -45,7 +50,8 @@ export function receiptsOf(fold) {
|
|
|
45
50
|
return canonical({ scope: `${sourced(fold.standing)}/${place}${shape}`, role: 'writes', form: 'alphabet', measure: 'digest', value: digest, by: writerFor(fold.standing, 'fold') ?? '', at: `place:${digest}` });
|
|
46
51
|
});
|
|
47
52
|
const place = under.replace(/\/$/, '');
|
|
48
|
-
const lines = [...named, ...[...cones].sort(), ...[...ran].sort(), ...paid]
|
|
53
|
+
const lines = [...named, ...[...cones].sort(), ...[...ran].sort(), ...paid]
|
|
54
|
+
.map((line) => (place && !fieldOf(line, 'sig') ? zoomed(line, place) : line));
|
|
49
55
|
if (!lines.length) {
|
|
50
56
|
const shipped = (observeText(join(fold.root, under, RECEIPTS)) ?? '').split('\n').filter(isWire);
|
|
51
57
|
const carried = carriedFrom(fold.store, shipped);
|
|
@@ -64,6 +70,8 @@ export function keepCarried(store, lines, from) {
|
|
|
64
70
|
verifiedCarried(at, digest);
|
|
65
71
|
}
|
|
66
72
|
else {
|
|
73
|
+
if (readOnly())
|
|
74
|
+
return `sha256:${digest}`;
|
|
67
75
|
mkdirSync(dirname(at), { recursive: true });
|
|
68
76
|
const src = from === undefined ? '' : storeAt(from, 'cas', 'carried', digest);
|
|
69
77
|
if (src && existsSync(src)) {
|
package/dist/fold/second.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readOnly } from "../host/read-only.js";
|
|
1
2
|
import { snapshotVerdict } from "./evidence.js";
|
|
2
3
|
import { dirname, resolve, spawnSync } from "../host/io.js";
|
|
3
4
|
import { ledgerLines } from "../land/ledger.js";
|
|
@@ -9,6 +10,8 @@ import { said } from "./wire.js";
|
|
|
9
10
|
import { sha } from "../host/hash.js";
|
|
10
11
|
const asked = new Map();
|
|
11
12
|
function askedNow(store, rests) {
|
|
13
|
+
if (readOnly())
|
|
14
|
+
return undefined;
|
|
12
15
|
const root = dirname(store);
|
|
13
16
|
const key = `${root} ${rests}`;
|
|
14
17
|
if (asked.has(key))
|
package/dist/host/capsule.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ContentNeeded } from "./content-needed.js";
|
|
2
|
+
import { readOnly } from "./read-only.js";
|
|
1
3
|
import { sha } from "./hash.js";
|
|
2
4
|
import { wireLinesOf as linesOf } from "../fold/claims.js";
|
|
3
5
|
import { createHash } from 'node:crypto';
|
|
@@ -47,8 +49,10 @@ export function capsuleAt(digest, entry, store) {
|
|
|
47
49
|
if (held !== undefined)
|
|
48
50
|
return held;
|
|
49
51
|
const bytes = observeFile(blobAt(store ?? ownStore(), digest));
|
|
50
|
-
if (bytes === undefined
|
|
51
|
-
|
|
52
|
+
if (bytes === undefined)
|
|
53
|
+
throw new ContentNeeded(digest, `REFUSE·pin ${digest} offered capsule unavailable · not laid`);
|
|
54
|
+
if (`sha256:${createHash('sha256').update(bytes).digest('hex')}` !== digest)
|
|
55
|
+
throw Error(`REFUSE·pin ${digest} content hash mismatch · not laid`);
|
|
52
56
|
const known = declared.get(digest) ?? ((lines) => ({ lines, declaration: declarationOf(lines) }))(linesOf(observeText(join(unpacked(digest, bytes), CAPSULE))));
|
|
53
57
|
declared.set(digest, known);
|
|
54
58
|
const names = Object.keys(known.declaration.regions);
|
|
@@ -108,6 +112,8 @@ function answered(digest, bytes, requests, names, tree, cacheStore = ownStore(),
|
|
|
108
112
|
}
|
|
109
113
|
});
|
|
110
114
|
const missing = requests.filter((_, i) => held[i] === undefined);
|
|
115
|
+
if (missing.length && readOnly())
|
|
116
|
+
throw Error(`REFUSE·preview capsule ${digest} has no verified answer for the proposed inputs; fold the declared inputs first`);
|
|
111
117
|
if (missing.length)
|
|
112
118
|
process.stderr.write(`CAPSULE ${digest.slice(7, 19)} · ${requests.length} asked · ${missing.length} run · ${missing.map((one) => `${one.rootScope}${one.region}`).join(' ')}\n`);
|
|
113
119
|
// Execute and validate the entire batch before committing any answer to the cache.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The synchronous host names unavailable content; the enclosing fold resolves exactly that request. */
|
|
2
|
+
export class ContentNeeded extends Error {
|
|
3
|
+
constructor(digest, why = `REFUSE·pin ${digest} content unavailable · not laid`) {
|
|
4
|
+
super(why);
|
|
5
|
+
this.digest = digest;
|
|
6
|
+
this.name = 'ContentNeeded';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
/** Prospective evaluation may read existing evidence and calculate values, but cannot acquire evidence or persist caches. */
|
|
3
|
+
const mode = new AsyncLocalStorage();
|
|
4
|
+
export const readOnly = () => mode.getStore() === true;
|
|
5
|
+
export const withoutEffects = (evaluate) => mode.run(true, evaluate);
|
|
6
|
+
export function requireEffects(operation) {
|
|
7
|
+
if (readOnly())
|
|
8
|
+
throw Error(`REFUSE·preview ${operation} requires an act; fold the declared inputs first`);
|
|
9
|
+
}
|
package/dist/host/release.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const laidAt: (store: string, digest: string) => string;
|
|
2
|
+
/** A host artifact binding, not a measured ceiling or an object act. */
|
|
3
|
+
export declare const isReleaseBinding: (line: string) => boolean;
|
|
2
4
|
export declare const releasesOf: (lock: readonly string[]) => readonly string[];
|
|
3
5
|
/**
|
|
4
6
|
* What the host does before anything runs: each release the instrument's own lock names by digest is a snapshot of
|
package/dist/host/release.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
+
import { ContentNeeded } from "./content-needed.js";
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { existsSync, lstatSync, mkdirSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { dirname, join, sep } from 'node:path';
|
|
4
5
|
import { canonical } from '@lapxo/topos/wire';
|
|
5
|
-
import { fieldOf, isWire } from "../fold/claims.js";
|
|
6
|
+
import { fieldOf, isWire, isConfig } from "../fold/claims.js";
|
|
6
7
|
import { archiveCoordinate, archiveDestination, members } from "./archive.js";
|
|
7
8
|
import { blobAt, holdStore, landBlob, replaceWhole, storeAt } from "../land/ledger.js";
|
|
8
9
|
import { coordinatesUnder, observeFile, observeText } from "../observe/files.js";
|
|
9
10
|
export const laidAt = (store, digest) => storeAt(store, 'cas', 'laid', digest.replace(/^[^:]*:/, ''));
|
|
10
|
-
|
|
11
|
+
/** A host artifact binding, not a measured ceiling or an object act. */
|
|
12
|
+
export const isReleaseBinding = (line) => isConfig(line)
|
|
13
|
+
&& fieldOf(line, 'scope').startsWith('dep/') && fieldOf(line, 'role') === 'reads'
|
|
14
|
+
&& fieldOf(line, 'value') !== 'withdraw'
|
|
15
|
+
&& (fieldOf(line, 'measure') === 'digest' || /^[a-zA-Z][a-zA-Z0-9-]*:[a-f0-9]+$/.test(fieldOf(line, 'value')));
|
|
16
|
+
export const releasesOf = (lock) => lock.filter(isReleaseBinding);
|
|
11
17
|
const laidMark = '.laid';
|
|
12
18
|
const digestOf = (bytes) => `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
13
19
|
const hashed = (digest, bytes) => createHash(digest.includes(':') ? digest.slice(0, digest.indexOf(':')) : 'sha256').update(bytes).digest('hex') === digest.replace(/^[^:]*:/, '');
|
|
@@ -119,6 +125,11 @@ function keepSnapshot(store, digest, bytes) {
|
|
|
119
125
|
export function layReleases(store, own, lock, contentStore = own) {
|
|
120
126
|
let laid = 0;
|
|
121
127
|
for (const line of releasesOf(lock)) {
|
|
128
|
+
// A release catalog entry without a mount is materialized only when requested.
|
|
129
|
+
// Its absence must not block an unrelated place's fold.
|
|
130
|
+
const needs = fieldOf(line, 'needs');
|
|
131
|
+
if (!needs)
|
|
132
|
+
continue;
|
|
122
133
|
const digest = fieldOf(line, 'value');
|
|
123
134
|
const at = laidAt(store, digest);
|
|
124
135
|
const dir = lstatSync(at, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
@@ -133,10 +144,7 @@ export function layReleases(store, own, lock, contentStore = own) {
|
|
|
133
144
|
if (bytes !== undefined && current !== undefined && !dir && !sameSnapshot(snapshotOf(current), members(bytes).map((one) => [one.name, digestOf(one.bytes)])))
|
|
134
145
|
throw new Error(`REFUSE·release snapshot for ${digest} omits or changes archive members; explicit rebuild required`);
|
|
135
146
|
if (bytes === undefined && !dir && current === undefined)
|
|
136
|
-
throw new
|
|
137
|
-
const needs = fieldOf(line, 'needs');
|
|
138
|
-
if (!needs)
|
|
139
|
-
continue;
|
|
147
|
+
throw new ContentNeeded(digest, `REFUSE·release ${fieldOf(line, 'scope')} has no available archive or snapshot`);
|
|
140
148
|
const root = fieldOf(line, 'shape');
|
|
141
149
|
const place = join(own, archiveCoordinate(needs));
|
|
142
150
|
const link = lstatSync(place, { throwIfNoEntry: false });
|
|
@@ -2,7 +2,7 @@ import { capsuleAt } from './capsule.ts';
|
|
|
2
2
|
import type { Capsule } from './capsule.ts';
|
|
3
3
|
import type { ToposResolver } from './selected-topos.ts';
|
|
4
4
|
/** Only explicit capsule offers request execution. Forms/classes/views do not need an executable alias. */
|
|
5
|
-
export declare function selectedCapsules(pins: readonly string[], resolve?: typeof capsuleAt, select?: ToposResolver): readonly {
|
|
5
|
+
export declare function selectedCapsules(pins: readonly string[], resolve?: typeof capsuleAt, select?: ToposResolver, host?: readonly string[]): readonly {
|
|
6
6
|
readonly capsule: Capsule;
|
|
7
7
|
readonly offer: string;
|
|
8
8
|
}[];
|
|
@@ -4,13 +4,17 @@ import { selectedTopoi, toposAt } from "./selected-topos.js";
|
|
|
4
4
|
import { canonical } from '@lapxo/topos/wire';
|
|
5
5
|
import { fieldOf } from "../fold/claims.js";
|
|
6
6
|
import { ownLock } from "../observe/runner.js";
|
|
7
|
+
import { archiveCoordinate } from "./archive.js";
|
|
7
8
|
/** Only explicit capsule offers request execution. Forms/classes/views do not need an executable alias. */
|
|
8
|
-
export function selectedCapsules(pins, resolve = capsuleAt, select = toposAt) {
|
|
9
|
+
export function selectedCapsules(pins, resolve = capsuleAt, select = toposAt, host = ownLock()) {
|
|
9
10
|
return selectedTopoi(pins, select).flatMap(({ digest, topos }) => capsuleOffers(topos).map(offer => {
|
|
10
|
-
const projections =
|
|
11
|
-
if (projections.length
|
|
11
|
+
const projections = host.filter(line => fieldOf(line, 'scope').startsWith('dep/') && fieldOf(line, 'role') === 'writes' && fieldOf(line, 'value') === offer.value);
|
|
12
|
+
if (!projections.length || projections.some(line => !fieldOf(line, 'shape')))
|
|
12
13
|
throw Error(`REFUSE·pin ${digest} capsule ${offer.value} needs one host projection`);
|
|
13
|
-
const
|
|
14
|
+
const entries = [...new Set(projections.map(line => archiveCoordinate(fieldOf(line, 'shape'))))];
|
|
15
|
+
if (entries.length !== 1)
|
|
16
|
+
throw Error(`REFUSE·pin ${digest} capsule ${offer.value} has conflicting host projections`);
|
|
17
|
+
const projected = { ...offer, shape: entries[0] };
|
|
14
18
|
const capsule = resolve(offer.value, projected.shape);
|
|
15
19
|
if (capsule === undefined)
|
|
16
20
|
throw Error(`REFUSE·pin ${digest} · ${offer.scope} · offered capsule ${offer.value} unavailable`);
|
|
@@ -10,7 +10,7 @@ export declare function toposAt(digest: string): StandingTopos;
|
|
|
10
10
|
export declare function contentAt(digest: string): Uint8Array;
|
|
11
11
|
/** A declared JS context offer uses this host adapter only after selection; execution is not selection. */
|
|
12
12
|
export declare function contextModuleAt(digest: string): string;
|
|
13
|
-
/** Selection verifies
|
|
13
|
+
/** Selection verifies standing identity. Requested capabilities verify their artifact bytes separately. */
|
|
14
14
|
export declare function selectedTopoi(pins: readonly string[], resolve?: ToposResolver): readonly SelectedTopos[];
|
|
15
15
|
/** Read a declared wire list without copying a world's claims or executing its offers.
|
|
16
16
|
* Independent declarations must agree; selection order cannot choose a contract.
|