@lapxo/bound 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/README.md +56 -20
  2. package/TARGET.bound +70 -27
  3. package/assets/wasm/guest.js +4 -0
  4. package/assets/wasm/manifest.json +27 -0
  5. package/assets/wasm/runtime.js +6 -0
  6. package/assets/wasm/runtime.wasm +0 -0
  7. package/dist/cli/accept.js +83 -59
  8. package/dist/cli/act-result.d.ts +9 -0
  9. package/dist/cli/act-result.js +68 -13
  10. package/dist/cli/args.d.ts +1 -0
  11. package/dist/cli/args.js +5 -2
  12. package/dist/cli/custodian.d.ts +1 -0
  13. package/dist/cli/custodian.js +29 -0
  14. package/dist/cli/native-descendant.d.ts +1 -0
  15. package/dist/cli/native-descendant.js +19 -0
  16. package/dist/cli/native.d.ts +1 -0
  17. package/dist/cli/native.js +60 -0
  18. package/dist/cli/observe.js +9 -2
  19. package/dist/cli/pass.js +9 -4
  20. package/dist/cli/place.d.ts +2 -0
  21. package/dist/cli/place.js +7 -4
  22. package/dist/cli/standing-result.js +7 -26
  23. package/dist/cli/verb.js +65 -9
  24. package/dist/engine/index.d.ts +11 -0
  25. package/dist/engine/index.js +9 -0
  26. package/dist/engine/object.d.ts +17 -0
  27. package/dist/engine/object.js +38 -0
  28. package/dist/fold/admission-history.js +2 -2
  29. package/dist/fold/claims.d.ts +4 -0
  30. package/dist/fold/claims.js +23 -3
  31. package/dist/fold/closed.d.ts +1 -1
  32. package/dist/fold/closed.js +36 -7
  33. package/dist/fold/digests.d.ts +1 -1
  34. package/dist/fold/digests.js +6 -4
  35. package/dist/fold/evidence.d.ts +1 -1
  36. package/dist/fold/kept.js +2 -1
  37. package/dist/fold/object.d.ts +3 -17
  38. package/dist/fold/object.js +2 -38
  39. package/dist/fold/observed.d.ts +1 -0
  40. package/dist/fold/observed.js +54 -9
  41. package/dist/fold/readers.d.ts +4 -3
  42. package/dist/fold/readers.js +18 -13
  43. package/dist/fold/resolved.js +12 -2
  44. package/dist/fold/signers.js +2 -2
  45. package/dist/host/adapters/modules/javascript.d.ts +2 -0
  46. package/dist/host/adapters/modules/javascript.js +8 -6
  47. package/dist/host/admitted-place.d.ts +5 -0
  48. package/dist/host/admitted-place.js +57 -0
  49. package/dist/host/capsule-process.d.ts +1 -1
  50. package/dist/host/capsule-process.js +4 -1
  51. package/dist/host/capsule.d.ts +4 -0
  52. package/dist/host/capsule.js +16 -2
  53. package/dist/host/cell-receipts.js +1 -1
  54. package/dist/host/contextual-place.d.ts +59 -0
  55. package/dist/host/contextual-place.js +77 -0
  56. package/dist/host/hash.d.ts +7 -0
  57. package/dist/host/hash.js +6 -0
  58. package/dist/host/io.d.ts +2 -0
  59. package/dist/host/io.js +2 -0
  60. package/dist/host/lifetime.js +61 -22
  61. package/dist/host/native/bounded-file.d.ts +8 -0
  62. package/dist/host/native/bounded-file.js +32 -0
  63. package/dist/host/native/command.d.ts +36 -0
  64. package/dist/host/native/command.js +91 -0
  65. package/dist/host/native/control-bridge-worker.d.ts +1 -0
  66. package/dist/host/native/control-bridge-worker.js +88 -0
  67. package/dist/host/native/control-bridge.d.ts +30 -0
  68. package/dist/host/native/control-bridge.js +110 -0
  69. package/dist/host/native/control-selection.d.ts +29 -0
  70. package/dist/host/native/control-selection.js +32 -0
  71. package/dist/host/native/custody.d.ts +5 -0
  72. package/dist/host/native/custody.js +91 -0
  73. package/dist/host/native/deadline.d.ts +12 -0
  74. package/dist/host/native/deadline.js +28 -0
  75. package/dist/host/native/descendants.d.ts +1 -0
  76. package/dist/host/native/descendants.js +528 -0
  77. package/dist/host/native/generated-custodian.d.ts +122 -0
  78. package/dist/host/native/generated-custodian.js +267 -0
  79. package/dist/host/native/generated.d.ts +217 -0
  80. package/dist/host/native/generated.js +379 -0
  81. package/dist/host/native/image.d.ts +36 -0
  82. package/dist/host/native/image.js +45 -0
  83. package/dist/host/native/jobs.d.ts +75 -0
  84. package/dist/host/native/jobs.js +148 -0
  85. package/dist/host/native/loader.d.ts +80 -0
  86. package/dist/host/native/loader.js +269 -0
  87. package/dist/host/native/materialize.d.ts +22 -0
  88. package/dist/host/native/materialize.js +175 -0
  89. package/dist/host/native/module-closure.d.ts +27 -0
  90. package/dist/host/native/module-closure.js +99 -0
  91. package/dist/host/native/module-syntax.d.ts +8 -0
  92. package/dist/host/native/module-syntax.js +63 -0
  93. package/dist/host/native/native-binary.d.ts +54 -0
  94. package/dist/host/native/native-binary.js +115 -0
  95. package/dist/host/native/native-program.d.ts +56 -0
  96. package/dist/host/native/native-program.js +72 -0
  97. package/dist/host/native/node-jobs.d.ts +84 -0
  98. package/dist/host/native/node-jobs.js +187 -0
  99. package/dist/host/native/node-launch.d.ts +72 -0
  100. package/dist/host/native/node-launch.js +159 -0
  101. package/dist/host/native/preload.d.ts +1 -0
  102. package/dist/host/native/preload.js +45 -0
  103. package/dist/host/native/reading.d.ts +91 -0
  104. package/dist/host/native/reading.js +249 -0
  105. package/dist/host/native/receipt-environment.d.ts +3 -0
  106. package/dist/host/native/receipt-environment.js +41 -0
  107. package/dist/host/native/references.d.ts +5 -0
  108. package/dist/host/native/references.js +30 -0
  109. package/dist/host/native/root.d.ts +17 -0
  110. package/dist/host/native/root.js +37 -0
  111. package/dist/host/native/route.d.ts +66 -0
  112. package/dist/host/native/route.js +216 -0
  113. package/dist/host/native/runtime.d.ts +4 -0
  114. package/dist/host/native/runtime.js +9 -0
  115. package/dist/host/native-reader-context.d.ts +3 -0
  116. package/dist/host/native-reader-context.js +8 -0
  117. package/dist/host/objects.d.ts +2 -1
  118. package/dist/host/objects.js +24 -4
  119. package/dist/host/place-clock-selection.d.ts +55 -0
  120. package/dist/host/place-clock-selection.js +124 -0
  121. package/dist/host/ports/local-act.d.ts +10 -0
  122. package/dist/host/ports/local-act.js +78 -6
  123. package/dist/host/ports/place-act.d.ts +46 -0
  124. package/dist/host/ports/place-act.js +169 -0
  125. package/dist/host/process-lifetime.js +8 -3
  126. package/dist/host/provide.d.ts +25 -0
  127. package/dist/host/provide.js +115 -13
  128. package/dist/host/reading-closures.d.ts +17 -1
  129. package/dist/host/reading-closures.js +98 -18
  130. package/dist/host/reading-context.d.ts +29 -0
  131. package/dist/host/reading-context.js +53 -0
  132. package/dist/host/reference-authenticated.d.ts +28 -0
  133. package/dist/host/reference-authenticated.js +84 -0
  134. package/dist/host/reference-native.d.ts +10 -0
  135. package/dist/host/reference-native.js +50 -0
  136. package/dist/host/reference-profile.d.ts +23 -0
  137. package/dist/host/reference-profile.js +62 -0
  138. package/dist/host/reference.d.ts +6 -0
  139. package/dist/host/reference.js +24 -0
  140. package/dist/host/root-clock-source.d.ts +85 -0
  141. package/dist/host/root-clock-source.js +305 -0
  142. package/dist/host/selected-capsules.js +3 -2
  143. package/dist/host/selected-topos.d.ts +1 -1
  144. package/dist/host/selected-topos.js +9 -1
  145. package/dist/land/act.d.ts +26 -0
  146. package/dist/land/act.js +96 -3
  147. package/dist/land/ledger.d.ts +4 -0
  148. package/dist/land/ledger.js +20 -2
  149. package/dist/observe/regions.d.ts +4 -0
  150. package/dist/observe/regions.js +109 -1
  151. package/dist/observe/runner.d.ts +4 -0
  152. package/dist/observe/runner.js +9 -2
  153. package/dist/wasm/adapter.d.ts +51 -0
  154. package/dist/wasm/adapter.js +180 -0
  155. package/dist/wasm/node-worker.d.ts +1 -0
  156. package/dist/wasm/node-worker.js +13 -0
  157. package/dist/wasm/object.d.ts +8 -0
  158. package/dist/wasm/object.js +76 -0
  159. package/dist/wasm/worker.d.ts +1 -0
  160. package/dist/wasm/worker.js +7 -0
  161. package/docs/contracts/authenticated-reference.publication.json +25 -0
  162. package/docs/contracts/cli.md +13 -0
  163. package/docs/contracts/engine-object-projection.publication.json +25 -0
  164. package/docs/contracts/engine.md +39 -0
  165. package/docs/contracts/native-publication.inventory.json +8 -0
  166. package/docs/contracts/optional-wasm-adapter.publication.json +25 -0
  167. package/docs/contracts/publication-additions.inventory.json +8 -0
  168. package/docs/contracts/reference-port.md +33 -0
  169. package/docs/contracts/reference-port.publication.json +25 -0
  170. package/licenses/wasm/bound.txt +21 -0
  171. package/licenses/wasm/fast-text-encoding.txt +201 -0
  172. package/licenses/wasm/jitl-quickjs-ffi-types.txt +21 -0
  173. package/licenses/wasm/jitl-quickjs-wasmfile-release-sync.txt +47 -0
  174. package/licenses/wasm/obligations.txt +21 -0
  175. package/licenses/wasm/quickjs-emscripten-core.txt +21 -0
  176. package/licenses/wasm/topos.txt +21 -0
  177. package/package.json +94 -4
  178. package/receipts.bound +7672 -573
  179. package/samples/authenticated-reference/no.json +31 -0
  180. package/samples/authenticated-reference/yes/editorial-state/TARGET.bound +57 -0
  181. package/samples/authenticated-reference/yes/editorial-state/inputs/arrival.json +1 -0
  182. package/samples/authenticated-reference/yes/editorial-state/inputs/coverage.json +1 -0
  183. package/samples/authenticated-reference/yes/editorial-state/inputs/era.json +1 -0
  184. package/samples/authenticated-reference/yes/editorial-state/inputs/order.json +1 -0
  185. package/samples/authenticated-reference/yes/editorial-state/inputs/signature.json +1 -0
  186. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.arrival.json +1 -0
  187. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.coverage.json +1 -0
  188. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.era.json +1 -0
  189. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.order.json +1 -0
  190. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.signature.json +1 -0
  191. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.yes.json +1 -0
  192. package/samples/authenticated-reference/yes/editorial-state/inputs/yes.json +1 -0
  193. package/samples/authenticated-reference/yes/weather-reading/TARGET.bound +57 -0
  194. package/samples/authenticated-reference/yes/weather-reading/inputs/arrival.json +1 -0
  195. package/samples/authenticated-reference/yes/weather-reading/inputs/coverage.json +1 -0
  196. package/samples/authenticated-reference/yes/weather-reading/inputs/era.json +1 -0
  197. package/samples/authenticated-reference/yes/weather-reading/inputs/order.json +1 -0
  198. package/samples/authenticated-reference/yes/weather-reading/inputs/signature.json +1 -0
  199. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.arrival.json +1 -0
  200. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.coverage.json +1 -0
  201. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.era.json +1 -0
  202. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.order.json +1 -0
  203. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.signature.json +1 -0
  204. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.yes.json +1 -0
  205. package/samples/authenticated-reference/yes/weather-reading/inputs/yes.json +1 -0
  206. package/samples/authenticated-reference/yes.json +19 -0
  207. package/samples/engine-alphabet/TARGET.bound +29 -0
  208. package/samples/engine-alphabet/inputs/sample.json +323 -0
  209. package/samples/engine-alphabet/package.json +1 -0
  210. package/samples/engine-alphabet/reader.js +1457 -0
  211. package/samples/engine-alphabet/receipts.bound +12 -0
  212. package/samples/engine-interval/TARGET.bound +29 -0
  213. package/samples/engine-interval/inputs/sample.json +277 -0
  214. package/samples/engine-interval/package.json +1 -0
  215. package/samples/engine-interval/reader.js +1457 -0
  216. package/samples/engine-interval/receipts.bound +12 -0
  217. package/samples/no/engine-order/TARGET.bound +29 -0
  218. package/samples/no/engine-order/inputs/sample.json +277 -0
  219. package/samples/no/engine-order/package.json +1 -0
  220. package/samples/no/engine-order/reader.js +1457 -0
  221. package/samples/reference-port/no/catalog/standing.json +1 -0
  222. package/samples/reference-port/no/catalog/transport.json +1 -0
  223. package/samples/reference-port/no/route-notes/standing.json +1 -0
  224. package/samples/reference-port/no/route-notes/transport.json +1 -0
  225. package/samples/reference-port/yes/catalog/TARGET.bound +33 -0
  226. package/samples/reference-port/yes/catalog/standing.json +1 -0
  227. package/samples/reference-port/yes/catalog/transport.json +1 -0
  228. package/samples/reference-port/yes/route-notes/TARGET.bound +33 -0
  229. package/samples/reference-port/yes/route-notes/standing.json +1 -0
  230. package/samples/reference-port/yes/route-notes/transport.json +1 -0
  231. package/samples/wasm/editorial-notes/TARGET.bound +65 -0
  232. package/samples/wasm/editorial-notes/esm.bound +59 -0
  233. package/samples/wasm/editorial-notes/no-deadline.bound +65 -0
  234. package/samples/wasm/editorial-notes/no-memory.bound +65 -0
  235. package/samples/wasm/editorial-notes/no-missing-limit.bound +64 -0
  236. package/samples/wasm/editorial-notes/receipts.bound +1 -0
  237. package/samples/wasm/water-level/TARGET.bound +65 -0
  238. package/samples/wasm/water-level/esm.bound +59 -0
  239. package/samples/wasm/water-level/no-deadline.bound +65 -0
  240. package/samples/wasm/water-level/no-memory.bound +65 -0
  241. package/samples/wasm/water-level/no-missing-limit.bound +64 -0
  242. package/samples/wasm/water-level/receipts.bound +1 -0
  243. package/vectors/engine-object.cli.json +548 -0
  244. package/vectors/native/authenticated-reference-formation.vector.json +53 -0
  245. package/vectors/native/authenticated-reference.cli.json +157 -0
  246. package/vectors/native/authenticated-reference.vector.json +440 -0
  247. package/vectors/reference-port.cli.json +688 -0
  248. package/vectors/wasm-object.cli.json +452 -0
