@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
@@ -5,12 +5,53 @@ import { canonical, parse, PROTOCOL, byBytes } from '@lapxo/topos/wire';
5
5
  import { replaceWhole, landShard, shardLines } from "../land/ledger.js";
6
6
  import { fieldOf } from "../fold/claims.js";
7
7
  import { readOnly } from "./read-only.js";
8
+ import { readingIdentityBytes, readingReceiptIdentity, responseIdentity, snapshotAccess, placeInputs, withPlaceInputs } from '@lapxo/topos/contract';
9
+ import { withReadingBoundary } from "./reading-context.js";
10
+ const receiptScope = (scope) => [scope, 'identity'].join('/');
8
11
  const fields = (line) => { const got = parse(line); if (got.kind !== 'fact')
9
12
  throw Error('REFUSE·input projection contains a non-wire line'); return got.value.fields; };
10
13
  export const inputLines = (files) => files.map(file => canonical({ scope: file.place, form: 'alphabet', measure: 'text', role: 'writes', value: 'lock', shape: file.place, about: file.text, by: 'host', at: 'policy:handed-input' }));
14
+ /** One portable native receipt carrier shared by file plans and native capsule batches. */
15
+ export function portableReadingReceipt(scope, by, closure, output, hash, modern = true, costs = []) {
16
+ const input = hash(closure), payload = hash(output), rows = output.split('\n').filter(Boolean);
17
+ return [...(modern ? [canonical({ scope: receiptScope(scope), role: 'writes', form: 'alphabet', measure: 'digest', value: readingReceiptIdentity(input, payload, bytes => hash(new TextDecoder().decode(bytes))), by, at: 'place:' + input })] : []), canonical({ scope, role: 'writes', form: 'alphabet', measure: 'observed', value: payload, by, at: 'place:' + payload }), canonical({ scope, role: 'writes', form: 'alphabet', measure: 'bytes', value: input, by, at: 'place:' + input, about: closure }), canonical({ scope, role: 'writes', form: 'interval', measure: 'count', value: `${rows.length}..${rows.length}`, by, at: 'place:' + payload, about: output }), ...costs];
18
+ }
11
19
  /** The selected world owns input projection and reading. The host only verifies closures and keeps native count/bytes receipts. */
