@lapxo/bound 0.1.9 → 0.2.1

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 (319) hide show
  1. package/README.md +40 -121
  2. package/TARGET.bound +85 -14
  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/pass.js +3 -3
  8. package/dist/cli/place.js +7 -4
  9. package/dist/cli/standing-result.js +4 -3
  10. package/dist/cli/verb.js +79 -9
  11. package/dist/engine/encoding.js +2 -0
  12. package/dist/engine/index.d.ts +13 -0
  13. package/dist/engine/index.js +11 -0
  14. package/dist/engine/object.js +38 -0
  15. package/dist/engine/ports.d.ts +32 -0
  16. package/dist/engine/ports.js +103 -0
  17. package/dist/fold/closed.js +22 -5
  18. package/dist/fold/digests.js +3 -2
  19. package/dist/fold/object.js +2 -38
  20. package/dist/fold/resolved.js +1 -1
  21. package/dist/host/admitted-place.js +10 -1
  22. package/dist/host/cell-receipts.js +1 -1
  23. package/dist/host/contextual-place.js +2 -2
  24. package/dist/host/io.js +2 -2
  25. package/dist/host/objects.js +24 -4
  26. package/dist/host/reading-closures.js +11 -13
  27. package/dist/host/reference-authenticated.d.ts +40 -0
  28. package/dist/host/reference-authenticated.js +100 -0
  29. package/dist/host/reference-contracts.js +6 -0
  30. package/dist/host/reference-export.d.ts +33 -0
  31. package/dist/host/reference-export.js +81 -0
  32. package/dist/host/reference-native-api.d.ts +3 -0
  33. package/dist/host/reference-native-api.js +3 -0
  34. package/dist/host/reference-native.d.ts +30 -0
  35. package/dist/host/reference-native.js +74 -0
  36. package/dist/host/reference-profile.d.ts +23 -0
  37. package/dist/host/reference-profile.js +63 -0
  38. package/dist/host/reference-standing.js +18 -0
  39. package/dist/host/reference.js +24 -0
  40. package/dist/host/refusals.d.ts +14 -0
  41. package/dist/host/refusals.js +43 -0
  42. package/dist/host/selected-topos.js +9 -1
  43. package/dist/host/signing.js +7 -1
  44. package/dist/observe/files.js +11 -1
  45. package/dist/observe/regions.js +109 -1
  46. package/dist/wasm/adapter.d.ts +51 -0
  47. package/dist/wasm/adapter.js +180 -0
  48. package/dist/wasm/node-worker.js +13 -0
  49. package/dist/wasm/object.js +76 -0
  50. package/dist/wasm/worker.js +7 -0
  51. package/docs/contracts/authenticated-reference.publication.json +25 -0
  52. package/docs/contracts/engine-object-projection.publication.json +25 -0
  53. package/docs/contracts/engine.md +39 -0
  54. package/docs/contracts/host-ports.md +23 -0
  55. package/docs/contracts/host-ports.publication.json +25 -0
  56. package/docs/contracts/native-publication.inventory.json +14 -0
  57. package/docs/contracts/optional-wasm-adapter.publication.json +25 -0
  58. package/docs/contracts/publication-additions.inventory.json +8 -0
  59. package/docs/contracts/readme.publication.json +25 -0
  60. package/docs/contracts/reference-backend.md +24 -0
  61. package/docs/contracts/reference-backend.publication.json +25 -0
  62. package/docs/contracts/reference-export.md +31 -0
  63. package/docs/contracts/reference-export.publication.json +25 -0
  64. package/docs/contracts/reference-port.md +40 -0
  65. package/docs/contracts/reference-port.publication.json +25 -0
  66. package/docs/contracts/refusals.bound +94 -0
  67. package/docs/contracts/refusals.md +47 -0
  68. package/docs/contracts/refusals.publication.json +25 -0
  69. package/docs/contracts/standing.bound +9 -0
  70. package/docs/contracts/standing.md +24 -0
  71. package/docs/contracts/standing.publication.json +25 -0
  72. package/licenses/wasm/bound.txt +21 -0
  73. package/licenses/wasm/fast-text-encoding.txt +201 -0
  74. package/licenses/wasm/jitl-quickjs-ffi-types.txt +21 -0
  75. package/licenses/wasm/jitl-quickjs-wasmfile-release-sync.txt +47 -0
  76. package/licenses/wasm/obligations.txt +21 -0
  77. package/licenses/wasm/quickjs-emscripten-core.txt +21 -0
  78. package/licenses/wasm/topos.txt +21 -0
  79. package/package.json +141 -6
  80. package/receipts.bound +9554 -613
  81. package/samples/authenticated-reference/compatibility.json +306 -0
  82. package/samples/authenticated-reference/no.json +31 -0
  83. package/samples/authenticated-reference/yes/editorial-state/TARGET.bound +57 -0
  84. package/samples/authenticated-reference/yes/editorial-state/inputs/arrival.json +1 -0
  85. package/samples/authenticated-reference/yes/editorial-state/inputs/coverage.json +1 -0
  86. package/samples/authenticated-reference/yes/editorial-state/inputs/era.json +1 -0
  87. package/samples/authenticated-reference/yes/editorial-state/inputs/order.json +1 -0
  88. package/samples/authenticated-reference/yes/editorial-state/inputs/signature.json +1 -0
  89. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.arrival.json +1 -0
  90. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.coverage.json +1 -0
  91. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.era.json +1 -0
  92. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.order.json +1 -0
  93. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.signature.json +1 -0
  94. package/samples/authenticated-reference/yes/editorial-state/inputs/transport.yes.json +1 -0
  95. package/samples/authenticated-reference/yes/editorial-state/inputs/yes.json +1 -0
  96. package/samples/authenticated-reference/yes/weather-reading/TARGET.bound +57 -0
  97. package/samples/authenticated-reference/yes/weather-reading/inputs/arrival.json +1 -0
  98. package/samples/authenticated-reference/yes/weather-reading/inputs/coverage.json +1 -0
  99. package/samples/authenticated-reference/yes/weather-reading/inputs/era.json +1 -0
  100. package/samples/authenticated-reference/yes/weather-reading/inputs/order.json +1 -0
  101. package/samples/authenticated-reference/yes/weather-reading/inputs/signature.json +1 -0
  102. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.arrival.json +1 -0
  103. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.coverage.json +1 -0
  104. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.era.json +1 -0
  105. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.order.json +1 -0
  106. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.signature.json +1 -0
  107. package/samples/authenticated-reference/yes/weather-reading/inputs/transport.yes.json +1 -0
  108. package/samples/authenticated-reference/yes/weather-reading/inputs/yes.json +1 -0
  109. package/samples/authenticated-reference/yes.json +19 -0
  110. package/samples/engine-alphabet/TARGET.bound +29 -0
  111. package/samples/engine-alphabet/inputs/sample.json +323 -0
  112. package/samples/engine-alphabet/package.json +1 -0
  113. package/samples/engine-alphabet/reader.js +1457 -0
  114. package/samples/engine-alphabet/receipts.bound +12 -0
  115. package/samples/engine-interval/TARGET.bound +29 -0
  116. package/samples/engine-interval/inputs/sample.json +277 -0
  117. package/samples/engine-interval/package.json +1 -0
  118. package/samples/engine-interval/reader.js +1457 -0
  119. package/samples/engine-interval/receipts.bound +12 -0
  120. package/samples/host-ports/no.json +16 -0
  121. package/samples/host-ports/yes.json +9 -0
  122. package/samples/no/engine-order/TARGET.bound +29 -0
  123. package/samples/no/engine-order/inputs/sample.json +277 -0
  124. package/samples/no/engine-order/package.json +1 -0
  125. package/samples/no/engine-order/reader.js +1457 -0
  126. package/samples/readme/no.json +1 -0
  127. package/samples/readme/yes.json +1 -0
  128. package/samples/reference-backend/no.json +1 -0
  129. package/samples/reference-backend/world-fixture.json +8 -0
  130. package/samples/reference-backend/yes.json +1 -0
  131. package/samples/reference-export/no.json +5 -0
  132. package/samples/reference-export/yes.json +15 -0
  133. package/samples/reference-port/no/catalog/TARGET.bound +33 -0
  134. package/samples/reference-port/no/catalog/standing.json +1 -0
  135. package/samples/reference-port/no/catalog/transport.json +1 -0
  136. package/samples/reference-port/no/route-notes/TARGET.bound +33 -0
  137. package/samples/reference-port/no/route-notes/standing.json +1 -0
  138. package/samples/reference-port/no/route-notes/transport.json +1 -0
  139. package/samples/reference-port/yes/catalog/TARGET.bound +33 -0
  140. package/samples/reference-port/yes/catalog/standing.json +1 -0
  141. package/samples/reference-port/yes/catalog/transport.json +1 -0
  142. package/samples/reference-port/yes/route-notes/TARGET.bound +33 -0
  143. package/samples/reference-port/yes/route-notes/standing.json +1 -0
  144. package/samples/reference-port/yes/route-notes/transport.json +1 -0
  145. package/samples/refusals/no.json +4 -0
  146. package/samples/refusals/yes.json +9 -0
  147. package/samples/standing/no.json +4 -0
  148. package/samples/standing/yes.json +21 -0
  149. package/samples/wasm/editorial-notes/TARGET.bound +65 -0
  150. package/samples/wasm/editorial-notes/esm.bound +59 -0
  151. package/samples/wasm/editorial-notes/no-deadline.bound +65 -0
  152. package/samples/wasm/editorial-notes/no-memory.bound +65 -0
  153. package/samples/wasm/editorial-notes/no-missing-limit.bound +64 -0
  154. package/samples/wasm/editorial-notes/receipts.bound +1 -0
  155. package/samples/wasm/water-level/TARGET.bound +65 -0
  156. package/samples/wasm/water-level/esm.bound +59 -0
  157. package/samples/wasm/water-level/no-deadline.bound +65 -0
  158. package/samples/wasm/water-level/no-memory.bound +65 -0
  159. package/samples/wasm/water-level/no-missing-limit.bound +64 -0
  160. package/samples/wasm/water-level/receipts.bound +1 -0
  161. package/vectors/engine-object.cli.json +548 -0
  162. package/vectors/native/authenticated-reference-formation.vector.json +107 -0
  163. package/vectors/native/authenticated-reference.cli.json +157 -0
  164. package/vectors/native/authenticated-reference.vector.json +76 -0
  165. package/vectors/native/host-ports.vector.json +107 -0
  166. package/vectors/native/readme.vector.json +76 -0
  167. package/vectors/native/reference-backend.vector.json +84 -0
  168. package/vectors/native/reference-export.vector.json +78 -0
  169. package/vectors/native/refusals.vector.json +95 -0
  170. package/vectors/native/standing.vector.json +116 -0
  171. package/vectors/reference-port.cli.json +688 -0
  172. package/vectors/wasm-object.cli.json +407 -0
  173. package/dist/cli/accept.d.ts +0 -28
  174. package/dist/cli/act-result.d.ts +0 -12
  175. package/dist/cli/args.d.ts +0 -11
  176. package/dist/cli/custodian.d.ts +0 -1
  177. package/dist/cli/host.d.ts +0 -1
  178. package/dist/cli/names.d.ts +0 -25
  179. package/dist/cli/native-descendant.d.ts +0 -1
  180. package/dist/cli/native.d.ts +0 -1
  181. package/dist/cli/observe.d.ts +0 -1
  182. package/dist/cli/owner.d.ts +0 -37
  183. package/dist/cli/pass.d.ts +0 -6
  184. package/dist/cli/place.d.ts +0 -74
  185. package/dist/cli/release-result.d.ts +0 -5
  186. package/dist/cli/standing-result.d.ts +0 -5
  187. package/dist/cli/takes.d.ts +0 -30
  188. package/dist/fold/admission-history.d.ts +0 -6
  189. package/dist/fold/ceilings.d.ts +0 -24
  190. package/dist/fold/claims.d.ts +0 -29
  191. package/dist/fold/closed.d.ts +0 -15
  192. package/dist/fold/configures.d.ts +0 -4
  193. package/dist/fold/digests.d.ts +0 -29
  194. package/dist/fold/evidence.d.ts +0 -32
  195. package/dist/fold/fold-worker.d.ts +0 -1
  196. package/dist/fold/kept.d.ts +0 -29
  197. package/dist/fold/keys.d.ts +0 -17
  198. package/dist/fold/laid.d.ts +0 -17
  199. package/dist/fold/nos.d.ts +0 -8
  200. package/dist/fold/observed.d.ts +0 -35
  201. package/dist/fold/opened.d.ts +0 -5
  202. package/dist/fold/own.d.ts +0 -24
  203. package/dist/fold/paid.d.ts +0 -11
  204. package/dist/fold/place-inputs.d.ts +0 -17
  205. package/dist/fold/places.d.ts +0 -24
  206. package/dist/fold/rank.d.ts +0 -3
  207. package/dist/fold/reach.d.ts +0 -18
  208. package/dist/fold/readers.d.ts +0 -47
  209. package/dist/fold/receipts.d.ts +0 -8
  210. package/dist/fold/region-declarations.d.ts +0 -2
  211. package/dist/fold/region.d.ts +0 -72
  212. package/dist/fold/release-policy.d.ts +0 -67
  213. package/dist/fold/resolved.d.ts +0 -18
  214. package/dist/fold/roles.d.ts +0 -17
  215. package/dist/fold/second.d.ts +0 -18
  216. package/dist/fold/shards.d.ts +0 -29
  217. package/dist/fold/signed.d.ts +0 -23
  218. package/dist/fold/signers.d.ts +0 -24
  219. package/dist/fold/stranger.d.ts +0 -3
  220. package/dist/fold/views.d.ts +0 -27
  221. package/dist/fold/wire.d.ts +0 -19
  222. package/dist/fold/zoom.d.ts +0 -16
  223. package/dist/host/act-cost.d.ts +0 -13
  224. package/dist/host/adapters/keys/ed25519.d.ts +0 -7
  225. package/dist/host/adapters/modules/javascript.d.ts +0 -3
  226. package/dist/host/adapters/transport/http.d.ts +0 -5
  227. package/dist/host/admitted-place.d.ts +0 -5
  228. package/dist/host/archive.d.ts +0 -11
  229. package/dist/host/capsule-process.d.ts +0 -21
  230. package/dist/host/capsule.d.ts +0 -19
  231. package/dist/host/cell-receipts.d.ts +0 -2
  232. package/dist/host/content-needed.d.ts +0 -5
  233. package/dist/host/content-store.d.ts +0 -8
  234. package/dist/host/contextual-place.d.ts +0 -59
  235. package/dist/host/digest.d.ts +0 -3
  236. package/dist/host/hash.d.ts +0 -9
  237. package/dist/host/io.d.ts +0 -11
  238. package/dist/host/lifetime.d.ts +0 -1
  239. package/dist/host/located.d.ts +0 -4
  240. package/dist/host/native/bounded-file.d.ts +0 -8
  241. package/dist/host/native/command.d.ts +0 -36
  242. package/dist/host/native/control-bridge-worker.d.ts +0 -1
  243. package/dist/host/native/control-bridge.d.ts +0 -30
  244. package/dist/host/native/control-selection.d.ts +0 -29
  245. package/dist/host/native/custody.d.ts +0 -5
  246. package/dist/host/native/deadline.d.ts +0 -12
  247. package/dist/host/native/descendants.d.ts +0 -1
  248. package/dist/host/native/generated-custodian.d.ts +0 -122
  249. package/dist/host/native/generated.d.ts +0 -217
  250. package/dist/host/native/image.d.ts +0 -36
  251. package/dist/host/native/jobs.d.ts +0 -75
  252. package/dist/host/native/loader.d.ts +0 -80
  253. package/dist/host/native/materialize.d.ts +0 -22
  254. package/dist/host/native/module-closure.d.ts +0 -27
  255. package/dist/host/native/module-syntax.d.ts +0 -8
  256. package/dist/host/native/native-binary.d.ts +0 -54
  257. package/dist/host/native/native-program.d.ts +0 -56
  258. package/dist/host/native/node-jobs.d.ts +0 -84
  259. package/dist/host/native/node-launch.d.ts +0 -72
  260. package/dist/host/native/preload.d.ts +0 -1
  261. package/dist/host/native/reading.d.ts +0 -91
  262. package/dist/host/native/receipt-environment.d.ts +0 -3
  263. package/dist/host/native/references.d.ts +0 -5
  264. package/dist/host/native/root.d.ts +0 -17
  265. package/dist/host/native/route.d.ts +0 -66
  266. package/dist/host/native/runtime.d.ts +0 -4
  267. package/dist/host/native-reader-context.d.ts +0 -3
  268. package/dist/host/objects.d.ts +0 -5
  269. package/dist/host/place-clock-selection.d.ts +0 -55
  270. package/dist/host/ports/ingress.d.ts +0 -24
  271. package/dist/host/ports/local-act.d.ts +0 -44
  272. package/dist/host/ports/place-act.d.ts +0 -46
  273. package/dist/host/ports/publication.d.ts +0 -10
  274. package/dist/host/ports/signing.d.ts +0 -31
  275. package/dist/host/ports/store.d.ts +0 -2
  276. package/dist/host/ports/transport.d.ts +0 -1
  277. package/dist/host/process-lifetime.d.ts +0 -11
  278. package/dist/host/provide.d.ts +0 -48
  279. package/dist/host/read-only.d.ts +0 -3
  280. package/dist/host/reading-closures.d.ts +0 -45
  281. package/dist/host/reading-context.d.ts +0 -29
  282. package/dist/host/release.d.ts +0 -14
  283. package/dist/host/root-clock-source.d.ts +0 -85
  284. package/dist/host/selected-capsules.d.ts +0 -8
  285. package/dist/host/selected-topos.d.ts +0 -24
  286. package/dist/host/signing.d.ts +0 -3
  287. package/dist/host/source-content.d.ts +0 -12
  288. package/dist/host/timing.d.ts +0 -2
  289. package/dist/host/walk.d.ts +0 -6
  290. package/dist/judge/late.d.ts +0 -7
  291. package/dist/judge/take.d.ts +0 -59
  292. package/dist/judge/widening.d.ts +0 -1
  293. package/dist/land/act.d.ts +0 -30
  294. package/dist/land/alias.d.ts +0 -10
  295. package/dist/land/held.d.ts +0 -7
  296. package/dist/land/inert.d.ts +0 -26
  297. package/dist/land/leaving.d.ts +0 -14
  298. package/dist/land/ledger.d.ts +0 -33
  299. package/dist/land/owned.d.ts +0 -23
  300. package/dist/land/sign.d.ts +0 -15
  301. package/dist/land/standing-pin.d.ts +0 -3
  302. package/dist/land/vouched.d.ts +0 -21
  303. package/dist/land/withdraw.d.ts +0 -14
  304. package/dist/observe/files.d.ts +0 -14
  305. package/dist/observe/identity.d.ts +0 -21
  306. package/dist/observe/match.d.ts +0 -1
  307. package/dist/observe/regions.d.ts +0 -29
  308. package/dist/observe/run.d.ts +0 -31
  309. package/dist/observe/runner.d.ts +0 -20
  310. package/dist/render/capsule.d.ts +0 -24
  311. package/dist/render/field.d.ts +0 -3
  312. package/dist/render/names.d.ts +0 -3
  313. package/dist/render/open.d.ts +0 -27
  314. package/dist/render/page.d.ts +0 -3
  315. package/dist/render/receipts.d.ts +0 -3
  316. package/dist/render/shape.d.ts +0 -16
  317. package/dist/render/target.d.ts +0 -18
  318. package/dist/render/view.d.ts +0 -22
  319. /package/dist/{fold → engine}/object.d.ts +0 -0
