@lapxo/bound 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +8 -8
  2. package/TARGET.bound +19 -18
  3. package/dist/cli/accept.d.ts +5 -0
  4. package/dist/cli/accept.js +57 -8
  5. package/dist/cli/act-result.d.ts +3 -0
  6. package/dist/cli/act-result.js +48 -0
  7. package/dist/cli/owner.js +3 -2
  8. package/dist/cli/pass.js +6 -4
  9. package/dist/cli/place.d.ts +1 -1
  10. package/dist/cli/place.js +1 -1
  11. package/dist/cli/release-result.d.ts +5 -0
  12. package/dist/cli/release-result.js +80 -0
  13. package/dist/cli/standing-result.d.ts +5 -0
  14. package/dist/cli/standing-result.js +39 -0
  15. package/dist/cli/verb.js +33 -2
  16. package/dist/fold/admission-history.d.ts +6 -0
  17. package/dist/fold/admission-history.js +22 -0
  18. package/dist/fold/closed.js +9 -3
  19. package/dist/fold/digests.js +13 -9
  20. package/dist/fold/kept.d.ts +1 -1
  21. package/dist/fold/kept.js +21 -6
  22. package/dist/fold/keys.js +16 -9
  23. package/dist/fold/observed.js +8 -7
  24. package/dist/fold/places.d.ts +5 -1
  25. package/dist/fold/places.js +19 -3
  26. package/dist/fold/readers.js +2 -1
  27. package/dist/fold/release-policy.js +0 -2
  28. package/dist/fold/signed.d.ts +5 -0
  29. package/dist/fold/signed.js +19 -15
  30. package/dist/fold/zoom.js +5 -4
  31. package/dist/host/capsule.d.ts +2 -1
  32. package/dist/host/capsule.js +8 -6
  33. package/dist/host/cell-receipts.js +7 -3
  34. package/dist/host/io.d.ts +1 -1
  35. package/dist/host/io.js +1 -1
  36. package/dist/host/ports/ingress.js +4 -28
  37. package/dist/host/ports/local-act.d.ts +34 -0
  38. package/dist/host/ports/local-act.js +100 -0
  39. package/dist/host/ports/publication.d.ts +10 -0
  40. package/dist/host/ports/publication.js +56 -0
  41. package/dist/host/reading-closures.d.ts +3 -0
  42. package/dist/host/reading-closures.js +5 -4
  43. package/dist/host/selected-capsules.js +4 -4
  44. package/dist/host/selected-topos.d.ts +4 -1
  45. package/dist/host/selected-topos.js +16 -1
  46. package/dist/land/ledger.d.ts +1 -0
  47. package/dist/land/ledger.js +21 -2
  48. package/dist/land/owned.d.ts +1 -1
  49. package/dist/land/owned.js +5 -4
  50. package/dist/land/standing-pin.d.ts +3 -0
  51. package/dist/land/standing-pin.js +49 -0
  52. package/dist/observe/files.js +12 -2
  53. package/docs/contracts/cli.md +16 -0
  54. package/docs/contracts/evidence-view.md +4 -0
  55. package/package.json +2 -2
  56. package/receipts.bound +593 -314
package/dist/fold/kept.js CHANGED
@@ -1,8 +1,9 @@
1
+ import { actLines, committedLocalActs } from "../host/ports/local-act.js";
1
2
  import { readOnly } from "../host/read-only.js";
2
3
  import { createHash, dirname, existsSync, join, mkdirSync, readdirSync, renameSync, writeFileSync } from "../host/io.js";
3
4
  import { bytesDigest, coordinateDigest } from "./digests.js";
4
5
  import { viewsOf } from "./views.js";
5
- import { landMemo, ledgerLines, memoAt, shardFile, writerFile } from "../land/ledger.js";
6
+ import { landMemo, ledgerLines, ledgerWriters, memoAt, shardFile, writerFile } from "../land/ledger.js";
6
7
  import { coordinatesUnder, observeFile, observeText } from "../observe/files.js";
7
8
  import { EXTENSION, fieldOf } from "./claims.js";
8
9
  import { keyFor, keyOf, lockSigners } from "./keys.js";
@@ -36,7 +37,7 @@ export function foldPoints(store, standing, under, keeper, instrument = '', obse
36
37
  `take ${digestOf(ledgerLines(store, 'take').filter((line) => reads.scopes.has(fieldOf(line, 'scope').replace(/^take\//, ''))))}`,
37
38
  `tree ${digestOf(ledgerLines(store, tree ?? '').filter((line) => (sourceOf(line) ?? '').startsWith(reads.place)))}`,
38
39
  ];
39
- const heads = (existsSync(join(store, 'ledger')) ? readdirSync(join(store, 'ledger')) : []).filter((name) => name.endsWith(EXTENSION)).map((name) => name.slice(0, -EXTENSION.length))
40
+ const heads = ledgerWriters(store)
40
41
  .filter((writer) => writer !== keeper && writer !== 'gate' && !writer.startsWith('reader') && !(reads && (signers.has(writer) || writer === 'judge' || writer === 'take' || writer === tree)))
41
42
  .map((writer) => `${writer} ${writer === liveWriter(store, standing) ? lastWord(store, writer) : bytesDigest(store, text(ledgerLines(store, writer).join('\n')))}`);
42
43
  const written = [...new Set([...viewsOf(standing).values()].flatMap((view) => ('regions' in view && view.shape ? [view.shape] : [])))].sort()
