@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
@@ -0,0 +1,148 @@
1
+ import { encodeUTF8 } from "../io.js";
2
+ const fail = (why) => { throw Error('REFUSE·job ' + why); };
3
+ const utf8 = encodeUTF8;
4
+ const by = (a, b) => { const x = utf8(a), y = utf8(b); for (let i = 0; i < Math.min(x.length, y.length); i++)
5
+ if (x[i] !== y[i])
6
+ return x[i] - y[i]; return x.length - y.length; };
7
+ const text = (x) => typeof x === 'string' && x.length > 0;
8
+ const positive = (x) => Number.isSafeInteger(x) && Number(x) > 0;
9
+ function coordinate(s) { if (!text(s) || s.startsWith('/') || s.includes('\\') || s.split('/').some(p => p === '' || p === '.' || p === '..'))
10
+ fail('unknown member coordinate ' + String(s)); }
11
+ function capped(s, max) { if (s.length > max || utf8(s).byteLength > max)
12
+ fail('byte budget'); }
13
+ /** digest is selected by the instrument; it is a pure naming implementation,
14
+ * not a callback that answers whether untrusted bytes/authority are "verified". */
15
+ export function planJobs(host, framed, digest) {
16
+ if (!host || !host.budget)
17
+ fail('missing admitted host handoff');
18
+ for (const key of ['jobs', 'members', 'edges', 'bytes', 'payloadBytes', 'env'])
19
+ if (!positive(host.budget[key]))
20
+ fail('missing resource ' + key);
21
+ const budget = host.budget;
22
+ if (!(framed instanceof Uint8Array) || framed.byteLength > budget.bytes)
23
+ fail('request byte budget');
24
+ if (typeof digest !== 'function')
25
+ fail('missing instrument digest');
26
+ if (!text(host.place) || !text(host.instrument) || !text(host.policy) || !text(host.authoritySnapshot))
27
+ fail('missing authority context');
28
+ for (const s of [host.place, host.instrument, host.policy, host.authoritySnapshot])
29
+ capped(s, budget.bytes);
30
+ if (!Array.isArray(host.members) || !Array.isArray(host.loads) || !Array.isArray(host.bindings) || host.members.length > budget.members || host.loads.length > budget.edges || host.bindings.length > budget.jobs)
31
+ fail('handoff count budget');
32
+ let total = framed.byteLength;
33
+ const charge = (s) => { capped(s, budget.bytes); total += Math.max(1, utf8(s).byteLength); if (total > budget.bytes)
34
+ fail('handoff byte budget'); };
35
+ for (const s of [host.place, host.instrument, host.policy, host.authoritySnapshot])
36
+ charge(s);
37
+ const members = new Map();
38
+ for (const m of host.members) {
39
+ if (!m || !(m.bytes instanceof Uint8Array))
40
+ fail('missing handed member');
41
+ coordinate(m.coordinate);
42
+ if (!text(m.digest))
43
+ fail('missing member digest ' + m.coordinate);
44
+ charge(m.coordinate);
45
+ charge(m.digest);
46
+ total += m.bytes.byteLength;
47
+ if (total > budget.bytes)
48
+ fail('member byte budget');
49
+ if (members.has(m.coordinate))
50
+ fail('duplicate handed member ' + m.coordinate);
51
+ members.set(m.coordinate, m);
52
+ }
53
+ const edges = new Map();
54
+ for (const e of host.loads) {
55
+ if (!e || !['module', 'worker', 'program'].includes(e.kind))
56
+ fail('unknown load kind');
57
+ coordinate(e.from);
58
+ if (typeof e.target !== 'string')
59
+ fail('unknown computed target ' + e.from);
60
+ coordinate(e.target);
61
+ charge(e.from);
62
+ charge(e.target);
63
+ if (!members.has(e.from) || !members.has(e.target))
64
+ fail('unhanded load ' + e.from + ' -> ' + e.target);
65
+ const key = e.from + '\u0000' + e.kind + '\u0000' + e.target;
66
+ if ((edges.get(e.from) ?? []).some(x => x.kind === e.kind && x.target === e.target))
67
+ fail('duplicate load ' + key);
68
+ edges.set(e.from, [...(edges.get(e.from) ?? []), e]);
69
+ }
70
+ let launchEdges = host.loads.length;
71
+ const bindings = new Map();
72
+ for (const b of host.bindings) {
73
+ if (!b || !text(b.id) || bindings.has(b.id))
74
+ fail('duplicate or missing job binding');
75
+ charge(b.id);
76
+ if (!['process', 'worker'].includes(b.kind) || (b.kind === 'worker' && !b.entry))
77
+ fail('unknown or incomplete execution kind ' + b.id);
78
+ if (!Array.isArray(b.places) || !Array.isArray(b.args) || !Array.isArray(b.inputs) || !Array.isArray(b.environment) || b.environment.length > budget.env || [b.places, b.args, b.inputs].some(x => Array.isArray(x) && x.length > budget.bytes))
79
+ fail('incomplete job binding ' + b.id);
80
+ if (new Set(b.places).size !== b.places.length || new Set(b.inputs).size !== b.inputs.length)
81
+ fail('duplicate binding coordinate ' + b.id);
82
+ for (const s of [...b.places, ...b.args, ...b.inputs]) {
83
+ if (typeof s !== 'string')
84
+ fail('nonliteral host argument ' + b.id);
85
+ charge(s);
86
+ }
87
+ for (const field of ['timeMs', 'responseBytes', 'slots'])
88
+ if (!b.limits || !positive(b.limits[field]))
89
+ fail('missing launch resource ' + b.id + '/' + field);
90
+ if (b.limits.slots > budget.jobs)
91
+ fail('launch slots exceed admitted jobs ' + b.id);
92
+ const env = new Set();
93
+ for (const pair of b.environment) {
94
+ if (!Array.isArray(pair) || pair.length !== 2 || !text(pair[0]) || typeof pair[1] !== 'string' || env.has(pair[0]))
95
+ fail('ambiguous environment ' + b.id);
96
+ env.add(pair[0]);
97
+ charge(pair[0]);
98
+ charge(pair[1]);
99
+ }
100
+ for (const c of [b.program, b.interpreter, b.entry, ...b.inputs].filter(x => x !== undefined)) {
101
+ coordinate(c);
102
+ if (!members.has(c))
103
+ fail('unhanded launch member ' + c);
104
+ }
105
+ launchEdges += [b.program, b.interpreter, b.entry, ...b.inputs].filter(x => x !== undefined).length;
106
+ if (launchEdges > budget.edges)
107
+ fail('launch edge budget');
108
+ bindings.set(b.id, b);
109
+ }
110
+ // All byte/count/declaration checks precede hashing or parsing world data.
111
+ let proposals;
112
+ try {
113
+ proposals = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(framed));
114
+ }
115
+ catch {
116
+ fail('malformed proposal frame');
117
+ }
118
+ if (!Array.isArray(proposals) || proposals.length > budget.jobs)
119
+ return fail('proposal count budget');
120
+ const ids = new Set(), jobs = [];
121
+ for (const p of proposals) {
122
+ if (!p || typeof p !== 'object' || Array.isArray(p) || Object.keys(p).some(k => k !== 'id' && k !== 'payload') || !text(p.id) || typeof p.payload !== 'string')
123
+ fail('world may propose only job id and data');
124
+ if (ids.has(p.id))
125
+ fail('duplicate proposed job ' + p.id);
126
+ ids.add(p.id);
127
+ capped(p.id, budget.bytes);
128
+ capped(p.payload, budget.payloadBytes);
129
+ const b = bindings.get(p.id);
130
+ if (!b)
131
+ return fail('unknown computed job target ' + p.id);
132
+ if (!b.places.includes(host.place))
133
+ fail('job not covered at place ' + host.place + ' ' + p.id);
134
+ jobs.push(p);
135
+ }
136
+ for (const m of members.values())
137
+ if (digest(m.bytes) !== m.digest)
138
+ fail('member digest differs ' + m.coordinate);
139
+ return jobs.map(p => { const b = bindings.get(p.id); const ref = (c) => [c, members.get(c).digest]; const seen = new Set(), closed = []; const visit = (c) => { if (seen.has(c))
140
+ return; seen.add(c); for (const e of edges.get(c) ?? []) {
141
+ const target = e.target;
142
+ closed.push({ from: c, kind: e.kind, target, digest: members.get(target).digest });
143
+ visit(target);
144
+ } }; for (const c of [b.program, b.interpreter, b.entry, ...b.inputs].filter(x => x !== undefined))
145
+ visit(c); closed.sort((a, b) => by(JSON.stringify(a), JSON.stringify(b))); for (const [kind, c] of [['launch', b.program], ['interpreter', b.interpreter], ['worker-entry', b.entry], ...b.inputs.map(c => ['input', c])])
146
+ if (c !== undefined)
147
+ closed.push({ from: 'job:' + p.id, kind: kind, target: c, digest: members.get(c).digest }); closed.sort((a, b) => by(JSON.stringify(a), JSON.stringify(b))); const environment = b.environment.map(p => [...p]).sort((a, b) => by(a[0], b[0])); const payload = utf8(p.payload), tuple = ['host-job-proposal', host.place, host.instrument, host.policy, host.authoritySnapshot, p.id, b.kind, ref(b.program), b.interpreter ? ref(b.interpreter) : null, b.entry ? ref(b.entry) : null, b.args, b.inputs.map(ref), environment, [b.limits.timeMs, b.limits.responseBytes, b.limits.slots], closed, digest(payload)]; return { id: p.id, kind: b.kind, identity: digest(utf8(JSON.stringify(tuple) + '\n')), place: host.place, program: ref(b.program), ...(b.interpreter ? { interpreter: ref(b.interpreter) } : {}), ...(b.entry ? { entry: ref(b.entry) } : {}), args: [...b.args], payload, environment, limits: { ...b.limits }, edges: closed }; });
148
+ }
@@ -0,0 +1,80 @@
1
+ /** Node loader registration is an executable capability, not merely a builtin
2
+ * name. The fixed host retains its captured registration API; imported programs
3
+ * cannot insert a loader returning invented bytes ahead of its load boundary.
4
+ * This does not establish confinement against arbitrary privileged JavaScript.
5
+ */
6
+ export declare function protectNativeLoaderRegistration(): void;
7
+ export interface Member {
8
+ readonly path: string;
9
+ readonly digest: string;
10
+ readonly bytes: number;
11
+ }
12
+ export interface Library {
13
+ readonly name: string;
14
+ readonly manifest: string;
15
+ readonly coordinate?: string;
16
+ }
17
+ export interface Declaration {
18
+ readonly members: readonly Member[];
19
+ readonly libraries: readonly Library[];
20
+ readonly builtins: readonly string[];
21
+ readonly entry: string;
22
+ readonly platform: string;
23
+ }
24
+ /** Fixed preload only: expose comparison with the installed resolution, never
25
+ * hook registration or a verifier supplied by a reader. A different module's
26
+ * marker cannot replace this host's closed capability before entry. */
27
+ export declare function installNativeResolutionReuse(declaration: Declaration): () => void;
28
+ interface Context {
29
+ parentURL?: string;
30
+ }
31
+ interface Resolution {
32
+ url: string;
33
+ }
34
+ /** Resolution boundary for an instrument-selected native command. It checks
35
+ * actual target bytes before Node executes that target. Native commands remain
36
+ * privileged host implementations: this is not an arbitrary-JS sandbox and
37
+ * cannot prevent trusted code from replacing Node's hooks. That trust condition
38
+ * must be admitted separately; a world cannot claim it in a payload.
39
+ * Native ABI edges refer to the declared platform context, never a fictitious
40
+ * file digest. Completeness of the platform's backing images is not proved here.
41
+ */
42
+ export declare function loadBoundary(declaration: Declaration): {
43
+ entry: string;
44
+ resolve(specifier: string, context: Context, next: (specifier: string, context: Context) => Resolution): Resolution;
45
+ load(url: string, context: {
46
+ format?: string;
47
+ }, next: (url: string, context: {
48
+ format?: string;
49
+ }) => {
50
+ format: string;
51
+ source?: string | Uint8Array | null;
52
+ }): {
53
+ format: string;
54
+ source?: string | Uint8Array | null;
55
+ };
56
+ assertClosed(): void;
57
+ edges: () => {
58
+ parent: string;
59
+ target: string;
60
+ digest?: string;
61
+ platform?: string;
62
+ }[];
63
+ };
64
+ /** Install before the entry of an instrument-selected privileged Node command.
65
+ * The bootstrap exception is restricted to this host function's exact selected
66
+ * entry. It is not a permission for a world to choose a verifier or a program.
67
+ * Hooks mediate imports; they do not establish a non-bypass JS sandbox.
68
+ */
69
+ export declare function installBoundary(declaration: Declaration): {
70
+ loadEntry: () => Promise<any>;
71
+ assertClosed: () => void;
72
+ edges: () => {
73
+ parent: string;
74
+ target: string;
75
+ digest?: string;
76
+ platform?: string;
77
+ }[];
78
+ close: () => void;
79
+ };
80
+ export {};
@@ -0,0 +1,269 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { sha256 as digest } from "../hash.js";
10
+ import { realpathSync } from 'node:fs';
11
+ import { dirname, join, relative, isAbsolute, sep } from 'node:path';
12
+ import { readBoundedFile } from "./bounded-file.js";
13
+ import { fileURLToPath, pathToFileURL } from 'node:url';
14
+ import * as nodeModule from 'node:module';
15
+ const { builtinModules } = nodeModule;
16
+ const moduleApi = nodeModule.createRequire(import.meta.url)('node:module');
17
+ const fixedRegisterHooks = moduleApi.registerHooks;
18
+ let registrationProtected = false;
19
+ /** Node loader registration is an executable capability, not merely a builtin
20
+ * name. The fixed host retains its captured registration API; imported programs
21
+ * cannot insert a loader returning invented bytes ahead of its load boundary.
22
+ * This does not establish confinement against arbitrary privileged JavaScript.
23
+ */
24
+ export function protectNativeLoaderRegistration() {
25
+ if (registrationProtected)
26
+ return;
27
+ registrationProtected = true;
28
+ for (const name of ['registerHooks', 'register'])
29
+ if (typeof moduleApi[name] === 'function')
30
+ Object.defineProperty(moduleApi, name, { value: () => { throw Error('REFUSE·load runtime loader registration is not a fixed host capability'); }, writable: false, configurable: false });
31
+ nodeModule.syncBuiltinESMExports();
32
+ }
33
+ const resolutionKey = Symbol.for('bound.native.resolution@1');
34
+ let resolutionCheck;
35
+ let resolutionIdentity;
36
+ let resolutionLibraryNames = Object.freeze([]);
37
+ let resolutionLibraries = Object.freeze([]);
38
+ const resolutionCapability = Object.freeze({ profile: 'fixed-native-resolution@1', get libraryNames() { return resolutionLibraryNames; }, get libraries() { return resolutionLibraries; }, assertPins(pins) {
39
+ if (!Array.isArray(pins))
40
+ throw Error('REFUSE·load pinned library request invalid');
41
+ if (!resolutionCheck)
42
+ throw Error('REFUSE·load no current fixed library resolution');
43
+ resolutionCheck(pins);
44
+ } });
45
+ /** Fixed preload only: expose comparison with the installed resolution, never
46
+ * hook registration or a verifier supplied by a reader. A different module's
47
+ * marker cannot replace this host's closed capability before entry. */
48
+ export function installNativeResolutionReuse(declaration) {
49
+ const prior = Object.getOwnPropertyDescriptor(globalThis, resolutionKey);
50
+ if (prior && prior.value !== resolutionCapability)
51
+ throw Error('REFUSE·load fixed resolution capability collision');
52
+ if (!prior)
53
+ Object.defineProperty(globalThis, resolutionKey, { value: resolutionCapability, writable: false, configurable: false });
54
+ const fail = (why) => { throw Error('REFUSE·load pinned library ' + why); };
55
+ const actual = (path, why) => { try {
56
+ return realpathSync(path);
57
+ }
58
+ catch {
59
+ return fail(why);
60
+ } };
61
+ const files = declaration.members.map(member => ({ ...member, path: realpathSync(member.path) }));
62
+ const libraries = new Map(declaration.libraries.map(library => [library.name, realpathSync(library.manifest)]));
63
+ const coordinates = new Map(declaration.libraries.map(library => [library.name, library.coordinate]));
64
+ if (libraries.size !== declaration.libraries.length)
65
+ fail('ambiguous declaration');
66
+ const namedCoordinates = new Set();
67
+ for (const coordinate of coordinates.values())
68
+ if (coordinate !== undefined) {
69
+ if (typeof coordinate !== 'string' || !coordinate || isAbsolute(coordinate) || coordinate.includes('\\') || coordinate.split('/').some(step => !step || step === '.' || step === '..') || namedCoordinates.has(coordinate))
70
+ fail('invalid/ambiguous logical library coordinate');
71
+ namedCoordinates.add(coordinate);
72
+ }
73
+ const order = (a, b) => a < b ? -1 : a > b ? 1 : 0;
74
+ const identity = digest(Buffer.from(JSON.stringify([declaration.platform, files.map(member => [member.path, member.digest, member.bytes]).sort((a, b) => order(String(a[0]), String(b[0]))), [...libraries].sort(([a], [b]) => order(a, b)).map(([name, manifest]) => [name, manifest, coordinates.get(name) ?? null])])));
75
+ if (resolutionIdentity !== undefined) {
76
+ if (identity !== resolutionIdentity)
77
+ fail('late resolution replacement');
78
+ if (!resolutionCheck)
79
+ fail('fixed resolution already closed');
80
+ return () => { };
81
+ }
82
+ resolutionIdentity = identity;
83
+ resolutionLibraryNames = Object.freeze([...libraries.keys()].sort(order));
84
+ resolutionLibraries = Object.freeze([...libraries].sort(([a], [b]) => order(a, b)).map(([name, manifest]) => Object.freeze({ name, manifest, ...(coordinates.get(name) === undefined ? {} : { coordinate: coordinates.get(name) }) })));
85
+ const inside = (base, path) => { const step = relative(base, path); return step !== '' && !isAbsolute(step) && step !== '..' && !step.startsWith('..' + sep); };
86
+ const check = (pins) => {
87
+ const names = new Set();
88
+ for (const pin of pins) {
89
+ if (!pin || typeof pin.name !== 'string' || typeof pin.at !== 'string' || !pin.at || names.has(pin.name))
90
+ fail('invalid/duplicate selection');
91
+ names.add(pin.name);
92
+ const manifest = libraries.get(pin.name) ?? fail('not declared ' + pin.name);
93
+ const root = dirname(manifest), pinned = actual(pin.at, 'tree absent ' + pin.name), nested = [...libraries.values()].map(dirname).filter(other => inside(root, other));
94
+ const members = files.filter(member => inside(root, member.path) && !nested.some(other => inside(other, member.path)));
95
+ if (!members.some(member => member.path === manifest))
96
+ fail('manifest not sealed ' + pin.name);
97
+ const manifestMember = members.find(member => member.path === manifest);
98
+ let pkg;
99
+ try {
100
+ pkg = JSON.parse(readBoundedFile(manifest, manifestMember.bytes, manifest).toString('utf8'));
101
+ }
102
+ catch {
103
+ return fail('manifest unreadable ' + pin.name);
104
+ }
105
+ if (pkg?.name !== pin.name)
106
+ fail('manifest name differs ' + pin.name);
107
+ for (const member of members) {
108
+ if (!Number.isSafeInteger(member.bytes) || member.bytes < 0 || !/^sha256:[a-f0-9]{64}$/.test(member.digest))
109
+ fail('member resource invalid ' + pin.name);
110
+ const target = actual(join(pinned, relative(root, member.path)), 'member absent ' + pin.name + '/' + relative(root, member.path));
111
+ if (!inside(pinned, target))
112
+ fail('target leaves pinned tree ' + pin.name);
113
+ if (digest(readBoundedFile(member.path, member.bytes, member.path)) !== member.digest || digest(readBoundedFile(target, member.bytes, target)) !== member.digest)
114
+ fail('member differs ' + pin.name + '/' + relative(root, member.path));
115
+ }
116
+ }
117
+ };
118
+ resolutionCheck = check;
119
+ return () => { resolutionCheck = undefined; };
120
+ }
121
+ /** Resolution boundary for an instrument-selected native command. It checks
122
+ * actual target bytes before Node executes that target. Native commands remain
123
+ * privileged host implementations: this is not an arbitrary-JS sandbox and
124
+ * cannot prevent trusted code from replacing Node's hooks. That trust condition
125
+ * must be admitted separately; a world cannot claim it in a payload.
126
+ * Native ABI edges refer to the declared platform context, never a fictitious
127
+ * file digest. Completeness of the platform's backing images is not proved here.
128
+ */
129
+ export function loadBoundary(declaration) {
130
+ const files = new Map(), libraries = new Map(), native = new Set(), edges = [];
131
+ let refused;
132
+ const fail = (why) => { refused ?? (refused = 'REFUSE·load ' + why); throw Error(refused); };
133
+ const verify = (url) => { let path; try {
134
+ path = realpathSync(fileURLToPath(url));
135
+ }
136
+ catch {
137
+ return fail('target absent ' + url);
138
+ } const expected = files.get(path); if (!expected)
139
+ return fail('target undeclared ' + url); if (digest(readBoundedFile(path, expected.bytes, path)) !== expected.digest)
140
+ return fail('target bytes differ ' + url); return expected.digest; };
141
+ for (const member of declaration.members) {
142
+ let path;
143
+ try {
144
+ path = realpathSync(member.path);
145
+ }
146
+ catch {
147
+ return fail('member absent ' + member.path);
148
+ }
149
+ if (files.has(path) || !Number.isSafeInteger(member.bytes) || member.bytes < 0 || !/^sha256:[0-9a-f]{64}$/.test(member.digest))
150
+ return fail('member ambiguous ' + member.path);
151
+ files.set(path, { ...member, path });
152
+ }
153
+ for (const member of declaration.members)
154
+ verify(pathToFileURL(member.path).href);
155
+ for (const lib of declaration.libraries) {
156
+ if (!/^(?:@[A-Za-z0-9_.-]+\/)?[A-Za-z0-9_.-]+$/.test(lib.name) || libraries.has(lib.name))
157
+ return fail('library ambiguous ' + lib.name);
158
+ const url = pathToFileURL(lib.manifest).href;
159
+ verify(url);
160
+ let pkg;
161
+ try {
162
+ pkg = JSON.parse(readBoundedFile(lib.manifest, files.get(realpathSync(lib.manifest)).bytes, lib.name).toString('utf8'));
163
+ }
164
+ catch {
165
+ return fail('library manifest invalid ' + lib.name);
166
+ }
167
+ if (!pkg || Array.isArray(pkg) || pkg.name !== lib.name)
168
+ return fail('library name differs ' + lib.name);
169
+ libraries.set(lib.name, { url, pkg });
170
+ }
171
+ for (const name of declaration.builtins) {
172
+ const normalized = name.startsWith('node:') ? name : 'node:' + name;
173
+ if (native.has(normalized) || (!builtinModules.includes(normalized) && !builtinModules.includes(normalized.slice(5))))
174
+ return fail('native ABI ambiguous ' + name);
175
+ native.add(normalized);
176
+ }
177
+ if (!/^sha256:[0-9a-f]{64}$/.test(declaration.platform))
178
+ return fail('declared platform identity absent');
179
+ const entry = pathToFileURL(declaration.entry).href;
180
+ verify(entry);
181
+ return {
182
+ entry,
183
+ resolve(specifier, context, next) {
184
+ if (refused)
185
+ throw Error(refused);
186
+ if (context.parentURL)
187
+ verify(context.parentURL);
188
+ const builtin = specifier.startsWith('node:') ? specifier : 'node:' + specifier;
189
+ if (builtinModules.includes(specifier) || specifier.startsWith('node:')) {
190
+ if (!native.has(builtin))
191
+ return fail('native ABI undeclared ' + specifier);
192
+ edges.push({ parent: context.parentURL ?? entry, target: builtin, platform: declaration.platform });
193
+ let resolved;
194
+ try {
195
+ resolved = next(builtin, context);
196
+ }
197
+ catch {
198
+ return fail('native target unresolved ' + builtin);
199
+ }
200
+ if (resolved.url !== builtin)
201
+ return fail('native target differs ' + builtin);
202
+ return resolved;
203
+ }
204
+ let selected = context, target = specifier;
205
+ if (!specifier.startsWith('.') && !specifier.startsWith('/') && !specifier.includes(':')) {
206
+ const name = specifier.startsWith('@') ? specifier.split('/').slice(0, 2).join('/') : specifier.split('/')[0];
207
+ const library = libraries.get(name);
208
+ if (!library)
209
+ return fail('library undeclared ' + name);
210
+ selected = { ...context, parentURL: library.url };
211
+ if (library.pkg.exports === undefined) {
212
+ const subpath = specifier.slice(name.length);
213
+ const main = subpath ? subpath.slice(1) : library.pkg.main;
214
+ if (typeof main !== 'string' || !main || main.startsWith('/') || main.includes('\\') || main.split('/').includes('..'))
215
+ return fail('library target undeclared ' + specifier);
216
+ target = main.startsWith('./') ? main : './' + main;
217
+ }
218
+ }
219
+ // Node supplies package/relative resolution semantics. There is no fallback
220
+ // permission: its result still has to be an exact host-sealed member.
221
+ let resolved;
222
+ try {
223
+ resolved = next(target, selected);
224
+ }
225
+ catch {
226
+ return fail('target unresolved ' + specifier);
227
+ }
228
+ if (!resolved.url.startsWith('file:'))
229
+ return fail('target scheme undeclared ' + resolved.url);
230
+ const seal = verify(resolved.url);
231
+ edges.push({ parent: context.parentURL ?? entry, target: resolved.url, digest: seal });
232
+ return resolved;
233
+ },
234
+ load(url, context, next) {
235
+ if (url.startsWith('node:'))
236
+ return next(url, context);
237
+ verify(url);
238
+ const path = realpathSync(fileURLToPath(url)), member = files.get(path);
239
+ const loaded = next(url, context);
240
+ if (loaded.source === undefined || loaded.source === null)
241
+ return fail('file loader did not expose executable bytes ' + url);
242
+ const bytes = typeof loaded.source === 'string' ? Buffer.from(loaded.source, 'utf8') : loaded.source;
243
+ if (bytes.byteLength > member.bytes || digest(bytes) !== member.digest)
244
+ return fail('actual loaded source differs ' + url);
245
+ return loaded;
246
+ },
247
+ assertClosed() { if (refused)
248
+ throw Error(refused); for (const member of declaration.members)
249
+ verify(pathToFileURL(member.path).href); },
250
+ edges: () => edges.map(edge => ({ ...edge })),
251
+ };
252
+ }
253
+ /** Install before the entry of an instrument-selected privileged Node command.
254
+ * The bootstrap exception is restricted to this host function's exact selected
255
+ * entry. It is not a permission for a world to choose a verifier or a program.
256
+ * Hooks mediate imports; they do not establish a non-bypass JS sandbox.
257
+ */
258
+ export function installBoundary(declaration) {
259
+ const boundary = loadBoundary(declaration);
260
+ const register = fixedRegisterHooks;
261
+ if (typeof register !== 'function')
262
+ throw Error('REFUSE·load native hook profile unsupported by runtime');
263
+ protectNativeLoaderRegistration();
264
+ const hooks = register({ resolve(specifier, context, next) {
265
+ const boot = context.parentURL === import.meta.url && specifier === boundary.entry;
266
+ return boundary.resolve(specifier, boot ? { ...context, parentURL: undefined } : context, next);
267
+ }, load: boundary.load });
268
+ return { loadEntry: () => import(__rewriteRelativeImportExtension(boundary.entry)), assertClosed: boundary.assertClosed, edges: boundary.edges, close: () => hooks.deregister() };
269
+ }
@@ -0,0 +1,22 @@
1
+ import { type NativeRoute } from './route.ts';
2
+ import type { ReadingReference } from '../reading-context.ts';
3
+ export interface NativeMaterialization {
4
+ readonly archive: string;
5
+ readonly cwd: string;
6
+ readonly mountIdentity: string;
7
+ readonly locations: readonly {
8
+ member: string;
9
+ path: string;
10
+ kind: 'module' | 'data' | 'binary';
11
+ }[];
12
+ }
13
+ /** Called under the actual Root lease before identity/cache preparation. Missing
14
+ * laid content is a named content demand, never a publisher authority fallback.
15
+ * One retained mount per immutable selection avoids copying it on each ask.
16
+ */
17
+ export declare function materializeNativeRoute(route: NativeRoute): NativeMaterialization;
18
+ /** Fixed child, lifetime supervisor and transitive host modules enter identity
19
+ * before the cache key. Existing instrument closure resolves actual dependencies;
20
+ * this descriptor never executes a module and never accepts a world verifier.
21
+ */
22
+ export declare function nativeExecutorInputs(route: NativeRoute): readonly ReadingReference[];