@lapxo/bound 0.1.4 → 0.1.5
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/LICENSE +0 -1
- package/README.md +26 -5
- package/TARGET.bound +83 -19
- package/dist/cli/accept.js +21 -9
- package/dist/cli/owner.d.ts +2 -0
- package/dist/cli/owner.js +17 -2
- package/dist/cli/pass.js +15 -10
- package/dist/cli/place.js +8 -1
- package/dist/cli/verb.js +35 -1
- package/dist/fold/claims.js +5 -1
- package/dist/fold/closed.js +44 -13
- package/dist/fold/digests.js +5 -2
- package/dist/fold/observed.js +195 -174
- package/dist/fold/resolved.js +6 -2
- package/dist/host/act-cost.d.ts +13 -0
- package/dist/host/act-cost.js +25 -0
- package/dist/host/adapters/modules/javascript.d.ts +1 -0
- package/dist/host/adapters/modules/javascript.js +48 -0
- package/dist/host/capsule-process.js +22 -19
- package/dist/host/capsule.js +5 -6
- package/dist/host/ports/ingress.d.ts +24 -0
- package/dist/host/ports/ingress.js +81 -0
- package/dist/host/reading-closures.d.ts +28 -0
- package/dist/host/reading-closures.js +146 -0
- package/dist/host/release.js +24 -2
- package/dist/host/walk.d.ts +6 -0
- package/dist/host/walk.js +193 -0
- package/dist/observe/regions.js +3 -5
- package/dist/render/field.d.ts +1 -7
- package/dist/render/field.js +6 -12
- package/dist/render/names.d.ts +1 -1
- package/dist/render/names.js +1 -1
- package/dist/render/view.js +16 -11
- package/docs/contracts/archive.md +11 -0
- package/docs/contracts/cli.md +11 -0
- package/docs/contracts/documents.md +35 -0
- package/docs/contracts/evidence-view.md +6 -0
- package/docs/contracts/publication.md +17 -0
- package/docs/contracts/typed-object.md +27 -0
- package/docs/contracts/view-walk.md +7 -0
- package/docs/contracts/views.md +29 -0
- package/docs/usage.md +6 -0
- package/package.json +6 -4
- package/receipts.bound +314 -92
package/dist/cli/accept.js
CHANGED
|
@@ -11,7 +11,7 @@ import { fieldOf, isConfig, foldClaims, sayingOf, selfName } from "../fold/claim
|
|
|
11
11
|
import { isReaderLock } from "../fold/observed.js";
|
|
12
12
|
import { authorityFor, publicKeyOf, rootSigner, writerFor } from "../fold/signers.js";
|
|
13
13
|
import { lockLines, lockStanding } from "../fold/keys.js";
|
|
14
|
-
import { keepVerdict, keptVerdict, land, landBlob, storeOf } from "../land/ledger.js";
|
|
14
|
+
import { keepVerdict, keptVerdict, land, landBlob, ledgerLines, storeOf } from "../land/ledger.js";
|
|
15
15
|
import { signConsentLot, verifiesOwnerLine } from "../land/sign.js";
|
|
16
16
|
import { signerFor } from "../host/signing.js";
|
|
17
17
|
import { appliedIn, ownLockOf } from "../fold/signed.js";
|
|
@@ -19,7 +19,7 @@ import { wordOf } from "../fold/wire.js";
|
|
|
19
19
|
import { hasVouchedTree, landCoordinates, locationsOf, vouchedCoordinates } from "../land/vouched.js";
|
|
20
20
|
import { entriesIn, observeFile } from "../observe/files.js";
|
|
21
21
|
import { coneOf, standingOf } from "./place.js";
|
|
22
|
-
import { ownLock, ownRoot
|
|
22
|
+
import { ownLock, ownRoot } from "../observe/runner.js";
|
|
23
23
|
import { widenedWithoutWitness } from "../judge/widening.js";
|
|
24
24
|
import { retractedTogether, withdrawal } from "../land/withdraw.js";
|
|
25
25
|
import { isCeiling } from "../fold/configures.js";
|
|
@@ -36,7 +36,7 @@ import { lateFacts } from "../judge/late.js";
|
|
|
36
36
|
import { paidOf } from "../fold/paid.js";
|
|
37
37
|
import { aliasPinned } from "../land/alias.js";
|
|
38
38
|
import { coordinatesWithdrawn } from "../land/leaving.js";
|
|
39
|
-
import { ownerOf, epochOf, forksItself, forksJoinedBy, renderInto, sideOf, signedDemands, signedOwnerLines } from "./owner.js";
|
|
39
|
+
import { ownerOf, epochOf, localActLines, forksItself, forksJoinedBy, renderInto, sideOf, signedDemands, signedOwnerLines } from "./owner.js";
|
|
40
40
|
import { claimLinesIn, entryOf, namedPlaces, placesOfFile, positionalsOf, valueOf } from "./args.js";
|
|
41
41
|
import { FLAGS } from "./names.js";
|
|
42
42
|
import { judgeDemand, landTake } from "./takes.js";
|
|
@@ -53,7 +53,15 @@ function admissionEvidence(root, signed, standing) {
|
|
|
53
53
|
if (coordinates.refused.length)
|
|
54
54
|
throw Error(`REFUSE·withdraw a file is still read: ${coordinates.refused.map(one => `${one.path} by ${one.by}`).join(' · ')}`);
|
|
55
55
|
const view = `${wordOf(standing, 'families', 'view')}/`;
|
|
56
|
-
const judged = signed.filter(one => isConfig(one) && fieldOf(one, 'role') === 'demands' && fieldOf(one, 'value') !== 'withdraw' && !fieldOf(one, 'scope').startsWith(view)).map(one =>
|
|
56
|
+
const judged = signed.filter(one => isConfig(one) && fieldOf(one, 'role') === 'demands' && fieldOf(one, 'value') !== 'withdraw' && !fieldOf(one, 'scope').startsWith(view)).map(one => {
|
|
57
|
+
const scope = fieldOf(one, 'scope');
|
|
58
|
+
const alreadyDemanded = standing.some(held => isConfig(held) && fieldOf(held, 'role') === 'demands'
|
|
59
|
+
&& fieldOf(held, 'value') !== 'withdraw' && fieldOf(held, 'scope') === scope
|
|
60
|
+
&& fieldOf(held, 'form') === fieldOf(one, 'form') && fieldOf(held, 'measure') === fieldOf(one, 'measure'));
|
|
61
|
+
// Declaring an obligation is not a claim to have discharged it.
|
|
62
|
+
return alreadyDemanded ? judgeDemand(root, one)
|
|
63
|
+
: { kind: 'owed', scope, why: 'new demand declared; no conformance reading is claimed' };
|
|
64
|
+
});
|
|
57
65
|
const refused = judged.filter(one => one.kind === 'refused' || (one.kind === 'judged' && one.verdict.kind === 'refuse'));
|
|
58
66
|
if (refused.length)
|
|
59
67
|
throw Error(`REFUSE·takes ${refused.map(one => `${one.scope}: ${one.kind === 'judged' ? one.verdict.why : one.kind === 'refused' ? one.why : ''}`).join(' · ')}`);
|
|
@@ -126,7 +134,7 @@ export async function signedBatch(root, args, files) {
|
|
|
126
134
|
process.stderr.write(`${selfName()}: REFUSE·place nothing landed · ${stray.join(', ')} is no place of the tree, and no line of the root founds it: --place names the own lock a line lands in\n`);
|
|
127
135
|
return { kind: 'exit', code: 2 };
|
|
128
136
|
}
|
|
129
|
-
const epoch = epochOf(
|
|
137
|
+
const epoch = epochOf(localActLines(root, destinations)) + 1;
|
|
130
138
|
const stale = proposed.filter((line) => fieldOf(line, 'sig')).flatMap((line) => ((by, key) => (key === undefined || !verifiesOwnerLine(line, key, signaturesOf(store).admitted)
|
|
131
139
|
? [`${fieldOf(line, 'scope')} · the signature does not hold for ${by || '∅'}`]
|
|
132
140
|
: []))(fieldOf(line, 'by'), publicKeyOf(root, fieldOf(line, 'by'))));
|
|
@@ -139,9 +147,13 @@ export async function signedBatch(root, args, files) {
|
|
|
139
147
|
const [named, places] = [namedPlaces(args), placesIn(root)];
|
|
140
148
|
const inLock = new Set(lockLines(store).map(sayingOf));
|
|
141
149
|
const inPlace = new Map(places.map((place) => [place, new Set(appliedIn(root, place).map(sayingOf))]));
|
|
150
|
+
const deliveryAuthority = authorityFor(lockLines(store), rootSigner(root), signaturesOf(store).admitted);
|
|
151
|
+
const delivered = (line) => Boolean(fieldOf(line, 'sig'))
|
|
152
|
+
&& deliveryAuthority.of(line).kind === 'admitted'
|
|
153
|
+
&& ledgerLines(store, fieldOf(line, 'by')).includes(line);
|
|
142
154
|
const held = (line, at = named.length ? named : byFile.flatMap(([heads, lines]) => (lines.includes(line) ? heads : []))) => at.length
|
|
143
155
|
? at.every((place) => inPlace.get(place)?.has(sayingOf(line)))
|
|
144
|
-
: inLock.has(sayingOf(line)) || places.some((place) => inPlace.get(place)?.has(sayingOf(line)));
|
|
156
|
+
: delivered(line) || inLock.has(sayingOf(line)) || places.some((place) => inPlace.get(place)?.has(sayingOf(line)));
|
|
145
157
|
if (proposed.every((line) => held(line))) {
|
|
146
158
|
if (proposed.some(line => !isConfig(line))) {
|
|
147
159
|
try {
|
|
@@ -335,7 +347,7 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
|
|
|
335
347
|
for (const name of openedBy(descent, told.standing, signed))
|
|
336
348
|
process.stderr.write(`OPEN ${name}\n`);
|
|
337
349
|
if (owned.own.size)
|
|
338
|
-
layReleases(store,
|
|
350
|
+
layReleases(store, root, told.standing, store);
|
|
339
351
|
const landed = took('land', () => [...new Set(signed.map((line) => fieldOf(line, 'by')))].map((by) => land(store, by, signed.filter((line) => fieldOf(line, 'by') === by))));
|
|
340
352
|
for (const [place, lines] of owned.own)
|
|
341
353
|
landOwned(root, place, lines);
|
|
@@ -392,7 +404,7 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
|
|
|
392
404
|
if (folder !== undefined)
|
|
393
405
|
land(store, folder, [beat(perLine, 'line'), beat(1, fold.kept ? 'kept' : 'folded'), beat(Date.now() - started, 'total')]);
|
|
394
406
|
process.stdout.write(`FACT signed ${signed.length + own} · landed ${landed.length ? landed.map((one) => `${one.appended} in ${one.at}`).join(' · ') : '0'}${[...owned.own].map(([place, lines]) => ` · ${lines.length} in ${place}/${LOCK}`).join('')} · epoch ${epoch}\n`
|
|
395
|
-
+ `TARGET ${rendered.standing} claims · ${rendered.forks.length} forks · ${rendered.vacuous.length} vacuous · ${rendered.grey.length} grey · ${rendered.refused.length} refused · ${Date.now() - started} ms\n`
|
|
407
|
+
+ `TARGET ${rendered.standing} claims · ${rendered.forks.length} forks · ${rendered.vacuous.length} vacuous · ${rendered.grey.length} grey · ${rendered.refused.length} refused · readings · no encounters computed · ${Date.now() - started} ms\n`
|
|
396
408
|
+ `SPENT ${spent.join(' ms · ')} ms · ${signed.length} lines\n`);
|
|
397
409
|
if (withdrawn) {
|
|
398
410
|
process.stdout.write(`WITHDRAW ${withdrawn.taken.length} taken · ${withdrawn.exact.length} exact on distributive scales · ${withdrawn.open.length} on open scales, not computed\n`);
|
|
@@ -444,7 +456,7 @@ function repaid(root, proposed, started) {
|
|
|
444
456
|
}
|
|
445
457
|
const rendered = renderInto(root, store);
|
|
446
458
|
process.stdout.write(`RENDER 0\n`);
|
|
447
|
-
process.stdout.write(`TARGET ${rendered.standing} claims · ${rendered.forks.length} forks · ${rendered.vacuous.length} vacuous · ${rendered.grey.length} grey · ${rendered.refused.length} refused · ${Date.now() - started} ms\n`);
|
|
459
|
+
process.stdout.write(`TARGET ${rendered.standing} claims · ${rendered.forks.length} forks · ${rendered.vacuous.length} vacuous · ${rendered.grey.length} grey · ${rendered.refused.length} refused · readings · no encounters computed · ${Date.now() - started} ms\n`);
|
|
448
460
|
return 0;
|
|
449
461
|
}
|
|
450
462
|
function packTake(one) {
|
package/dist/cli/owner.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export declare function historyOf(root: string, store: string, demands: readonly
|
|
|
14
14
|
};
|
|
15
15
|
export declare function signedDemands(store: string, root?: string): string[];
|
|
16
16
|
export declare function forkedScopes(store: string): Set<string>;
|
|
17
|
+
/** Import receipts are local acts; foreign evidence and sender epochs never enter this input. */
|
|
18
|
+
export declare function localActLines(root: string, placed?: readonly string[]): readonly string[];
|
|
17
19
|
export declare function epochOf(lines: readonly string[]): number;
|
|
18
20
|
export declare function targetFile(root: string): string;
|
|
19
21
|
export declare function renderInto(root: string, store: string): {
|
package/dist/cli/owner.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { ingressReceipts } from "../host/ports/ingress.js";
|
|
1
3
|
import { readOnly } from "../host/read-only.js";
|
|
2
4
|
import { keyFor, lockLines, releaseOf, signedLockLines } from "../fold/keys.js";
|
|
3
5
|
import { existsSync, renameSync, writeFileSync } from 'node:fs';
|
|
4
6
|
import { join, resolve } from 'node:path';
|
|
5
|
-
import { bytesDigest, fullDigest, memoized, signaturesOf } from "../fold/digests.js";
|
|
7
|
+
import { bytesDigest, fullDigest, memoized, signaturesOf, instrumentDigest } from "../fold/digests.js";
|
|
6
8
|
import { claimKey, isConfig, fieldOf, foldClaims, LOCK, selfName } from "../fold/claims.js";
|
|
7
9
|
import { asksThePlace } from "../fold/configures.js";
|
|
8
10
|
import { receiptsIn } from "../fold/receipts.js";
|
|
@@ -10,6 +12,7 @@ import { authorityFor, releaseSigner, rootSigner } from "../fold/signers.js";
|
|
|
10
12
|
import { storeRoot } from "../host/ports/store.js";
|
|
11
13
|
import { blobAt, landBlob, ledgerLines, storeOf } from "../land/ledger.js";
|
|
12
14
|
import { observeText } from "../observe/files.js";
|
|
15
|
+
import { appliedIn } from "../fold/signed.js";
|
|
13
16
|
import { everLanded, inside, locationsOf } from "../land/vouched.js";
|
|
14
17
|
import { renderTarget } from "../render/target.js";
|
|
15
18
|
export const ownerOf = (store, told = []) => keyFor(store, 'authorize', told);
|
|
@@ -47,6 +50,18 @@ export function forkedScopes(store) {
|
|
|
47
50
|
const lines = signedOwnerLines(store);
|
|
48
51
|
return new Set(foldClaims(lines, forkReceipts(store, lines)).forks.flatMap((f) => f.lines.map((line) => fieldOf(line, 'scope'))));
|
|
49
52
|
}
|
|
53
|
+
/** Import receipts are local acts; foreign evidence and sender epochs never enter this input. */
|
|
54
|
+
export function localActLines(root, placed = []) {
|
|
55
|
+
const store = storeOf(root), local = lockLines(store), receipts = ingressReceipts(store);
|
|
56
|
+
if (receipts.length) {
|
|
57
|
+
const authority = authorityFor(local, rootSigner(root), signaturesOf(store).admitted);
|
|
58
|
+
for (const receipt of receipts) {
|
|
59
|
+
if (fieldOf(receipt, 'scope') !== 'receipts' || fieldOf(receipt, 'role') !== 'writes' || fieldOf(receipt, 'form') !== 'alphabet' || fieldOf(receipt, 'measure') !== 'digest' || authority.of(receipt).kind !== 'admitted')
|
|
60
|
+
throw Error('REFUSE·ingress local import receipt is not admitted');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return [...local, ...receipts, ...placed.flatMap(place => appliedIn(root, place))];
|
|
64
|
+
}
|
|
50
65
|
export function epochOf(lines) {
|
|
51
66
|
return lines.reduce((top, line) => Math.max(top, Number(fieldOf(line, 'epoch')) || 0), 0);
|
|
52
67
|
}
|
|
@@ -92,7 +107,7 @@ export function renderedLock(root, store, told = []) {
|
|
|
92
107
|
}
|
|
93
108
|
const anchor = [join(storeRoot(root), `${selfName()}.keys`), join(storeOf(root), `${selfName()}.keys`)].map((at) => observeText(at) ?? '').join('\n');
|
|
94
109
|
const judged = ledgerLines(store, 'judge');
|
|
95
|
-
const key = `lock ${fullDigest(store, [new Date().toISOString().slice(0, 10), anchor, `${judged.length} ${judged.at(-1) ?? ''}`, ...owned])}`;
|
|
110
|
+
const key = `lock ${fullDigest(store, [instrumentDigest(store, fileURLToPath(import.meta.url), [], root), new Date().toISOString().slice(0, 10), anchor, `${judged.length} ${judged.at(-1) ?? ''}`, ...owned])}`;
|
|
96
111
|
const held = memoized(store, key, () => {
|
|
97
112
|
const bytes = new TextEncoder().encode(JSON.stringify(render()));
|
|
98
113
|
const digest = bytesDigest(store, bytes);
|
package/dist/cli/pass.js
CHANGED
|
@@ -6,9 +6,9 @@ import { foldPlace, standingOf } from "./place.js";
|
|
|
6
6
|
import { fieldOf, selfName } from "../fold/claims.js";
|
|
7
7
|
import { observedClaims } from "../fold/observed.js";
|
|
8
8
|
import { layReleases, resolvedOf } from "../host/release.js";
|
|
9
|
-
import { ownLock, ownRoot,
|
|
9
|
+
import { ownLock, ownRoot, processOf } from "../observe/runner.js";
|
|
10
10
|
import { landLaid } from "../fold/laid.js";
|
|
11
|
-
import { actSeconds, idleCount, instrumentKeep, instrumentKept, meets, noteIdle, receiptPlaces, receiptsSeen } from "../fold/closed.js";
|
|
11
|
+
import { actSeconds, closeReceipts, idleCount, instrumentKeep, instrumentKept, meets, noteIdle, receiptPlaces, receiptsSeen } from "../fold/closed.js";
|
|
12
12
|
import { algorithmCost, instrumentOf, instrumentDigest } from "../fold/digests.js";
|
|
13
13
|
import { writerFor } from "../fold/signers.js";
|
|
14
14
|
import { defaulted, placesOf, viewsOf } from "../fold/views.js";
|
|
@@ -24,6 +24,7 @@ import { releasePolicyLines } from "../fold/release-policy.js";
|
|
|
24
24
|
import { carriedFrom } from "../fold/resolved.js";
|
|
25
25
|
import { wireLine } from "../fold/wire.js";
|
|
26
26
|
import { isWire, RECEIPTS } from "../fold/claims.js";
|
|
27
|
+
import { withoutEffects } from "../host/read-only.js";
|
|
27
28
|
/**
|
|
28
29
|
* The pass of a place, in one process: the releases its instrument names are laid out first, its readers observe
|
|
29
30
|
* once, each place a view writes in is folded once, the places before the root whose door reads them, and every coordinate a
|
|
@@ -74,7 +75,7 @@ async function checked(root, entry, under) {
|
|
|
74
75
|
export async function pass(root, entry, under, check) {
|
|
75
76
|
if (check) {
|
|
76
77
|
// Verification reads existing evidence; it grants no admission authority.
|
|
77
|
-
return emptyLedger(storeOf(root)) ? checked(root, entry, under) : underTheLock(storeOf(root), 'check', () => checked(root, entry, under));
|
|
78
|
+
return withoutEffects(() => emptyLedger(storeOf(root)) ? checked(root, entry, under) : underTheLock(storeOf(root), 'check', () => checked(root, entry, under)));
|
|
78
79
|
}
|
|
79
80
|
if (!under && emptyLedger(storeOf(root))) {
|
|
80
81
|
sayStranger(root);
|
|
@@ -85,8 +86,11 @@ export async function pass(root, entry, under, check) {
|
|
|
85
86
|
const swept = await sweep(root, entry, under, check, []);
|
|
86
87
|
for (const coordinate of check ? swept.written : [])
|
|
87
88
|
process.stdout.write(`DIFFERS ${coordinate}\n`);
|
|
89
|
+
// Byte seals describe the completed render pass, not its intermediate outputs.
|
|
90
|
+
if (!swept.written.length)
|
|
91
|
+
await closeReceipts(root, storeOf(root));
|
|
88
92
|
const closed = meets(root, storeOf(root), under ?? '') && !swept.written.length;
|
|
89
|
-
const fold = foldPlace(root, entry, under, false, { standing: swept.standingAt.get(under ?? '') ?? standingOf(root, under), observed: swept.observed.length ? swept.observed : observedClaims(root, storeOf(root), { wait: false }), free:
|
|
93
|
+
const fold = foldPlace(root, entry, under, false, { standing: swept.standingAt.get(under ?? '') ?? standingOf(root, under), observed: swept.settled ? [] : swept.observed.length ? swept.observed : observedClaims(root, storeOf(root), { wait: false }), free: !swept.settled });
|
|
90
94
|
for (const line of releasePolicyLines(fold.release))
|
|
91
95
|
process.stdout.write(`${line}\n`);
|
|
92
96
|
if (closed) {
|
|
@@ -140,14 +144,15 @@ async function sweep(root, entry, under, check, told, journal, given, keep, reac
|
|
|
140
144
|
process.stderr.write(`CLOSED ${under} · lock ${lock} ms · hash ${hash} ms\n`);
|
|
141
145
|
const seconds = actSeconds();
|
|
142
146
|
process.stderr.write(`COST act ${seconds} s\n`);
|
|
143
|
-
return { same: 0, written: [], places: under === undefined ? read.count : 1, observed: [], standingAt: new Map(), folds: new Map(), said: [] };
|
|
147
|
+
return { same: 0, written: [], places: under === undefined ? read.count : 1, observed: [], standingAt: new Map(), folds: new Map(), said: [], settled: true };
|
|
144
148
|
}
|
|
145
|
-
|
|
149
|
+
const lock = standingOf(root, undefined, told);
|
|
150
|
+
// A place asks for its own mounts; the instrument catalog is not a consumer dependency.
|
|
151
|
+
layReleases(store, root, lock.standing, store);
|
|
146
152
|
const descent = reach ?? (under ? [under.replace(/\/$/, '')] : undefined);
|
|
147
153
|
const limited = descent !== undefined;
|
|
148
154
|
const observed = given ?? observedClaims(root, store, { wait: true, ...(descent !== undefined ? { reach: descent } : {}) });
|
|
149
|
-
|
|
150
|
-
landLaid(store, ownRoot(), ownLock(), writerFor(lock.standing, 'fold'));
|
|
155
|
+
landLaid(store, root, lock.standing, writerFor(lock.standing, 'fold'));
|
|
151
156
|
const shaped = (standing) => [...viewsOf(standing).values()].flatMap((view) => ('regions' in view && view.shape ? [view] : []));
|
|
152
157
|
const views = shaped(lock.standing);
|
|
153
158
|
const tree = viewsOf(lock.lock);
|
|
@@ -264,8 +269,8 @@ async function sweep(root, entry, under, check, told, journal, given, keep, reac
|
|
|
264
269
|
}
|
|
265
270
|
const said = [...answered.values()].flat();
|
|
266
271
|
if (!check && journal === undefined)
|
|
267
|
-
landSaid(store, writerFor(lock.standing, 'fold'), [...resolvedOf(store,
|
|
268
|
-
return { same, written, places: places.length + (under ? 0 : 1), observed, standingAt, folds, said: journal === undefined ? [] : said };
|
|
272
|
+
landSaid(store, writerFor(lock.standing, 'fold'), [...resolvedOf(store, root, lock.standing, store), ...said]);
|
|
273
|
+
return { same, written, places: places.length + (under ? 0 : 1), observed, standingAt, folds, said: journal === undefined ? [] : said, settled: false };
|
|
269
274
|
}
|
|
270
275
|
function drawParallel(jobs) {
|
|
271
276
|
const out = new Map();
|
package/dist/cli/place.js
CHANGED
|
@@ -7,7 +7,7 @@ import { basename, join } from 'node:path';
|
|
|
7
7
|
import { observeText } from "../observe/files.js";
|
|
8
8
|
import { placesOf, viewsOf } from "../fold/views.js";
|
|
9
9
|
import { prerender, viewOf } from "../render/view.js";
|
|
10
|
-
import { meets } from "../fold/closed.js";
|
|
10
|
+
import { meets, instrumentKept } from "../fold/closed.js";
|
|
11
11
|
import { foldPoints, keepFold, keptPlace } from "../fold/kept.js";
|
|
12
12
|
import { openCells } from "../render/open.js";
|
|
13
13
|
import { boundOf, packRegions, regionsNamed, regionsOf, unlikeReplay } from "../fold/region.js";
|
|
@@ -165,6 +165,13 @@ function inheritedOf(root, standing) {
|
|
|
165
165
|
export function coneOf(root, entry, ceilings, descent) {
|
|
166
166
|
const store = storeOf(root);
|
|
167
167
|
const { standing, rendered } = standingOf(root);
|
|
168
|
+
// Admission consumes the verified completed fold; it does not ask providers again.
|
|
169
|
+
// Receipt bytes, reader history and instrument identity must all still agree.
|
|
170
|
+
if (meets(root, store, '') && instrumentKept(store) === instrumentOf(store, entry, standing, root, ownLock())) {
|
|
171
|
+
const held = keptPlace(store);
|
|
172
|
+
if (held !== undefined)
|
|
173
|
+
return { standing, observed: held.observed, own: held.own, kept: true };
|
|
174
|
+
}
|
|
168
175
|
const named = regionsNamed(standing);
|
|
169
176
|
const reach = descent !== undefined && descent.length ? [...descent] : [...new Set(ceilings.map((line) => boundOf(named, line).region))];
|
|
170
177
|
const read = observedClaims(root, store, { wait: false, ...(reach.includes('') ? {} : { reach }) });
|
package/dist/cli/verb.js
CHANGED
|
@@ -10,6 +10,7 @@ import { objectFold } from "../fold/object.js";
|
|
|
10
10
|
import { OBJECT_VIEW } from '@lapxo/topos/wire';
|
|
11
11
|
import { toposStanding } from '@lapxo/topos/standing';
|
|
12
12
|
import { wireAt, parse as parseWire } from '@lapxo/topos/wire';
|
|
13
|
+
import { lockStanding } from "../fold/keys.js";
|
|
13
14
|
import { ownLock } from "../observe/runner.js";
|
|
14
15
|
import { signedOwnerLines } from "./owner.js";
|
|
15
16
|
import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
@@ -42,6 +43,9 @@ import { ownerOf, renderInto, signedDemands, targetFile } from "./owner.js";
|
|
|
42
43
|
import { claimLinesIn, entryOf, namedPlaces, positionalsOf, valueOf } from "./args.js";
|
|
43
44
|
import { EXIT, FLAGS, NAMED_VIEWS, helpLines } from "./names.js";
|
|
44
45
|
import { accept, signedBatch } from "./accept.js";
|
|
46
|
+
import { renderWalk, landWalk, foreignWalkLines } from "../host/walk.js";
|
|
47
|
+
import { isWalkHeader } from '@lapxo/topos/walk';
|
|
48
|
+
import { ingressBundles, ingressReceipts } from "../host/ports/ingress.js";
|
|
45
49
|
/** The first land of a lock: TARGET.bound into an empty owner ledger, then the sources that lock names. */
|
|
46
50
|
async function firstLand(root, args) {
|
|
47
51
|
const started = Date.now();
|
|
@@ -184,6 +188,17 @@ async function view(root, under, asked, told = [], coord, key, includePolicy = t
|
|
|
184
188
|
process.stderr.write(`${selfName()}: the owner ledger is empty; land it before rendering a view\n`);
|
|
185
189
|
return EXIT.refuse;
|
|
186
190
|
}
|
|
191
|
+
if (name === 'evidence' && !under && !told.length && ingressBundles(storeOf(root)).length) {
|
|
192
|
+
const declared = viewsOf(lockStanding(storeOf(root))).get(name);
|
|
193
|
+
if (!declared || declared.shape || !declared.regions.some(region => region.name === 'evidence'))
|
|
194
|
+
throw Error('REFUSE·view foreign evidence requires a declared evidence view');
|
|
195
|
+
const received = await foreignWalkLines(root), authority = authorityFor(lockLines(storeOf(root)), rootSigner(root), signaturesOf(storeOf(root)).admitted);
|
|
196
|
+
const local = [...new Set(authority.admitted.flatMap(key => ledgerLines(storeOf(root), key.id)))].filter(line => authority.of(line).kind === 'admitted');
|
|
197
|
+
for (const line of [...new Set([...local, ...ingressReceipts(storeOf(root)), ...received])])
|
|
198
|
+
process.stdout.write(line + '\n');
|
|
199
|
+
process.stdout.write('FACT authenticated histories · readings · no encounters computed\n');
|
|
200
|
+
return EXIT.closed;
|
|
201
|
+
}
|
|
187
202
|
const at = Date.now();
|
|
188
203
|
const place = under?.replace(/\/$/, '');
|
|
189
204
|
const released = releaseOf(storeOf(root));
|
|
@@ -367,6 +382,12 @@ async function landVerb(root, rest) {
|
|
|
367
382
|
process.once('uncaughtException', (error) => die(error instanceof Error ? error.message : String(error)));
|
|
368
383
|
process.once('unhandledRejection', (error) => die(error instanceof Error ? error.message : String(error)));
|
|
369
384
|
try {
|
|
385
|
+
const proposed = batch.flatMap(claimLinesIn);
|
|
386
|
+
if (proposed.some(isWalkHeader)) {
|
|
387
|
+
for (const line of await landWalk(root, proposed, valueOf(rest, FLAGS.key), valueOf(rest, FLAGS.keyFile), valueOf(rest, FLAGS.signer)))
|
|
388
|
+
process.stdout.write(line + '\n');
|
|
389
|
+
return EXIT.closed;
|
|
390
|
+
}
|
|
370
391
|
return await accept(root, rest);
|
|
371
392
|
}
|
|
372
393
|
catch (error) {
|
|
@@ -395,7 +416,7 @@ function foldFile(at) {
|
|
|
395
416
|
process.stdout.write(`${line}\n`);
|
|
396
417
|
for (const fork of folded.forks)
|
|
397
418
|
process.stdout.write(`FORK ${fork.key}${fork.state ? ` · ${fork.state}` : ''}\n`);
|
|
398
|
-
process.stdout.write(`FACT ${folded.standing.length} standing · ${folded.forks.length} forks · ${basename(at)}\n`);
|
|
419
|
+
process.stdout.write(`FACT ${folded.standing.length} standing · ${folded.forks.length} forks · ${basename(at)} · readings · no encounters computed\n`);
|
|
399
420
|
return EXIT.closed;
|
|
400
421
|
}
|
|
401
422
|
function foldVerb(root, rest) {
|
|
@@ -429,6 +450,19 @@ async function foldResolved(root, under, argv) {
|
|
|
429
450
|
// Public content is authenticated by its digest, independently of owner history.
|
|
430
451
|
// A named view does not request mounting every release of the instrument.
|
|
431
452
|
const receiptCheck = as === undefined && argv.includes(FLAGS.check);
|
|
453
|
+
if (as?.split('@')[0] === 'walk') {
|
|
454
|
+
if (argv.includes(FLAGS.check))
|
|
455
|
+
throw Error('REFUSE·walk check does not initiate or sign an exchange');
|
|
456
|
+
if (under || inputs.length > 1)
|
|
457
|
+
throw Error('REFUSE·walk one place and one authenticated peer inventory per fold');
|
|
458
|
+
const depth = as.split('@')[1];
|
|
459
|
+
if (depth !== undefined && (!/^(0|[1-9][0-9]*)$/.test(depth) || !Number.isSafeInteger(Number(depth))))
|
|
460
|
+
throw Error('REFUSE·walk invalid resolution');
|
|
461
|
+
const peer = inputs.length ? claimLinesIn(inputs[0]) : [];
|
|
462
|
+
for (const line of await renderWalk(root, depth === undefined ? undefined : Number(depth), peer, valueOf(argv, FLAGS.key), valueOf(argv, FLAGS.keyFile), valueOf(argv, FLAGS.signer)))
|
|
463
|
+
process.stdout.write(line + '\n');
|
|
464
|
+
return EXIT.closed;
|
|
465
|
+
}
|
|
432
466
|
if (as === OBJECT_VIEW) {
|
|
433
467
|
const inputs = positionalsOf(argv).filter(one => existsSync(resolve(one)) && !lstatSync(resolve(one)).isDirectory());
|
|
434
468
|
if (inputs.length > 1)
|
package/dist/fold/claims.js
CHANGED
|
@@ -38,7 +38,11 @@ function regionsOf(line) {
|
|
|
38
38
|
}
|
|
39
39
|
function contracted(line, sides, receipts) {
|
|
40
40
|
const scope = fieldsOf(line)['scope'] ?? '';
|
|
41
|
-
const
|
|
41
|
+
const named = regionsOf(line);
|
|
42
|
+
// File receipts can discharge only declared file regions. No region is not an empty proof.
|
|
43
|
+
if (named.length === 0)
|
|
44
|
+
return false;
|
|
45
|
+
const mine = exclusive(named, sides.filter((other) => other !== line).map(regionsOf));
|
|
42
46
|
const touched = receipts.filter((r) => r.scope === scope).flatMap((r) => r.moved.map((coordinate) => point(coordinate)));
|
|
43
47
|
return contracts(mine, touched);
|
|
44
48
|
}
|
package/dist/fold/closed.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { actCosts } from "../host/act-cost.js";
|
|
2
|
+
import { verifiedReadingReceipts } from "../host/reading-closures.js";
|
|
1
3
|
import { dirname, existsSync, join, mkdirSync, relative, resolve, statSync, writeFileSync } from "../host/io.js";
|
|
2
|
-
import { canonical, LOCK, parse, roleAt, roleClaimsOf } from '@lapxo/topos/wire';
|
|
3
|
-
import { RECEIPTS, fieldOf, isWire } from "./claims.js";
|
|
4
|
+
import { byBytes, canonical, LOCK, parse, publicationOf, roleAt, roleClaimsOf } from '@lapxo/topos/wire';
|
|
5
|
+
import { RECEIPTS, fieldOf, foldClaims, isConfig, isWire } from "./claims.js";
|
|
4
6
|
import { bytesDigest as hashBytes, fullDigest as hashFull } from "../host/digest.js";
|
|
5
7
|
import { worldAt } from "./places.js";
|
|
6
8
|
import { ownLockOf } from "./signed.js";
|
|
@@ -9,15 +11,14 @@ import { storeAt, storeOf } from "../land/ledger.js";
|
|
|
9
11
|
import { coordinatesUnder, observeFile, observeText } from "../observe/files.js";
|
|
10
12
|
import { carriedFrom } from "./resolved.js";
|
|
11
13
|
import { takenFor } from "../fold/place-inputs.js";
|
|
12
|
-
/** A region is closed when the digest of its bytes is the one its receipt names. Folding a closed region is idle: idleCount is how many were already closed, actSeconds
|
|
14
|
+
/** A region is closed when the digest of its bytes is the one its receipt names. Folding a closed region is idle: idleCount is how many were already closed, actSeconds the largest measured act or unpartitioned host work, receiptsSeen how many receipts were read and how long that took. */
|
|
13
15
|
let idle = 0;
|
|
14
|
-
const began = Date.now();
|
|
15
16
|
let seen = { count: 0, ms: 0 };
|
|
16
17
|
export const idleCount = () => idle;
|
|
17
18
|
export const noteIdle = () => {
|
|
18
19
|
idle += 1;
|
|
19
20
|
};
|
|
20
|
-
export const actSeconds = () =>
|
|
21
|
+
export const actSeconds = () => actCosts.seconds();
|
|
21
22
|
export const receiptsSeen = () => seen;
|
|
22
23
|
const algorithmIn = (text) => text.match(/(?:value=|place:)([a-z0-9]+):[0-9a-f]{64}/)?.[1] ?? '';
|
|
23
24
|
const lockStamp = (root, place) => {
|
|
@@ -55,6 +56,14 @@ function filesOf(root, place, region, inputs) {
|
|
|
55
56
|
return [...new Set([...owned, ...observed, ...own])].filter(rel => rel !== receipt && !rel.split('/').some(part => part === '.bound' || part === '.cache' || part === '.git'));
|
|
56
57
|
}
|
|
57
58
|
const rooted = new Map();
|
|
59
|
+
function publicConfiguration(lines) {
|
|
60
|
+
if (!lines.length || !lines.every(isConfig))
|
|
61
|
+
return lines;
|
|
62
|
+
const folded = foldClaims(lines);
|
|
63
|
+
if (folded.forks.length)
|
|
64
|
+
throw Error('REFUSE·receipt disputed configuration standing');
|
|
65
|
+
return publicationOf(folded.standing.filter(line => fieldOf(line, 'value') !== 'withdraw'));
|
|
66
|
+
}
|
|
58
67
|
function rootLines(root, place) {
|
|
59
68
|
const name = place.replace(/\/$/, '');
|
|
60
69
|
if (!name)
|
|
@@ -63,7 +72,7 @@ function rootLines(root, place) {
|
|
|
63
72
|
const held = rooted.get(key);
|
|
64
73
|
if (held !== undefined)
|
|
65
74
|
return held;
|
|
66
|
-
const standing = (observeText(join(root, LOCK)) ?? '').split('\n').filter(isWire);
|
|
75
|
+
const standing = publicConfiguration((observeText(join(root, LOCK)) ?? '').split('\n').filter(isWire));
|
|
67
76
|
// Authority validates admission separately; it is not a rendered file input.
|
|
68
77
|
const lines = standing.filter((line) => fieldOf(line, 'value') !== 'withdraw' && !fieldOf(line, 'scope').startsWith('keys/'));
|
|
69
78
|
const take = takenFor(lines, `${name}/`, worldAt(root, name) ? { own: ownLockOf(root, name), tree: viewsOf(standing) } : undefined);
|
|
@@ -73,8 +82,20 @@ function rootLines(root, place) {
|
|
|
73
82
|
}
|
|
74
83
|
/** File-fold identity uses public inputs; admission history stays in its separate ledger guard. */
|
|
75
84
|
export function fileRegionDigest(root, place, region, algorithm, inputs = []) {
|
|
76
|
-
const files = filesOf(root, place, region, inputs);
|
|
77
|
-
|
|
85
|
+
const files = [...filesOf(root, place, region, inputs)].sort(byBytes);
|
|
86
|
+
const own = join(root, place, LOCK);
|
|
87
|
+
const bytesOf = (rel) => {
|
|
88
|
+
const at = join(root, rel), bytes = observeFile(at) ?? new Uint8Array();
|
|
89
|
+
if (resolve(at) !== resolve(own))
|
|
90
|
+
return bytes;
|
|
91
|
+
const lines = new TextDecoder().decode(bytes).split('\n').filter(isWire);
|
|
92
|
+
// Object history keeps its own contract. Configuration's public identity is
|
|
93
|
+
// the existing Topos publication projection, not its admission envelopes.
|
|
94
|
+
if (!lines.length || !lines.every(isConfig))
|
|
95
|
+
return bytes;
|
|
96
|
+
return new TextEncoder().encode(publicConfiguration(lines).join('\n') + '\n');
|
|
97
|
+
};
|
|
98
|
+
return hashFull([...files.map((rel) => `${relative(join(root, place), join(root, rel))} ${hashBytes(bytesOf(rel), algorithm)}`), ...rootLines(root, place)], algorithm);
|
|
78
99
|
}
|
|
79
100
|
function namedReceipts(root, store) {
|
|
80
101
|
const text = observeText(join(root, RECEIPTS));
|
|
@@ -89,13 +110,15 @@ function namedReceipts(root, store) {
|
|
|
89
110
|
}
|
|
90
111
|
return out;
|
|
91
112
|
}
|
|
113
|
+
// Only the instrument's file seals describe file-region bytes. Reader receipts keep their own semantic identity.
|
|
114
|
+
const fileSeal = (line) => isWire(line) && fieldOf(line, 'measure') === 'bytes' && fieldOf(line, 'by') === 'bound';
|
|
92
115
|
function ownBytes(root) {
|
|
93
|
-
return (observeText(join(root, RECEIPTS)) ?? '').split('\n').filter(
|
|
116
|
+
return (observeText(join(root, RECEIPTS)) ?? '').split('\n').filter(fileSeal);
|
|
94
117
|
}
|
|
95
118
|
function bytesMeet(root, place) {
|
|
96
119
|
const shipped = (observeText(join(root, place, RECEIPTS)) ?? '').split('\n').filter(isWire);
|
|
97
120
|
const inputs = carriedFrom(storeOf(root), shipped) ?? [];
|
|
98
|
-
const lines = shipped.filter(
|
|
121
|
+
const lines = shipped.filter(fileSeal);
|
|
99
122
|
const algorithm = algorithmIn(lines.join('\n'));
|
|
100
123
|
return lines.length > 0 && algorithm !== '' && lines.map((line) => fieldOf(line, 'value')).join('\n') === lines.map((line) => fileRegionDigest(root, place, fieldOf(line, 'scope'), algorithm, inputs)).join('\n');
|
|
101
124
|
}
|
|
@@ -115,8 +138,8 @@ function rewritePlace(root, place) {
|
|
|
115
138
|
const algorithm = algorithmIn(text);
|
|
116
139
|
if (algorithm === '')
|
|
117
140
|
return;
|
|
118
|
-
const kept = text.split('\n').filter((line) => line === '' || !
|
|
119
|
-
const regions = kept.filter((line) => isWire(line) && fieldOf(line, 'measure') === 'count' && fieldOf(line, 'scope').startsWith('receipts/'));
|
|
141
|
+
const kept = text.split('\n').filter((line) => line === '' || !fileSeal(line));
|
|
142
|
+
const regions = kept.filter((line) => isWire(line) && fieldOf(line, 'measure') === 'count' && fieldOf(line, 'by') === 'bound' && fieldOf(line, 'scope').startsWith('receipts/'));
|
|
120
143
|
const bytes = regions.map((line) => {
|
|
121
144
|
const scope = fieldOf(line, 'scope');
|
|
122
145
|
const inputs = carriedFrom(storeOf(root), kept.filter(isWire)) ?? [];
|
|
@@ -146,11 +169,19 @@ export function meets(root, store, place) {
|
|
|
146
169
|
const own = ownBytes(root);
|
|
147
170
|
const carried = own.length ? carriedFrom(store, (observeText(join(root, RECEIPTS)) ?? '').split('\n').filter(isWire)) : undefined;
|
|
148
171
|
const ownClosed = own.length > 0 && carried !== undefined && carried.length > 0 && bytesMeet(root, '');
|
|
149
|
-
|
|
172
|
+
let closed = (own.length > 0 ? ownClosed : named.size > 0) && [...named].every(([one, expected]) => {
|
|
150
173
|
const bytes = observeFile(join(root, one, RECEIPTS));
|
|
151
174
|
const algorithm = expected.split(':')[0];
|
|
152
175
|
return bytes !== undefined && hashBytes(bytes, algorithm) === expected && meets(root, store, `${one}/`);
|
|
153
176
|
});
|
|
177
|
+
if (closed && carried) {
|
|
178
|
+
const algorithm = algorithmIn(own.join('\n'));
|
|
179
|
+
const readings = verifiedReadingReceipts(store, carried, text => hashFull(text, algorithm));
|
|
180
|
+
if (readings === undefined)
|
|
181
|
+
closed = false;
|
|
182
|
+
else if (readings)
|
|
183
|
+
process.stderr.write(`RECEIPTS ${readings} read · 0 opened · reading closures\n`);
|
|
184
|
+
}
|
|
154
185
|
seen = { count: named.size + (own.length > 0 ? 1 : 0), ms: Date.now() - at };
|
|
155
186
|
return closed;
|
|
156
187
|
}
|
package/dist/fold/digests.js
CHANGED
|
@@ -7,6 +7,7 @@ import { EXTENSION, fieldOf, foldClaims, selfName } from "./claims.js";
|
|
|
7
7
|
import { wireLine } from "./wire.js";
|
|
8
8
|
import { publicLock, LOCK } from '@lapxo/topos/wire';
|
|
9
9
|
import { builtinModules, createRequire } from 'node:module';
|
|
10
|
+
import { moduleImports } from "../host/adapters/modules/javascript.js";
|
|
10
11
|
const held = new Map();
|
|
11
12
|
let algorithmMs = 0;
|
|
12
13
|
export const algorithmCost = () => algorithmMs;
|
|
@@ -110,6 +111,8 @@ export function coordinateDigest(store, at) {
|
|
|
110
111
|
const stamp = stampOf(at);
|
|
111
112
|
if (stamp?.kind !== 'file')
|
|
112
113
|
return stamp === undefined ? undefined : bytesDigest(store, observeFile(at) ?? new Uint8Array());
|
|
114
|
+
if (readOnly())
|
|
115
|
+
return bytesDigest(store, observeFile(at) ?? new Uint8Array());
|
|
113
116
|
const key = `${at} ${stamp.stamp}`;
|
|
114
117
|
const memo = memoOf(store);
|
|
115
118
|
const held = memo.get(key);
|
|
@@ -140,10 +143,10 @@ const imported = new Map();
|
|
|
140
143
|
const importsOf = (store, at, digest) => {
|
|
141
144
|
const key = `imports ${fullDigest(store, importsOf.toString())} ${digest}`;
|
|
142
145
|
const memo = memoOf(store);
|
|
143
|
-
const held = imported.get(digest) ?? memo.get(key)?.split('|').filter(Boolean);
|
|
146
|
+
const held = imported.get(digest) ?? (readOnly() ? undefined : memo.get(key)?.split('|').filter(Boolean));
|
|
144
147
|
if (held !== undefined)
|
|
145
148
|
return held;
|
|
146
|
-
const found =
|
|
149
|
+
const found = moduleImports(new TextDecoder().decode(observeFile(at) ?? new Uint8Array()), at);
|
|
147
150
|
imported.set(digest, found);
|
|
148
151
|
memo.set(key, found.join('|'));
|
|
149
152
|
if (!readOnly())
|