@@ -100,10 +101,20 @@ function readingContext(store) {
100
101
  const speaker = keyOf(store, 'read');
101
102
  const files = speaker === undefined ? [] : [writerFile(store, speaker), ...coordinatesUnder(dirname(shardFile(store, speaker, '')), store)
102
103
  .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')));
104
+ const segments = speaker === undefined ? [] : committedLocalActs(store).flatMap(act => act.bundle.placements?.flatMap(placement => {
105
+ const records = actLines(placement.records).filter(line => fieldOf(line, 'by') === speaker);
106
+ return records.length ? [placement.place + '\n' + records.join('\n')] : [];
107
+ }) ?? []);
108
+ return bytesDigest(store, text([...files.filter(file => existsSync(file)).sort()
109
+ .map(file => bytesDigest(store, observeFile(file) ?? new Uint8Array())), ...segments].join('\n')));
105
110
  }
106
- export function keptPlace(store, under) {
111
+ /** A new judgment changes the result even when the files and reader history do not. */
112
+ function judgmentContext(store, demands) {
113
+ const scopes = new Set(demands.map(line => fieldOf(line, 'scope')));
114
+ return bytesDigest(store, text(ledgerLines(store, 'judge')
115
+ .filter(line => scopes.has(fieldOf(line, 'scope'))).join('\n')));
116
+ }
117
+ export function keptPlace(store, under, currentInstrument) {
107
118
  const key = observeText(memoAt(store, lastOf([`place ${under ?? '.'}`])))?.trim();
108
119
  if (key === undefined || key === '')
109
120
  return undefined;
@@ -114,6 +125,10 @@ export function keptPlace(store, under) {
114
125
  if (blob.readingContext !== readingContext(store))
115
126
  return undefined;
116
127
  const fold = blob.fold;
128
+ if (currentInstrument !== undefined && fold.instrument !== currentInstrument)
129
+ return undefined;
130
+ if (blob.judgmentContext !== judgmentContext(store, linesOf(fold.demands)))
131
+ return undefined;
117
132
  return {
118
133
  ...fold,
119
134
  standing: linesOf(fold.standing),
@@ -149,7 +164,7 @@ export function keepFold(store, key, fold, points, instrument = '') {
149
164
  signed: [...fold.signed],
150
165
  kept: false,
151
166
  };
152
- landMemo(store, `fold/${key}`, text(JSON.stringify({ fold: packed, points, instrument, readingContext: readingContext(store) })));
167
+ landMemo(store, `fold/${key}`, text(JSON.stringify({ fold: packed, points, instrument, readingContext: readingContext(store), judgmentContext: judgmentContext(store, fold.demands) })));
153
168
  const at = memoAt(store, lastOf(points));
154
169
  mkdirSync(dirname(at), { recursive: true });
155
170
  writeFileSync(`${at}.${process.pid}.part`, `${key}\n`);
package/dist/fold/keys.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { dirname, existsSync, join } from "../host/io.js";
2
- import { ledgerLines, signedLines } from "../land/ledger.js";
3
- import { EXTENSION, fieldOf, foldClaims, isWire, LOCK, selfName } from "./claims.js";
4
- import { entriesIn, observeText } from "../observe/files.js";
2
+ import { ledgerLines, ledgerWriters, signedLines } from "../land/ledger.js";
3
+ import { fieldOf, foldClaims, isWire, LOCK, selfName } from "./claims.js";
4
+ import { observeText } from "../observe/files.js";
5
+ import { publicLock } from '@lapxo/topos/wire';
5
6
  const held = new Map();
7
+ const keyInputs = new Map();
6
8
  const merged = new Map();
7
9
  /**
8
10
  * Whose lines carry a class of writing, as the lock says it. Every ledger the store keeps is read, the key claims
@@ -47,13 +49,15 @@ export function signedLockLines(store, told = []) {
47
49
  return lockSigners(store, told).flatMap((id) => signedLines(store, id));
48
50
  }
49
51
  function keysOf(store, told) {
50
- const mine = told.length ? read(store, told) : held.get(store) ?? read(store);
52
+ const input = ledgerKeyLines(store).join('\n');
53
+ const mine = told.length ? read(store, told) : keyInputs.get(store) === input ? held.get(store) ?? read(store) : read(store);
54
+ if (!told.length)
55
+ keyInputs.set(store, input);
51
56
  if (!told.length)
52
57
  held.set(store, mine);
53
58
  return mine;
54
59
  }
55
- const ledgerKeyLines = (store) => (existsSync(join(store, 'ledger')) ? entriesIn(join(store, 'ledger')).map((entry) => entry.name) : [])
56
- .filter((name) => name.endsWith(EXTENSION)).flatMap((name) => ledgerLines(store, name.slice(0, -EXTENSION.length)))
60
+ const ledgerKeyLines = (store) => ledgerWriters(store).flatMap(writer => ledgerLines(store, writer))
57
61
  .filter((line) => /\bsig=/.test(line) && /(?:^|\s)scope=keys\/[^/\s]+\s/.test(line));
58
62
  const releases = new Map();
59
63
  /**
@@ -65,13 +69,16 @@ export function releaseOf(store) {
65
69
  return releases.get(store);
66
70
  const at = join(dirname(store), LOCK);
67
71
  const lines = ledgerKeyLines(store).length || !existsSync(at) ? [] : (observeText(at) ?? '').split('\n').filter(isWire);
68
- const got = lines.some((line) => fieldOf(line, 'scope') === 'publish/bootstrap' && fieldOf(line, 'value') !== 'withdraw') ? lines : undefined;
72
+ const published = lines.some(line => fieldOf(line, 'scope').startsWith('keys/') && fieldOf(line, 'measure') === 'public-key' && fieldOf(line, 'sig'))
73
+ && publicLock(lines) !== undefined;
74
+ const got = published || lines.some((line) => fieldOf(line, 'scope') === 'publish/bootstrap' && fieldOf(line, 'value') !== 'withdraw') ? lines : undefined;
69
75
  releases.set(store, got);
70
76
  return got;
71
77
  }
72
78
  function read(store, told = []) {
73
- const said = [...ledgerKeyLines(store), ...releaseOf(store) ?? [], ...told]
74
- .filter((line) => /\bsig=/.test(line) && /(?:^|\s)scope=keys\/[^/\s]+\s/.test(line));
79
+ const release = releaseOf(store) ?? [], published = publicLock(release) === undefined ? [] : release;
80
+ const said = [...ledgerKeyLines(store), ...release, ...told]
81
+ .filter((line) => (/\bsig=/.test(line) || published.includes(line)) && /(?:^|\s)scope=keys\/[^/\s]+\s/.test(line));
75
82
  const standing = foldClaims(said).standing.filter((line) => fieldOf(line, 'value') !== 'withdraw')
76
83
  .sort((a, b) => (Number(fieldOf(a, 'epoch')) || 0) - (Number(fieldOf(b, 'epoch')) || 0));
77
84
  const idOf = (line) => fieldOf(line, 'scope').slice('keys/'.length);
@@ -267,13 +267,14 @@ export function observedClaims(root, store, options = {}) {
267
267
  }
268
268
  function observeOne(reader, started) {
269
269
  const module = resolve(root, reader.module);
270
+ const lifetime = readerLifetime(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), { id: reader.id, module: reader.module });
270
271
  const asked = reader.capsule === undefined ? undefined : reader.capsule.selected ?? capsuleAt(reader.capsule.digest, reader.capsule.entry);
271
272
  const projection = reader.capsule === undefined ? [] : (asked?.lines ?? []).filter(line => fieldOf(line, 'scope') === `region/${reader.capsule.region}` && fieldOf(line, 'measure') === 'input-projection' && fieldOf(line, 'value') !== 'withdraw');
272
273
  if (projection.length) {
273
274
  if (projection.length !== 1 || !asked)
274
275
  throw Error('REFUSE·input projection is ambiguous ' + reader.module);
275
276
  const files = [...new Set(reader.where.flatMap(where => handedAt(root, reader, roleOf, look, where)))].sort().map(place => ({ place, text: observeText(resolve(root, place)) ?? '' }));
276
- const result = readingClosures({ root, store, reader: reader.module, speaker, digest: text => bytesDigest(store, Buffer.from(text)), capsule: asked, region: reader.capsule.region, projection: fieldOf(projection[0], 'value'), allowsEmpty: readerAllowsEmpty(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module), files, emit: line => process.stderr.write(line + '\n') });
277
+ const result = readingClosures({ root, store, reader: reader.module, speaker, digest: text => bytesDigest(store, Buffer.from(text)), capsule: asked, region: reader.capsule.region, projection: fieldOf(projection[0], 'value'), readerReference: { id: reader.id, module: reader.module }, lifetime, allowsEmpty: readerAllowsEmpty(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module), files, emit: line => process.stderr.write(line + '\n') });
277
278
  out.push(...result.lines);
278
279
  ran += result.executed;
279
280
  read += result.read - result.opened;
@@ -287,7 +288,7 @@ export function observedClaims(root, store, options = {}) {
287
288
  assertReaderInputs(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module, offered.length);
288
289
  const vectors = reader.capsule === undefined ? observeText(resolve(root, vectorsOf(reader.module, standing))) : undefined;
289
290
  const kept = whole && legacy === undefined && answered === undefined && source ? sha([
290
- speaker, reader.module, reader.kind, reader.shape.join('|'), reader.where.join('|'), bundle, vectors === undefined ? 'no vectors' : sha(vectors),
291
+ speaker, reader.module, JSON.stringify(lifetime), reader.kind, reader.shape.join('|'), reader.where.join('|'), bundle, vectors === undefined ? 'no vectors' : sha(vectors),
291
292
  reader.capsule === undefined ? '' : `${reader.capsule.selected?.selection ?? reader.capsule.digest} ${reader.capsule.region} ${reader.capsule.reads.join('|')} ${[...reader.capsule.globs].map(([place, globs]) => `${place}${globs.join('|')}`).join(' ')}`,
292
293
  ...offered.map((offer) => `${offer.place} ${offer.digest?.() ?? bytesDigest(store, offer.bytes())}`),
293
294
  ].join('\n')) : undefined;
@@ -302,9 +303,9 @@ export function observedClaims(root, store, options = {}) {
302
303
  const home = regionIn(regions, reader.module);
303
304
  const own = shardLines(store, speaker, home).filter((line) => fieldOf(line, 'scope') === `reader/${reader.module}`);
304
305
  const lineage = own.filter((line) => fieldOf(line, 'measure') === 'identity').map((line) => fieldOf(line, 'value'));
305
- const sum = source && reader.capsule === undefined ? bytesDigest(store, new TextEncoder().encode(`${reader.module} ${bundle}`)).replace(/^sha256:/, '').slice(0, 12) : 'none';
306
- const measured = reader.capsule !== undefined ? { by: source ? sha(`${reader.capsule.selected?.selection ?? reader.capsule.digest}\n${reader.capsule.region}`).slice(0, 12) : 'none', lines: [] }
307
- : source ? identityOf({ store, root, speaker, module: reader.module, kind: reader.kind, code: sum, lineage, lifetime: readerLifetime(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module), kept: own.filter((line) => /(?:^|\s)measure=(?:extension|emits)\s/.test(line)) })
306
+ const sum = source && reader.capsule === undefined ? bytesDigest(store, new TextEncoder().encode(`${reader.module} ${bundle}${lifetime === undefined ? "" : "\n" + JSON.stringify(lifetime)}`)).replace(/^sha256:/, '').slice(0, 12) : 'none';
307
+ const measured = reader.capsule !== undefined ? { by: source ? sha(`${reader.capsule.selected?.selection ?? reader.capsule.digest}\n${reader.capsule.region}${lifetime === undefined ? "" : "\n" + JSON.stringify(lifetime)}`).slice(0, 12) : 'none', lines: [] }
308
+ : source ? identityOf({ store, root, speaker, module: reader.module, kind: reader.kind, code: sum, lineage, lifetime, kept: own.filter((line) => /(?:^|\s)measure=(?:extension|emits)\s/.test(line)) })
308
309
  : { by: 'none', lines: [] };
309
310
  const by = measured.by;
310
311
  const level = [...measured.lines];
@@ -419,12 +420,12 @@ export function observedClaims(root, store, options = {}) {
419
420
  ? asked.ask(due.map((d) => ({
420
421
  protocol: PROTOCOL, verb: 'read', rootScope: d.place, region: reader.capsule.region, held: heldOf(d.place, d.region),
421
422
  files: (reader.capsule.reads.some((read) => read.includes('@')) ? handedAt(root, reader, roleOf, look, d.place) : [d.place]).map((coordinate) => ({ place: coordinate, text: observeText(resolve(root, coordinate)) ?? '' })),
422
- }))).map((answer) => {
423
+ })), { id: reader.id, module: reader.module }).map((answer) => {
423
424
  if (answer?.kind !== 'fact')
424
425
  throw new Error(`REFUSE·reader ${reader.module} · ${answer?.why ?? 'no capsule answer'}`);
425
426
  return (answer.claims ?? []);
426
427
  })
427
- : runReader(module, root, due.map((d) => ({ place: d.place, ...(reader.kind === 'js' ? { text: new TextDecoder().decode(d.bytes) } : {}), held: heldOf(d.place, d.region) })), readerLifetime(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module));
428
+ : runReader(module, root, due.map((d) => ({ place: d.place, ...(reader.kind === 'js' ? { text: new TextDecoder().decode(d.bytes) } : {}), held: heldOf(d.place, d.region) })), lifetime);
428
429
  }
429
430
  catch (error) {
430
431
  const why = `REFUSE·reader ${reader.module} · ${error instanceof Error ? error.message : String(error)}`;
@@ -2,7 +2,11 @@ type Spoken = {
2
2
  readonly place: string;
3
3
  readonly line: string;
4
4
  };
5
- export declare const placesIn: (root: string) => readonly string[];
5
+ /** A dependency's lock describes the dependency, never a child of its host.
6
+ * Retiring a mount does not turn its retained bytes into local declarations. */
7
+ export declare function mountedCoordinatesIn(root: string, history?: readonly string[]): readonly string[];
8
+ export declare function localDirectoriesIn(root: string, history?: readonly string[]): readonly string[];
9
+ export declare function placesIn(root: string, history?: readonly string[]): readonly string[];
6
10
  /** A directory with no own lock is founded by the root: a line of its region family names the directory's coordinates, and a covered batch that names the place lands its first own lock. */
7
11
  export declare const foundableIn: (root: string, standing: readonly string[], family: string, name: string) => boolean;
8
12
  export declare const capsuleAt: (root: string, name: string) => boolean;
@@ -1,13 +1,29 @@
1
1
  import { join } from "../host/io.js";
2
- import { CAPSULE, canonical, LOCK, matches, parse } from '@lapxo/topos/wire';
2
+ import { CAPSULE, canonical, EXTENSION, LOCK, matches, parse } from '@lapxo/topos/wire';
3
+ import { lockLines } from "./keys.js";
4
+ import { releasesOf } from "../host/release.js";
5
+ import { archiveCoordinate } from "../host/archive.js";
3
6
  import { fieldOf } from "./claims.js";
4
7
  import { isCeiling } from "./configures.js";
5
8
  import { entriesIn, observeText } from "../observe/files.js";
6
9
  import { ownLock } from "../observe/runner.js";
7
10
  import { said } from "./wire.js";
8
11
  import { ownLockOf } from "./signed.js";
9
- export const placesIn = (root) => entriesIn(root)
10
- .filter((entry) => entry.dir && observeText(join(root, entry.name, LOCK)) !== undefined).map((entry) => entry.name).sort();
12
+ /** A dependency's lock describes the dependency, never a child of its host.
13
+ * Retiring a mount does not turn its retained bytes into local declarations. */
14
+ export function mountedCoordinatesIn(root, history = lockLines(join(root, EXTENSION))) {
15
+ return releasesOf(history).flatMap(line => {
16
+ const needs = fieldOf(line, 'needs');
17
+ return needs ? [archiveCoordinate(needs)] : [];
18
+ });
19
+ }
20
+ export function localDirectoriesIn(root, history = lockLines(join(root, EXTENSION))) {
21
+ const mounts = new Set(mountedCoordinatesIn(root, history));
22
+ return entriesIn(root).filter(entry => entry.dir && !mounts.has(entry.name)).map(entry => entry.name).sort();
23
+ }
24
+ export function placesIn(root, history = lockLines(join(root, EXTENSION))) {
25
+ return localDirectoriesIn(root, history).filter(name => observeText(join(root, name, LOCK)) !== undefined);
26
+ }
11
27
  /** A directory with no own lock is founded by the root: a line of its region family names the directory's coordinates, and a covered batch that names the place lands its first own lock. */
12
28
  export const foundableIn = (root, standing, family, name) => !placesIn(root).includes(name)
13
29
  && (entriesIn(root).find((entry) => entry.name === name)?.dir ?? false)
@@ -43,6 +43,7 @@ const reachOfLine = (module, where) => {
43
43
  */
44
44
  export function capsuleReadersOf(root, store, standing, lines, resolve = capsulesFor) {
45
45
  const reaching = new Set(lines.flatMap((reader) => reachOfLine(reader.module, reader.where)));
46
+ const coordinateMeasure = wordOn(standing, 'region-coordinate', ownLock());
46
47
  const localMeasures = wordsOf(standing, 'region-measures');
47
48
  const pins = standing.filter(line => fieldOf(line, 'scope').startsWith('uses/') && fieldOf(line, 'value') !== 'withdraw');
48
49
  const measures = localMeasures.length || !pins.length ? localMeasures
@@ -51,7 +52,7 @@ export function capsuleReadersOf(root, store, standing, lines, resolve = capsule
51
52
  const groups = new Map();
52
53
  for (const place of ['', ...placesIn(root)]) {
53
54
  const where = place ? `${place}/` : '';
54
- const own = ownLockOf(root, place).filter((line) => fieldOf(line, 'scope').startsWith('region/') && measures.includes(fieldOf(line, 'measure')) && fieldOf(line, 'value') !== 'withdraw');
55
+ const own = ownLockOf(root, place).filter((line) => fieldOf(line, 'scope').startsWith('region/') && measures.includes(fieldOf(line, 'measure')) && fieldOf(line, 'measure') === coordinateMeasure && fieldOf(line, 'value') !== 'withdraw');
55
56
  const offered = [...own, ...(capsuleAt(root, place) ? world.filter((line) => !own.some((one) => fieldOf(one, 'scope') === fieldOf(line, 'scope'))) : [])];
56
57
  if (!offered.length)
57
58
  continue;
@@ -92,8 +92,6 @@ export function releasePolicyLines(result) {
92
92
  if (result === undefined)
93
93
  return [];
94
94
  const gates = (result.gates ?? []).map(g => `GATE ${g.scope} ${g.status} · requires=${g.required.join('|')} · ${g.evidence.map(e => `${e.coordinate}=${e.value}`).join(' | ') || g.reason || 'no evidence'}`);
95
- if (result.status === 'absent')
96
- return gates;
97
95
  return [
98
96
  ...gates,
99
97
  `RELEASE policy ${result.status} · fold ${result.snapshot.fold} · instrument ${result.snapshot.instrument}`,
@@ -1,3 +1,4 @@
1
+ import { authorityFor } from './signers.ts';
1
2
  /** Lines told to a place's own lock for a preview: folded as if the file held them, and standing only as its signed lines do. */
2
3
  export declare const tellOwn: (place: string, lines: readonly string[]) => void;
3
4
  /**
@@ -16,3 +17,7 @@ export declare const admittedIn: (root: string, place: string, line: string) =>
16
17
  export declare const appliedIn: (root: string, place: string) => readonly string[];
17
18
  export declare const forksIn: (root: string, place: string) => readonly string[];
18
19
  export declare const writtenIn: (root: string, place: string) => number;
20
+ /** Authenticated history excludes prospective lines told only for admission. */
21
+ export declare const appliedHistoryIn: (root: string, place: string) => readonly string[];
22
+ /** Shared signature/window/coordinate coverage boundary for own history. */
23
+ export declare function ownRecordAdmitted(line: string, place: string, family: string, signers: ReturnType<typeof authorityFor>['admitted'], algorithms: readonly string[]): boolean;
@@ -13,7 +13,7 @@ const held = new Map();
13
13
  const told = new Map();
14
14
  /** Lines told to a place's own lock for a preview: folded as if the file held them, and standing only as its signed lines do. */
15
15
  export const tellOwn = (place, lines) => void told.set(place, lines);
16
- function verified(root, place) {
16
+ function verified(root, place, preview = true) {
17
17
  const store = storeOf(root);
18
18
  const lock = lockLines(store);
19
19
  const publicText = observeText(join(root, place, LOCK)) ?? '';
@@ -21,7 +21,7 @@ function verified(root, place) {
21
21
  const algorithms = hasAuthority ? signaturesOf(store).admitted : [];
22
22
  const signers = algorithms.length ? authorityFor(lock, rootSigner(root), algorithms).admitted : [];
23
23
  const history = signers.flatMap(signer => place ? shardLines(store, signer.id, `${place}/${LOCK}`) : ledgerLines(store, signer.id)).filter(line => fieldOf(line, 'sig') !== '');
24
- const text = [publicText, ...history, ...(told.get(place) ?? [])].join('\n');
24
+ const text = [publicText, ...history, ...(preview ? told.get(place) ?? [] : [])].join('\n');
25
25
  const key = `${root} ${place}`;
26
26
  const last = held.get(key);
27
27
  if (last !== undefined && last.text === text && last.lock === lock)
@@ -30,19 +30,7 @@ function verified(root, place) {
30
30
  // Unrestricted coverage does not construct a region coordinate. Restricted
31
31
  // coverage still requires the lock's declared namespace before comparison.
32
32
  const family = signers.some(signer => !signer.coverage.includes('*')) ? wordOf(lockStanding(store), 'families', 'region') : '';
33
- const stands = (line) => {
34
- const got = parse(line);
35
- if (got.kind !== 'fact' || got.value.fields['sig'] === undefined)
36
- return false;
37
- const fields = got.value.fields;
38
- const signer = signers.find((one) => one.id === fields['by']);
39
- const epoch = Number(fields['epoch']) || 0;
40
- if (signer === undefined || !verifiesFields(fields, signer.publicKey, algorithms))
41
- return false;
42
- const window = windowAt(signer, epoch);
43
- return epoch >= window.start && (window.close === null || epoch <= window.close)
44
- && signer.coverage.some((prefix) => prefix === '*' || within(region(`${family}/${place}/${fields['scope'] ?? ''}`, './'), region(prefix, './')));
45
- };
33
+ const stands = (line) => ownRecordAdmitted(line, place, family, signers, algorithms);
46
34
  const applied = new Set(lines.filter(stands));
47
35
  // A published copy of an authenticated delivery is not another inscription.
48
36
  const projected = new Set([...applied].map(sayingOf));
@@ -75,3 +63,19 @@ export const admittedIn = (root, place, line) => verified(root, place).applied.h
75
63
  export const appliedIn = (root, place) => [...verified(root, place).applied];
76
64
  export const forksIn = (root, place) => ((got) => foldClaims(got.lines.filter((line) => got.applied.has(line) || (!/ sig=/.test(line) && !got.projected.has(sayingOf(line))))).forks.map((fork) => fork.key))(verified(root, place));
77
65
  export const writtenIn = (root, place) => ((got) => got.lines.filter((line) => !got.applied.has(line)).length)(verified(root, place));
66
+ /** Authenticated history excludes prospective lines told only for admission. */
67
+ export const appliedHistoryIn = (root, place) => [...verified(root, place, false).applied];
68
+ /** Shared signature/window/coordinate coverage boundary for own history. */
69
+ export function ownRecordAdmitted(line, place, family, signers, algorithms) {
70
+ const got = parse(line);
71
+ if (got.kind !== 'fact' || got.value.fields['sig'] === undefined)
72
+ return false;
73
+ const fields = got.value.fields;
74
+ const signer = signers.find((one) => one.id === fields['by']);
75
+ const epoch = Number(fields['epoch']) || 0;
76
+ if (signer === undefined || !verifiesFields(fields, signer.publicKey, algorithms))
77
+ return false;
78
+ const window = windowAt(signer, epoch);
79
+ return epoch >= window.start && (window.close === null || epoch <= window.close)
80
+ && signer.coverage.some((prefix) => prefix === '*' || within(region(`${family}/${place}/${fields['scope'] ?? ''}`, './'), region(prefix, './')));
81
+ }
package/dist/fold/zoom.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { join } from "../host/io.js";
2
2
  import { canonical, LOCK, parse, RECEIPTS } from '@lapxo/topos/wire';
3
3
  import { fieldOf, foldClaims, isWire } from "./claims.js";
4
- import { entriesIn, observeText } from "../observe/files.js";
4
+ import { observeText } from "../observe/files.js";
5
+ import { placesIn } from "./places.js";
5
6
  /**
6
7
  * A line of the tree seen from inside a place: every path it names under the place is read from the place's root, and
7
8
  * a path outside it is dropped, since the place alone cannot read it. The place's name drops out of the scope, the condition
@@ -46,10 +47,10 @@ export function zoomed(line, place) {
46
47
  }
47
48
  /** The lines of each place's own lock that still name the place: seen from inside a place, no scope carries its name. */
48
49
  export function namingTheirPlace(root) {
49
- return entriesIn(root).filter((entry) => entry.dir && !entry.name.startsWith('.')).flatMap((entry) => {
50
- const own = (observeText(join(root, entry.name, LOCK)) ?? '').split('\n').filter(isWire);
50
+ return placesIn(root).filter(name => !name.startsWith('.')).flatMap((name) => {
51
+ const own = (observeText(join(root, name, LOCK)) ?? '').split('\n').filter(isWire);
51
52
  return foldClaims(own).standing.filter((line) => fieldOf(line, 'value') !== 'withdraw')
52
- .map((line) => fieldOf(line, 'scope')).filter((scope) => scope.startsWith(`${entry.name}/`) || scope.includes(`/${entry.name}/`));
53
+ .map((line) => fieldOf(line, 'scope')).filter((scope) => scope.startsWith(`${name}/`) || scope.includes(`/${name}/`));
53
54
  }).length;
54
55
  }
55
56
  /**
@@ -1,3 +1,4 @@
1
+ import type { ReaderReference } from '@lapxo/topos/wire';
1
2
  import type { Declaration } from '@lapxo/topos/capsule';
2
3
  import type { Request, Response } from '@lapxo/topos/contract';
3
4
  export interface Capsule {
@@ -5,7 +6,7 @@ export interface Capsule {
5
6
  readonly digest: string;
6
7
  readonly declaration: Declaration;
7
8
  readonly lines: readonly string[];
8
- readonly ask: (requests: readonly Request[]) => readonly (Response | undefined)[];
9
+ readonly ask: (requests: readonly Request[], reader?: ReaderReference) => readonly (Response | undefined)[];
9
10
  }
10
11
  /** A blob is loaded once in an act. A later ask for the same digest reads that capsule, and does not hash or unpack it again. */
11
12
  export declare function capsuleAt(digest: string, entry: string, store?: string): Capsule | undefined;
@@ -60,10 +60,10 @@ export function capsuleAt(digest, entry, store) {
60
60
  if (!entry || isAbsolute(entry) || (coordinate === '..' || coordinate.startsWith(`..${sep}`)) || entry.split(/[\\/]/).includes('..')) {
61
61
  throw new Error(`REFUSE·capsule ${digest} declared entry ${entry || '(absent)'} unavailable`);
62
62
  }
63
- const capsule = { digest, ...known, ask: (requests) => {
63
+ const capsule = { digest, ...known, ask: (requests, reader) => {
64
64
  if (!statSync(module, { throwIfNoEntry: false })?.isFile())
65
65
  throw new Error(`REFUSE·capsule ${digest} declared entry ${entry} unavailable`);
66
- return answered(digest, bytes, requests, names, root, cacheStore, module);
66
+ return answered(digest, bytes, requests, names, root, cacheStore, module, reader);
67
67
  } };
68
68
  capsules.set(identity, capsule);
69
69
  return capsule;
@@ -86,14 +86,16 @@ const spawnOf = (at, names) => {
86
86
  return child;
87
87
  };
88
88
  export const childFlags = (argv = process.execArgv) => argv.filter((flag, i) => !/^(-e|-p|--eval|--print)$/.test(argv[i - 1] ?? '') && !/^(-e|-p|--eval|--print|--input-type)(=|$)/.test(flag));
89
- function answered(digest, bytes, requests, names, tree, cacheStore = ownStore(), entry) {
89
+ function answered(digest, bytes, requests, names, tree, cacheStore = ownStore(), entry, reader) {
90
90
  const child = entry === undefined ? spawnOf(tree ?? unpacked(digest, bytes), names) : [entry];
91
91
  if (!child.length) {
92
92
  process.stderr.write(`LOAD regions · none of ${names.length} under the directories release/layout names\n`);
93
93
  return requests.map(() => undefined);
94
94
  }
95
95
  const loader = entry !== undefined ? `${createHash('sha256').update(`${relative(tree, entry)}\n`).digest('hex')}\n` : child.length > 1 ? `${createHash('sha256').update(observeFile(child[0]) ?? new Uint8Array()).digest('hex')}\n` : '';
96
- const hitOf = (request) => sha(`${digest}\n${loader}${JSON.stringify(request)}`);
96
+ const lifetime = readerLifetime(contentPolicy().flatMap(line => { const p = parse(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader ?? digest);
97
+ const policy = lifetime === undefined ? '' : JSON.stringify(lifetime) + '\n';
98
+ const hitOf = (request) => sha(`${digest}\n${loader}${policy}${JSON.stringify(request)}`);
97
99
  const hits = requests.map(hitOf);
98
100
  const kept = hits.map(hit => storeAt(cacheStore, 'cas', 'answers', hit));
99
101
  const held = kept.map((at, i) => {
@@ -114,7 +116,7 @@ function answered(digest, bytes, requests, names, tree, cacheStore = ownStore(),
114
116
  if (missing.length)
115
117
  process.stderr.write(`CAPSULE ${digest.slice(7, 19)} · ${requests.length} asked · ${missing.length} run · ${missing.map((one) => `${one.rootScope}${one.region}`).join(' ')}\n`);
116
118
  // Execute and validate the entire batch before committing any answer to the cache.
117
- const ran = !missing.length ? [] : executeCapsule(process.execPath, [...childFlags(), hostOf(), ...child], missing, {}, readerLifetime(contentPolicy().flatMap(line => { const p = parse(line); return p.kind === 'fact' ? [p.value.fields] : []; }), digest));
119
+ const ran = !missing.length ? [] : executeCapsule(process.execPath, [...childFlags(), hostOf(), ...child], missing, {}, lifetime);
118
120
  return requests.map((request, i) => {
119
121
  if (held[i] !== undefined)
120
122
  return held[i];
@@ -148,5 +150,5 @@ export function locatedAt(tree, place, cacheStore = ownStore()) {
148
150
  }
149
151
  }
150
152
  const digest = `sha256:${hash.digest('hex')}`;
151
- return { digest, ...known, ask: (requests) => answered(digest, new Uint8Array(), requests, names, at, cacheStore) };
153
+ return { digest, ...known, ask: (requests, reader) => answered(digest, new Uint8Array(), requests, names, at, cacheStore, undefined, reader) };
152
154
  }
@@ -3,7 +3,7 @@ import { join, dirname } from 'node:path';
3
3
  import { performance } from 'node:perf_hooks';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { canonical, objectHistory, parse, byBytes, wireAt, RECEIPTS } from '@lapxo/topos/wire';
6
- import { cellInputs, cellReceiptContract } from '@lapxo/topos/cell-inputs';
6
+ import { cellInputs, cellReceiptContract, cellInputProfile } from '@lapxo/topos/cell-inputs';
7
7
  import { renderCells } from '@lapxo/topos/cells-view';
8
8
  import { admittedObjects } from "./objects.js";
9
9
  import { objectFold } from "../fold/object.js";
@@ -30,6 +30,7 @@ export async function cellsWithReceipts(root, history, emit) {
30
30
  throw Error(`REFUSE·authority receipt input ${record.fields.id}`);
31
31
  const config = parsed.value.configuration.filter(f => authority.of(canonical(f)).kind === 'admitted');
32
32
  cellReceiptContract(config);
33
+ const profile = cellInputProfile(config);
33
34
  const instrument = ownLock().map(fields);
34
35
  const wire = wireAt(instrument, Number.MAX_SAFE_INTEGER);
35
36
  if (!wire)
@@ -38,7 +39,7 @@ export async function cellsWithReceipts(root, history, emit) {
38
39
  if (!algorithm)
39
40
  throw Error('REFUSE·receipt no digest algorithm');
40
41
  const code = implementation(algorithm);
41
- const inputs = cellInputs(parsed.value.objects), semantic = new Map(), inputBytes = new Map();
42
+ const inputs = cellInputs(parsed.value.objects, profile), semantic = new Map(), inputBytes = new Map();
42
43
  const pins = new Map();
43
44
  for (const record of parsed.value.objects.filter(r => r.record === 'cell')) {
44
45
  const pin = record.fields.topos;
@@ -47,7 +48,10 @@ export async function cellsWithReceipts(root, history, emit) {
47
48
  pins.set(pin, [pin, ...top.dependencies]);
48
49
  }
49
50
  const scope = `receipts/cells/${record.fields.scope}`;
50
- const bytes = [...inputs.get(record.fields.scope), ...pins.get(pin), ...code, 'resolution=1'].sort(byBytes).join('\n');
51
+ const ownInputs = inputs.get(record.fields.scope);
52
+ const metadata = [...pins.get(pin), ...code, 'resolution=1'];
53
+ const bytes = profile === 'semantic-live@1' ? [...ownInputs, ...metadata].sort(byBytes).join('\n') :
54
+ [...ownInputs, ...metadata.sort(byBytes), `receipt-inputs=${profile}`].join('\n');
51
55
  inputBytes.set(scope, bytes);
52
56
  semantic.set(scope, fullDigest(bytes, algorithm));
53
57
  }
package/dist/host/io.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /** The host's node surface: kernel folders import here, never `node:`. World reads still go through observeFile. */
2
2
  export { spawn, spawnSync } from 'node:child_process';
3
3
  export { createHash } from 'node:crypto';
4
- export { appendFileSync, closeSync, existsSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
4
+ export { appendFileSync, closeSync, existsSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
5
5
  export { tmpdir } from 'node:os';
6
6
  export { basename, dirname, join, relative, resolve } from 'node:path';
7
7
  export { fileURLToPath } from 'node:url';
package/dist/host/io.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /** The host's node surface: kernel folders import here, never `node:`. World reads still go through observeFile. */
2
2
  export { spawn, spawnSync } from 'node:child_process';
3
3
  export { createHash } from 'node:crypto';
4
- export { appendFileSync, closeSync, existsSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
4
+ export { appendFileSync, closeSync, existsSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
5
5
  export { tmpdir } from 'node:os';
6
6
  export { basename, dirname, join, relative, resolve } from 'node:path';
7
7
  export { fileURLToPath } from 'node:url';
@@ -1,23 +1,10 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
3
2
  import { join } from 'node:path';
4
3
  import { requireEffects } from "../read-only.js";
5
4
  import { underTheLock } from "../../land/act.js";
6
- const syncDirectory = (path) => { const fd = openSync(path, 'r'); try {
7
- fsyncSync(fd);
8
- }
9
- finally {
10
- closeSync(fd);
11
- } };
12
- const durableFile = (path, text) => { const fd = openSync(path, 'wx', 0o600); try {
13
- writeFileSync(fd, text);
14
- fsyncSync(fd);
15
- }
16
- finally {
17
- closeSync(fd);
18
- } };
5
+ import { nativePublicationStorage, publishBundle } from "./publication.js";
19
6
  /** Directory publication makes the pair visible together; there is no later ledger append that can expose half. */
20
- export const nativeIngressStorage = { publish(staged, committed) { renameSync(staged, committed); syncDirectory(join(committed, '..')); } };
7
+ export const nativeIngressStorage = nativePublicationStorage;
21
8
  const directory = (store) => join(store, 'ingress');
22
9
  const nameOf = (identity) => {
23
10
  if (!identity || identity.includes('\0'))
@@ -58,18 +45,7 @@ export async function commitIngress(store, identity, prepare, verify, storage =
58
45
  const bundle = await prepare(ingressReceipts(store));
59
46
  if (!bundle.evidence || !bundle.receipt || verify(bundle) !== true)
60
47
  throw Error('REFUSE·ingress evidence and receipt do not verify');
61
- mkdirSync(root, { recursive: true });
62
- const staged = join(root, '.stage-' + randomUUID());
63
- mkdirSync(staged, { mode: 0o700 });
64
- try {
65
- durableFile(join(staged, 'evidence.bound'), bundle.evidence);
66
- durableFile(join(staged, 'receipt.bound'), bundle.receipt);
67
- syncDirectory(staged);
68
- storage.publish(staged, at);
69
- }
70
- finally {
71
- rmSync(staged, { recursive: true, force: true });
72
- }
48
+ publishBundle(at, { 'evidence.bound': bundle.evidence, 'receipt.bound': bundle.receipt }, storage);
73
49
  return measured('landed', bundle);
74
50
  });
75
51
  }
@@ -0,0 +1,34 @@
1
+ import type { PublicationStorage } from './publication.ts';
2
+ /** Prepared and verified by the selected Topos admission/result contract. */
3
+ export interface LocalActBundle {
4
+ readonly records: string;
5
+ readonly receipt: string;
6
+ readonly placements?: readonly {
7
+ place: string;
8
+ records: string;
9
+ }[];
10
+ }
11
+ export type LocalActVerification = (identity: string, bundle: LocalActBundle) => boolean;
12
+ export declare const actLines: (bytes: string) => readonly string[];
13
+ /** Journal integrity is not authority. Signature/coverage checks remain the
14
+ * same admission boundary; the hash algorithm here is the stored seal's named
15
+ * algorithm, not a new selection of the place's wire.
16
+ */
17
+ export declare function intactLocalAct(identity: string, bundle: LocalActBundle): boolean;
18
+ /** Committed segments are part of this ledger, not a second authority source. */
19
+ export declare function committedLocalActs(store: string): readonly {
20
+ identity: string;
21
+ bundle: LocalActBundle;
22
+ }[];
23
+ /** A public result is available only as a verified committed pair.
24
+ * No writer-tail guess, newest-result selection or implicit authority.
25
+ */
26
+ export declare function localActResult(store: string, identity: string, verify: LocalActVerification): LocalActBundle | undefined;
27
+ /** One publication point for records and their native result. Re-delivery
28
+ * reads the original pair without preparation, signing or a new local epoch.
29
+ * The caller must also integrate this boundary into admission-history readers;
30
+ * this storage port alone does not activate a grammar or land authority.
31
+ */
32
+ export declare function commitLocalAct(store: string, identity: string, regions: readonly string[], prepare: () => Promise<LocalActBundle>, verify: LocalActVerification, storage?: PublicationStorage): Promise<LocalActBundle & {
33
+ readonly kind: 'landed' | 'once';
34
+ }>;