@@ -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";
@@ -7,9 +10,10 @@ import { bytesDigest as hashBytes, fullDigest as hashFull } from "../host/digest
7
10
  import { mountedCoordinatesIn, worldAt } from "./places.js";
8
11
  import { ownLockOf } from "./signed.js";
9
12
  import { viewsOf } from "./views.js";
13
+ import { keptPlace } from "./kept.js";
10
14
  import { storeAt, storeOf } from "../land/ledger.js";
11
15
  import { coordinatesUnder, observeFile, observeText } from "../observe/files.js";
12
- import { carriedFrom } from "./resolved.js";
16
+ import { bytesOnly, carriedFrom } from "./resolved.js";
13
17
  import { takenFor } from "../fold/place-inputs.js";
14
18
  /** A region is closed when the digest of its bytes is the one its receipt names. Folding a closed region is idle: idleCount is how many were already closed, actSeconds the largest measured act or unpartitioned host work, receiptsSeen how many receipts were read and how long that took. */
15
19
  let idle = 0;
@@ -60,13 +64,21 @@ function filesOf(root, place, region, inputs) {
60
64
  return [...new Set([...owned, ...observed, ...own])].filter(rel => rel !== receipt && !rel.split('/').some(part => part === '.bound' || part === '.cache' || part === '.git'));
61
65
  }
