@lapxo/bound 0.1.8 → 0.1.9

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 (128) hide show
  1. package/README.md +56 -20
  2. package/TARGET.bound +25 -25
  3. package/dist/cli/accept.js +83 -59
  4. package/dist/cli/act-result.d.ts +9 -0
  5. package/dist/cli/act-result.js +68 -13
  6. package/dist/cli/args.d.ts +1 -0
  7. package/dist/cli/args.js +5 -2
  8. package/dist/cli/custodian.d.ts +1 -0
  9. package/dist/cli/custodian.js +29 -0
  10. package/dist/cli/native-descendant.d.ts +1 -0
  11. package/dist/cli/native-descendant.js +19 -0
  12. package/dist/cli/native.d.ts +1 -0
  13. package/dist/cli/native.js +60 -0
  14. package/dist/cli/observe.js +9 -2
  15. package/dist/cli/pass.js +6 -1
  16. package/dist/cli/standing-result.js +7 -26
  17. package/dist/cli/verb.js +24 -5
  18. package/dist/fold/admission-history.js +2 -2
  19. package/dist/fold/claims.d.ts +4 -0
  20. package/dist/fold/claims.js +23 -3
  21. package/dist/fold/closed.d.ts +1 -1
  22. package/dist/fold/closed.js +16 -4
  23. package/dist/fold/digests.d.ts +1 -1
  24. package/dist/fold/digests.js +3 -2
  25. package/dist/fold/evidence.d.ts +1 -1
  26. package/dist/fold/kept.js +2 -1
  27. package/dist/fold/observed.d.ts +1 -0
  28. package/dist/fold/observed.js +54 -9
  29. package/dist/fold/readers.d.ts +4 -3
  30. package/dist/fold/readers.js +18 -13
  31. package/dist/fold/resolved.js +11 -1
  32. package/dist/fold/signers.js +2 -2
  33. package/dist/host/adapters/modules/javascript.d.ts +2 -0
  34. package/dist/host/adapters/modules/javascript.js +8 -6
  35. package/dist/host/admitted-place.d.ts +5 -0
  36. package/dist/host/admitted-place.js +54 -0
  37. package/dist/host/capsule-process.d.ts +1 -1
  38. package/dist/host/capsule-process.js +4 -1
  39. package/dist/host/capsule.d.ts +4 -0
  40. package/dist/host/capsule.js +16 -2
  41. package/dist/host/contextual-place.d.ts +59 -0
  42. package/dist/host/contextual-place.js +77 -0
  43. package/dist/host/hash.d.ts +7 -0
  44. package/dist/host/hash.js +6 -0
  45. package/dist/host/io.d.ts +2 -0
  46. package/dist/host/io.js +2 -0
  47. package/dist/host/lifetime.js +61 -22
  48. package/dist/host/native/bounded-file.d.ts +8 -0
  49. package/dist/host/native/bounded-file.js +32 -0
  50. package/dist/host/native/command.d.ts +36 -0
  51. package/dist/host/native/command.js +91 -0
  52. package/dist/host/native/control-bridge-worker.d.ts +1 -0
  53. package/dist/host/native/control-bridge-worker.js +88 -0
  54. package/dist/host/native/control-bridge.d.ts +30 -0
  55. package/dist/host/native/control-bridge.js +110 -0
  56. package/dist/host/native/control-selection.d.ts +29 -0
  57. package/dist/host/native/control-selection.js +32 -0
  58. package/dist/host/native/custody.d.ts +5 -0
  59. package/dist/host/native/custody.js +91 -0
  60. package/dist/host/native/deadline.d.ts +12 -0
  61. package/dist/host/native/deadline.js +28 -0
  62. package/dist/host/native/descendants.d.ts +1 -0
  63. package/dist/host/native/descendants.js +528 -0
  64. package/dist/host/native/generated-custodian.d.ts +122 -0
  65. package/dist/host/native/generated-custodian.js +267 -0
  66. package/dist/host/native/generated.d.ts +217 -0
  67. package/dist/host/native/generated.js +379 -0
  68. package/dist/host/native/image.d.ts +36 -0
  69. package/dist/host/native/image.js +45 -0
  70. package/dist/host/native/jobs.d.ts +75 -0
  71. package/dist/host/native/jobs.js +148 -0
  72. package/dist/host/native/loader.d.ts +80 -0
  73. package/dist/host/native/loader.js +269 -0
  74. package/dist/host/native/materialize.d.ts +22 -0
  75. package/dist/host/native/materialize.js +175 -0
  76. package/dist/host/native/module-closure.d.ts +27 -0
  77. package/dist/host/native/module-closure.js +99 -0
  78. package/dist/host/native/module-syntax.d.ts +8 -0
  79. package/dist/host/native/module-syntax.js +63 -0
  80. package/dist/host/native/native-binary.d.ts +54 -0
  81. package/dist/host/native/native-binary.js +115 -0
  82. package/dist/host/native/native-program.d.ts +56 -0
  83. package/dist/host/native/native-program.js +72 -0
  84. package/dist/host/native/node-jobs.d.ts +84 -0
  85. package/dist/host/native/node-jobs.js +187 -0
  86. package/dist/host/native/node-launch.d.ts +72 -0
  87. package/dist/host/native/node-launch.js +159 -0
  88. package/dist/host/native/preload.d.ts +1 -0
  89. package/dist/host/native/preload.js +45 -0
  90. package/dist/host/native/reading.d.ts +91 -0
  91. package/dist/host/native/reading.js +249 -0
  92. package/dist/host/native/receipt-environment.d.ts +3 -0
  93. package/dist/host/native/receipt-environment.js +41 -0
  94. package/dist/host/native/references.d.ts +5 -0
  95. package/dist/host/native/references.js +30 -0
  96. package/dist/host/native/root.d.ts +17 -0
  97. package/dist/host/native/root.js +37 -0
  98. package/dist/host/native/route.d.ts +66 -0
  99. package/dist/host/native/route.js +216 -0
  100. package/dist/host/native/runtime.d.ts +4 -0
  101. package/dist/host/native/runtime.js +9 -0
  102. package/dist/host/native-reader-context.d.ts +3 -0
  103. package/dist/host/native-reader-context.js +8 -0
  104. package/dist/host/place-clock-selection.d.ts +55 -0
  105. package/dist/host/place-clock-selection.js +124 -0
  106. package/dist/host/ports/local-act.d.ts +10 -0
  107. package/dist/host/ports/local-act.js +78 -6
  108. package/dist/host/ports/place-act.d.ts +46 -0
  109. package/dist/host/ports/place-act.js +169 -0
  110. package/dist/host/process-lifetime.js +8 -3
  111. package/dist/host/provide.d.ts +25 -0
  112. package/dist/host/provide.js +115 -13
  113. package/dist/host/reading-closures.d.ts +15 -1
  114. package/dist/host/reading-closures.js +88 -6
  115. package/dist/host/reading-context.d.ts +29 -0
  116. package/dist/host/reading-context.js +53 -0
  117. package/dist/host/root-clock-source.d.ts +85 -0
  118. package/dist/host/root-clock-source.js +305 -0
  119. package/dist/host/selected-capsules.js +3 -2
  120. package/dist/land/act.d.ts +26 -0
  121. package/dist/land/act.js +96 -3
  122. package/dist/land/ledger.d.ts +4 -0
  123. package/dist/land/ledger.js +20 -2
  124. package/dist/observe/runner.d.ts +4 -0
  125. package/dist/observe/runner.js +9 -2
  126. package/docs/contracts/cli.md +13 -0
  127. package/package.json +2 -2
  128. package/receipts.bound +296 -256