@@ -0,0 +1,81 @@
1
+ import { installedReferenceContracts } from "./reference-contracts.js";
2
+ import { BoundRefusal, describeRefusal, readRefusalCatalog } from "./refusals.js";
3
+ import { readFileSync } from 'node:fs';
4
+ import { authorityAtEpoch, canonical, parse } from '@lapxo/topos/wire';
5
+ import { sha256 as hash } from "./hash.js";
6
+ import { signerFor } from "./signing.js";
7
+ import { signConsentLot } from "../land/sign.js";
8
+ import { underRootAuthority } from "../land/act.js";
9
+ import { storeOf } from "../land/ledger.js";
10
+ import { authorityFor, rootSigner } from "../fold/signers.js";
11
+ import { admissionAnchor, admissionHistoryAt } from "../fold/admission-history.js";
12
+ import { authenticatedRootStanding } from "./admitted-place.js";
13
+ import { eraOf } from "../fold/digests.js";
14
+ import { epochOf } from "../cli/owner.js";
15
+ import { fieldOf } from "../fold/claims.js";
16
+ import { prepareNativeReference } from "./reference-native.js";
17
+ import { referenceContextBytes, referenceContracts, referenceStanding } from "./reference-standing.js";
18
+ import { encodeReference } from "./reference-profile.js";
19
+ const catalog = () => readFileSync(new URL('../../docs/contracts/refusals.bound', import.meta.url), 'utf8');
20
+ export { BoundRefusal as ReferenceExportRefusal } from "./refusals.js";
21
+ function fail(code) {
22
+ const refusal = new BoundRefusal('reference-export/' + code, 'REFUSE·reference-export/' + code);
23
+ const entry = describeRefusal(refusal, readRefusalCatalog(catalog()));
24
+ throw new BoundRefusal(entry.code, 'REFUSE·' + entry.code + ' ' + entry.meaning);
25
+ }
26
+ /** Signs a public snapshot proof; never lands, refreshes evidence or changes the Root. */
27
+ export async function exportAuthenticatedReference(root, source, options = {}) {
28
+ try {
29
+ encodeReference({ class: 'standing', algorithm: 'sha256', digest: '0'.repeat(64), blob: 'sha256:' + '0'.repeat(64), source });
30
+ }
31
+ catch {
32
+ fail('source');
33
+ }
34
+ return underRootAuthority(storeOf(root), 'reference export', async () => {
35
+ const history = admissionHistoryAt(root, Number.MAX_SAFE_INTEGER), standing = authenticatedRootStanding(root), epoch = epochOf(history), era = eraOf(standing);
36
+ const authority = authorityFor(history, admissionAnchor(root), era.admitted);
37
+ const one = (scope, measure = 'id', form = 'alphabet') => { const rows = standing.filter(line => fieldOf(line, 'scope') === scope && fieldOf(line, 'value') !== 'withdraw'); if (rows.length !== 1 || fieldOf(rows[0], 'role') !== 'writes' || fieldOf(rows[0], 'measure') !== measure || fieldOf(rows[0], 'form') !== form || authority.of(rows[0]).kind !== 'admitted')
38
+ fail('context'); return fieldOf(rows[0], 'value'); };
39
+ if (one('reference/authentication') !== 'authenticated-standing@1')
40
+ fail('context');
41
+ const proofScope = one('reference/proof-scope'), contracts = { refusals: one('reference/contracts/refusals'), standing: one('reference/contracts/standing') };
42
+ const installed = installedReferenceContracts();
43
+ if (!referenceContracts(contracts) || contracts.refusals !== installed.refusals || contracts.standing !== installed.standing)
44
+ fail('context');
45
+ const count = one('reference/records'), maxRecords = Number(count), range = /^(0|[1-9][0-9]*)\.\.(0|[1-9][0-9]*)$/.exec(one('reference/export-bytes', 'bytes', 'interval'));
46
+ if (!/^(0|[1-9][0-9]*)$/.test(count) || !Number.isSafeInteger(maxRecords) || maxRecords < 1 || !range || Number(range[1]) > Number(range[2]) || !Number.isSafeInteger(Number(range[2])) || Number(range[2]) < 1)
47
+ fail('budget');
48
+ const maxBytes = Number(range[2]);
49
+ const originals = standing.filter(line => fieldOf(line, 'value') !== 'withdraw' || fieldOf(line, 'type') !== '');
50
+ if (!originals.length || originals.length > maxRecords || Buffer.byteLength(originals.join('\n')) > maxBytes)
51
+ fail('budget');
52
+ if (originals.some(line => authority.of(line).kind !== 'admitted'))
53
+ fail('history');
54
+ const key = options.key ?? rootSigner(root).id, signer = authority.admitted.find(s => s.id === key);
55
+ const stamp = canonical({ scope: proofScope, role: 'writes', form: 'alphabet', measure: 'digest', value: hash(JSON.stringify(['authenticated-standing@1', originals]) + '\n'), by: 'target', at: 'witness:' + 'sha256:' + '0'.repeat(64) });
56
+ const probe = parse(stamp);
57
+ if (!signer || probe.kind !== 'fact' || authorityAtEpoch({ ...probe.value.fields, by: key, epoch: String(epoch) }, signer, epoch, proofScope, 'authority').kind !== 'admitted')
58
+ fail('authority');
59
+ const held = await prepareNativeReference(root), semantic = referenceStanding(originals, held.records), semanticIdentity = hash(semantic);
60
+ const context = { profile: 'reference-export-context@1', semanticIdentity, epoch, proofScope, policy: held.policy, formImplementation: held.formImplementation, signers: authority.admitted, algorithms: era.admitted, contracts, maxBytes, maxRecords };
61
+ const snapshot = hash(referenceContextBytes(context));
62
+ if (Buffer.byteLength(JSON.stringify({ originals, context, snapshot }) + '\n') > maxBytes)
63
+ fail('budget');
64
+ const drafted = canonical({ ...probe.value.fields, at: 'witness:' + snapshot });
65
+ let signed;
66
+ try {
67
+ signed = await signConsentLot([drafted], { epoch, by: key, algorithm: era.era }, signer.publicKey, era.admitted, signerFor(standing, key, options.signer, options.keyFile, options.hostSigner));
68
+ }
69
+ catch {
70
+ fail('signer');
71
+ }
72
+ if (signed.kind !== 'fact')
73
+ fail('signer');
74
+ const carrier = JSON.stringify(['authenticated-standing@1', originals, signed.lines[0]]) + '\n';
75
+ const reference = encodeReference({ class: 'standing', algorithm: 'sha256', digest: semanticIdentity.slice(7), blob: hash(carrier), source });
76
+ const result = { profile: 'reference-export@1', reference, carrier, context: { ...context, snapshot } };
77
+ if (Buffer.byteLength(JSON.stringify(result) + '\n') > maxBytes)
78
+ fail('budget');
79
+ return result;
80
+ });
81
+ }
@@ -0,0 +1,3 @@
1
+ /** Native host boundary. The platform-free reference inspector remains /reference. */
2
+ export { openAuthenticatedReference } from './reference-native.ts';
3
+ export { exportAuthenticatedReference } from './reference-export.ts';
@@ -0,0 +1,3 @@
1
+ /** Native host boundary. The platform-free reference inspector remains /reference. */
2
+ export { openAuthenticatedReference } from "./reference-native.js";
3
+ export { exportAuthenticatedReference } from "./reference-export.js";
@@ -0,0 +1,30 @@
1
+ import { type ContentReadPort } from '../engine/ports.ts';
2
+ export declare function authenticatedReferenceSelected(standing: readonly string[]): boolean;
3
+ /** One context preparation shared by native export and native inspection. */
4
+ export declare function prepareNativeReference(root: string): Promise<{
5
+ policy: string;
6
+ formImplementation: string;
7
+ records: readonly import("@lapxo/topos/wire").ObjectRecord[];
8
+ context: import("@lapxo/topos/wire").ObjectContext;
9
+ configuration: Readonly<Record<string, string>>[];
10
+ }>;
11
+ export declare function openAuthenticatedReference(root: string, fragment: string, options?: {
12
+ readonly signal?: AbortSignal;
13
+ readonly content?: {
14
+ readonly name: string;
15
+ readonly read: ContentReadPort;
16
+ };
17
+ }): Promise<{
18
+ backend: string;
19
+ readings: readonly import("../engine/object.ts").ObjectReading[];
20
+ contracts?: {
21
+ refusals: string;
22
+ standing: string;
23
+ } | undefined;
24
+ reference: import("./reference-profile.ts").StandingReference;
25
+ records: readonly import("@lapxo/topos/wire").ObjectRecord[];
26
+ semanticIdentity: string;
27
+ authorityContextIdentity: string;
28
+ semanticStanding: string;
29
+ originalRecords: readonly string[];
30
+ }>;
@@ -0,0 +1,74 @@
1
+ import { readContent } from "../engine/ports.js";
2
+ import { installedReferenceContracts } from "./reference-contracts.js";
3
+ import { BoundRefusal } from "./refusals.js";
4
+ import { foldObjectsWithWasm } from "../wasm/object.js";
5
+ /** Native ports for authenticated #bound opening; trusted policy belongs to the selected local place. */
6
+ import { sha256 as hash, isSha256 } from "./hash.js";
7
+ import { canonical, parse, transportSelection } from '@lapxo/topos/wire';
8
+ import { semanticStandingBytes } from '@lapxo/topos/standing';
9
+ import { authorityFor, rootSigner } from "../fold/signers.js";
10
+ import { eraOf } from "../fold/digests.js";
11
+ import { lockStanding, lockLines } from "../fold/keys.js";
12
+ import { signedOwnerLines } from "../cli/owner.js";
13
+ import { admittedObjects } from "./objects.js";
14
+ import { storeOf } from "../land/ledger.js";
15
+ import { objectFold } from "../engine/index.js";
16
+ import { toposAt, contentAt } from "./selected-topos.js";
17
+ import { transportBytes } from "./ports/transport.js";
18
+ import { decodeReference } from "./reference-profile.js";
19
+ import { inspectAuthenticatedReference } from "./reference-authenticated.js";
20
+ import { verifyBytes } from "./adapters/keys/ed25519.js";
21
+ export function authenticatedReferenceSelected(standing) {
22
+ const selected = standing.flatMap(line => { const p = parse(line); return p.kind === 'fact' && p.value.fields.scope === 'reference/authentication' ? [p.value.fields] : []; });
23
+ if (!selected.length)
24
+ return false;
25
+ if (selected.length !== 1 || selected[0].value !== 'authenticated-standing@1')
26
+ throw Error('REFUSE·reference conflicting or unknown authentication profile');
27
+ return true;
28
+ }
29
+ /** One context preparation shared by native export and native inspection. */
30
+ export async function prepareNativeReference(root) {
31
+ const store = storeOf(root), held = await admittedObjects(root, signedOwnerLines(store), () => { }, true);
32
+ const policy = held.configuration.map(fields => canonical(fields));
33
+ 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)
34
+ 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)
35
+ 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)
36
+ contentAt(digest, store); return [pin, offer.value, closure]; });
37
+ return { ...held, policy: hash(semanticStandingBytes(policy)), formImplementation: hash(JSON.stringify(implementations)) };
38
+ }
39
+ export async function openAuthenticatedReference(root, fragment, options = {}) {
40
+ const store = storeOf(root), standing = lockStanding(store), era = eraOf(standing), authority = authorityFor(lockLines(store), rootSigner(root), era.admitted);
41
+ if (!authenticatedReferenceSelected(standing))
42
+ throw Error('REFUSE·reference no admitted authenticated-standing@1 profile');
43
+ 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' || found[0].fields.form !== 'alphabet' || authority.of(found[0].line).kind !== 'admitted')
44
+ throw new BoundRefusal(scope === 'engine/object' ? 'reference/backend' : 'authenticated-reference/context', 'REFUSE·reference missing, conflicting or unauthenticated ' + scope); return found[0].fields.value; };
45
+ if (one('reference/authentication') !== 'authenticated-standing@1')
46
+ throw Error('REFUSE·reference authentication profile');
47
+ const snapshot = one('reference/snapshot'), epochText = one('reference/epoch'), proofScope = one('reference/proof-scope'), recordsText = one('reference/records');
48
+ if (!isSha256(snapshot) || !/^\d+$/.test(epochText) || !Number.isSafeInteger(Number(epochText)) || !/^\d+$/.test(recordsText) || !Number.isSafeInteger(Number(recordsText)) || Number(recordsText) < 1 || !proofScope)
49
+ throw Error('REFUSE·reference authority snapshot or resource contract');
50
+ const backend = one('engine/object');
51
+ if (!['esm@1', 'wasm@1'].includes(backend))
52
+ throw new BoundRefusal('reference/backend', 'REFUSE·reference unsupported declared backend');
53
+ if (options.signal?.aborted)
54
+ throw new BoundRefusal('reference/cancelled', 'REFUSE·reference cancelled');
55
+ const reference = decodeReference(fragment);
56
+ // Provider selection is made from HOST-declared cells/configuration, never carrier-picked code.
57
+ const held = await prepareNativeReference(root);
58
+ if (!held.records.some(r => r.record === 'cell'))
59
+ throw Error('REFUSE·reference host has no declared object interpretation');
60
+ const context = held.context, policy = held.configuration.map(fields => canonical(fields)), selection = transportSelection(held.configuration, new URL(reference.source).protocol.slice(0, -1));
61
+ if (!selection)
62
+ throw Error('REFUSE·reference no admitted transport policy');
63
+ if (options.content && options.content.name !== selection.name)
64
+ throw new BoundRefusal('authenticated-reference/transport', 'REFUSE·reference injected content binding conflicts with declaration');
65
+ const contracts = { refusals: one('reference/contracts/refusals'), standing: one('reference/contracts/standing') }, expectedPolicy = one('reference/policy'), expectedImplementation = one('reference/form-implementation');
66
+ const installed = installedReferenceContracts();
67
+ if (contracts.refusals !== installed.refusals || contracts.standing !== installed.standing || !isSha256(expectedPolicy) || expectedImplementation !== held.formImplementation)
68
+ throw new BoundRefusal('authenticated-reference/context', 'REFUSE·reference expected interpretation differs');
69
+ const result = await inspectAuthenticatedReference(fragment, { snapshot, epoch: Number(epochText), proofScope, contracts, policy: expectedPolicy, formImplementation: expectedImplementation, signers: authority.admitted, algorithms: era.admitted, objects: context, maxBytes: selection.responseBytes, maxRecords: Number(recordsText), digestBytes: hash, verifyBytes, fetchBytes: (source, blob) => options.content ? readContent(source, blob, { read: options.content.read, digestBytes: hash }, { timeoutMs: selection.timeoutMs, maxBytes: selection.responseBytes }, options.signal) : transportBytes(source, blob, policy) });
70
+ if (options.signal?.aborted)
71
+ throw new BoundRefusal('reference/cancelled', 'REFUSE·reference cancelled');
72
+ const readings = backend === 'wasm@1' ? await foldObjectsWithWasm(root, result.records, context, held.configuration, options.signal) : objectFold(result.records, context);
73
+ return { ...result, backend, readings };
74
+ }
@@ -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,63 @@
1
+ import { BoundRefusal } from "./refusals.js";
2
+ const hex = /^[0-9a-f]{64}$/;
3
+ const refuse = (cause) => { throw new BoundRefusal('reference/format', `REFUSE·reference ${cause}`); };
4
+ const keys = ['class', 'algorithm', 'digest', 'blob', 'source'];
5
+ function check(reference) {
6
+ if (Object.keys(reference).length !== keys.length || keys.some(key => !Object.prototype.hasOwnProperty.call(reference, key)))
7
+ refuse('fields');
8
+ if (reference.class !== 'standing')
9
+ refuse('unknown class');
10
+ if (reference.algorithm !== 'sha256')
11
+ refuse('unknown algorithm');
12
+ if (!hex.test(reference.digest) || !/^sha256:[0-9a-f]{64}$/.test(reference.blob))
13
+ refuse('digest');
14
+ let source;
15
+ try {
16
+ source = new URL(reference.source);
17
+ }
18
+ catch {
19
+ return refuse('source URL');
20
+ }
21
+ if (source.username || source.password || source.hash)
22
+ refuse('source credentials or fragment');
23
+ if (source.href !== reference.source)
24
+ refuse('noncanonical source URL');
25
+ return reference;
26
+ }
27
+ export function encodeReference(reference) {
28
+ check(reference);
29
+ return '#bound:1?' + keys.map(key => key + '=' + encodeURIComponent(reference[key])).join('&');
30
+ }
31
+ export function decodeReference(fragment) {
32
+ if (!fragment.startsWith('#bound:1?'))
33
+ refuse('profile');
34
+ const parameters = new URLSearchParams(fragment.slice('#bound:1?'.length));
35
+ if ([...parameters].length !== keys.length || new Set(parameters.keys()).size !== keys.length)
36
+ refuse('duplicate or missing field');
37
+ const reference = check(Object.fromEntries(parameters));
38
+ if (encodeReference(reference) !== fragment)
39
+ refuse('noncanonical encoding');
40
+ return Object.freeze(reference);
41
+ }
42
+ export function inspectReferenceBytes(reference, bytes, { maxBytes, readSemanticStanding, semanticStandingBytes, digestBytes }) {
43
+ if (bytes.byteLength > maxBytes)
44
+ refuse('bytes budget');
45
+ if (digestBytes(bytes) !== reference.blob.slice(7))
46
+ refuse('blob integrity');
47
+ let document;
48
+ try {
49
+ document = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
50
+ }
51
+ catch {
52
+ refuse('document');
53
+ }
54
+ if (document === null || typeof document !== 'object' || document.profile !== 'semantic-standing@1' || typeof document.standing !== 'string' || Object.keys(document).length !== 2)
55
+ refuse('document profile');
56
+ if (new TextDecoder('utf-8', { fatal: true }).decode(bytes) !== JSON.stringify({ profile: document.profile, standing: document.standing }))
57
+ refuse('noncanonical document wrapper');
58
+ const lines = readSemanticStanding(document.standing);
59
+ const semantic = semanticStandingBytes(lines);
60
+ if (digestBytes(new TextEncoder().encode(semantic)) !== reference.digest)
61
+ refuse('standing identity');
62
+ return { reference, lines, standing: semantic, authentication: 'not demonstrated; inspect only' };
63
+ }
@@ -0,0 +1,18 @@
1
+ import { parse } from '@lapxo/topos/wire';
2
+ import { cellInputs } from '@lapxo/topos/cell-inputs';
3
+ import { semanticStandingBytes } from '@lapxo/topos/standing';
4
+ /** Shared projection after admission; transport order never replaces effective order. */
5
+ export function referenceStanding(originals, records) {
6
+ const membership = new Set([...cellInputs(records, 'semantic-live@2').values()].flat().map(line => semanticStandingBytes([line])));
7
+ const fields = (line) => { const p = parse(line); if (p.kind !== 'fact')
8
+ throw Error('REFUSE·authenticated-reference original parse'); return p.value.fields; };
9
+ const effective = [...originals].sort((a, b) => Number(fields(a).epoch) - Number(fields(b).epoch));
10
+ return semanticStandingBytes(effective.filter(line => fields(line).type === undefined || membership.has(semanticStandingBytes([line]))));
11
+ }
12
+ export function referenceContracts(value) {
13
+ return value !== null && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 2 && /^sha256:[a-f0-9]{64}$/.test(value.refusals) && /^sha256:[a-f0-9]{64}$/.test(value.standing);
14
+ }
15
+ /** This versioned context frame binds interpretation contracts without changing wire fields. */
16
+ export function referenceContextBytes(context) {
17
+ return new TextEncoder().encode(JSON.stringify(['reference-export-context@1', context.semanticIdentity, context.epoch, context.proofScope, context.policy, context.formImplementation, context.signers, context.algorithms, context.contracts.refusals, context.contracts.standing, context.maxBytes, context.maxRecords]) + '\n');
18
+ }
@@ -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
+ }
@@ -0,0 +1,14 @@
1
+ export interface RefusalEntry {
2
+ readonly code: string;
3
+ readonly category: string;
4
+ readonly meaning: string;
5
+ readonly next: string;
6
+ }
7
+ /** Stable code assigned at the cause. Details are diagnostic, never parsed as policy. */
8
+ export declare class BoundRefusal extends Error {
9
+ readonly code: string;
10
+ constructor(code: string, diagnostic: string);
11
+ }
12
+ /** Pure machine contract reader. A catalog is data, never an authority grant. */
13
+ export declare function readRefusalCatalog(text: string): readonly RefusalEntry[];
14
+ export declare function describeRefusal(error: BoundRefusal, catalog: readonly RefusalEntry[]): RefusalEntry;
@@ -0,0 +1,43 @@
1
+ import { parse, canonical } from '@lapxo/topos/wire';
2
+ /** Stable code assigned at the cause. Details are diagnostic, never parsed as policy. */
3
+ export class BoundRefusal extends Error {
4
+ constructor(code, diagnostic) { super(diagnostic); this.name = 'BoundRefusal'; this.code = code; }
5
+ }
6
+ /** Pure machine contract reader. A catalog is data, never an authority grant. */
7
+ export function readRefusalCatalog(text) {
8
+ const groups = new Map();
9
+ let profile = false;
10
+ for (const line of text.trimEnd().split('\n')) {
11
+ const parsed = parse(line);
12
+ if (parsed.kind !== 'fact' || canonical(parsed.value.fields) !== line)
13
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·invalid refusal catalog line');
14
+ const f = parsed.value.fields;
15
+ if (f.scope === 'refusal/catalog' && f.measure === 'id' && f.value === 'bound-refusals@1' && f.role === 'writes' && f.form === 'alphabet' && !profile) {
16
+ profile = true;
17
+ continue;
18
+ }
19
+ if (f.role !== 'writes' || f.form !== 'alphabet' || !f.scope?.startsWith('refusal/') || !['code', 'class', 'next'].includes(f.measure ?? ''))
20
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·invalid refusal catalog record');
21
+ const group = groups.get(f.scope) ?? new Map();
22
+ if (group.has(f.measure))
23
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·duplicate refusal catalog field');
24
+ group.set(f.measure, f);
25
+ groups.set(f.scope, group);
26
+ }
27
+ const entries = [];
28
+ for (const [scope, group] of groups) {
29
+ const code = group.get('code')?.value, category = group.get('class')?.value, meaning = group.get('class')?.about, next = group.get('next')?.value;
30
+ if (group.size !== 3 || !code || scope !== 'refusal/' + code || !category || !meaning || !next)
31
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·incomplete refusal catalog entry');
32
+ entries.push(Object.freeze({ code, category, meaning, next }));
33
+ }
34
+ if (!profile || !entries.length)
35
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·missing refusal catalog profile');
36
+ return Object.freeze(entries);
37
+ }
38
+ export function describeRefusal(error, catalog) {
39
+ const entry = catalog.find(row => row.code === error.code);
40
+ if (!entry)
41
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·undeclared refusal code');
42
+ return entry;
43
+ }
@@ -2,6 +2,8 @@ import { verifiedContent } from "./content-store.js";
2
2
  import { readStanding } from '@lapxo/topos/standing';