62
66
  const rooted = new Map();
67
+ let lastConfiguration;
63
68
  function publicConfiguration(lines) {
69
+ const input = lines.join('\n');
70
+ // Retain only this pure projection. Callers still read and hash the actual
71
+ // files; neither a timestamp nor a previous seal proves their current bytes.
72
+ if (lastConfiguration?.input === input)
73
+ return lastConfiguration.output;
64
74
  if (!lines.length || !lines.every(isConfig))
65
75
  return lines;
66
76
  const folded = foldClaims(lines);
67
77
  if (folded.forks.length)
68
78
  throw Error('REFUSE·receipt disputed configuration standing');
69
- return publicationOf(folded.standing.filter(line => fieldOf(line, 'value') !== 'withdraw'));
79
+ const output = publicationOf(folded.standing.filter(line => fieldOf(line, 'value') !== 'withdraw'));
80
+ lastConfiguration = { input, output };
81
+ return output;
70
82
  }
71
83
  function rootLines(root, place) {
72
84
  const name = place.replace(/\/$/, '');
@@ -167,7 +179,7 @@ export async function closeReceipts(root, store, semantic) {
167
179
  return open;
168
180
  }
169
181
  /** One comparison. A place meets its receipt; the root meets when every receipt it names meets. */
170
- export function meets(root, store, place) {
182
+ export function meets(root, store, place, current = true, active) {
171
183
  const name = place.replace(/\/$/, '');
172
184
  if (name === '') {
173
185
  const at = Date.now();
@@ -178,14 +190,31 @@ export function meets(root, store, place) {
178
190
  let closed = (own.length > 0 ? ownClosed : named.size > 0) && [...named].every(([one, expected]) => {
179
191
  const bytes = observeFile(join(root, one, RECEIPTS));
180
192
  const algorithm = expected.split(':')[0];
181
- return bytes !== undefined && hashBytes(bytes, algorithm) === expected && meets(root, store, `${one}/`);
193
+ return bytes !== undefined && hashBytes(bytes, algorithm) === expected && meets(root, store, `${one}/`, current);
182
194
  });
183
195
  if (closed && carried) {
184
- const algorithm = algorithmIn(own.join('\n'));
185
- const readings = verifiedReadingReceipts(store, carried, text => hashFull(text, algorithm));
196
+ const algorithm = algorithmIn(own.join('\n')), hash = (text) => hashFull(text, algorithm);
197
+ const profile = readingProfile((observeText(join(root, LOCK)) ?? '').split('\n').filter(isWire), fieldOf);
198
+ // History keeps its canonical identity and bytes. It does not become a
199
+ // current execution merely because it shares a public carrier with one.
200
+ const readings = verifiedReadingReceipts(store, carried, hash, undefined, profile);
186
201
  if (readings === undefined)
187
202
  closed = false;
188
- else if (readings)
203
+ else if (current && profile && readings) {
204
+ const stamp = instrumentKept(store);
205
+ const snapshot = active ?? (stamp ? keptPlace(store, undefined, stamp)?.observed : undefined);
206
+ if (snapshot === undefined)
207
+ closed = false;
208
+ else {
209
+ const units = snapshot.filter(line => fieldOf(line, 'scope').startsWith('receipts/readings/') && fieldOf(line, 'role') === 'writes');
210
+ const supplied = new Set(carried);
211
+ const present = units.length > 0 && units.every(line => supplied.has(line) || (fieldOf(line, 'measure') === 'observed' && supplied.has(bytesOnly(line))));
212
+ const fresh = present ? verifiedReadingReceipts(store, snapshot, hash, currentReadingEnvironment(store, root, hash, childFlags()), profile) : undefined;
213
+ if (!fresh)
214
+ closed = false;
215
+ }
216
+ }
217
+ if (closed && readings)
189
218
  process.stderr.write(`RECEIPTS ${readings} read · 0 opened · reading closures\n`);
190
219
  }
191
220
  seen = { count: named.size + (own.length > 0 ? 1 : 0), ms: Date.now() - at };
@@ -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>;
@@ -160,19 +160,21 @@ const importsOf = (store, at, digest) => {
160
160
  * A fold is a function of its points and the instrument is one of them, so a fold kept by another bound is another
161
161
  * origin's fold — readable as history, never as this instrument's own state.
162
162
  */
163
- export function instrumentOf(store, entry, standing = [], root = dirname(resolve(entry)), releases = []) {
164
- return instrumentDigest(store, entry, standing, root, releases).replace(/^[^:]*:/, '').slice(0, 12);
163
+ export function instrumentOf(store, entry, standing = [], root, releases = []) {
164
+ const coordinate = root === undefined ? resolve(entry) : resolve(root, entry);
165
+ return instrumentDigest(store, coordinate, standing, root ?? dirname(coordinate), releases).replace(/^[^:]*:/, '').slice(0, 12);
165
166
  }
166
167
  export function instrumentDigest(store, entry, standing = [], root = dirname(resolve(entry)), releases = []) {
167
168
  const seen = instrumentCoordinates(store, entry, standing, root, releases);
168
169
  return bytesDigest(store, new TextEncoder().encode([...seen.values()].sort().join('\n')));
169
170
  }
170
- export function instrumentCoordinates(store, entry, standing = [], root = dirname(resolve(entry)), releases = []) {
171
+ export function instrumentCoordinates(store, entry, standing = [], root = dirname(resolve(entry)), releases = [], fresh = false) {
171
172
  const seen = new Map();
172
173
  const walk = (at) => {
173
174
  if (seen.has(at))
174
175
  return;
175
- const digest = coordinateDigest(store, at);
176
+ const bytes = fresh ? observeFile(at) : undefined;
177
+ const digest = fresh ? (bytes === undefined ? undefined : bytesDigest(store, bytes)) : coordinateDigest(store, at);
176
178
  if (digest === undefined)
177
179
  return;
178
180
  // 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');
@@ -1,17 +1,3 @@
1
- import type { Mark, Cell } from '@lapxo/obligations/views/field';
2
- import type { ObjectRecord, ObjectContext } from '@lapxo/topos/wire';
3
- /** Projection dispatches admitted acts into Obligatory; it defines no meet or propagation. */
4
- export interface ObjectReading {
5
- cell: string;
6
- state: string;
7
- parts: Cell<unknown>;
8
- rest: readonly string[];
9
- ownMarks: readonly (string | undefined)[];
10
- ownClaims: readonly {
11
- id: string | undefined;
12
- origin: string;
13
- }[];
14
- marks: readonly Mark<unknown>[];
15
- claims: readonly unknown[];
16
- }
17
- export declare function objectFold(records: readonly ObjectRecord[], context: ObjectContext): readonly ObjectReading[];
1
+ /** Compatibility path; the engine owns the single object projection. */
2
+ export { objectFold } from '../engine/index.ts';
3
+ export type { ObjectReading } from '../engine/index.ts';
@@ -1,38 +1,2 @@
1
- import { cell, sign, join, require as requireFloor, observe, mark, parts, state, live, restOf } from '@lapxo/obligations/views/field';
2
- import { restCoordinates } from '@lapxo/topos/wire';
3
- export function objectFold(records, context) {
4
- let world = new Map();
5
- const definitions = new Map(records.filter(r => r.record === 'cell').map(r => [r.fields.scope, r]));
6
- const codecs = new Map([...definitions].map(([name, c]) => [name, context.resolve(c)]));
7
- const ids = new Map(records.map(r => [r.fields.id, r]));
8
- for (const record of [...records].sort((a, b) => Number(a.fields.epoch) - Number(b.fields.epoch))) {
9
- const f = record.fields, name = f.scope, at = Number(f.epoch);
10
- if (record.record === 'cell') {
11
- world = new Map([...world, [name, cell(name, at, [], restCoordinates(f.restsOn))]]);
12
- continue;
13
- }
14
- const codec = codecs.get(name), L = codec.form.lattice(codec.params), held = world.get(name);
15
- if (record.record === 'claim') {
16
- const target = f.sign === '-1' ? ids.get(f.takes).fields : f;
17
- const changed = observe(held, { id: f.id, at, origin: target.origin, span: codec.form.parse(codec.params, target.value), ...(f.takes ? { takes: f.takes } : {}) }, f.sign === '-1' ? -1 : 1);
18
- const seen = [...changed.seen];
19
- seen[seen.length - 1] = { ...seen.at(-1), id: f.id };
20
- world = new Map([...world, [name, { ...changed, seen }]]);
21
- }
22
- else if (f.sign === '-1') {
23
- const target = ids.get(f.takes).fields;
24
- world = new Map([...world, [name, { ...held, marks: [...(held.marks ?? []), mark({ id: f.id, at, takes: f.takes, pole: target.pole, reach: 'local', span: codec.form.parse(codec.params, target.value) })] }]]);
25
- }
26
- else if (f.widens !== undefined) {
27
- const changed = join(L, held, codec.form.parse(codec.params, f.value), f.widens, at);
28
- const marks = [...(changed.marks ?? [])];
29
- marks[marks.length - 1] = { ...marks.at(-1), id: f.id };
30
- world = new Map([...world, [name, { ...changed, marks }]]);
31
- }
32
- else {
33
- const span = codec.form.parse(codec.params, f.value);
34
- world = f.pole === 'floor' ? requireFloor(L, world, name, span, { id: f.id, at }) : sign(L, world, name, span, { id: f.id, at });
35
- }
36
- }
37
- return [...world].map(([name, c]) => { const codec = codecs.get(name), L = codec.form.lattice(codec.params); return { cell: name, state: state(L, c, world), parts: parts(L, c, world), rest: restOf(c, world), ownMarks: live(c.marks ?? []).map(m => m.id), ownClaims: live(c.seen).map(s => ({ id: s.id, origin: s.origin })), marks: live(c.marks ?? []), claims: live(c.seen) }; });
38
- }
1
+ /** Compatibility path; the engine owns the single object projection. */
2
+ export { objectFold } from "../engine/index.js";
@@ -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))))));
@@ -50,7 +60,7 @@ export function receiptsOf(fold) {
50
60
  return canonical({ scope: `${sourced(fold.standing)}/${place}${shape}`, role: 'writes', form: 'alphabet', measure: 'digest', value: digest, by: writerFor(fold.standing, 'fold') ?? '', at: `place:${digest}` });
51
61
  });
52
62
  const place = under.replace(/\/$/, '');
53
- const lines = [...named, ...[...cones].sort(), ...[...ran].sort(), ...paid]
63
+ const lines = [...new Set([...named, ...[...cones].sort(), ...[...ran].sort(), ...paid, ...(fold.receiptHistory ?? [])])]
54
64
  .map((line) => (place && !fieldOf(line, 'sig') ? zoomed(line, place) : line));
55
65
  if (!lines.length) {
56
66
  const shipped = (observeText(join(fold.root, under, RECEIPTS)) ?? '').split('\n').filter(isWire);
@@ -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,57 @@
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 => {
39
+ const scope = fieldOf(record, 'scope');
40
+ return scope.startsWith('wire/') || scope.startsWith('audit/wire/');
41
+ });
42
+ authority = authorityFor(snapshot, anchor, eraOf(foldClaims(wire).standing).admitted);
43
+ snapshots.set(epoch, authority);
44
+ }
45
+ return authority.of(line).kind === 'admitted';
46
+ });
47
+ // The host authenticates against historical authority; Topos projects the
48
+ // authenticated history using the instrument's existing fold.
49
+ const { standing } = foldedEvidence(admitted, {
50
+ classification: () => 'local',
51
+ fold: lines => foldClaims(lines).standing,
52
+ });
53
+ historicalProjection = { identity, standing: Object.freeze([...standing]) };
54
+ return [...standing];
55
+ }
56
+ /** Legacy Root lines remain unchanged; contextual public output has its own carrier. */
57
+ 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[];
@@ -1,4 +1,5 @@
1
1
  import { actCosts } from "./act-cost.js";