12
20
  export function readingClosures(options) {
13
21
  const { root, store, reader, speaker, digest: hash, capsule, region, projection, files } = options, emit = options.emit ?? (() => { });
22
+ options.assertCurrent?.();
23
+ if (options.context)
24
+ readingIdentityBytes(options.context);
25
+ let access = [];
26
+ if (options.current) {
27
+ if (!options.context)
28
+ throw Error('REFUSE·current reading requires a declared identity');
29
+ const { snapshot, kept, current, permission } = options.current;
30
+ if (current.implementation !== options.context.implementation)
31
+ throw Error('REFUSE·current reading implementation differs from its verified context');
32
+ const response = responseIdentity(snapshot, current, hash), allowed = snapshotAccess(snapshot, kept, current, permission, hash);
33
+ if (!allowed.inspect || !allowed.reuse)
34
+ throw Error('REFUSE·current reading is not admitted for reuse');
35
+ access = [['response', response], ['permission', hash(JSON.stringify([permission.inspect, permission.reuse, permission.freshness]))]];
36
+ }
37
+ if (options.execution && (!options.context || JSON.stringify(readingIdentityBytes(options.execution.context)) !== JSON.stringify(readingIdentityBytes(options.context))))
38
+ throw Error('REFUSE·reading execution differs from declared context');
39
+ const projectionFor = (name) => {
40
+ if (!capsule.selectedWorld)
41
+ return undefined;
42
+ const { standing, offer } = capsule.selectedWorld;
43
+ const selectors = standing.parts.some(part => part.scope === `${offer}/place-inputs/${name}` && part.measure === 'reads');
44
+ if (!selectors)
45
+ return undefined;
46
+ if (!options.context || !options.execution || !options.livePlace)
47
+ throw Error('REFUSE·place inputs require authenticated shadow and reading context ' + name);
48
+ return placeInputs(standing, offer, name, options.livePlace(), bytes => hash(new TextDecoder().decode(bytes)));
49
+ };
50
+ const projectedPlace = projectionFor(projection), readingPlace = projectionFor(region);
51
+ const commit = (context, name, place) => place && context ? withPlaceInputs(context, JSON.stringify(['place-inputs', capsule.selectedWorld.offer, name]), place) : context;
52
+ const ask = (identity, requests, context = options.context) => withReadingBoundary(identity, options.execution && context ? { ...options.execution, context } : options.execution, () => capsule.ask(requests, options.readerReference));
53
+ const projectionContext = commit(options.context, projection, projectedPlace);
54
+ const base = projectionContext ? hash(new TextDecoder().decode(readingIdentityBytes(projectionContext))) : '';
14
55
  const algorithm = hash('').split(':')[0], handed = new Map(files.map(file => [file.place, file.text]));
15
56
  if (handed.size !== files.length)
16
57
  throw Error('REFUSE·input projection has duplicate handed coordinates');
@@ -18,7 +59,7 @@ export function readingClosures(options) {
18
59
  if (!readFileSync(join(root, file.place)).equals(Buffer.from(file.text)))
19
60
  throw Error('REFUSE·input changed ' + file.place); };
20
61
  verify();
21
- const projected = capsule.ask([{ protocol: PROTOCOL, verb: 'render', rootScope: '', region: projection, at: 1, reads: ['**'], lines: inputLines(files), files: [] }], options.readerReference)[0];
62
+ const projected = ask(base, [{ ...(projectedPlace ? { place: projectedPlace } : {}), protocol: PROTOCOL, verb: 'render', rootScope: '', region: projection, at: 1, reads: ['**'], lines: inputLines(files), files: [] }], projectionContext)[0];
22
63
  if (projected?.kind !== 'fact' || !Array.isArray(projected.lines))
23
64
  throw Error('REFUSE·input projection ' + (projected?.why ?? 'no lines'));
24
65
  const plans = new Map();
@@ -56,7 +97,12 @@ export function readingClosures(options) {
56
97
  let read = 0, opened = 0, executed = 0;
57
98
  for (const plan of ordered) {
58
99
  actCosts.measure(() => {
59
- const closure = [plan.line, canonical({ scope: 'implementation', role: 'writes', form: 'alphabet', measure: 'digest', value: identity, by: 'host', at: 'policy:input-identity' }), ...plan.inputs.map(input => canonical({ scope: input, role: 'writes', form: 'alphabet', measure: 'digest', value: hash(handed.get(input)), by: 'host', at: 'policy:input-identity' })), ...plan.needs.map(need => canonical({ scope: need, role: 'writes', form: 'alphabet', measure: 'digest', value: results.get(need).digest, by: 'host', at: 'policy:input-identity' }))].sort(byBytes).join('\n') + '\n';
100
+ // Retained evidence does not authorize a current answer.
101
+ options.assertCurrent?.();
102
+ const legacyClosure = [plan.line, canonical({ scope: 'implementation', role: 'writes', form: 'alphabet', measure: 'digest', value: identity, by: 'host', at: 'policy:input-identity' }), ...plan.inputs.map(input => canonical({ scope: input, role: 'writes', form: 'alphabet', measure: 'digest', value: hash(handed.get(input)), by: 'host', at: 'policy:input-identity' })), ...plan.needs.map(need => canonical({ scope: need, role: 'writes', form: 'alphabet', measure: 'digest', value: results.get(need).digest, by: 'host', at: 'policy:input-identity' }))].sort(byBytes).join('\n') + '\n';
103
+ const leafContext = commit(options.context, region, readingPlace);
104
+ const context = leafContext && { ...leafContext, policy: hash(JSON.stringify([leafContext.policy, options.allowsEmpty === true, options.lifetime ?? null, options.readerReference ?? null, capsule.selection ?? null, capsule.lines])), inputs: [...[...leafContext.inputs, ...access].map(([name, digest]) => [JSON.stringify(['context', name]), digest]), [JSON.stringify(['plan']), hash(plan.line)], ...plan.inputs.map(input => [JSON.stringify(['file', input]), hash(handed.get(input))]), ...plan.needs.map(need => [JSON.stringify(['receipt', need]), results.get(need).digest])] };
105
+ const closure = context ? new TextDecoder().decode(readingIdentityBytes(context)) : legacyClosure;
60
106
  const input = hash(closure), scope = 'receipts/readings/' + hash(plan.scope).split(':')[1] + '/' + input.split(':')[1], matching = prior.filter(line => fieldOf(line, 'scope') === scope && fieldOf(line, 'measure') === 'bytes' && fieldOf(line, 'value') === input).at(-1);
61
107
  const count = matching && prior.filter(line => fieldOf(line, 'scope') === scope && fieldOf(line, 'measure') === 'count' && fieldOf(line, 'by') === by).at(-1);
62
108
  const heldOutput = count && load(fieldOf(count, 'at').replace(/^place:/, ''));
@@ -75,7 +121,7 @@ export function readingClosures(options) {
75
121
  if (readOnly())
76
122
  throw Error('REFUSE·preview reading closure is unpaid ' + plan.scope);
77
123
  const started = process.hrtime.bigint();
78
- const dependencies = plan.needs.flatMap(need => results.get(need).lines), answer = capsule.ask([{ protocol: PROTOCOL, verb: 'read', rootScope: plan.scope, name: plan.scope, region, at: plan.resolution, reads: ['**'], lines: [...inputLines(plan.inputs.map(place => ({ place, text: handed.get(place) }))), ...dependencies], files: [] }], options.readerReference)[0];
124
+ const dependencies = plan.needs.flatMap(need => results.get(need).lines), answer = ask(input, [{ ...(readingPlace ? { place: readingPlace } : {}), protocol: PROTOCOL, verb: 'read', rootScope: plan.scope, name: plan.scope, region, at: plan.resolution, reads: ['**'], lines: [...inputLines(plan.inputs.map(place => ({ place, text: handed.get(place) }))), ...dependencies], files: [] }], context)[0];
79
125
  if (answer?.kind !== 'fact' || !Array.isArray(answer.claims))
80
126
  throw Error('REFUSE·reading ' + plan.scope + ' · ' + (answer?.why ?? 'no claims'));
81
127
  lines = answer.claims.map((unknown) => { if (typeof unknown?.scope !== 'string' || typeof unknown?.measure !== 'string' || typeof unknown?.role !== 'string' || !unknown.bound || !['interval', 'enumerated'].includes(unknown.bound.kind))
@@ -84,6 +130,7 @@ export function readingClosures(options) {
84
130
  throw Error('REFUSE·reading empty response is not admitted ' + plan.scope);
85
131
  executed++;
86
132
  verify();
133
+ options.assertCurrent?.();
87
134
  payload = saved(lines.length ? [...lines].sort(byBytes).join('\n') + '\n' : '');
88
135
  if (saved(closure) !== input)
89
136
  throw Error('REFUSE·receipt input closure mismatch');
@@ -92,22 +139,24 @@ export function readingClosures(options) {
92
139
  if (plan.resolution === 1)
93
140
  opened++;
94
141
  }
95
- results.set(plan.scope, { digest: payload, lines });
96
- out.push(canonical({ scope: plan.scope, role: 'writes', form: 'alphabet', measure: 'observed', value: input, by, at: 'place:' + input }), ...lines);
142
+ const receipt = context ? readingReceiptIdentity(input, payload, bytes => hash(new TextDecoder().decode(bytes))) : payload;
143
+ if (context && plan.resolution !== 1)
144
+ out.push(canonical({ scope: receiptScope(scope), role: 'writes', form: 'alphabet', measure: 'digest', value: receipt, by, at: 'place:' + input }));
145
+ results.set(plan.scope, { digest: receipt, lines });
146
+ out.push(canonical({ scope: plan.scope, role: 'writes', form: 'alphabet', measure: 'observed', value: input, by, at: 'place:' + input }),
147
+ // A logical reading name is not a physical input coordinate. Preserve the
148
+ // host-verified handoff beside the result so portable file seals retain
149
+ // every input, including explicitly read foreign files.
150
+ // Input witnesses do not locate the result at each input. Result attribution
151
+ // remains the logical reading's place; two inputs are not two new origins.
152
+ ...plan.inputs.map(coordinate => canonical({ scope: coordinate, role: 'writes', form: 'alphabet', measure: 'observed', value: hash(handed.get(coordinate)), by, at: 'witness:' + input })), ...lines);
97
153
  if (plan.resolution === 1) {
98
154
  read++;
99
- const native = shardLines(store, speaker, journal).filter(line => fieldOf(line, 'scope') === scope && ((fieldOf(line, 'measure') === 'bytes' && fieldOf(line, 'value') === input) || (fieldOf(line, 'measure') === 'count') || (fieldOf(line, 'measure') === 's' && fieldOf(line, 'at') === 'place:' + input))).map(line => {
100
- const f = fields(line), id = f.measure === 'bytes' ? f.value : f.measure === 'count' ? (f.at ?? '').replace(/^place:/, '') : undefined;
101
- if (id === undefined)
102
- return line;
103
- const bytes = load(id);
104
- if (bytes === undefined)
105
- throw Error('REFUSE·receipt missing verified carrier ' + id);
106
- // The full-resolution native row carries the actual verified UTF-8 bytes.
107
- // Its semantic count/digest is unchanged; a public copy needs no private CAS.
108
- return canonical({ ...f, about: bytes });
109
- });
110
- out.push(canonical({ scope, role: 'writes', form: 'alphabet', measure: 'observed', value: payload, by, at: 'place:' + payload }), ...native);
155
+ const output = load(payload);
156
+ if (output === undefined)
157
+ throw Error('REFUSE·receipt missing verified carrier ' + payload);
158
+ const costs = shardLines(store, speaker, journal).filter(line => fieldOf(line, 'scope') === scope && fieldOf(line, 'measure') === 's' && fieldOf(line, 'at') === 'place:' + input);
159
+ out.push(...portableReadingReceipt(scope, by, closure, output, hash, !!context, costs));
111
160
  }
112
161
  });
113
162
  }
@@ -116,7 +165,7 @@ export function readingClosures(options) {
116
165
  return { lines: out, read, opened, executed };
117
166
  }
118
167
  /** Verify the native reading carriers before a file seal may reuse them. These are semantic receipts, not file-region seals. */
119
- export function verifiedReadingReceipts(store, lines, digest) {
168
+ export function verifiedReadingReceipts(store, lines, digest, environment, requireContext = false) {
120
169
  const units = lines.filter(line => fieldOf(line, 'scope').startsWith('receipts/readings/') && fieldOf(line, 'measure') === 'bytes' && fieldOf(line, 'role') === 'writes');
121
170
  const load = (id, row) => {
122
171
  const hex = id.split(':')[1];
@@ -139,6 +188,37 @@ export function verifiedReadingReceipts(store, lines, digest) {
139
188
  const count = counts[0], output = load(fieldOf(count, 'at').replace(/^place:/, ''), count);
140
189
  if (output === undefined)
141
190
  return;
191
+ const inputBytes = load(input, unit);
192
+ if (inputBytes.trimStart().startsWith('[')) {
193
+ let context;
194
+ try {
195
+ context = JSON.parse(inputBytes);
196
+ }
197
+ catch {
198
+ return;
199
+ }
200
+ if (!Array.isArray(context) || context.length !== 6 || context[0] !== 'reading-context@1')
201
+ return;
202
+ const canonicalContext = { instrument: context[1], implementation: context[2], policy: context[3], inputs: context[4], environment: context[5] };
203
+ try {
204
+ if (new TextDecoder().decode(readingIdentityBytes(canonicalContext)) !== inputBytes)
205
+ return;
206
+ }
207
+ catch {
208
+ return;
209
+ }
210
+ if (environment) {
211
+ const expected = typeof environment === 'function' ? environment(canonicalContext) : environment;
212
+ if (expected === undefined || JSON.stringify([...expected].sort((a, b) => byBytes(a[0], b[0]))) !== JSON.stringify(context[5]))
213
+ return;
214
+ }
215
+ const identity = lines.filter(line => fieldOf(line, 'scope') === receiptScope(scope) && fieldOf(line, 'measure') === 'digest' && fieldOf(line, 'by') === by && fieldOf(line, 'role') === 'writes' && fieldOf(line, 'at') === 'place:' + input);
216
+ const expected = readingReceiptIdentity(input, fieldOf(count, 'at').replace(/^place:/, ''), bytes => digest(new TextDecoder().decode(bytes)));
217
+ if (identity.length !== 1 || fieldOf(identity[0], 'value') !== expected)
218
+ return;
219
+ }
220
+ else if (requireContext || !inputBytes.endsWith('\n') || inputBytes.split('\n').filter(Boolean).some(line => parse(line).kind !== 'fact'))
221
+ return;
142
222
  const rows = output.split('\n').filter(Boolean);
143
223
  if (fieldOf(count, 'value') !== `${rows.length}..${rows.length}` || rows.some(line => fieldOf(line, 'by') !== by || fieldOf(line, 'at') !== 'place:' + input))
144
224
  return;
@@ -0,0 +1,29 @@
1
+ import type { ReadingIdentityContext } from '@lapxo/topos/contract';
2
+ export type ReadingReference = readonly [string, string];
3
+ export interface ReadingExecution {
4
+ readonly context: ReadingIdentityContext;
5
+ readonly env: Readonly<Record<string, string>>;
6
+ readonly cwd: string;
7
+ readonly flags: readonly string[];
8
+ }
9
+ interface Boundary {
10
+ readonly identity: string;
11
+ readonly execution?: ReadingExecution;
12
+ }
13
+ export declare const readingBoundary: () => Boundary | undefined;
14
+ export declare function withReadingBoundary<T>(identity: string, execution: ReadingExecution | undefined, run: () => T): T;
15
+ export declare function readingEnvironment(hash: (text: string) => string, env?: Readonly<Record<string, string | undefined>>, cwd?: string, flags?: readonly string[]): readonly ReadingReference[];
16
+ export declare function readingProfile(lines: readonly string[], field: (line: string, name: string) => string): boolean;
17
+ /** The caller supplies verified implementation and transitive references. This
18
+ * constructor adds the process boundary that the host will actually hand over. */
19
+ export declare function readingExecution(input: {
20
+ instrument: string;
21
+ implementation: string;
22
+ policy: string;
23
+ inputs: readonly ReadingReference[];
24
+ }, hash: (text: string) => string, boundary?: {
25
+ env: Readonly<Record<string, string | undefined>>;
26
+ cwd: string;
27
+ flags: readonly string[];
28
+ }): ReadingExecution;
29
+ export {};
@@ -0,0 +1,53 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { createHash } from 'node:crypto';
3
+ import { readFileSync, realpathSync } from 'node:fs';
4
+ import { release } from 'node:os';
5
+ import { readingIdentityBytes } from '@lapxo/topos/contract';
6
+ const active = new AsyncLocalStorage();
7
+ export const readingBoundary = active.getStore.bind(active);
8
+ export function withReadingBoundary(identity, execution, run) {
9
+ if (execution) {
10
+ const algorithm = identity.split(':')[0];
11
+ const hash = (text) => algorithm + ':' + createHash(algorithm).update(text).digest('hex');
12
+ const expected = readingEnvironment(hash, execution.env, execution.cwd, execution.flags);
13
+ const named = [...execution.context.environment].sort(([a], [b]) => Buffer.compare(Buffer.from(a), Buffer.from(b)));
14
+ if (JSON.stringify([...expected].sort(([a], [b]) => Buffer.compare(Buffer.from(a), Buffer.from(b)))) !== JSON.stringify(named))
15
+ throw Error('REFUSE·reading execution environment differs from its identity');
16
+ }
17
+ return active.run({ identity, execution }, run);
18
+ }
19
+ /** References commit the actual inherited boundary. Values are hashed, never
20
+ * carried as environment secrets. No empty map is substituted for unknown input. */
21
+ // The running runtime is resolved once for this act; mutable input bytes are not
22
+ // stored in this slot. Later processes establish their own runtime reference.
23
+ const runtimes = new Map();
24
+ const runtimeOf = (algorithm) => { const held = runtimes.get(algorithm); if (held)
25
+ return held; const digest = algorithm + ':' + createHash(algorithm).update(readFileSync(process.execPath)).digest('hex'); runtimes.set(algorithm, digest); return digest; };
26
+ export function readingEnvironment(hash, env = process.env, cwd = process.cwd(), flags = process.execArgv) {
27
+ const inherited = Object.entries(env).filter((entry) => entry[1] !== undefined);
28
+ return [
29
+ ['variables', hash(JSON.stringify(inherited.sort(([a], [b]) => Buffer.compare(Buffer.from(a), Buffer.from(b)))))],
30
+ ['cwd', hash(realpathSync(cwd))], ['flags', hash(JSON.stringify(flags))],
31
+ ['runtime', runtimeOf(hash('').split(':')[0])],
32
+ ['platform', hash(JSON.stringify([process.platform, process.arch, process.versions, release()]))],
33
+ ];
34
+ }
35
+ export function readingProfile(lines, field) {
36
+ const values = (scope) => lines.filter(line => field(line, 'scope') === scope && field(line, 'role') === 'writes' && field(line, 'value') !== 'withdraw');
37
+ const contexts = values('wire/reading-context'), receipts = values('wire/reading-receipt');
38
+ if (!contexts.length && !receipts.length)
39
+ return false;
40
+ if (contexts.length !== 1 || receipts.length !== 1 || field(contexts[0], 'value') !== 'reading-context@1' || field(receipts[0], 'value') !== 'reading-receipt@1')
41
+ throw Error('REFUSE·reading identity ambiguous or incomplete profiles');
42
+ return true;
43
+ }
44
+ /** The caller supplies verified implementation and transitive references. This
45
+ * constructor adds the process boundary that the host will actually hand over. */
46
+ export function readingExecution(input, hash, boundary = { env: process.env, cwd: process.cwd(), flags: process.execArgv }) {
47
+ const env = Object.fromEntries(Object.entries(boundary.env).filter((entry) => entry[1] !== undefined));
48
+ const cwd = boundary.cwd, flags = Object.freeze([...boundary.flags]);
49
+ Object.freeze(env);
50
+ const context = { ...input, environment: readingEnvironment(hash, env, cwd, flags) };
51
+ readingIdentityBytes(context);
52
+ return { context, env, cwd, flags };
53
+ }
@@ -0,0 +1,28 @@
1
+ import { authorityOf, validateObjectContext } from '@lapxo/topos/wire';
2
+ type ObjectContext = Parameters<typeof validateObjectContext>[1];
3
+ type Signer = Parameters<typeof authorityOf>[1][number];
4
+ export interface ReferenceTrust {
5
+ readonly snapshot: string;
6
+ readonly epoch: number;
7
+ readonly proofScope: string;
8
+ readonly policy: string;
9
+ readonly formImplementation: string;
10
+ readonly signers: readonly Signer[];
11
+ readonly algorithms: readonly string[];
12
+ readonly objects: ObjectContext;
13
+ readonly maxBytes: number;
14
+ readonly maxRecords: number;
15
+ readonly digestBytes: (bytes: Uint8Array) => string | Promise<string>;
16
+ readonly verifyBytes: (bytes: string, key: string, signature: string, algorithms: readonly string[]) => boolean | Promise<boolean>;
17
+ readonly fetchBytes: (source: string, blob: string) => Promise<Uint8Array>;
18
+ }
19
+ /** Prospective authenticated-standing@1 host composition; no effects and no authority from transport. */
20
+ export declare function inspectAuthenticatedReference(fragment: string, trust: ReferenceTrust): Promise<{
21
+ reference: import("./reference-profile.ts").StandingReference;
22
+ records: readonly import("@lapxo/topos/wire").ObjectRecord[];
23
+ semanticIdentity: string;
24
+ authorityContextIdentity: string;
25
+ semanticStanding: string;
26
+ originalRecords: readonly string[];
27
+ }>;
28
+ export { encodeReference, decodeReference } from './reference-profile.ts';
@@ -0,0 +1,84 @@
1
+ import { parse, canonical, authorityOf, authorityAtEpoch, signedBytes, objectHistory, validateObjectContext } from '@lapxo/topos/wire';
2
+ import { cellInputs } from '@lapxo/topos/cell-inputs';
3
+ import { semanticStandingBytes } from '@lapxo/topos/standing';
4
+ import { decodeReference } from "./reference-profile.js";
5
+ function fail(why) { throw Error('REFUSE·authenticated-reference ' + why); }
6
+ const utf8 = (s) => new TextEncoder().encode(s);
7
+ /** Prospective authenticated-standing@1 host composition; no effects and no authority from transport. */
8
+ export async function inspectAuthenticatedReference(fragment, trust) {
9
+ if (new Set(trust.signers.map(s => s.id)).size !== trust.signers.length)
10
+ fail('duplicate host signer');
11
+ const reference = decodeReference(fragment);
12
+ if (!Number.isSafeInteger(trust.maxBytes) || trust.maxBytes < 1 || !Number.isSafeInteger(trust.maxRecords) || trust.maxRecords < 1)
13
+ fail('resource budget');
14
+ const received = await trust.fetchBytes(reference.source, reference.blob);
15
+ if (received.byteLength > trust.maxBytes)
16
+ fail('bytes budget');
17
+ if (typeof SharedArrayBuffer !== 'undefined' && received.buffer instanceof SharedArrayBuffer)
18
+ fail('shared transport bytes');
19
+ const bytes = new Uint8Array(received);
20
+ if (await trust.digestBytes(bytes) !== reference.blob)
21
+ fail('blob integrity');
22
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
23
+ let packet;
24
+ try {
25
+ packet = JSON.parse(text);
26
+ }
27
+ catch {
28
+ fail('carrier framing');
29
+ }
30
+ if (!Array.isArray(packet) || packet.length !== 3 || packet[0] !== 'authenticated-standing@1' || !Array.isArray(packet[1]) || packet[1].length < 1 || packet[1].length > trust.maxRecords || packet[1].some((x) => typeof x !== 'string') || typeof packet[2] !== 'string' || JSON.stringify(packet) + '\n' !== text)
31
+ fail('carrier framing');
32
+ const originals = packet[1], proofLine = packet[2];
33
+ const parsed = [...originals, proofLine].map(line => { const p = parse(line); if (p.kind !== 'fact' || canonical(p.value.fields) !== line)
34
+ fail('original canonical'); return p.value.fields; });
35
+ const proof = parsed[parsed.length - 1];
36
+ if (proof.scope !== trust.proofScope || proof.at !== 'witness:' + trust.snapshot || proof.role !== 'writes' || proof.form !== 'alphabet' || proof.measure !== 'digest' || proof.epoch !== String(trust.epoch))
37
+ fail('untrusted snapshot binding');
38
+ if (proof.value !== await trust.digestBytes(utf8(JSON.stringify(['authenticated-standing@1', originals]) + '\n')))
39
+ fail('snapshot commitment');
40
+ // Async crypto port is called on exact native signed bytes; authorityOf consumes only those results.
41
+ const verifiedCrypto = new Set();
42
+ const key = (f, pub) => JSON.stringify([signedBytes(f), pub, f.sig, trust.algorithms]);
43
+ for (const f of parsed)
44
+ for (const s of trust.signers)
45
+ if (s.id === f.by && await trust.verifyBytes(signedBytes(f), s.publicKey, f.sig, trust.algorithms))
46
+ verifiedCrypto.add(key(f, s.publicKey));
47
+ const verifies = (f, pub) => verifiedCrypto.has(key(f, pub));
48
+ const authentic = authorityOf(proof, trust.signers, verifies);
49
+ if (authentic.kind !== 'admitted')
50
+ fail('proof ' + authentic.kind + ' ' + authentic.why);
51
+ const signer = trust.signers.find(s => s.id === proof.by);
52
+ if (!signer)
53
+ fail('proof signer');
54
+ const current = authorityAtEpoch(proof, signer, trust.epoch, trust.proofScope, 'authority');
55
+ if (current.kind !== 'admitted')
56
+ fail('current proof ' + current.kind + ' ' + current.why);
57
+ const verified = new Set();
58
+ for (let i = 0; i < originals.length; i++) {
59
+ const admitted = authorityOf(parsed[i], trust.signers, verifies);
60
+ if (admitted.kind !== 'admitted')
61
+ fail('original ' + admitted.kind + ' ' + admitted.why);
62
+ verified.add(originals[i]);
63
+ }
64
+ const history = objectHistory(originals);
65
+ if (history.kind !== 'fact')
66
+ fail('wire ' + history.why);
67
+ if (history.value.configuration.some(f => f.value === 'withdraw'))
68
+ fail('configuration history requires admitted standing fold');
69
+ const context = { ...trust.objects, admit: (r) => verified.has(canonical(r.fields)) && trust.objects.admit(r) };
70
+ const checked = validateObjectContext(history.value.objects, context);
71
+ if (checked.kind !== 'fact')
72
+ throw Error('REFUSE·' + checked.why);
73
+ const membership = new Set([...cellInputs(checked.value, 'semantic-live@2').values()].flat().map(l => semanticStandingBytes([l])));
74
+ const fields = (line) => { const p = parse(line); if (p.kind !== 'fact')
75
+ fail('original parse'); return p.value.fields; };
76
+ const effective = [...originals].sort((a, b) => Number(fields(a).epoch) - Number(fields(b).epoch));
77
+ const live = effective.filter(line => { const f = fields(line); return f.type === undefined || membership.has(semanticStandingBytes([line])); });
78
+ const semantic = semanticStandingBytes(live), semanticIdentity = await trust.digestBytes(utf8(semantic));
79
+ if (semanticIdentity !== 'sha256:' + reference.digest)
80
+ fail('standing identity');
81
+ const authorityContextIdentity = await trust.digestBytes(utf8(JSON.stringify(['reference-inspection-trust@1', trust.snapshot, trust.epoch, trust.proofScope, trust.policy, trust.formImplementation, trust.signers, trust.algorithms])));
82
+ return { reference, records: checked.value, semanticIdentity, authorityContextIdentity, semanticStanding: semantic, originalRecords: originals };
83
+ }
84
+ export { encodeReference, decodeReference } from "./reference-profile.js";
@@ -0,0 +1,10 @@
1
+ export declare function authenticatedReferenceSelected(standing: readonly string[]): boolean;
2
+ export declare function openAuthenticatedReference(root: string, fragment: string): Promise<{
3
+ readings: readonly import("../engine/object.ts").ObjectReading[];
4
+ reference: import("./reference-profile.ts").StandingReference;
5
+ records: readonly import("@lapxo/topos/wire").ObjectRecord[];
6
+ semanticIdentity: string;
7
+ authorityContextIdentity: string;
8
+ semanticStanding: string;
9
+ originalRecords: readonly string[];
10
+ }>;
@@ -0,0 +1,50 @@
1
+ /** Native ports for authenticated #bound opening; trusted policy belongs to the selected local place. */
2
+ import { sha256 as hash, isSha256 } from "./hash.js";
3
+ import { canonical, parse, transportSelection } from '@lapxo/topos/wire';
4
+ import { semanticStandingBytes } from '@lapxo/topos/standing';
5
+ import { authorityFor, rootSigner } from "../fold/signers.js";
6
+ import { eraOf } from "../fold/digests.js";
7
+ import { lockStanding, lockLines } from "../fold/keys.js";
8
+ import { signedOwnerLines } from "../cli/owner.js";
9
+ import { admittedObjects } from "./objects.js";
10
+ import { storeOf } from "../land/ledger.js";
11
+ import { objectFold } from "../engine/index.js";
12
+ import { toposAt, contentAt } from "./selected-topos.js";
13
+ import { transportBytes } from "./ports/transport.js";
14
+ import { decodeReference } from "./reference-profile.js";
15
+ import { inspectAuthenticatedReference } from "./reference-authenticated.js";
16
+ import { verifyBytes } from "./adapters/keys/ed25519.js";
17
+ export function authenticatedReferenceSelected(standing) {
18
+ const selected = standing.flatMap(line => { const p = parse(line); return p.kind === 'fact' && p.value.fields.scope === 'reference/authentication' ? [p.value.fields] : []; });
19
+ if (!selected.length)
20
+ return false;
21
+ if (selected.length !== 1 || selected[0].value !== 'authenticated-standing@1')
22
+ throw Error('REFUSE·reference conflicting or unknown authentication profile');
23
+ return true;
24
+ }
25
+ export async function openAuthenticatedReference(root, fragment) {
26
+ const store = storeOf(root), standing = lockStanding(store), era = eraOf(standing), authority = authorityFor(lockLines(store), rootSigner(root), era.admitted);
27
+ if (!authenticatedReferenceSelected(standing))
28
+ throw Error('REFUSE·reference no admitted authenticated-standing@1 profile');
29
+ const one = (scope) => { const found = standing.flatMap(line => { const p = parse(line); return p.kind === 'fact' && p.value.fields.scope === scope ? [{ line, fields: p.value.fields }] : []; }); if (found.length !== 1 || found[0].fields.role !== 'writes' || found[0].fields.measure !== 'id' || authority.of(found[0].line).kind !== 'admitted')
30
+ throw Error('REFUSE·reference missing, conflicting or unauthenticated ' + scope); return found[0].fields.value; };
31
+ if (one('reference/authentication') !== 'authenticated-standing@1')
32
+ throw Error('REFUSE·reference authentication profile');
33
+ const snapshot = one('reference/snapshot'), epochText = one('reference/epoch'), proofScope = one('reference/proof-scope'), recordsText = one('reference/records');
34
+ if (!isSha256(snapshot) || !/^\d+$/.test(epochText) || !Number.isSafeInteger(Number(epochText)) || !/^\d+$/.test(recordsText) || !Number.isSafeInteger(Number(recordsText)) || Number(recordsText) < 1 || !proofScope)
35
+ throw Error('REFUSE·reference authority snapshot or resource contract');
36
+ const reference = decodeReference(fragment);
37
+ // Provider selection is made from HOST-declared cells/configuration, never carrier-picked code.
38
+ const held = await admittedObjects(root, signedOwnerLines(store), () => { }, true);
39
+ if (!held.records.some(r => r.record === 'cell'))
40
+ throw Error('REFUSE·reference host has no declared object interpretation');
41
+ const context = held.context, policy = held.configuration.map(fields => canonical(fields)), selection = transportSelection(held.configuration, new URL(reference.source).protocol.slice(0, -1));
42
+ if (!selection)
43
+ throw Error('REFUSE·reference no admitted transport policy');
44
+ const implementations = [...new Set(held.records.filter(r => r.record === 'cell').map(r => r.fields.topos))].sort().map(pin => { const selected = toposAt(pin, store), selectors = selected.parts.filter(f => f.scope === 'wire/topos/context-view'); if (selectors.length !== 1)
45
+ throw Error('REFUSE·reference host context selector'); const offers = selected.parts.filter(f => f.kind === 'check' && f.view === selectors[0].value); if (offers.length !== 1)
46
+ throw Error('REFUSE·reference host context offer'); const offer = offers[0], closure = [...new Set((offer.restsOn ?? '').split('|').filter(Boolean))].sort(); for (const digest of closure)
47
+ contentAt(digest, store); return [pin, offer.value, closure]; });
48
+ const result = await inspectAuthenticatedReference(fragment, { snapshot, epoch: Number(epochText), proofScope, policy: hash(semanticStandingBytes(policy)), formImplementation: hash(JSON.stringify(implementations)), signers: authority.admitted, algorithms: era.admitted, objects: context, maxBytes: selection.responseBytes, maxRecords: Number(recordsText), digestBytes: hash, verifyBytes, fetchBytes: (source, blob) => transportBytes(source, blob, policy) });
49
+ return { ...result, readings: objectFold(result.records, context) };
50
+ }
@@ -0,0 +1,23 @@
1
+ /** reference-profile@1: platform URL parsing, no authority or transport effects. */
2
+ export interface StandingReference {
3
+ readonly class: 'standing';
4
+ readonly algorithm: 'sha256';
5
+ readonly digest: string;
6
+ readonly blob: string;
7
+ readonly source: string;
8
+ }
9
+ interface InspectionPort {
10
+ readonly maxBytes: number;
11
+ readonly readSemanticStanding: (bytes: string) => readonly string[];
12
+ readonly semanticStandingBytes: (lines: readonly string[]) => string;
13
+ readonly digestBytes: (bytes: Uint8Array) => string;
14
+ }
15
+ export declare function encodeReference(reference: StandingReference): string;
16
+ export declare function decodeReference(fragment: string): StandingReference;
17
+ export declare function inspectReferenceBytes(reference: StandingReference, bytes: Uint8Array, { maxBytes, readSemanticStanding, semanticStandingBytes, digestBytes }: InspectionPort): {
18
+ reference: StandingReference;
19
+ lines: readonly string[];
20
+ standing: string;
21
+ authentication: string;
22
+ };
23
+ export {};
@@ -0,0 +1,62 @@
1
+ const hex = /^[0-9a-f]{64}$/;
2
+ const refuse = (cause) => { throw Error(`REFUSE·reference ${cause}`); };
3
+ const keys = ['class', 'algorithm', 'digest', 'blob', 'source'];
4
+ function check(reference) {
5
+ if (Object.keys(reference).length !== keys.length || keys.some(key => !Object.prototype.hasOwnProperty.call(reference, key)))
6
+ refuse('fields');
7
+ if (reference.class !== 'standing')
8
+ refuse('unknown class');
9
+ if (reference.algorithm !== 'sha256')
10
+ refuse('unknown algorithm');
11
+ if (!hex.test(reference.digest) || !/^sha256:[0-9a-f]{64}$/.test(reference.blob))
12
+ refuse('digest');
13
+ let source;
14
+ try {
15
+ source = new URL(reference.source);
16
+ }
17
+ catch {
18
+ return refuse('source URL');
19
+ }
20
+ if (source.username || source.password || source.hash)
21
+ refuse('source credentials or fragment');
22
+ if (source.href !== reference.source)
23
+ refuse('noncanonical source URL');
24
+ return reference;
25
+ }
26
+ export function encodeReference(reference) {
27
+ check(reference);
28
+ return '#bound:1?' + keys.map(key => key + '=' + encodeURIComponent(reference[key])).join('&');
29
+ }
30
+ export function decodeReference(fragment) {
31
+ if (!fragment.startsWith('#bound:1?'))
32
+ refuse('profile');
33
+ const parameters = new URLSearchParams(fragment.slice('#bound:1?'.length));
34
+ if ([...parameters].length !== keys.length || new Set(parameters.keys()).size !== keys.length)
35
+ refuse('duplicate or missing field');
36
+ const reference = check(Object.fromEntries(parameters));
37
+ if (encodeReference(reference) !== fragment)
38
+ refuse('noncanonical encoding');
39
+ return Object.freeze(reference);
40
+ }
41
+ export function inspectReferenceBytes(reference, bytes, { maxBytes, readSemanticStanding, semanticStandingBytes, digestBytes }) {
42
+ if (bytes.byteLength > maxBytes)
43
+ refuse('bytes budget');
44
+ if (digestBytes(bytes) !== reference.blob.slice(7))
45
+ refuse('blob integrity');
46
+ let document;
47
+ try {
48
+ document = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
49
+ }
50
+ catch {
51
+ refuse('document');
52
+ }
53
+ if (document === null || typeof document !== 'object' || document.profile !== 'semantic-standing@1' || typeof document.standing !== 'string' || Object.keys(document).length !== 2)
54
+ refuse('document profile');
55
+ if (new TextDecoder('utf-8', { fatal: true }).decode(bytes) !== JSON.stringify({ profile: document.profile, standing: document.standing }))
56
+ refuse('noncanonical document wrapper');
57
+ const lines = readSemanticStanding(document.standing);
58
+ const semantic = semanticStandingBytes(lines);
59
+ if (digestBytes(new TextEncoder().encode(semantic)) !== reference.digest)
60
+ refuse('standing identity');
61
+ return { reference, lines, standing: semantic, authentication: 'not demonstrated; inspect only' };
62
+ }
@@ -0,0 +1,6 @@
1
+ export declare function inspectReference(fragment: string, policy: readonly string[]): Promise<{
2
+ reference: import("./reference-profile.ts").StandingReference;
3
+ lines: readonly string[];
4
+ standing: string;
5
+ authentication: string;
6
+ }>;
@@ -0,0 +1,24 @@
1
+ /** Host composition. Location supplies bytes, never identity or authority. No store writes. */
2
+ import { createHash } from 'node:crypto';
3
+ import { readSemanticStanding, semanticStandingBytes } from '@lapxo/topos/core';
4
+ import { parse, transportSelection } from '@lapxo/topos/wire';
5
+ import { transportBytes } from "./ports/transport.js";
6
+ import { decodeReference, inspectReferenceBytes } from "./reference-profile.js";
7
+ export async function inspectReference(fragment, policy) {
8
+ const reference = decodeReference(fragment), scheme = new URL(reference.source).protocol.slice(0, -1);
9
+ const fields = policy.flatMap(line => { const got = parse(line); return got.kind === 'fact' ? [got.value.fields] : []; });
10
+ const selected = transportSelection(fields, scheme);
11
+ if (selected === undefined)
12
+ throw Error(`REFUSE·transport ${scheme} no admitted content-request/1 policy`);
13
+ let bytes;
14
+ try {
15
+ bytes = await transportBytes(reference.source, reference.blob, policy);
16
+ }
17
+ catch (error) {
18
+ const cause = error instanceof Error ? error.message : 'transport failed';
19
+ if (cause.startsWith('REFUSE·'))
20
+ throw error;
21
+ throw Error(`REFUSE·transport ${scheme} ${cause}`);
22
+ }
23
+ return inspectReferenceBytes(reference, bytes, { maxBytes: selected.responseBytes, readSemanticStanding, semanticStandingBytes, digestBytes: value => createHash('sha256').update(value).digest('hex') });
24
+ }