3
3
  import { parse, wireAt, fromLine } from '@lapxo/topos/wire';
4
4
  import { fieldOf } from "../fold/claims.js";
5
+ import { moduleClosure } from "./native/module-closure.js";
6
+ import { sha256, isSha256 } from "./hash.js";
5
7
  /** Local grammar is sufficient for locally declared records. Resolve selected
6
8
  * contracts only when a reader is missing; a local refusal remains a refusal. */
7
9
  export function admittedLine(line, local, epoch, pins, resolve = toposAt) {
@@ -26,7 +28,13 @@ export function contentAt(digest, store) {
26
28
  return verifiedContent(digest, store).bytes;
27
29
  }
28
30
  /** The declared module runs from the store that supplied its verified bytes. No copy or live-checkout fallback. */
29
- export function contextModuleAt(digest, store) {
31
+ export function contextModuleAt(digest, store, dependencies) {
32
+ if (dependencies !== undefined) {
33
+ if (!isSha256(digest) || !dependencies.includes(digest) || dependencies.some(pin => !isSha256(pin)))
34
+ throw Error('REFUSE·context module closure requires declared SHA-256 members');
35
+ const pins = [...new Set(dependencies)].sort(), members = new Map(pins.map(pin => [pin.slice(pin.indexOf(':') + 1), { digest: pin, source: new TextDecoder('utf-8', { fatal: true }).decode(contentAt(pin, store)) }]));
36
+ moduleClosure({ identity: sha256(JSON.stringify(pins)), entry: digest.slice(digest.indexOf(':') + 1), members }, () => undefined);
37
+ }
30
38
  return verifiedContent(digest, store).path;
31
39
  }
32
40
  /** Selection verifies standing identity. Requested capabilities verify their artifact bytes separately. */
@@ -1,3 +1,4 @@
1
+ import { invokeSigningPort } from "../engine/ports.js";
1
2
  import { readFileSync } from 'node:fs';
2
3
  import { parse, signerSelection, signerRequestBytes, signerResponseBytes, signerResponseOf } from '@lapxo/topos/wire';
3
4
  import { invokeSigner, SigningFailure } from "./ports/signing.js";
@@ -31,11 +32,16 @@ function binding(name, keyFile) {
31
32
  }
32
33
  }
33
34
  /** Resolve data before acquisition; all mechanisms use one wire protocol and verification path. */
34
- export function signerFor(lines, keyId, selected, keyFile) {
35
+ export function signerFor(lines, keyId, selected, keyFile, injected) {
35
36
  const fields = lines.flatMap(line => { const got = parse(line); return got.kind === 'fact' ? [got.value.fields] : []; });
36
37
  const declaration = signerSelection(fields, keyId);
37
38
  if (selected !== undefined && selected !== declaration.name)
38
39
  throw new SigningFailure(`REFUSE·signer ${keyId} CLI selection conflicts with declaration`);
40
+ if (injected) {
41
+ if (injected.name !== declaration.name || keyFile)
42
+ throw new SigningFailure('REFUSE·signer injected host binding conflicts with declaration or key file');
43
+ return (lot) => invokeSigningPort(lot, injected.sign, { timeoutMs: declaration.timeoutMs, maxBytes: declaration.responseBytes });
44
+ }
39
45
  const mechanism = binding(declaration.name, keyFile);
40
46
  const protocol = { request: signerRequestBytes, response: signerResponseBytes, reply: signerResponseOf };
41
47
  return (lot) => invokeSigner(lot, mechanism, protocol, declaration);
@@ -24,7 +24,17 @@ function bytesAt(at) {
24
24
  const held = seen.get(at);
25
25
  if (held?.stamp === stamp)
26
26
  return held.bytes;
27
- const bytes = readFileSync(at);
27
+ let bytes;
28
+ try {
29
+ bytes = readFileSync(at);
30
+ }
31
+ catch (error) {
32
+ // A concurrent act can retire a coordinate after its stamp was read.
33
+ if (error.code !== 'ENOENT')
34
+ throw error;
35
+ seen.delete(at);
36
+ return undefined;
37
+ }
28
38
  seen.set(at, { stamp, bytes });
29
39
  return bytes;
30
40
  }
@@ -1,3 +1,11 @@
1
+ import { childFlags } from "../host/capsule.js";
2
+ import { readingProfile, readingExecution, withReadingBoundary } from "../host/reading-context.js";
3
+ import { readingIdentityBytes } from '@lapxo/topos/contract';
4
+ import { portableReadingReceipt, verifiedReadingReceipts } from "../host/reading-closures.js";
5
+ import { bytesDigest, instrumentCoordinates } from "../fold/digests.js";
6
+ import { ownRoot, processOf } from "./runner.js";
7
+ import { contentPolicy } from "../host/content-store.js";
8
+ import { carriedFrom } from "../fold/resolved.js";
1
9
  import { selectedCapsules } from "../host/selected-capsules.js";
2
10
  import { selectionLines, projectionLines } from "../fold/reach.js";
3
11
  export { selectionLines } from "../fold/reach.js";
@@ -22,6 +30,7 @@ import { ownLock } from "./runner.js";
22
30
  import { nameOf } from "../render/page.js";
23
31
  import { placeLines, takenFor } from "../fold/place-inputs.js";
24
32
  import { isRegionDeclaration } from "../fold/region-declarations.js";
33
+ const receiptHasher = (store) => (text) => bytesDigest(store, new TextEncoder().encode(text));
25
34
  const fact = (scope, value, form = 'alphabet', measure = 'fact') => canonical({ scope, role: 'writes', form, measure, value, by: 'fold', at: 'place:fold' });
26
35
  const described = (local = []) => {
27
36
  const lock = ownLock();
@@ -164,12 +173,30 @@ function capsuleReadings(fold) {
164
173
  return known.get(place) ?? known.set(place, readingsAt(fold, place)).get(place);
165
174
  }
166
175
  function readingsAt(fold, place) {
167
- const asking = askingOf(fold);
168
176
  const speaker = keyFor(fold.store, 'read');
169
177
  return capsulesOf(fold).flatMap((capsule) => {
170
178
  const writing = new Set(capsule.lines.filter((line) => fieldOf(line, 'scope').startsWith('region/') && fieldOf(line, 'measure') === 'writes').map((line) => fieldOf(line, 'scope').slice('region/'.length)));
171
179
  const receipts = Object.keys(capsule.declaration.writes).filter((name) => capsule.declaration.writes[name] === 'receipt' && !writing.has(name)).sort();
180
+ const target = modernReceiptInputs(fold, capsule.selection ?? capsule.digest);
181
+ const asking = askingOf({ ...fold, observed: target.current });
172
182
  const requests = receipts.flatMap((name) => ((request) => (request === undefined ? [] : [request]))(asking(capsule.declaration, { name, at: 3 }, 'read', '')));
183
+ const modern = readingProfile(fold.standing, fieldOf);
184
+ if (modern && requests.length) {
185
+ const hash = receiptHasher(fold.store);
186
+ const batch = hash(JSON.stringify([capsule.selection ?? capsule.digest, capsule.digest, receipts]));
187
+ const coordinates = instrumentCoordinates(fold.store, processOf('verb'), [], ownRoot(), ownLock(), true), instrument = hash([...coordinates.values()].sort().join('\n'));
188
+ const legacy = target.legacy;
189
+ return requests.flatMap(request => {
190
+ const execution = readingExecution({ instrument, implementation: capsule.digest, policy: hash(JSON.stringify([contentPolicy(), capsule.selection ?? null, capsule.lines])), inputs: [['world', capsule.selection ?? capsule.digest], ['batch', batch], ['request', hash(JSON.stringify(request))], ...legacy, ...[...coordinates].map(([name, id]) => ['host/' + relative(ownRoot(), name), id])] }, hash, { env: process.env, cwd: fold.root, flags: childFlags() });
191
+ const closure = new TextDecoder().decode(readingIdentityBytes(execution.context)), input = hash(closure), said = withReadingBoundary(input, execution, () => capsule.ask([request]))[0];
192
+ if (said?.kind !== 'fact')
193
+ throw Error(`REFUSE·capsule ${capsule.digest} · ${request.region} · ${said?.why ?? 'no answer'}`);
194
+ const by = speaker + ':' + batch.slice(7), at = 'place:' + input;
195
+ const lines = (said.claims ?? []).map((row) => canonical({ scope: row.scope, role: row.role, measure: row.measure, form: row.bound.kind === 'interval' ? 'interval' : 'alphabet', value: row.bound.kind === 'interval' ? `${row.bound.lo ?? 0}..${row.bound.hi ?? 0}` : (row.bound.values ?? []).join('|'), by, at }));
196
+ const output = lines.join('\n') + (lines.length ? '\n' : ''), scope = 'receipts/readings/' + hash(request.region ?? '').slice(7) + '/' + input.slice(7);
197
+ return [canonical({ scope: place, role: 'writes', form: 'alphabet', measure: 'observed', value: input.slice(7, 23), by, at }), ...lines, ...portableReadingReceipt(scope, by, closure, output, hash)];
198
+ });
199
+ }
173
200
  const answers = capsule.ask(requests);
174
201
  const refused = requests.filter((_, i) => answers[i]?.kind !== 'fact');
175
202
  if (refused.length)
@@ -196,3 +223,84 @@ export function capsuleReadingsOver(fold) {
196
223
  return [...capsuleReadings(fold), ...places.flatMap((place) => capsuleReadings({ ...fold, under: `${place}/`,
197
224
  standing: [...new Set([...said.filter((one) => one.place === place).map((one) => one.line), ...fold.standing])] }))];
198
225
  }
226
+ /** Current judgement excludes authenticated outcomes of the selected producer batches; retained evidence stays in its carrier. */
227
+ export const currentReceiptObservations = (fold) => modernReceiptInputs(fold).current;
228
+ /** Preserve prior producer outcomes in the owning place's public carrier, independently of current judgement. */
229
+ export const retiredReceiptHistory = (fold) => { const carried = carriedFrom(fold.store, (observeText(join(fold.root, fold.under ?? '', 'receipts.bound')) ?? '').split('\n').filter(line => line.startsWith('bound-lock/'))) ?? []; const all = [...new Set([...fold.observed, ...carried])], current = new Set(modernReceiptInputs({ ...fold, observed: all }).current); return all.filter(line => !current.has(line)); };
230
+ /** A modern capsule batch cannot consume its own retained outcomes. Full context units bind history; legacy conversion never supplies a current answer. */
231
+ function modernReceiptInputs(fold, target) {
232
+ if (!readingProfile(fold.standing, fieldOf))
233
+ return { current: fold.observed, legacy: [] };
234
+ const observed = [...new Set(fold.observed)];
235
+ const hash = receiptHasher(fold.store), allSelected = capsulesOf(fold), selected = allSelected.filter(c => { const writing = new Set(c.lines.filter(l => fieldOf(l, 'measure') === 'writes').map(l => fieldOf(l, 'scope').slice(7))); return Object.keys(c.declaration.writes).some(n => c.declaration.writes[n] === 'receipt' && !writing.has(n)); });
236
+ if (!selected.length)
237
+ return { current: fold.observed, legacy: [] };
238
+ const speaker = keyFor(fold.store, 'read'), removed = new Set(), refs = [];
239
+ const units = observed.filter(l => fieldOf(l, 'scope').startsWith('receipts/readings/') && fieldOf(l, 'measure') === 'bytes' && fieldOf(l, 'role') === 'writes');
240
+ if (verifiedReadingReceipts(fold.store, observed, hash, undefined, true) === undefined)
241
+ throw Error('REFUSE·capsule receipt carrier context invalid');
242
+ for (const capsule of selected) {
243
+ if (target !== undefined && (capsule.selection ?? capsule.digest) !== target)
244
+ continue;
245
+ const writing = new Set(capsule.lines.filter(l => fieldOf(l, 'measure') === 'writes').map(l => fieldOf(l, 'scope').slice(7))), receipts = Object.keys(capsule.declaration.writes).filter(n => capsule.declaration.writes[n] === 'receipt' && !writing.has(n)).sort();
246
+ if (!receipts.length)
247
+ continue;
248
+ const batch = hash(JSON.stringify([capsule.selection ?? capsule.digest, capsule.digest, receipts])), knownLegacy = new Set();
249
+ for (const unit of units) {
250
+ const about = fieldOf(unit, 'about');
251
+ if (!about)
252
+ continue;
253
+ let c;
254
+ try {
255
+ c = JSON.parse(about);
256
+ }
257
+ catch {
258
+ continue;
259
+ }
260
+ if (!Array.isArray(c) || c[0] !== 'reading-context@1' || c[2] !== capsule.digest || !Array.isArray(c[4]) || !c[4].some(([n, id]) => n === 'batch' && id === batch) || !c[4].some(([n, id]) => n === 'world' && id === (capsule.selection ?? capsule.digest)))
261
+ continue;
262
+ const expectedBy = speaker + ':' + batch.slice(7);
263
+ if (fieldOf(unit, 'by') !== expectedBy)
264
+ continue;
265
+ if (fold.standing.some(line => fieldOf(line, 'scope') === 'keys/' + expectedBy && fieldOf(line, 'value') !== 'withdraw'))
266
+ throw Error('REFUSE·modern capsule receipt producer ambiguous');
267
+ const namedScope = fieldOf(unit, 'scope'), inputIdentity = fieldOf(unit, 'value');
268
+ if (!receipts.some(name => namedScope === 'receipts/readings/' + hash(name).slice(7) + '/' + inputIdentity.slice(7)))
269
+ throw Error('REFUSE·modern capsule receipt scope differs from declared region');
270
+ for (const [n, id] of c[4])
271
+ if (typeof n === 'string' && n.startsWith('legacy/output/') && n === 'legacy/output/' + id) {
272
+ knownLegacy.add(id);
273
+ refs.push([n, id]);
274
+ }
275
+ const by = fieldOf(unit, 'by'), input = fieldOf(unit, 'value'), scope = fieldOf(unit, 'scope'), output = observed.find(l => fieldOf(l, 'scope') === scope && fieldOf(l, 'by') === by && fieldOf(l, 'measure') === 'count');
276
+ const payload = output ? fieldOf(output, 'at') : '';
277
+ for (const l of observed)
278
+ if (fieldOf(l, 'by') === by && (fieldOf(l, 'at') === 'place:' + input || fieldOf(l, 'at') === payload || fieldOf(l, 'scope') === scope || fieldOf(l, 'scope') === [scope, 'identity'].join('/')))
279
+ removed.add(l);
280
+ }
281
+ const alias = capsule.digest.slice(7, 19), by = speaker + ':' + alias, groups = new Map();
282
+ for (const l of observed)
283
+ if (fieldOf(l, 'by') === by) {
284
+ const at = fieldOf(l, 'at');
285
+ groups.set(at, [...(groups.get(at) ?? []), l]);
286
+ }
287
+ if (groups.size) {
288
+ if (selected.filter(c => c.digest === capsule.digest).length !== 1 || receipts.length !== 1 || new Set(allSelected.filter(c => c.digest.slice(7, 19) === alias).map(c => c.digest)).size !== 1 || fold.standing.some(l => fieldOf(l, 'scope') === 'keys/' + by && fieldOf(l, 'value') !== 'withdraw'))
289
+ throw Error('REFUSE·legacy capsule receipt producer ambiguous');
290
+ for (const [at, group] of groups) {
291
+ if (!/^place:sha256:[a-f0-9]{64}$/.test(at) || group.filter(l => fieldOf(l, 'measure') === 'observed' && fieldOf(l, 'scope') === (fold.under || './')).length !== 1)
292
+ throw Error('REFUSE·legacy capsule receipt occurrence ambiguous');
293
+ const id = hash(group.join('\n'));
294
+ if (!knownLegacy.has(id)) {
295
+ const candidate = observed.filter(l => !removed.has(l) && !group.includes(l)), request = askingOf({ ...fold, observed: candidate })(capsule.declaration, { name: receipts[0], at: 3 }, 'read', '');
296
+ if (!request || at !== 'place:' + hash(JSON.stringify(request)))
297
+ throw Error('REFUSE·legacy capsule receipt context unavailable · expected ' + at + ' · current ' + (request ? hash(JSON.stringify(request)) : 'absent'));
298
+ }
299
+ refs.push(['legacy/output/' + id, id]);
300
+ for (const l of group)
301
+ removed.add(l);
302
+ }
303
+ }
304
+ }
305
+ return { current: observed.filter(l => !removed.has(l)), legacy: [...new Map(refs.map(([n, id]) => [n + ' ' + id, [n, id]])).values()] };
306
+ }