2
+ import { fileURLToPath } from 'node:url';
2
3
  import { boundedProcess } from "./process-lifetime.js";
3
4
  import { spawnSync } from 'node:child_process';
4
5
  import { PROTOCOL } from '@lapxo/topos/wire';
@@ -35,7 +36,9 @@ export function validResponse(value, request) {
35
36
  /** Last-line JSON is the wire framing. No partial batch, failed exit or synthetic missing response is accepted. */
36
37
  export function executeCapsule(command, args, requests, limits = {}, lifetime) {
37
38
  return actCosts.measure(() => {
38
- const run = lifetime === undefined ? spawnSync(command, args, { input: JSON.stringify(requests), encoding: 'utf8', maxBuffer: 1 << 26, ...limits }) : boundedProcess(command, args, JSON.stringify(requests), lifetime);
39
+ const cwd = limits.cwd;
40
+ const directory = cwd === undefined ? undefined : typeof cwd === 'string' ? cwd : fileURLToPath(cwd);
41
+ const run = lifetime === undefined ? spawnSync(command, args, { input: JSON.stringify(requests), encoding: 'utf8', maxBuffer: 1 << 26, ...limits }) : boundedProcess(command, args, JSON.stringify(requests), lifetime, directory, limits.env);
39
42
  if ('error' in run && run.error)
40
43
  throw new CapsuleProcessError(`failed: ${run.error.message}`, run);
41
44
  if (run.signal !== null || run.status !== 0)