@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,110 @@
1
+ import { sha256 as digest } from "../hash.js";
2
+ /** Root-fixed synchronous transport. It retains no grants or command results. */
3
+ import { Worker, isMainThread } from 'node:worker_threads';
4
+ import { readBoundedFile } from "./bounded-file.js";
5
+ import { remainingDeadlineMs } from "./deadline.js";
6
+ const FixedWorker = Worker;
7
+ const HEADER_BYTES = 32;
8
+ let controlWorkerOccupied = false;
9
+ export function createFixedControlBridge(inputBinding) {
10
+ // @1 is one main-thread control worker per OS process, never a WorldWorker grant.
11
+ if (!isMainThread)
12
+ throw Error('REFUSE·Root control transport requires main-thread realm');
13
+ const refuse = (why) => { throw Error('REFUSE·Root control transport ' + why); };
14
+ const integer = (value) => Number.isSafeInteger(value) && value > 0;
15
+ const sourceBytes = inputBinding.workerBytes;
16
+ const workerSelection = Object.freeze({ ...inputBinding.worker });
17
+ if (!(sourceBytes instanceof Uint8Array) || !workerSelection.coordinate || !integer(workerSelection.bytes) || sourceBytes.byteLength !== workerSelection.bytes)
18
+ refuse('selected worker bytes/type bound differs before copy');
19
+ const binding = Object.freeze({ ...inputBinding,
20
+ endpoint: Object.freeze({ ...inputBinding.endpoint }), resources: Object.freeze({ ...inputBinding.resources }),
21
+ runtime: Object.freeze({ ...inputBinding.runtime }), worker: workerSelection,
22
+ workerBytes: Buffer.from(sourceBytes) });
23
+ const { endpoint, resources, runtime, worker: selected } = binding;
24
+ if (!selected.coordinate || !integer(selected.bytes) || binding.workerBytes.byteLength !== selected.bytes || digest(binding.workerBytes) !== selected.digest)
25
+ refuse('selected worker bytes differ');
26
+ if (runtime.path !== process.execPath || !integer(runtime.bytes) || digest(readBoundedFile(runtime.path, runtime.bytes, runtime.path)) !== runtime.digest)
27
+ refuse('selected runtime bytes differ');
28
+ if (![endpoint.timeoutMs, endpoint.responseBytes, endpoint.deadlineMs, binding.deadlineMs].every(integer) || binding.deadlineMs > endpoint.deadlineMs)
29
+ refuse('parent time/byte resource absent or widened');
30
+ // host-control-transport@1 encodes each byte length in a signed Int32 cell.
31
+ // Refuse an incompatible declaration; never wrap or clamp its bound.
32
+ if (resources.inputBytes > 2147483647 || resources.outputBytes > 2147483647)
33
+ refuse('host-control-transport@1 signed Int32 length representation');
34
+ const sharedBytes = HEADER_BYTES + resources.inputBytes + resources.outputBytes;
35
+ if (!Object.values(resources).every(integer) || !Number.isSafeInteger(sharedBytes) || resources.inputBytes > endpoint.responseBytes || resources.outputBytes > endpoint.responseBytes || sharedBytes > resources.sharedBytes)
36
+ refuse('shared/input/output resource absent or exceeded');
37
+ const remainingMs = Math.min(endpoint.timeoutMs, binding.deadlineMs - Date.now());
38
+ if (remainingMs <= 0)
39
+ refuse('parent deadline');
40
+ const deadline = process.hrtime.bigint() + BigInt(remainingMs) * 1000000n;
41
+ const remaining = remainingDeadlineMs.bind(undefined, deadline);
42
+ if (controlWorkerOccupied)
43
+ refuse('one live main-thread control worker resource exhausted');
44
+ let shared;
45
+ try {
46
+ shared = new SharedArrayBuffer(sharedBytes);
47
+ }
48
+ catch {
49
+ return refuse('shared resource allocation');
50
+ }
51
+ const header = new Int32Array(shared, 0, 8), input = new Uint8Array(shared, HEADER_BYTES, resources.inputBytes), output = new Uint8Array(shared, HEADER_BYTES + resources.inputBytes, resources.outputBytes);
52
+ // Independently sealed bytes are immutable here. A payload cannot select a path or verifier.
53
+ const code = Buffer.from(binding.workerBytes).toString('base64');
54
+ controlWorkerOccupied = true;
55
+ let worker;
56
+ try {
57
+ worker = new FixedWorker(new URL('data:text/javascript;base64,' + code), { execArgv: [], env: {}, workerData: { endpoint: { ...endpoint }, deadlineMs: binding.deadlineMs, shared, inputBytes: resources.inputBytes, outputBytes: resources.outputBytes } });
58
+ }
59
+ catch {
60
+ controlWorkerOccupied = false;
61
+ return refuse('worker construction interrupted');
62
+ }
63
+ let termination;
64
+ const stop = () => termination ?? (termination = worker.terminate().finally(() => { controlWorkerOccupied = false; }));
65
+ worker.once('exit', () => { controlWorkerOccupied = false; });
66
+ worker.unref();
67
+ let red, closed = false, sequence = 0;
68
+ const fail = (why) => { red ?? (red = 'REFUSE·Root control transport ' + why); void stop(); throw Error(red); };
69
+ // Error events still need a listener when a synchronous wait prevents JS callbacks.
70
+ worker.on('error', error => { red ?? (red = 'REFUSE·Root control transport worker interrupted: ' + error.message); });
71
+ if (Atomics.load(header, 5) !== 1 && Atomics.wait(header, 5, 0, remaining()) === 'timed-out')
72
+ fail('startup deadline/interruption');
73
+ return {
74
+ exchange(frame) {
75
+ if (red)
76
+ throw Error(red);
77
+ if (closed)
78
+ fail('closed');
79
+ if (remaining() <= 0)
80
+ fail('parent deadline');
81
+ if (typeof frame !== 'string' || !frame.length || Buffer.byteLength(frame, 'utf8') > resources.inputBytes)
82
+ fail('request bytes before copy');
83
+ const bytes = Buffer.from(frame, 'utf8');
84
+ if (Atomics.load(header, 0) !== 0 || sequence >= 2147483647)
85
+ fail('overlapping request/sequence');
86
+ input.set(bytes);
87
+ Atomics.store(header, 2, bytes.length);
88
+ Atomics.store(header, 1, ++sequence);
89
+ Atomics.store(header, 0, 1);
90
+ worker.postMessage(sequence);
91
+ if (Atomics.wait(header, 0, 1, remaining()) === 'timed-out' || remaining() <= 0)
92
+ fail('deadline/interruption');
93
+ const state = Atomics.load(header, 0), length = Atomics.load(header, 3);
94
+ if (length < 1 || length > resources.outputBytes)
95
+ fail('reply bytes');
96
+ let reply;
97
+ try {
98
+ reply = new TextDecoder('utf-8', { fatal: true }).decode(output.slice(0, length));
99
+ }
100
+ catch {
101
+ return fail('reply encoding');
102
+ }
103
+ if (state !== 2)
104
+ fail(reply);
105
+ Atomics.store(header, 0, 0);
106
+ return reply;
107
+ },
108
+ async close() { closed = true; await stop(); }
109
+ };
110
+ }
@@ -0,0 +1,29 @@
1
+ import { type RootStanding, type PortProfile } from './reading.ts';
2
+ export interface HostControlProfile {
3
+ readonly profile: 'host-control-transport@1';
4
+ readonly realm: 'main-thread';
5
+ readonly coordinate: string;
6
+ readonly selector: {
7
+ readonly port: string;
8
+ readonly implementation: string;
9
+ };
10
+ readonly worker: {
11
+ readonly coordinate: string;
12
+ readonly digest: string;
13
+ readonly bytes: number;
14
+ };
15
+ readonly runtime: {
16
+ readonly coordinate: string;
17
+ readonly digest: string;
18
+ };
19
+ readonly limits: {
20
+ readonly workersPerProcess: 1;
21
+ readonly inFlight: 1;
22
+ readonly sharedBytes: number;
23
+ };
24
+ }
25
+ export interface HostControlSelection {
26
+ readonly line: string;
27
+ readonly profile: HostControlProfile;
28
+ }
29
+ export declare function selectedHostControl(root: RootStanding, parent: PortProfile): HostControlSelection | undefined;
@@ -0,0 +1,32 @@
1
+ /** Optional Root-selected control transport. This is fixed host machinery,
2
+ * never a world's worker permission or a change to producer message/work caps.
3
+ * One retained control worker belongs only to the main thread of each process;
4
+ * other Node realms keep the existing bounded fixed-client transport. */
5
+ import { parsedLine as parse } from "../../fold/claims.js";
6
+ import { authenticateSignedPort } from "./reading.js";
7
+ const fail = (why) => { throw Error('REFUSE·host control ' + why); };
8
+ const keys = (value) => value && typeof value === 'object' && !Array.isArray(value) ? Object.keys(value).sort().join(',') : '';
9
+ const digest = (value) => typeof value === 'string' && /^sha256:[a-f0-9]{64}$/.test(value);
10
+ export function selectedHostControl(root, parent) {
11
+ const coordinate = [parent.port.coordinate, 'control'].join('/'), rows = root.live.filter(line => { const got = parse(line); return got.kind === 'fact' && got.value.fields.scope === coordinate; });
12
+ if (!rows.length)
13
+ return undefined;
14
+ const selected = authenticateSignedPort(root, coordinate), profile = selected.profile, parsed = parse(selected.line);
15
+ if (parsed.kind !== 'fact' || parsed.value.fields.by !== root.signer.id)
16
+ fail('selection is not Root signed ' + coordinate);
17
+ if (keys(profile) !== 'coordinate,limits,profile,realm,runtime,selector,worker' || profile.profile !== 'host-control-transport@1' || profile.realm !== 'main-thread' || profile.coordinate !== coordinate || keys(profile.selector) !== 'implementation,port' || profile.selector.port !== parent.port.coordinate || profile.selector.implementation !== parent.port.implementation)
18
+ fail('profile selector differs ' + coordinate);
19
+ if (keys(profile.worker) !== 'bytes,coordinate,digest' || !/^native\/executor\/(?:dist|src)\/host\/native\/control-bridge-worker\.(?:js|ts)$/.test(profile.worker.coordinate) || !digest(profile.worker.digest) || !Number.isSafeInteger(profile.worker.bytes) || profile.worker.bytes < 1)
20
+ fail('fixed worker binding absent ' + coordinate);
21
+ const runtime = parent.selection.members.find(member => member.coordinate === parent.selection.runtime);
22
+ if (keys(profile.runtime) !== 'coordinate,digest' || !runtime || profile.runtime.coordinate !== runtime.coordinate || profile.runtime.digest !== runtime.digest)
23
+ fail('fixed runtime selection differs ' + coordinate);
24
+ if (keys(profile.limits) !== 'inFlight,sharedBytes,workersPerProcess' || profile.limits.workersPerProcess !== 1 || profile.limits.inFlight !== 1 || !Number.isSafeInteger(profile.limits.sharedBytes) || profile.limits.sharedBytes < 32)
25
+ fail('named resource invalid ' + coordinate);
26
+ if (!Number.isSafeInteger(parent.port.responseBytes) || parent.port.responseBytes < 1 || parent.port.responseBytes > 2147483647)
27
+ fail('message cap outside declared signed Int32 representation ' + coordinate);
28
+ const required = 32 + 2 * parent.port.responseBytes;
29
+ if (!Number.isSafeInteger(required) || profile.limits.sharedBytes < required)
30
+ fail('shared resource cannot preserve message caps ' + coordinate);
31
+ return Object.freeze({ line: selected.line, profile });
32
+ }
@@ -0,0 +1,5 @@
1
+ import { type RootStanding, type PortProfile } from './reading.ts';
2
+ import type { RootJobCustody, NodeImage } from './node-jobs.ts';
3
+ import type { Handoff } from './jobs.ts';
4
+ import type { LogicalLoader } from './image.ts';
5
+ export declare function nativeRootJobCustody(root: RootStanding, profile: PortProfile, handoff: Handoff, image: NodeImage, logical: LogicalLoader): RootJobCustody;
@@ -0,0 +1,91 @@
1
+ /** Fixed Root constructor for finite descendant custody. It selects only current
2
+ * authenticated Root records matching the already planned native parent job.
3
+ */
4
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join, extname } from 'node:path';
7
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
+ import { createHash } from 'node:crypto';
9
+ import { parse } from '@lapxo/topos/wire';
10
+ import { processOf, ownRoot } from "../../observe/runner.js";
11
+ import { selectedHostControl } from "./control-selection.js";
12
+ import { readBoundedFile } from "./bounded-file.js";
13
+ import { authenticateSignedPort } from "./reading.js";
14
+ import { createProducerJobCustody } from "./generated-custodian.js";
15
+ const fail = (why) => { throw Error('REFUSE·native custody ' + why); };
16
+ const sha = (raw) => 'sha256:' + createHash('sha256').update(raw).digest('hex');
17
+ export function nativeRootJobCustody(root, profile, handoff, image, logical) {
18
+ const data = logical.members.map(member => { const location = image.locations.find(loc => loc.member === member.coordinate); if (!location)
19
+ return fail('literal member absent'); return { path: location.path, digest: member.digest }; }), libraries = logical.libraries.map(lib => { const location = image.locations.find(loc => loc.member === lib.manifest); if (!location)
20
+ return fail('library absent'); return { name: lib.name, manifest: location.path, coordinate: lib.manifest }; });
21
+ const candidates = root.live.flatMap(line => { const row = parse(line); if (row.kind !== 'fact' || !row.value.fields.scope.startsWith('port/'))
22
+ return []; let p; try {
23
+ p = JSON.parse(row.value.fields.about ?? '');
24
+ }
25
+ catch {
26
+ return [];
27
+ } return p?.selector?.port === profile.port.coordinate ? [{ coordinate: row.value.fields.scope, profile: p }] : []; });
28
+ return { async open(parent) {
29
+ const matching = candidates.filter(candidate => candidate.profile.selector.binding === parent.binding && candidate.profile.selector.implementation === parent.implementation);
30
+ const producers = matching.filter(candidate => candidate.profile.profile === 'host-generated-executable@3');
31
+ if (producers.length !== 1)
32
+ return fail('requires one current Root producer for ' + parent.binding);
33
+ const selected = producers[0];
34
+ authenticateSignedPort(root, selected.coordinate);
35
+ const control = selectedHostControl(root, profile);
36
+ const implementation = fileURLToPath(new URL('./generated' + extname(new URL(import.meta.url).pathname), import.meta.url)), implementationBytes = readBoundedFile(implementation, root.budget.bytes, implementation);
37
+ const runtime = image.locations.find(location => location.member === image.runtime), runtimeMember = handoff.members.find(member => member.coordinate === image.runtime);
38
+ if (!runtime || !runtimeMember)
39
+ return fail('fixed actual runtime absent');
40
+ const binaries = matching.filter(candidate => ['host-native-binary@1', 'host-native-binary-origin@1'].includes(candidate.profile.profile)).map(candidate => { const p = candidate.profile, location = image.locations.find(loc => loc.member === p.entry?.path), member = handoff.members.find(m => m.coordinate === p.entry?.path); if (!location || !member)
41
+ return fail('binary outside actual selected image'); return { coordinate: candidate.coordinate, privateBinding: { coordinate: member.coordinate, path: location.path, digest: member.digest } }; });
42
+ const programs = matching.filter(candidate => candidate.profile.profile === 'host-native-program@1').map(candidate => ({ coordinate: candidate.coordinate, runtimeBinding: { coordinate: image.runtime, path: runtime.path, digest: runtimeMember.digest, members: image.locations.map(location => ({ coordinate: location.member, path: location.path })) } }));
43
+ const directory = mkdtempSync(join(tmpdir(), 'bound-native-custody-')), contextFile = join(directory, 'context.json'), configFile = join(directory, 'loader.json');
44
+ let acquired;
45
+ try {
46
+ const service = createProducerJobCustody({ root, coordinate: selected.coordinate, implementationBytes, snapshot: { budget: { members: handoff.budget.members, bytes: handoff.budget.bytes }, members: data, libraries, builtins: logical.builtins, platform: logical.platform }, nativeImplementations: new Set(logical.members.map(member => member.digest)), directory, contextFile, nativeBinaries: binaries, programSelections: programs });
47
+ acquired = await service.open(parent);
48
+ const client = processOf('custodian'), clientBytes = readBoundedFile(client, parent.responseBytes, client), preload = new URL(pathToFileURL(processOf('native-descendant')).href);
49
+ preload.searchParams.set('config', configFile);
50
+ preload.searchParams.set('bytes', String(handoff.budget.bytes));
51
+ let controlEnvelope;
52
+ if (control) {
53
+ const worker = control.profile.worker, path = join(ownRoot(), worker.coordinate.slice('native/executor/'.length)), raw = readBoundedFile(path, worker.bytes, worker.coordinate);
54
+ if (raw.length !== worker.bytes || sha(raw) !== worker.digest)
55
+ return fail('sealed control worker bytes differ');
56
+ if (control.profile.runtime.coordinate !== image.runtime || control.profile.runtime.digest !== runtimeMember.digest)
57
+ return fail('sealed control runtime differs');
58
+ const shared = 32 + 2 * parent.responseBytes;
59
+ if (!Number.isSafeInteger(shared) || shared > control.profile.limits.sharedBytes)
60
+ return fail('shared control resource absent');
61
+ controlEnvelope = { profileLine: control.line, realm: control.profile.realm, worker: { ...worker, path }, resources: { inputBytes: parent.responseBytes, outputBytes: parent.responseBytes, sharedBytes: control.profile.limits.sharedBytes } };
62
+ }
63
+ const config = { ...(controlEnvelope ? { control: controlEnvelope } : {}), members: data, libraries, builtins: logical.builtins, platform: logical.platform, executableDigests: logical.members.filter(member => /\.(?:[mc]?js|ts)$/.test(member.coordinate)).map(member => member.digest), metadataDigests: logical.members.filter(member => member.coordinate.endsWith('.json')).map(member => member.digest), inputBytes: handoff.budget.bytes, runtimeBytes: runtimeMember.bytes.byteLength, clientBytes: clientBytes.length, contextBytes: parent.responseBytes, custodian: { contextFile, client, runtime: runtime.path, preload: preload.href, runtimeDigest: runtimeMember.digest, clientDigest: sha(clientBytes) } };
64
+ // Loader metadata describes the authenticated input image, not the command's
65
+ // stdout. Its declared input budget stays separate from responseBytes.
66
+ const raw = Buffer.from(JSON.stringify(config));
67
+ if (raw.length > handoff.budget.bytes)
68
+ return fail('operational descendant envelope bytes');
69
+ writeFileSync(configFile, raw, { flag: 'wx', mode: 0o400 });
70
+ // The URL carries an independently fixed expected digest of the private host
71
+ // envelope. This is operational custody, never authority from JSON.
72
+ preload.searchParams.set('digest', sha(raw));
73
+ config.custodian.preload = preload.href;
74
+ // The recursive preload URL excludes its own digest while writing the file;
75
+ // its fixed child validates the exact file before installing any hook.
76
+ const descendant = { ...config, custodian: { ...config.custodian, preload: preload.href } };
77
+ const held = acquired;
78
+ return { descendant, assertClosed: () => held.assertClosed(), async close() { try {
79
+ await held.close();
80
+ }
81
+ finally {
82
+ rmSync(directory, { recursive: true, force: true });
83
+ } } };
84
+ }
85
+ catch (error) {
86
+ await acquired?.close();
87
+ rmSync(directory, { recursive: true, force: true });
88
+ throw error;
89
+ }
90
+ } };
91
+ }
@@ -0,0 +1,12 @@
1
+ /** Remaining monotonic lease, rounded up for host APIs that accept milliseconds. */
2
+ export declare function remainingDeadlineMs(deadline: bigint): number;
3
+ interface Clock {
4
+ readonly now: () => bigint;
5
+ readonly schedule: (callback: () => void, delayMs: number) => unknown;
6
+ readonly cancel: (handle: unknown) => void;
7
+ }
8
+ /** The admitted deadline is never clamped to the platform timer range. Each
9
+ * timer is merely a wake-up slice; expiry checks the original monotonic clock.
10
+ * Injected clock exists only for deterministic platform-boundary vectors. */
11
+ export declare function scheduleDeadline(deadline: bigint, expire: () => void, clock?: Clock): () => void;
12
+ export {};
@@ -0,0 +1,28 @@
1
+ const TIMER_MAX_MS = 2147483647;
2
+ /** Remaining monotonic lease, rounded up for host APIs that accept milliseconds. */
3
+ export function remainingDeadlineMs(deadline) {
4
+ return Math.max(0, Number((deadline - process.hrtime.bigint() + 999999n) / 1000000n));
5
+ }
6
+ const native = { now: () => process.hrtime.bigint(), schedule: (callback, delay) => setTimeout(callback, delay), cancel: handle => clearTimeout(handle) };
7
+ /** The admitted deadline is never clamped to the platform timer range. Each
8
+ * timer is merely a wake-up slice; expiry checks the original monotonic clock.
9
+ * Injected clock exists only for deterministic platform-boundary vectors. */
10
+ export function scheduleDeadline(deadline, expire, clock = native) {
11
+ let active = true, handle;
12
+ const arm = () => {
13
+ if (!active)
14
+ return;
15
+ const remaining = deadline - clock.now();
16
+ if (remaining <= 0n) {
17
+ active = false;
18
+ expire();
19
+ return;
20
+ }
21
+ const ceilMs = (remaining + 999999n) / 1000000n;
22
+ const slice = ceilMs > BigInt(TIMER_MAX_MS) ? TIMER_MAX_MS : Number(ceilMs);
23
+ handle = clock.schedule(arm, slice);
24
+ };
25
+ arm();
26
+ return () => { active = false; if (handle !== undefined)
27
+ clock.cancel(handle); };
28
+ }
@@ -0,0 +1 @@
1
+ export declare function installDescendantBoundary(config: any): void;