package/dist/cli/verb.js CHANGED
@@ -42,9 +42,9 @@ import { placesIn } from "../fold/places.js";
42
42
  import { isCeiling } from "../fold/configures.js";
43
43
  import { readingsIn } from "../land/inert.js";
44
44
  import { pass } from "./pass.js";
45
- import { underTheLock, underTheRegions } from "../land/act.js";
45
+ import { underTheLock, underTheRegions, underRootAuthority } from "../land/act.js";
46
46
  import { ownerOf, renderInto, signedDemands, targetFile } from "./owner.js";
47
- import { claimLinesIn, entryOf, namedPlaces, positionalsOf, valueOf } from "./args.js";
47
+ import { claimLinesIn, entryOf, namedPlaces, positionalIndexes, positionalsOf, valueOf } from "./args.js";
48
48
  import { EXIT, FLAGS, NAMED_VIEWS, helpLines } from "./names.js";
49
49
  import { accept, signedBatch } from "./accept.js";
50
50
  import { renderWalk, landWalk, foreignWalkLines } from "../host/walk.js";
@@ -182,12 +182,18 @@ function beat(root, fold, ms) {
182
182
  * as if the batch had landed: the batch is signed and folded, never landed, and nothing is written — `--as rank <batch>`
183
183
  * is what a batch would move, read before land.
184
184
  */
185
- async function view(root, under, asked, told = [], coord, key, includePolicy = true) {
185
+ async function view(root, under, asked, told = [], coord, key, includePolicy = true, authorityHeld = false) {
186
186
  const [name = asked, depth] = asked.split('@');
187
187
  if (name === NAMED_VIEWS.help) {
188
188
  process.stdout.write(`${helpLines().join('\n')}\n`);
189
189
  return EXIT.closed;
190
190
  }
191
+ // A declared executable reader holds the acting Root's authority throughout
192
+ // observation and publication. This predicate acquires exclusion only; the
193
+ // native route authenticates the actual declaration before use.
194
+ if (!authorityHeld && !told.length && !readOnly() && lockStanding(storeOf(root)).some(line => /^reader\/.+\/port$/.test(fieldOf(line, 'scope')) && fieldOf(line, 'role') === 'writes' && fieldOf(line, 'value') !== 'withdraw')) {
195
+ return underRootAuthority(storeOf(root), 'view reader authority', () => view(root, under, asked, told, coord, key, includePolicy, true));
196
+ }
191
197
  if (!lockLines(storeOf(root)).length) {
192
198
  process.stderr.write(`${selfName()}: the owner ledger is empty; land it before rendering a view\n`);
193
199
  return EXIT.refuse;
@@ -565,8 +571,21 @@ function main(argv) {
565
571
  const run = verb in VERBS ? VERBS[verb] : undefined;
566
572
  const places = positionalsOf(run === undefined ? argv : argv.slice(1)).filter((one) => existsSync(resolve(one)) && lstatSync(resolve(one)).isDirectory());
567
573
  if (run !== undefined) {
568
- const root = resolve(places[0] ?? '.');
569
- return withContentStore(storeOf(root), () => run(root, argv.slice(1), argv));
574
+ const rest = argv.slice(1);
575
+ const roots = positionalIndexes(rest).filter(index => {
576
+ const path = resolve(rest[index]);
577
+ return existsSync(path) && lstatSync(path).isDirectory();
578
+ });
579
+ if (roots.length > 1) {
580
+ process.stderr.write(`${selfName()}: REFUSE·root name one acting directory\n`);
581
+ return EXIT.usage;
582
+ }
583
+ const selected = roots[0];
584
+ const root = resolve(selected === undefined ? '.' : rest[selected]);
585
+ const normalized = selected === undefined ? rest : rest.filter((_, index) => index !== selected);
586
+ return withContentStore(storeOf(root), () => verb === 'fold' && selected !== undefined
587
+ ? foldAt(root, undefined, normalized)
588
+ : run(root, normalized, [verb, ...normalized]));
570
589
  }
571
590
  const under = argv[0] ?? '';
572
591
  if (under && !under.startsWith('-') && places.includes(under)) {
@@ -1,12 +1,12 @@
1
1
  import { join } from 'node:path';
2
2
  import { readFileSync } from 'node:fs';
3
- import { ledgerLines, ledgerWriters, storeOf } from "../land/ledger.js";
3
+ import { rootLedgerLines, storeOf } from "../land/ledger.js";
4
4
  import { fieldOf, isConfig, selfName } from "./claims.js";
5
5
  /** Local admission history only. Foreign ingress is evidence, never coverage.
6
6
  * The clock cutoff is meaningful for configuration; object clocks stay intact. */
7
7
  export function admissionHistoryAt(root, epoch) {
8
8
  const store = storeOf(root);
9
- return [...new Set(ledgerWriters(store).flatMap(writer => ledgerLines(store, writer)))]
9
+ return [...new Set(rootLedgerLines(store))]
10
10
  .filter(line => fieldOf(line, 'sig') !== '' && (!isConfig(line) || Number(fieldOf(line, 'epoch')) <= epoch));
11
11
  }
12
12
  /** The existing bootstrap's public root of trust, not today's key selection. */
@@ -1,4 +1,8 @@
1
+ import { parse } from '@lapxo/topos/wire';
1
2
  export { CAPSULE, EXTENSION, LOCK, RECEIPTS } from '@lapxo/topos/wire';
3
+ /** Pure syntax is retained by the exact record bytes, never by a path, epoch or
4
+ * claimed digest. This does not retain admission, coverage or current grants. */
5
+ export declare function parsedLine(line: string): ReturnType<typeof parse>;
2
6
  export declare const fieldOf: (line: string, name: string) => string;
3
7
  export declare const fieldsOf: (line: string) => Record<string, string>;
4
8
  export declare function selfName(): string;
@@ -4,12 +4,31 @@ import { ENVELOPE, PROTOCOL, recordKind, signedBytes, parse } from '@lapxo/topos
4
4
  export { CAPSULE, EXTENSION, LOCK, RECEIPTS } from '@lapxo/topos/wire';
5
5
  import { ownRoot } from "../observe/runner.js";
6
6
  const decoded = new Map();
7
+ const parsed = new Map();
8
+ const kinds = new Map();
9
+ /** Pure syntax is retained by the exact record bytes, never by a path, epoch or
10
+ * claimed digest. This does not retain admission, coverage or current grants. */
11
+ export function parsedLine(line) {
12
+ const held = parsed.get(line);
13
+ if (held !== undefined)
14
+ return held;
15
+ const got = parse(line);
16
+ if (got.kind === 'fact') {
17
+ Object.freeze(got.value.fields);
18
+ if (got.value.keys)
19
+ Object.freeze(got.value.keys);
20
+ Object.freeze(got.value);
21
+ }
22
+ Object.freeze(got);
23
+ parsed.set(line, got);
24
+ return got;
25
+ }
7
26
  /** Field access uses the Topos wire parser; decoding is shared for this CLI process. */
8
27
  function wireFields(line) {
9
28
  const held = decoded.get(line);
10
29
  if (held !== undefined)
11
30
  return held;
12
- const got = parse(line);
31
+ const got = parsedLine(line);
13
32
  const fields = got.kind === 'fact' ? got.value.fields : {};
14
33
  decoded.set(line, fields);
15
34
  return fields;
@@ -19,8 +38,9 @@ export const fieldsOf = (line) => ({ ...wireFields(line) });
19
38
  export function selfName() { return basename(ownRoot()); }
20
39
  export function isWire(line) { return line.startsWith(PROTOCOL); }
21
40
  export const wireLinesOf = (text) => (text ?? '').split('\n').filter(isWire);
22
- export const isConfig = (line) => { const k = recordKind(line); return k.kind === 'fact' && k.value === 'config'; };
23
- export const sayingOf = (line) => !isConfig(line) ? ((p) => p.kind === 'fact' ? signedBytes(p.value.fields) : line)(parse(line)) : Object.entries(fieldsOf(line)).filter(([k]) => !ENVELOPE.has(k)).sort(([a], [b]) => (a < b ? -1 : 1)).map(([k, v]) => `${k}=${v}`).join(' ');
41
+ export const isConfig = (line) => { const held = kinds.get(line); if (held !== undefined)
42
+ return held; const k = recordKind(line), value = k.kind === 'fact' && k.value === 'config'; kinds.set(line, value); return value; };
43
+ export const sayingOf = (line) => !isConfig(line) ? ((p) => p.kind === 'fact' ? signedBytes(p.value.fields) : line)(parsedLine(line)) : Object.entries(fieldsOf(line)).filter(([k]) => !ENVELOPE.has(k)).sort(([a], [b]) => (a < b ? -1 : 1)).map(([k, v]) => `${k}=${v}`).join(' ');
24
44
  /** One key per claim: scope, role and measure; what a line says, its envelope set aside, is one however often it landed. */
25
45
  export function claimKey(line) {
26
46
  const f = fieldsOf(line);
@@ -12,4 +12,4 @@ export declare function instrumentKeep(store: string, id: string): void;
12
12
  export declare function receiptPlaces(root: string, store: string): readonly string[];
13
13
  export declare function closeReceipts(root: string, store: string, semantic?: ReadonlyMap<string, string>): Promise<readonly string[]>;
14
14
  /** One comparison. A place meets its receipt; the root meets when every receipt it names meets. */
15
- export declare function meets(root: string, store: string, place: string): boolean;
15
+ export declare function meets(root: string, store: string, place: string, current?: boolean): boolean;
@@ -1,5 +1,8 @@
1
1
  import { actCosts } from "../host/act-cost.js";
2
2
  import { verifiedReadingReceipts } from "../host/reading-closures.js";
3
+ import { readingProfile } from "../host/reading-context.js";
4
+ import { currentReadingEnvironment } from "../host/native/receipt-environment.js";
5
+ import { childFlags } from "../host/capsule.js";
3
6
  import { dirname, existsSync, join, mkdirSync, relative, resolve, statSync, writeFileSync } from "../host/io.js";
4
7
  import { byBytes, canonical, LOCK, parse, publicationOf, roleAt, roleClaimsOf } from '@lapxo/topos/wire';
5
8
  import { RECEIPTS, fieldOf, foldClaims, isConfig, isWire } from "./claims.js";
@@ -60,13 +63,21 @@ function filesOf(root, place, region, inputs) {
60
63
  return [...new Set([...owned, ...observed, ...own])].filter(rel => rel !== receipt && !rel.split('/').some(part => part === '.bound' || part === '.cache' || part === '.git'));
61
64
  }
62
65
  const rooted = new Map();
66
+ let lastConfiguration;
63
67
  function publicConfiguration(lines) {
68
+ const input = lines.join('\n');
69
+ // Retain only this pure projection. Callers still read and hash the actual
70
+ // files; neither a timestamp nor a previous seal proves their current bytes.
71
+ if (lastConfiguration?.input === input)
72
+ return lastConfiguration.output;
64
73
  if (!lines.length || !lines.every(isConfig))
65
74
  return lines;
66
75
  const folded = foldClaims(lines);
67
76
  if (folded.forks.length)
68
77
  throw Error('REFUSE·receipt disputed configuration standing');
69
- return publicationOf(folded.standing.filter(line => fieldOf(line, 'value') !== 'withdraw'));
78
+ const output = publicationOf(folded.standing.filter(line => fieldOf(line, 'value') !== 'withdraw'));
79
+ lastConfiguration = { input, output };
80
+ return output;
70
81
  }
71
82
  function rootLines(root, place) {
72
83
  const name = place.replace(/\/$/, '');
@@ -167,7 +178,7 @@ export async function closeReceipts(root, store, semantic) {
167
178
  return open;
168
179
  }
169
180
  /** One comparison. A place meets its receipt; the root meets when every receipt it names meets. */
170
- export function meets(root, store, place) {
181
+ export function meets(root, store, place, current = true) {
171
182
  const name = place.replace(/\/$/, '');
172
183
  if (name === '') {
173
184
  const at = Date.now();
@@ -178,11 +189,12 @@ export function meets(root, store, place) {
178
189
  let closed = (own.length > 0 ? ownClosed : named.size > 0) && [...named].every(([one, expected]) => {
179
190
  const bytes = observeFile(join(root, one, RECEIPTS));
180
191
  const algorithm = expected.split(':')[0];
181
- return bytes !== undefined && hashBytes(bytes, algorithm) === expected && meets(root, store, `${one}/`);
192
+ return bytes !== undefined && hashBytes(bytes, algorithm) === expected && meets(root, store, `${one}/`, current);
182
193
  });
183
194
  if (closed && carried) {
184
195
  const algorithm = algorithmIn(own.join('\n'));
185
- const readings = verifiedReadingReceipts(store, carried, text => hashFull(text, algorithm));
196
+ const profile = readingProfile((observeText(join(root, LOCK)) ?? '').split('\n').filter(isWire), fieldOf);
197
+ const readings = verifiedReadingReceipts(store, carried, text => hashFull(text, algorithm), current && profile ? currentReadingEnvironment(store, root, text => hashFull(text, algorithm), childFlags()) : undefined, profile);
186
198
  if (readings === undefined)
187
199
  closed = false;
188
200
  else if (readings)
@@ -26,4 +26,4 @@ export declare function memoized(store: string, key: string, compute: () => stri
26
26
  */
27
27
  export declare function instrumentOf(store: string, entry: string, standing?: readonly string[], root?: string, releases?: readonly string[]): string;
28
28
  export declare function instrumentDigest(store: string, entry: string, standing?: readonly string[], root?: string, releases?: readonly string[]): string;
29
- export declare function instrumentCoordinates(store: string, entry: string, standing?: readonly string[], root?: string, releases?: readonly string[]): ReadonlyMap<string, string>;
29
+ export declare function instrumentCoordinates(store: string, entry: string, standing?: readonly string[], root?: string, releases?: readonly string[], fresh?: boolean): ReadonlyMap<string, string>;
@@ -167,12 +167,13 @@ export function instrumentDigest(store, entry, standing = [], root = dirname(res
167
167
  const seen = instrumentCoordinates(store, entry, standing, root, releases);
168
168
  return bytesDigest(store, new TextEncoder().encode([...seen.values()].sort().join('\n')));
169
169
  }
170
- export function instrumentCoordinates(store, entry, standing = [], root = dirname(resolve(entry)), releases = []) {
170
+ export function instrumentCoordinates(store, entry, standing = [], root = dirname(resolve(entry)), releases = [], fresh = false) {
171
171
  const seen = new Map();
172
172
  const walk = (at) => {
173
173
  if (seen.has(at))
174
174
  return;
175
- const digest = coordinateDigest(store, at);
175
+ const bytes = fresh ? observeFile(at) : undefined;
176
+ const digest = fresh ? (bytes === undefined ? undefined : bytesDigest(store, bytes)) : coordinateDigest(store, at);
176
177
  if (digest === undefined)
177
178
  return;
178
179
  // The host loader resolves links before loading a module. Count that module
@@ -22,7 +22,7 @@ export interface SnapshotInput {
22
22
  };
23
23
  }
24
24
  export declare function snapshotVerdict(input: SnapshotInput): {
25
- verdict: "none" | "grey" | "agrees" | "forks" | "stale";
25
+ verdict: "none" | "grey" | "stale" | "agrees" | "forks";
26
26
  external: boolean;
27
27
  epoch: number;
28
28
  };
package/dist/fold/kept.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { encodeUTF8 } from "../host/io.js";
1
2
  import { actLines, committedLocalActs } from "../host/ports/local-act.js";
2
3
  import { readOnly } from "../host/read-only.js";
3
4
  import { createHash, dirname, existsSync, join, mkdirSync, readdirSync, renameSync, writeFileSync } from "../host/io.js";
@@ -10,7 +11,7 @@ import { keyFor, keyOf, lockSigners } from "./keys.js";
10
11
  import { sourceOf, vouching } from "../land/vouched.js";
11
12
  import { wordsOf } from "./wire.js";
12
13
  import { ownLock } from "../observe/runner.js";
13
- const text = (s) => new TextEncoder().encode(s);
14
+ const text = encodeUTF8;
14
15
  const atOf = (line) => {
15
16
  if (line.includes(' about='))
16
17
  return fieldOf(line, 'at');
@@ -30,5 +30,6 @@ export declare function observedClaims(root: string, store: string, options?: {
30
30
  readonly only?: string;
31
31
  readonly reach?: readonly string[];
32
32
  readonly answered?: Map<string, string>;
33
+ readonly selected?: string;
33
34
  }): readonly string[];
34
35
  export {};
@@ -1,11 +1,17 @@
1
+ import { authenticatedStanding } from "../host/admitted-place.js";
1
2
  import { actCosts } from "../host/act-cost.js";
2
3
  import { readingClosures } from "../host/reading-closures.js";
4
+ import { readingExecution, readingProfile } from "../host/reading-context.js";
5
+ import { withNativeReaderRoute } from "../host/native-reader-context.js";
6
+ import { prepareNativeReaderRoute, assertNativeRouteCurrent, materializeNativeRoute } from "../host/native/route.js";
7
+ import { verifiedContent } from "../host/content-store.js";
8
+ import { ownLock, ownRoot, ownStore } from "../observe/runner.js";
3
9
  import { readerLifetime, readerAllowsEmpty, assertReaderInputs, parse as parseLifetime } from '@lapxo/topos/wire';
4
10
  import { readOnly } from "../host/read-only.js";
5
- import { basename, dirname, existsSync, mkdirSync, resolve, rmSync, spawn, statSync, writeFileSync } from "../host/io.js";
11
+ import { basename, dirname, relative, existsSync, mkdirSync, resolve, rmSync, spawn, statSync, writeFileSync } from "../host/io.js";
6
12
  import { bundleOf, processOf } from "../observe/runner.js";
7
13
  import { canonical, parse, PROTOCOL } from '@lapxo/topos/wire';
8
- import { bytesDigest, memoized } from "./digests.js";
14
+ import { bytesDigest, memoized, instrumentCoordinates } from "./digests.js";
9
15
  import { land, landMemo, landShard, memoAt, shardLines, storeAt } from "../land/ledger.js";
10
16
  import { fieldOf, isWire } from "./claims.js";
11
17
  import { rolesOf } from "./roles.js";
@@ -154,8 +160,10 @@ function carried(store) {
154
160
  return carriedIn(store).flatMap((file) => (observeText(file) ?? '').split('\n'))
155
161
  .filter((line) => isWire(line) && speaker !== undefined && fieldOf(line, 'by').split(':')[0] === speaker);
156
162
  }
163
+ const readerSelection = (reader) => reader.capsule?.selected?.selection === undefined ? undefined : 'sha256:' + sha(JSON.stringify([reader.capsule.selected.selection, reader.capsule.region, reader.module, reader.where, [...reader.capsule.globs]]));
164
+ const readerScope = (reader) => reader.capsule?.selected?.selection === undefined ? `reader/${reader.module}` : `reader/${reader.module}/${sha(`${reader.capsule.selected.selection}\n${reader.capsule.region}`)}`;
157
165
  const lineagesOf = (store, speaker, readers, regions) => new Set(readers.flatMap((reader) => shardLines(store, speaker, regionIn(regions, reader.module))
158
- .filter((line) => fieldOf(line, 'scope') === `reader/${reader.module}` && /(?:^|\s)measure=(?:identity|extension)\s/.test(line)).map((line) => fieldOf(line, 'value'))));
166
+ .filter((line) => fieldOf(line, 'scope') === readerScope(reader) && /(?:^|\s)measure=(?:identity|extension)\s/.test(line)).map((line) => fieldOf(line, 'value'))));
159
167
  export function readerLines(store) {
160
168
  const readers = readersOf(store);
161
169
  const regions = [...new Set(readers.flatMap((reader) => reader.where.map(trimmed)))];
@@ -188,8 +196,28 @@ export function observedClaims(root, store, options = {}) {
188
196
  const standing = lockStanding(store);
189
197
  const lines = readersOf(store);
190
198
  const readers = [...lines, ...capsuleReadersOf(root, store, standing, lines)];
199
+ if (options.selected !== undefined) {
200
+ if (!/^sha256:[a-f0-9]{64}$/.test(options.selected) || options.only === undefined)
201
+ throw Error('REFUSE·reader invalid internal background selector');
202
+ if (readers.filter(reader => reader.module === options.only && readerSelection(reader) === options.selected).length !== 1)
203
+ throw Error('REFUSE·reader internal background selector is not uniquely adopted');
204
+ }
191
205
  if (!readers.length)
192
206
  return carried(store);
207
+ const modern = readingProfile(standing, fieldOf);
208
+ const implementation = modern ? instrumentCoordinates(store, processOf('verb'), [], ownRoot(), ownLock(), true) : undefined;
209
+ if (implementation)
210
+ for (const module of [processOf('host'), processOf('lifetime', 'host')])
211
+ for (const [coordinate, digest] of instrumentCoordinates(store, module, [], ownRoot(), ownLock(), true))
212
+ implementation.set(coordinate, digest);
213
+ const code = implementation ? bytesDigest(store, new TextEncoder().encode([...implementation.values()].sort().join('\n'))) : undefined;
214
+ const transitive = implementation ? [...implementation].map(([coordinate, digest]) => [relative(ownRoot(), coordinate), digest]) : [];
215
+ if (modern)
216
+ for (const line of ownLock().filter(line => fieldOf(line, 'scope').startsWith('dep/') && fieldOf(line, 'role') === 'reads' && fieldOf(line, 'value') !== 'withdraw')) {
217
+ const digest = fieldOf(line, 'value');
218
+ verifiedContent(digest, store, ownStore());
219
+ transitive.push([fieldOf(line, 'scope'), digest]);
220
+ }
193
221
  const speaker = keyFor(store, 'read');
194
222
  const regions = [...new Set(readers.flatMap((reader) => reader.where.map(trimmed)))];
195
223
  const whole = options.only === undefined && options.reach === undefined;
@@ -258,6 +286,8 @@ export function observedClaims(root, store, options = {}) {
258
286
  for (const reader of readers) {
259
287
  if (options.only !== undefined && reader.module !== options.only)
260
288
  continue;
289
+ if (options.selected !== undefined && readerSelection(reader) !== options.selected)
290
+ continue;
261
291
  if (!readerReaches(options.reach, reader.where))
262
292
  continue;
263
293
  const started = Date.now();
@@ -274,12 +304,23 @@ export function observedClaims(root, store, options = {}) {
274
304
  if (projection.length !== 1 || !asked)
275
305
  throw Error('REFUSE·input projection is ambiguous ' + reader.module);
276
306
  const files = [...new Set(reader.where.flatMap(where => handedAt(root, reader, roleOf, look, where)))].sort().map(place => ({ place, text: observeText(resolve(root, place)) ?? '' }));
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') });
307
+ const hash = (text) => bytesDigest(store, Buffer.from(text));
308
+ const allowsEmpty = readerAllowsEmpty(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module);
309
+ const native = prepareNativeReaderRoute(store, { id: reader.id, module: reader.module });
310
+ if (native && !modern)
311
+ throw Error('REFUSE·native reader requires complete reading identity ' + reader.module);
312
+ if (native && asked.digest !== native.profile.selection.archive)
313
+ throw Error('REFUSE·native reader implementation differs ' + reader.module);
314
+ const image = native ? materializeNativeRoute(native) : undefined;
315
+ const execution = modern ? readingExecution({ instrument: code, implementation: asked.digest, policy: hash(JSON.stringify([reader.id, reader.module, reader.kind, reader.shape, reader.where, lifetime ?? null, allowsEmpty])), inputs: [...transitive, ...(native?.inputs ?? []), ...(image ? [['native/mount', image.mountIdentity]] : [])] }, hash, { env: process.env, cwd: image?.cwd ?? root, flags: native ? [] : childFlags() }) : undefined;
316
+ const result = withNativeReaderRoute(native, () => readingClosures({ root, store, ...(execution ? { context: execution.context, execution } : {}), reader: reader.module, speaker, digest: text => bytesDigest(store, Buffer.from(text)), capsule: asked, ...(native ? { assertCurrent: () => assertNativeRouteCurrent(native) } : {}), region: reader.capsule.region, projection: fieldOf(projection[0], 'value'), livePlace: () => authenticatedStanding(root), readerReference: { id: reader.id, module: reader.module }, lifetime, allowsEmpty, files, emit: line => process.stderr.write(line + '\n') }));
278
317
  out.push(...result.lines);
279
318
  ran += result.executed;
280
319
  read += result.read - result.opened;
281
320
  return false;
282
321
  }
322
+ if (modern)
323
+ throw Error('REFUSE·reader ' + reader.module + ' complete reading identity requires a declared input projection');
283
324
  const endAct = actCosts.begin();
284
325
  try {
285
326
  const source = reader.capsule === undefined ? observeFile(module) : asked === undefined ? undefined : new Uint8Array();
@@ -287,9 +328,11 @@ export function observedClaims(root, store, options = {}) {
287
328
  const offered = offersOf(root, store, reader, roleOf, look, (where) => reached(trimmed(where)));
288
329
  assertReaderInputs(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module, offered.length);
289
330
  const vectors = reader.capsule === undefined ? observeText(resolve(root, vectorsOf(reader.module, standing))) : undefined;
331
+ const selectedBy = reader.capsule?.selected?.selection === undefined ? undefined : sha(`${reader.capsule.selected.selection}\n${reader.capsule.region}${lifetime === undefined ? "" : "\n" + JSON.stringify(lifetime)}`);
290
332
  const kept = whole && legacy === undefined && answered === undefined && source ? sha([
291
333
  speaker, reader.module, JSON.stringify(lifetime), reader.kind, reader.shape.join('|'), reader.where.join('|'), bundle, vectors === undefined ? 'no vectors' : sha(vectors),
292
334
  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(' ')}`,
335
+ ...(selectedBy === undefined ? [] : [selectedBy]),
293
336
  ...offered.map((offer) => `${offer.place} ${offer.digest?.() ?? bytesDigest(store, offer.bytes())}`),
294
337
  ].join('\n')) : undefined;
295
338
  const stored = kept === undefined ? undefined : observeText(memoAt(store, `observed/${kept}`));
@@ -301,10 +344,12 @@ export function observedClaims(root, store, options = {}) {
301
344
  }
302
345
  const mark = out.length;
303
346
  const home = regionIn(regions, reader.module);
304
- const own = shardLines(store, speaker, home).filter((line) => fieldOf(line, 'scope') === `reader/${reader.module}`);
305
- const lineage = own.filter((line) => fieldOf(line, 'measure') === 'identity').map((line) => fieldOf(line, 'value'));
347
+ const own = shardLines(store, speaker, home).filter((line) => fieldOf(line, 'scope') === readerScope(reader));
348
+ // Short selected-origin identities remain stored history; they cannot name
349
+ // an unambiguous current origin or supply its held observations.
350
+ const lineage = own.filter((line) => fieldOf(line, 'measure') === 'identity').map((line) => fieldOf(line, 'value')).filter(value => selectedBy === undefined || /^[a-f0-9]{64}$/.test(value));
306
351
  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: [] }
352
+ const measured = reader.capsule !== undefined ? { by: source ? selectedBy ?? sha(`${reader.capsule.digest}\n${reader.capsule.region}${lifetime === undefined ? "" : "\n" + JSON.stringify(lifetime)}`).slice(0, 12) : 'none', lines: [] }
308
353
  : 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)) })
309
354
  : { by: 'none', lines: [] };
310
355
  const by = measured.by;
@@ -400,7 +445,7 @@ export function observedClaims(root, store, options = {}) {
400
445
  const running = existsSync(marker) && Date.now() - statSync(marker).mtimeMs < 30 * 60 * 1000 && Number.isInteger(pid) && pid > 0 && alive(pid);
401
446
  if (!running) {
402
447
  mkdirSync(dirname(marker), { recursive: true });
403
- const child = spawn(process.execPath, [...childFlags(), processOf('observe'), root, reader.module], { cwd: root, detached: true, stdio: 'ignore' });
448
+ const child = spawn(process.execPath, [...childFlags(), processOf('observe'), root, reader.module, ...(readerSelection(reader) === undefined ? [] : [readerSelection(reader)])], { cwd: root, detached: true, stdio: 'ignore' });
404
449
  child.unref();
405
450
  writeFileSync(marker, `${reader.module}\n${child.pid ?? ''}\n`);
406
451
  }
@@ -455,7 +500,7 @@ export function observedClaims(root, store, options = {}) {
455
500
  const over = bytesDigest(store, new TextEncoder().encode(due.map((d) => d.digest).sort().join('\n'))).replace(/^sha256:/, '');
456
501
  level.push(canonical({ scope: `cost/reader/${reader.module}`, role: 'writes', form: 'interval', measure: 'ms', value: `${spent}..${spent}`, by: `${speaker}:${by}`, at: `place:${over}` }));
457
502
  if (lineage.at(-1) !== by)
458
- level.push(canonical({ scope: `reader/${reader.module}`, role: 'writes', form: 'alphabet', measure: 'identity', value: by, by: `${speaker}:${by}`, at: `place:${by}` }));
503
+ level.push(canonical({ scope: readerScope(reader), role: 'writes', form: 'alphabet', measure: 'identity', value: by, by: `${speaker}:${by}`, at: `place:${by}` }));
459
504
  rmSync(storeAt(store, 'pending', by), { force: true });
460
505
  }
461
506
  for (const plan of plans.filter((one) => one.said !== undefined)) {
@@ -30,9 +30,10 @@ export type Look = {
30
30
  export declare function lookOf(root: string, roleOf: (coordinate: string) => CoordinateRole): Look;
31
31
  /**
32
32
  * The reader regions of the capsules a place can run, where the place's own lock offers them or, at a world, the wire's offers/world list does for a
33
- * region its own lock does not name: `region/<name>` under a measure the wire's region-measures list names gives the region and its coordinates, and the first capsule, pinned before carried, that writes the region
34
- * reads them, handed the coordinates it declares it reads among those the place offers. A capsule region takes over what a
35
- * place's declared region. A standing reader for that same provider and region keeps ownership, so nothing is read twice.
33
+ * region its own lock does not name: each selected capsule that writes a declared region reads the coordinates
34
+ * it declares among the place's offered coordinates. Selection identifies attribution, never independence.
35
+ * An existing reader suppresses only that same selected provider, region and place; an ambiguous legacy
36
+ * owner refuses instead of hiding another provider or invoking the same provider twice.
36
37
  * A newly declared region does not need a retired reader in private history to be observed.
37
38
  */
38
39
  export declare function capsuleReadersOf(root: string, store: string, standing: readonly string[], lines: readonly ReaderClaim[], resolve?: typeof capsulesFor): readonly ReaderClaim[];
@@ -1,5 +1,5 @@
1
1
  import { resolve } from "../host/io.js";
2
- import { canonical, fields, matches } from '@lapxo/topos/wire';
2
+ import { canonical, fields, matches, byBytes } from '@lapxo/topos/wire';
3
3
  import { namesAt, wordOn, wordsOf, wireLine } from "./wire.js";
4
4
  import { fits } from "../observe/match.js";
5
5
  import { bytesDigest, coordinateDigest } from "./digests.js";
@@ -36,13 +36,14 @@ const reachOfLine = (module, where) => {
36
36
  };
37
37
  /**
38
38
  * The reader regions of the capsules a place can run, where the place's own lock offers them or, at a world, the wire's offers/world list does for a
39
- * region its own lock does not name: `region/<name>` under a measure the wire's region-measures list names gives the region and its coordinates, and the first capsule, pinned before carried, that writes the region
40
- * reads them, handed the coordinates it declares it reads among those the place offers. A capsule region takes over what a
41
- * place's declared region. A standing reader for that same provider and region keeps ownership, so nothing is read twice.
39
+ * region its own lock does not name: each selected capsule that writes a declared region reads the coordinates
40
+ * it declares among the place's offered coordinates. Selection identifies attribution, never independence.
41
+ * An existing reader suppresses only that same selected provider, region and place; an ambiguous legacy
42
+ * owner refuses instead of hiding another provider or invoking the same provider twice.
42
43
  * A newly declared region does not need a retired reader in private history to be observed.
43
44
  */
44
45
  export function capsuleReadersOf(root, store, standing, lines, resolve = capsulesFor) {
45
- const reaching = new Set(lines.flatMap((reader) => reachOfLine(reader.module, reader.where)));
46
+ const origin = (capsule) => capsule?.selected?.selection;
46
47
  const coordinateMeasure = wordOn(standing, 'region-coordinate', ownLock());
47
48
  const localMeasures = wordsOf(standing, 'region-measures');
48
49
  const pins = standing.filter(line => fieldOf(line, 'scope').startsWith('uses/') && fieldOf(line, 'value') !== 'withdraw');
@@ -59,16 +60,20 @@ export function capsuleReadersOf(root, store, standing, lines, resolve = capsule
59
60
  const capsules = resolve(standing, place, root);
60
61
  for (const line of offered) {
61
62
  const region = fieldOf(line, 'scope').slice('region/'.length);
62
- const by = capsules.find(({ capsule }) => capsule.lines.some((one) => fieldOf(one, 'scope') === `region/${region}` && fieldOf(one, 'measure') === 'writes'));
63
- const pkg = fieldOf(by?.offer ?? '', 'scope').split('/')[1] ?? '';
64
- if (by === undefined || reaching.has(`${pkg} ${region} ${where || '/'}`))
65
- continue;
66
- const key = `${by.capsule.selection ?? by.capsule.digest} ${region}`;
67
- const group = groups.get(key) ?? groups.set(key, { module: `${pkg}/${region}`, digest: by.capsule.digest, entry: fieldOf(by.offer, 'shape'), selected: by.capsule, region, reads: by.capsule.declaration.regions[region] ?? [], globs: new Map() }).get(key);
68
- group.globs.set(where, fieldOf(line, 'value').split('|').filter(Boolean));
63
+ for (const by of capsules.filter(({ capsule }) => capsule.lines.some((one) => fieldOf(one, 'scope') === `region/${region}` && fieldOf(one, 'measure') === 'writes'))) {
64
+ const pkg = fieldOf(by.offer, 'scope').split('/')[1] ?? '';
65
+ const reaches = lines.filter(reader => reachOfLine(reader.module, reader.where).includes(`${pkg} ${region} ${where || '/'}`));
66
+ if (reaches.some(reader => origin(reader.capsule) === undefined))
67
+ throw Error(`REFUSE·reader ${pkg}/${region} existing provider origin unmeasured at ${where || '/'}`);
68
+ if (by.capsule.selection !== undefined && lines.some(reader => origin(reader.capsule) === by.capsule.selection && reader.capsule?.region === region && reader.where.some(place => place.replace(/\/?$/, '/') === (where || '/'))))
69
+ continue;
70
+ const key = `${by.capsule.selection ?? by.capsule.digest} ${region}`;
71
+ const group = groups.get(key) ?? groups.set(key, { module: `${pkg}/${region}`, digest: by.capsule.digest, entry: fieldOf(by.offer, 'shape'), selected: by.capsule, region, reads: by.capsule.declaration.regions[region] ?? [], globs: new Map() }).get(key);
72
+ group.globs.set(where, fieldOf(line, 'value').split('|').filter(Boolean));
73
+ }
69
74
  }
70
75
  }
71
- return [...groups.values()].map((group) => ({ id: group.module, shape: ['run'], where: [...group.globs.keys()], module: group.module, kind: 'process',
76
+ return [...groups.entries()].sort(([a], [b]) => byBytes(a, b)).map(([, group]) => ({ id: group.module, shape: ['run'], where: [...group.globs.keys()], module: group.module, kind: 'process',
72
77
  capsule: { selected: group.selected, digest: group.digest, entry: group.entry, region: group.region, reads: group.reads, globs: group.globs } }));
73
78
  }
74
79
  export function handedAt(root, reader, roleOf, look, place) {
@@ -41,7 +41,17 @@ export function receiptsOf(fold) {
41
41
  const ran = [...new Set(fold.observed.filter((line) => fieldOf(line, 'measure') !== 'observed'
42
42
  && mine.has(`${fieldOf(line, 'by')} ${fieldOf(line, 'at')}`)))];
43
43
  const ranAt = new Set(ran.map((line) => `${fieldOf(line, 'by')} ${fieldOf(line, 'at')}`));
44
- const cones = new Set(fold.observed.filter((line) => fieldOf(line, 'measure') === 'observed' && own(fieldOf(line, 'scope')) && ranAt.has(`${fieldOf(line, 'by')} ${fieldOf(line, 'at')}`)).map(bytesOnly));
44
+ const cones = new Set(fold.observed.filter((line) => {
45
+ if (fieldOf(line, 'measure') !== 'observed')
46
+ return false;
47
+ const at = fieldOf(line, 'at'), by = fieldOf(line, 'by');
48
+ // A physical input witnesses an owned reading's base, without becoming
49
+ // another location of its result. Even a foreign input belongs in that
50
+ // reading's portable carrier; its own child place is not the reader.
51
+ if (at.startsWith('witness:'))
52
+ return ranAt.has(`${by} place:${at.slice('witness:'.length)}`);
53
+ return own(fieldOf(line, 'scope')) && ranAt.has(`${by} ${at}`);
54
+ }).map(bytesOnly));
45
55
  const paid = ledgerLines(fold.store, 'judge').filter((line) => fold.paid.some((demand) => fieldOf(demand, 'scope') === fieldOf(line, 'scope')
46
56
  && !(closed.length > 0 && fieldOf(demand, 'needs').split('|').filter(Boolean).length > 0
47
57
  && fieldOf(demand, 'needs').split('|').filter(Boolean).every((need) => closed.some((one) => need.startsWith(one))))));
@@ -1,7 +1,7 @@
1
1
  import { existsSync, join } from "../host/io.js";
2
- import { authorityOf, isKeyClaim, parse, signersOf } from '@lapxo/topos/wire';
2
+ import { authorityOf, isKeyClaim, signersOf } from '@lapxo/topos/wire';
3
3
  import { verifiesFields } from "../land/sign.js";
4
- import { fieldOf, selfName } from "./claims.js";
4
+ import { fieldOf, selfName, parsedLine as parse } from "./claims.js";
5
5
  import { keyFor, lockStanding } from "./keys.js";
6
6
  import { storeRoot } from "../host/ports/store.js";
7
7
  import { storeOf } from "../land/ledger.js";
@@ -1 +1,3 @@
1
1
  export declare function moduleImports(source: string, coordinate: string): readonly string[];
2
+ /** A JavaScript literal value; syntax inventory never executes it. */
3
+ export declare const stringLiteral: (node: unknown) => string | undefined;
@@ -15,12 +15,7 @@ export function moduleImports(source, coordinate) {
15
15
  throw Error('REFUSE·source module syntax cannot be read: ' + coordinate);
16
16
  }
17
17
  const found = new Set();
18
- const literal = (node) => {
19
- if (!node || typeof node !== 'object')
20
- return;
21
- const n = node;
22
- return n['type'] === 'Literal' && typeof n['value'] === 'string' ? n['value'] : undefined;
23
- };
18
+ const literal = stringLiteral;
24
19
  const visit = (node) => {
25
20
  if (!node || typeof node !== 'object')
26
21
  return;
@@ -46,3 +41,10 @@ export function moduleImports(source, coordinate) {
46
41
  visit(tree);
47
42
  return [...found];
48
43
  }
44
+ /** A JavaScript literal value; syntax inventory never executes it. */
45
+ export const stringLiteral = (node) => {
46
+ if (!node || typeof node !== 'object')
47
+ return;
48
+ const n = node;
49
+ return n['type'] === 'Literal' && typeof n['value'] === 'string' ? n['value'] : undefined;
50
+ };
@@ -0,0 +1,5 @@
1
+ /** Existing admission-snapshot authentication and folded live projection.
2
+ * Consumer configuration evidence only; never a new authority lock. */
3
+ export declare function authenticatedRootStanding(root: string): readonly string[];
4
+ /** Legacy Root lines remain unchanged; contextual public output has its own carrier. */
5
+ export declare const authenticatedStanding: typeof authenticatedRootStanding;
@@ -0,0 +1,54 @@
1
+ import { hashText } from "./hash.js";
2
+ import { admissionAnchor, admissionHistoryAt } from "../fold/admission-history.js";
3
+ import { authorityFor } from "../fold/signers.js";
4
+ import { eraOf } from "../fold/digests.js";
5
+ import { fieldOf, foldClaims, isConfig } from "../fold/claims.js";
6
+ import { foldedEvidence } from '@lapxo/topos/contract';
7
+ import { createHash } from 'node:crypto';
8
+ // One bounded historical projection, never a current authority/lease verdict.
9
+ // Its complete inputs are reread before lookup, including original signatures.
10
+ let historicalProjection;
11
+ function historicalIdentity(history, anchor) {
12
+ const hash = createHash('sha256'), text = hashText.bind(undefined, hash);
13
+ text('authenticated-root-history/1');
14
+ text(String(history.length));
15
+ for (const line of history)
16
+ text(line);
17
+ text(JSON.stringify(anchor));
18
+ return hash.digest('hex');
19
+ }
20
+ /** Existing admission-snapshot authentication and folded live projection.
21
+ * Consumer configuration evidence only; never a new authority lock. */
22
+ export function authenticatedRootStanding(root) {
23
+ const history = admissionHistoryAt(root, Number.MAX_SAFE_INTEGER), anchor = admissionAnchor(root);
24
+ const identity = historicalIdentity(history, anchor);
25
+ if (historicalProjection?.identity === identity)
26
+ return [...historicalProjection.standing];
27
+ const snapshots = new Map();
28
+ const admitted = history.filter(line => {
29
+ const epoch = Number(fieldOf(line, 'epoch')) || 0;
30
+ let authority = snapshots.get(epoch);
31
+ if (!authority) {
32
+ // This invocation has freshly read the complete signed inventory. Derive
33
+ // the same historical cutoff without reopening it for every epoch; the
34
+ // next invocation still rereads every authority input.
35
+ const snapshot = history.filter(record => !isConfig(record) || Number(fieldOf(record, 'epoch')) <= epoch);
36
+ // The signature vocabulary is configuration, not the as-yet
37
+ // unauthenticated evidence whose values are about to be folded.
38
+ const wire = snapshot.filter(record => fieldOf(record, 'scope').startsWith('wire/'));
39
+ authority = authorityFor(snapshot, anchor, eraOf(foldClaims(wire).standing).admitted);
40
+ snapshots.set(epoch, authority);
41
+ }
42
+ return authority.of(line).kind === 'admitted';
43
+ });
44
+ // The host authenticates against historical authority; Topos projects the
45
+ // authenticated history using the instrument's existing fold.
46
+ const { standing } = foldedEvidence(admitted, {
47
+ classification: () => 'local',
48
+ fold: lines => foldClaims(lines).standing,
49
+ });
50
+ historicalProjection = { identity, standing: Object.freeze([...standing]) };
51
+ return [...standing];
52
+ }
53
+ /** Legacy Root lines remain unchanged; contextual public output has its own carrier. */
54
+ export const authenticatedStanding = authenticatedRootStanding;
@@ -18,4 +18,4 @@ export declare class CapsuleProcessError extends Error {
18
18
  }
19
19
  export declare function validResponse(value: unknown, request: Request): value is Response;
20
20
  /** Last-line JSON is the wire framing. No partial batch, failed exit or synthetic missing response is accepted. */
21
- export declare function executeCapsule(command: string, args: readonly string[], requests: readonly Request[], limits?: Pick<SpawnSyncOptionsWithStringEncoding, 'timeout' | 'maxBuffer'>, lifetime?: ReaderLifetime): readonly Response[];
21
+ export declare function executeCapsule(command: string, args: readonly string[], requests: readonly Request[], limits?: Pick<SpawnSyncOptionsWithStringEncoding, 'timeout' | 'maxBuffer' | 'env' | 'cwd'>, lifetime?: ReaderLifetime): readonly Response[];