@lapxo/bound 0.1.7 → 0.1.9
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.
- package/README.md +56 -20
- package/TARGET.bound +27 -27
- package/dist/cli/accept.js +83 -59
- package/dist/cli/act-result.d.ts +9 -0
- package/dist/cli/act-result.js +68 -13
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.js +5 -2
- package/dist/cli/custodian.d.ts +1 -0
- package/dist/cli/custodian.js +29 -0
- package/dist/cli/native-descendant.d.ts +1 -0
- package/dist/cli/native-descendant.js +19 -0
- package/dist/cli/native.d.ts +1 -0
- package/dist/cli/native.js +60 -0
- package/dist/cli/observe.js +9 -2
- package/dist/cli/pass.js +6 -1
- package/dist/cli/standing-result.js +7 -26
- package/dist/cli/takes.js +5 -2
- package/dist/cli/verb.js +24 -5
- package/dist/fold/admission-history.js +2 -2
- package/dist/fold/claims.d.ts +4 -0
- package/dist/fold/claims.js +23 -3
- package/dist/fold/closed.d.ts +1 -1
- package/dist/fold/closed.js +16 -4
- package/dist/fold/digests.d.ts +1 -1
- package/dist/fold/digests.js +3 -2
- package/dist/fold/evidence.d.ts +1 -1
- package/dist/fold/kept.js +2 -1
- package/dist/fold/observed.d.ts +1 -0
- package/dist/fold/observed.js +54 -9
- package/dist/fold/readers.d.ts +4 -3
- package/dist/fold/readers.js +18 -13
- package/dist/fold/resolved.js +11 -1
- package/dist/fold/signers.js +2 -2
- package/dist/host/adapters/modules/javascript.d.ts +2 -0
- package/dist/host/adapters/modules/javascript.js +8 -6
- package/dist/host/admitted-place.d.ts +5 -0
- package/dist/host/admitted-place.js +54 -0
- package/dist/host/capsule-process.d.ts +1 -1
- package/dist/host/capsule-process.js +4 -1
- package/dist/host/capsule.d.ts +4 -0
- package/dist/host/capsule.js +16 -2
- package/dist/host/contextual-place.d.ts +59 -0
- package/dist/host/contextual-place.js +77 -0
- package/dist/host/hash.d.ts +7 -0
- package/dist/host/hash.js +6 -0
- package/dist/host/io.d.ts +2 -0
- package/dist/host/io.js +2 -0
- package/dist/host/lifetime.js +61 -22
- package/dist/host/native/bounded-file.d.ts +8 -0
- package/dist/host/native/bounded-file.js +32 -0
- package/dist/host/native/command.d.ts +36 -0
- package/dist/host/native/command.js +91 -0
- package/dist/host/native/control-bridge-worker.d.ts +1 -0
- package/dist/host/native/control-bridge-worker.js +88 -0
- package/dist/host/native/control-bridge.d.ts +30 -0
- package/dist/host/native/control-bridge.js +110 -0
- package/dist/host/native/control-selection.d.ts +29 -0
- package/dist/host/native/control-selection.js +32 -0
- package/dist/host/native/custody.d.ts +5 -0
- package/dist/host/native/custody.js +91 -0
- package/dist/host/native/deadline.d.ts +12 -0
- package/dist/host/native/deadline.js +28 -0
- package/dist/host/native/descendants.d.ts +1 -0
- package/dist/host/native/descendants.js +528 -0
- package/dist/host/native/generated-custodian.d.ts +122 -0
- package/dist/host/native/generated-custodian.js +267 -0
- package/dist/host/native/generated.d.ts +217 -0
- package/dist/host/native/generated.js +379 -0
- package/dist/host/native/image.d.ts +36 -0
- package/dist/host/native/image.js +45 -0
- package/dist/host/native/jobs.d.ts +75 -0
- package/dist/host/native/jobs.js +148 -0
- package/dist/host/native/loader.d.ts +80 -0
- package/dist/host/native/loader.js +269 -0
- package/dist/host/native/materialize.d.ts +22 -0
- package/dist/host/native/materialize.js +175 -0
- package/dist/host/native/module-closure.d.ts +27 -0
- package/dist/host/native/module-closure.js +99 -0
- package/dist/host/native/module-syntax.d.ts +8 -0
- package/dist/host/native/module-syntax.js +63 -0
- package/dist/host/native/native-binary.d.ts +54 -0
- package/dist/host/native/native-binary.js +115 -0
- package/dist/host/native/native-program.d.ts +56 -0
- package/dist/host/native/native-program.js +72 -0
- package/dist/host/native/node-jobs.d.ts +84 -0
- package/dist/host/native/node-jobs.js +187 -0
- package/dist/host/native/node-launch.d.ts +72 -0
- package/dist/host/native/node-launch.js +159 -0
- package/dist/host/native/preload.d.ts +1 -0
- package/dist/host/native/preload.js +45 -0
- package/dist/host/native/reading.d.ts +91 -0
- package/dist/host/native/reading.js +249 -0
- package/dist/host/native/receipt-environment.d.ts +3 -0
- package/dist/host/native/receipt-environment.js +41 -0
- package/dist/host/native/references.d.ts +5 -0
- package/dist/host/native/references.js +30 -0
- package/dist/host/native/root.d.ts +17 -0
- package/dist/host/native/root.js +37 -0
- package/dist/host/native/route.d.ts +66 -0
- package/dist/host/native/route.js +216 -0
- package/dist/host/native/runtime.d.ts +4 -0
- package/dist/host/native/runtime.js +9 -0
- package/dist/host/native-reader-context.d.ts +3 -0
- package/dist/host/native-reader-context.js +8 -0
- package/dist/host/place-clock-selection.d.ts +55 -0
- package/dist/host/place-clock-selection.js +124 -0
- package/dist/host/ports/local-act.d.ts +10 -0
- package/dist/host/ports/local-act.js +78 -6
- package/dist/host/ports/place-act.d.ts +46 -0
- package/dist/host/ports/place-act.js +169 -0
- package/dist/host/process-lifetime.js +8 -3
- package/dist/host/provide.d.ts +25 -0
- package/dist/host/provide.js +115 -13
- package/dist/host/reading-closures.d.ts +15 -1
- package/dist/host/reading-closures.js +88 -6
- package/dist/host/reading-context.d.ts +29 -0
- package/dist/host/reading-context.js +53 -0
- package/dist/host/root-clock-source.d.ts +85 -0
- package/dist/host/root-clock-source.js +305 -0
- package/dist/host/selected-capsules.js +3 -2
- package/dist/judge/take.d.ts +2 -0
- package/dist/judge/take.js +1 -1
- package/dist/land/act.d.ts +26 -0
- package/dist/land/act.js +96 -3
- package/dist/land/ledger.d.ts +4 -0
- package/dist/land/ledger.js +20 -2
- package/dist/observe/runner.d.ts +4 -0
- package/dist/observe/runner.js +9 -2
- package/docs/contracts/cli.md +13 -0
- package/package.json +2 -2
- package/receipts.bound +607 -567
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { sha256 } from "../hash.js";
|
|
2
|
+
/** Fixed Root native preload. One parent custodian holds generated provenance
|
|
3
|
+
* and counters. Original child APIs execute actual programs; nothing synthesizes
|
|
4
|
+
* their results. This privileged native profile is not an adversarial sandbox. */
|
|
5
|
+
import nativeChildProcess from 'node:child_process';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { traverseGeneratedClosure } from "./generated.js";
|
|
8
|
+
import nativeWorkerThreads from 'node:worker_threads';
|
|
9
|
+
import { realpathSync, existsSync, statSync } from 'node:fs';
|
|
10
|
+
import { resolve, dirname, extname } from 'node:path';
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
12
|
+
import * as nativeModule from "node:module";
|
|
13
|
+
import { syncBuiltinESMExports, builtinModules } from 'node:module';
|
|
14
|
+
import { nodeLaunch, projectNativeProgramLaunch, selectNativeProgramParent, nativeEvalParent, actualNodeEvalOptions } from "./node-launch.js";
|
|
15
|
+
import { readBoundedFile } from "./bounded-file.js";
|
|
16
|
+
import { createFixedControlBridge } from "./control-bridge.js";
|
|
17
|
+
import { installNativeResolutionReuse } from "./loader.js";
|
|
18
|
+
const childProcess = nativeChildProcess, workerThreads = nativeWorkerThreads;
|
|
19
|
+
const registerHooks = nativeModule.registerHooks;
|
|
20
|
+
export function installDescendantBoundary(config) {
|
|
21
|
+
const sha = sha256, originalSync = childProcess.spawnSync;
|
|
22
|
+
let red, transport, reporting = false;
|
|
23
|
+
const fail = (why) => {
|
|
24
|
+
red ?? (red = 'REFUSE·native descendant ' + why);
|
|
25
|
+
if (transport && !reporting) {
|
|
26
|
+
reporting = true;
|
|
27
|
+
try {
|
|
28
|
+
transport({ operation: 'violation', token: endpoint.token, why: red });
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
finally {
|
|
32
|
+
reporting = false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
throw Error(red);
|
|
36
|
+
};
|
|
37
|
+
const { contextFile, client, runtime, preload, runtimeDigest, clientDigest } = config.custodian ?? {};
|
|
38
|
+
let execution;
|
|
39
|
+
try {
|
|
40
|
+
execution = JSON.parse(readBoundedFile(contextFile, config.contextBytes, contextFile).toString('utf8'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
fail('Root execution custody absent');
|
|
44
|
+
}
|
|
45
|
+
const { endpoint, parentExecution } = execution;
|
|
46
|
+
const programs = execution.programs ?? [];
|
|
47
|
+
if (!parentExecution?.identity || endpoint?.parent !== parentExecution.identity)
|
|
48
|
+
fail('Root custody parent differs');
|
|
49
|
+
if (!endpoint || !runtime || !preload || !client || sha(readBoundedFile(runtime, config.runtimeBytes, runtime)) !== runtimeDigest || sha(readBoundedFile(client, config.clientBytes, client)) !== clientDigest)
|
|
50
|
+
fail('Root custodian binding absent/different');
|
|
51
|
+
// File validity is checked only at an actual disk-execution boundary, never cached.
|
|
52
|
+
const runtimeForExecution = (path, why) => {
|
|
53
|
+
let valid = false;
|
|
54
|
+
try {
|
|
55
|
+
valid = realpathSync(path) === realpathSync(runtime) && sha(readBoundedFile(path, config.runtimeBytes, path)) === runtimeDigest;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return fail(why);
|
|
59
|
+
}
|
|
60
|
+
if (!valid)
|
|
61
|
+
fail(why);
|
|
62
|
+
};
|
|
63
|
+
const known = new Map(config.members.map((m) => [realpathSync(m.path), m.digest])), executableDigests = new Set(config.executableDigests ?? []), metadataDigests = new Set(config.metadataDigests ?? config.members.filter((m) => m.path.endsWith('.json')).map((m) => m.digest)), native = new Set(config.builtins.map((n) => n.startsWith('node:') ? n : 'node:' + n)), library = new Map(config.libraries.map((l) => [l.name, l]));
|
|
64
|
+
// Reuse this fixed resolver only after comparing every required library
|
|
65
|
+
// member with the instrument's pins. The inherited input resource also
|
|
66
|
+
// bounds metadata prepared for that comparison; no world supplies a checker.
|
|
67
|
+
let resolutionBytes = 0;
|
|
68
|
+
const resolutionMembers = config.members.map((member) => { const path = realpathSync(member.path), stat = statSync(path); if (!stat.isFile() || !Number.isSafeInteger(config.inputBytes) || config.inputBytes < 1 || (resolutionBytes += stat.size) > config.inputBytes)
|
|
69
|
+
fail('fixed library resolution resource exceeded'); return { path, digest: member.digest, bytes: stat.size }; });
|
|
70
|
+
installNativeResolutionReuse({ members: resolutionMembers, libraries: config.libraries, builtins: config.builtins, entry: resolutionMembers[0]?.path ?? '', platform: config.platform });
|
|
71
|
+
let controlBridge;
|
|
72
|
+
if (config.control && config.control.realm !== 'main-thread')
|
|
73
|
+
fail('unknown Root control realm');
|
|
74
|
+
// The signed host profile selects retention only in the actual main realm.
|
|
75
|
+
// WorldWorker realms keep the same bounded fixed-client/checks, not a SAB verifier.
|
|
76
|
+
if (config.control && nativeWorkerThreads.isMainThread) {
|
|
77
|
+
const c = config.control;
|
|
78
|
+
controlBridge = createFixedControlBridge({ endpoint, workerBytes: readBoundedFile(c.worker.path, c.worker.bytes, c.worker.coordinate), worker: c.worker, runtime: { path: runtime, digest: runtimeDigest, bytes: config.runtimeBytes }, resources: c.resources, deadlineMs: endpoint.deadlineMs });
|
|
79
|
+
process.once('exit', () => { void controlBridge?.close(); });
|
|
80
|
+
process.once('beforeExit', () => { void controlBridge?.close(); });
|
|
81
|
+
}
|
|
82
|
+
const exchange = (body) => {
|
|
83
|
+
const frame = JSON.stringify({ protocol: 'command-lot/1', coordinate: endpoint.coordinate, implementation: endpoint.implementation, identity: endpoint.identity, requests: [JSON.stringify(body)] });
|
|
84
|
+
if (Buffer.byteLength(frame) > endpoint.responseBytes)
|
|
85
|
+
fail('custodian request bytes');
|
|
86
|
+
let stdout;
|
|
87
|
+
if (controlBridge) {
|
|
88
|
+
try {
|
|
89
|
+
stdout = controlBridge.exchange(frame);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
void controlBridge.close();
|
|
93
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// This branch executes disk bytes now; retention never stands in for this check.
|
|
98
|
+
runtimeForExecution(runtime, 'Root sync bridge runtime bytes differ');
|
|
99
|
+
let clientValid = false;
|
|
100
|
+
try {
|
|
101
|
+
clientValid = sha(readBoundedFile(client, config.clientBytes, client)) === clientDigest;
|
|
102
|
+
}
|
|
103
|
+
catch { /* A missing or oversized executable is a refusal too. */ }
|
|
104
|
+
if (!clientValid)
|
|
105
|
+
fail('Root sync bridge bytes differ');
|
|
106
|
+
const got = Reflect.apply(originalSync, childProcess, [runtime, [client, String(config.contextBytes)], { input: JSON.stringify({ endpoint, frame }), encoding: 'utf8', timeout: endpoint.timeoutMs, maxBuffer: endpoint.responseBytes, env: { ...process.env }, shell: false }]);
|
|
107
|
+
if (got.error || got.signal || got.status !== 0 || got.stderr)
|
|
108
|
+
fail('custodian client interrupted/refused ' + (got.error?.message ?? got.stderr ?? got.signal ?? got.status));
|
|
109
|
+
stdout = got.stdout;
|
|
110
|
+
}
|
|
111
|
+
let reply;
|
|
112
|
+
try {
|
|
113
|
+
reply = JSON.parse(stdout);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return fail('custodian response malformed');
|
|
117
|
+
}
|
|
118
|
+
if (reply.protocol !== 'command-lot/1' || reply.coordinate !== endpoint.coordinate || reply.implementation !== endpoint.implementation || reply.identity !== endpoint.identity || !Array.isArray(reply.responses) || reply.responses.length !== 1)
|
|
119
|
+
fail('custodian response identity/count');
|
|
120
|
+
return reply.responses[0];
|
|
121
|
+
};
|
|
122
|
+
transport = exchange;
|
|
123
|
+
const verifiedEdges = new Map();
|
|
124
|
+
let candidateHandoff = () => [], candidateHanded = ignoreEmission;
|
|
125
|
+
const request = (entry, operation = 'entry') => {
|
|
126
|
+
if (red)
|
|
127
|
+
throw Error(red);
|
|
128
|
+
const candidates = operation === 'entry' ? candidateHandoff() : [];
|
|
129
|
+
const one = exchange({ operation, token: endpoint.token, request: candidates.length ? { ...entry, candidates } : entry });
|
|
130
|
+
if (one.kind === 'refuse')
|
|
131
|
+
fail(one.why);
|
|
132
|
+
if (one.kind !== 'entry-context' || !one.context?.identity)
|
|
133
|
+
fail('custodian context absent');
|
|
134
|
+
if (one.context.parent !== parentExecution.identity || one.context.producer !== endpoint.identity)
|
|
135
|
+
fail('custodian context parent differs');
|
|
136
|
+
candidateHanded(candidates);
|
|
137
|
+
if (operation === 'entry' && Array.isArray(one.context.edges)) {
|
|
138
|
+
for (const edge of one.context.edges) {
|
|
139
|
+
if (typeof edge.target === 'string' && edge.target.startsWith('/') && /^sha256:[0-9a-f]{64}$/.test(edge.digest ?? '')) {
|
|
140
|
+
const prior = verifiedEdges.get(edge.target);
|
|
141
|
+
if (prior && prior !== edge.digest)
|
|
142
|
+
fail('verified edge bytes changed ' + edge.target);
|
|
143
|
+
verifiedEdges.set(edge.target, edge.digest);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return one.context;
|
|
148
|
+
};
|
|
149
|
+
const invocation = (args, cwd, env, options = {}) => ({ cwd: realpathSync(resolve(cwd ?? process.cwd())), argv: [...args], environment: Object.entries(env ?? process.env).sort(), runtime, timeoutMs: Math.min(options.timeout ?? endpoint.timeoutMs, endpoint.timeoutMs), responseBytes: Math.min(options.maxBuffer ?? endpoint.responseBytes, endpoint.responseBytes) });
|
|
150
|
+
let evalParent;
|
|
151
|
+
const actualEval = nativeWorkerThreads.isMainThread && actualNodeEvalOptions(process.execArgv);
|
|
152
|
+
if (actualEval) {
|
|
153
|
+
if (realpathSync(process.execPath) !== realpathSync(runtime))
|
|
154
|
+
fail('actual eval runtime differs from Root selection');
|
|
155
|
+
const actual = { argv: [...process.execArgv, ...process.argv.slice(1)], rootPrefix: ['--import', preload], cwd: process.cwd(), environment: Object.entries(process.env).sort(), runtime };
|
|
156
|
+
const retained = request(actual, 'eval-context');
|
|
157
|
+
evalParent = nativeEvalParent(retained, actual);
|
|
158
|
+
}
|
|
159
|
+
let flushEmissions = ignoreEmission;
|
|
160
|
+
let flushExecutable = ignoreEmission;
|
|
161
|
+
const interpretedMetadata = new Map();
|
|
162
|
+
const modeOf = (path, call) => {
|
|
163
|
+
const ext = extname(path);
|
|
164
|
+
if (ext === '.mjs' || ext === '.mts')
|
|
165
|
+
return 'module';
|
|
166
|
+
if (ext === '.cjs' || ext === '.cts')
|
|
167
|
+
return 'commonjs';
|
|
168
|
+
if (ext !== '' && ext !== '.js' && ext !== '.ts')
|
|
169
|
+
fail('executable extension unsupported ' + ext);
|
|
170
|
+
let directory = dirname(path);
|
|
171
|
+
for (;;) {
|
|
172
|
+
const manifest = resolve(directory, 'package.json');
|
|
173
|
+
if (existsSync(manifest)) {
|
|
174
|
+
flushEmissions([realpathSync(manifest)]);
|
|
175
|
+
if (statSync(manifest).size > (config.inputBytes ?? endpoint.responseBytes))
|
|
176
|
+
fail('package interpretation bytes');
|
|
177
|
+
const raw = readBoundedFile(manifest, config.inputBytes, manifest);
|
|
178
|
+
const digest = sha(raw);
|
|
179
|
+
if (!metadataDigests.has(digest)) {
|
|
180
|
+
const key = JSON.stringify([manifest, digest, call]);
|
|
181
|
+
if (!interpretedMetadata.has(key)) {
|
|
182
|
+
const held = request({ path: manifest, interpretation: 'node-package-json@1', invocation: call }, 'interpretation');
|
|
183
|
+
if (held.kind !== 'derived-interpretation/1' || held.path !== realpathSync(manifest) || held.digest !== digest || held.interpretation !== 'node-package-json@1' || sha(readBoundedFile(manifest, config.inputBytes, manifest)) !== digest)
|
|
184
|
+
fail('package interpretation changed after custody');
|
|
185
|
+
interpretedMetadata.set(key, held.identity);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
let pkg;
|
|
189
|
+
try {
|
|
190
|
+
pkg = JSON.parse(raw.toString('utf8'));
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
fail('package interpretation invalid');
|
|
194
|
+
}
|
|
195
|
+
if (pkg.type === undefined || pkg.type === 'commonjs')
|
|
196
|
+
return 'commonjs';
|
|
197
|
+
if (pkg.type === 'module')
|
|
198
|
+
return 'module';
|
|
199
|
+
fail('package interpretation type unknown');
|
|
200
|
+
}
|
|
201
|
+
const parent = dirname(directory);
|
|
202
|
+
if (parent === directory)
|
|
203
|
+
return 'commonjs';
|
|
204
|
+
directory = parent;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const sealFile = (path, call, forceCustody = false, loaded = undefined) => {
|
|
208
|
+
let actual;
|
|
209
|
+
try {
|
|
210
|
+
actual = realpathSync(path);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return fail('executable target absent ' + path);
|
|
214
|
+
}
|
|
215
|
+
flushEmissions([actual]);
|
|
216
|
+
const expected = known.get(actual);
|
|
217
|
+
if (statSync(actual).size > (config.inputBytes ?? endpoint.responseBytes))
|
|
218
|
+
fail('input bytes before hash');
|
|
219
|
+
const bytes = readBoundedFile(actual, config.inputBytes, actual), digest = sha(bytes);
|
|
220
|
+
if (actual.endsWith('.json')) {
|
|
221
|
+
if (forceCustody)
|
|
222
|
+
fail('JSON executable entry unsupported');
|
|
223
|
+
if (!metadataDigests.has(digest))
|
|
224
|
+
fail('JSON interpretation not Root-selected ' + actual);
|
|
225
|
+
return actual;
|
|
226
|
+
}
|
|
227
|
+
if (expected && digest !== expected)
|
|
228
|
+
fail('retained target bytes differ ' + actual);
|
|
229
|
+
const priorEdge = verifiedEdges.get(actual);
|
|
230
|
+
if (priorEdge && digest !== priorEdge)
|
|
231
|
+
fail('verified edge bytes differ ' + actual);
|
|
232
|
+
if (!forceCustody && priorEdge === digest && !(extname(actual) === '' && !loaded))
|
|
233
|
+
return actual;
|
|
234
|
+
if (extname(actual) === '' && !loaded)
|
|
235
|
+
return actual;
|
|
236
|
+
if (!forceCustody && !loaded && (executableDigests.has(digest) || (expected && extname(actual) === '.json')))
|
|
237
|
+
return actual;
|
|
238
|
+
const selectedMode = loaded?.format ?? modeOf(actual, call);
|
|
239
|
+
flushExecutable(actual, selectedMode, bytes);
|
|
240
|
+
const context = request({ kind: 'file', path: actual, mode: selectedMode, ...(loaded ? { interpretation: 'node-ambiguous-javascript@1' } : {}), invocation: call });
|
|
241
|
+
if (context.path !== actual && context.target?.path !== actual)
|
|
242
|
+
fail('custodian target changed');
|
|
243
|
+
if (context.digest !== sha(readBoundedFile(actual, config.inputBytes, actual)))
|
|
244
|
+
fail('derived target changed after seal');
|
|
245
|
+
return actual;
|
|
246
|
+
};
|
|
247
|
+
const prepare = (program, args, options = {}) => {
|
|
248
|
+
if (red)
|
|
249
|
+
throw Error(red);
|
|
250
|
+
if (options.shell)
|
|
251
|
+
fail('shell target undeclared');
|
|
252
|
+
let command = program;
|
|
253
|
+
const native = programs.filter((p) => p.alias === program && p.kind === 'native-binary');
|
|
254
|
+
if (native.length) {
|
|
255
|
+
if (native.length !== 1)
|
|
256
|
+
fail('native binary alias ambiguous');
|
|
257
|
+
const selected = native[0], call = invocation(args, options.cwd, options.env, options), held = request({ alias: program, path: selected.entry, invocation: call }, 'native-program');
|
|
258
|
+
if (held.kind !== 'native-program-transaction/1' || held.target !== selected.entry || held.digest !== selected.entryDigest || sha(readBoundedFile(selected.entry, selected.entryBytes, selected.entry)) !== held.digest)
|
|
259
|
+
fail('native binary changed before entry');
|
|
260
|
+
return { command: selected.entry, args: [...args], options: { ...options, timeout: call.timeoutMs, maxBuffer: call.responseBytes, shell: false } };
|
|
261
|
+
}
|
|
262
|
+
if (programs.some((p) => p.alias === program)) {
|
|
263
|
+
let declared;
|
|
264
|
+
try {
|
|
265
|
+
declared = projectNativeProgramLaunch(programs, program, args, { path: realpathSync(runtime), digest: runtimeDigest });
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
269
|
+
}
|
|
270
|
+
command = declared.interpreter;
|
|
271
|
+
args = [declared.entry, ...declared.args];
|
|
272
|
+
}
|
|
273
|
+
let selected;
|
|
274
|
+
try {
|
|
275
|
+
selected = realpathSync(command);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return fail('native program undeclared ' + program);
|
|
279
|
+
}
|
|
280
|
+
runtimeForExecution(selected, 'native runtime target undeclared ' + program);
|
|
281
|
+
const launch = nodeLaunch(args), call = invocation(args, options.cwd, options.env, options);
|
|
282
|
+
// Root bootstrap prefix is instrumentation; the original argv/text stays exact.
|
|
283
|
+
if (launch.kind === 'eval') {
|
|
284
|
+
flushExecutable(resolve(call.cwd, launch.mode === 'module' ? '[eval1].js' : '[eval].cjs'), launch.mode, Buffer.from(launch.text));
|
|
285
|
+
request({ kind: 'eval', argv: ['--import', preload, ...args], rootPrefix: ['--import', preload], mode: launch.mode, invocation: call });
|
|
286
|
+
}
|
|
287
|
+
else
|
|
288
|
+
sealFile(resolve(call.cwd, launch.entry), call, true);
|
|
289
|
+
for (const input of launch.imports)
|
|
290
|
+
sealFile(input.startsWith('file:') ? fileURLToPath(input) : resolve(call.cwd, input), call);
|
|
291
|
+
return { command, args: ['--import', preload, ...args], options: { ...options, timeout: call.timeoutMs, maxBuffer: call.responseBytes, shell: false } };
|
|
292
|
+
};
|
|
293
|
+
for (const name of ['spawn', 'spawnSync', 'execFile', 'execFileSync']) {
|
|
294
|
+
const original = childProcess[name];
|
|
295
|
+
childProcess[name] = function (program, args, options, ...tail) {
|
|
296
|
+
if (!Array.isArray(args) || typeof options === 'function')
|
|
297
|
+
fail('native call shape unsupported ' + name);
|
|
298
|
+
const checked = prepare(program, args, options);
|
|
299
|
+
return Reflect.apply(original, this, [checked.command, checked.args, checked.options, ...tail]);
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
childProcess.exec = childProcess.execSync = () => fail('shell native profile absent');
|
|
303
|
+
const originalFork = childProcess.fork;
|
|
304
|
+
childProcess.fork = function (module, args = [], options = {}) {
|
|
305
|
+
if (!Array.isArray(args))
|
|
306
|
+
fail('fork argv shape');
|
|
307
|
+
if (options.execPath && realpathSync(options.execPath) !== realpathSync(runtime))
|
|
308
|
+
fail('fork runtime undeclared');
|
|
309
|
+
const originalArgv = options.execArgv ?? process.execArgv, withoutRoot = originalArgv[0] === '--import' && originalArgv[1] === preload ? originalArgv.slice(2) : originalArgv;
|
|
310
|
+
const launch = nodeLaunch([...withoutRoot, module, ...args]);
|
|
311
|
+
if (launch.kind !== 'file' || launch.entry !== module)
|
|
312
|
+
fail('fork executable mode unsupported');
|
|
313
|
+
for (const input of launch.imports)
|
|
314
|
+
sealFile(input.startsWith('file:') ? fileURLToPath(input) : resolve(options.cwd ?? process.cwd(), input), invocation([module, ...args], options.cwd, options.env, options));
|
|
315
|
+
sealFile(resolve(options.cwd ?? process.cwd(), module), invocation([module, ...args], options.cwd, options.env, options), true);
|
|
316
|
+
// Fork executes a runtime from disk, unlike a retained control message.
|
|
317
|
+
const forkRuntime = options.execPath ?? process.execPath;
|
|
318
|
+
runtimeForExecution(forkRuntime, 'fork runtime bytes differ from Root selection');
|
|
319
|
+
return Reflect.apply(originalFork, this, [module, args, { ...options, execArgv: ['--import', preload, ...withoutRoot] }]);
|
|
320
|
+
};
|
|
321
|
+
const OriginalWorker = workerThreads.Worker;
|
|
322
|
+
workerThreads.Worker = class extends OriginalWorker {
|
|
323
|
+
constructor(target, options = {}) {
|
|
324
|
+
const args = options.execArgv ?? process.execArgv, withoutRoot = args[0] === '--import' && args[1] === preload ? args.slice(2) : args;
|
|
325
|
+
if (options.eval)
|
|
326
|
+
fail('Worker string-eval profile absent');
|
|
327
|
+
sealFile(target instanceof URL ? fileURLToPath(target) : resolve(target), invocation([String(target), ...withoutRoot], process.cwd(), options.env), true);
|
|
328
|
+
super(target, { ...options, execArgv: ['--import', preload, ...withoutRoot] });
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
// This fixed host observer is enabled only by the actual Root producer grant.
|
|
332
|
+
// The original write completes first; failed writes produce no output evidence.
|
|
333
|
+
if (endpoint.interpretations?.includes('host-file-emission@1')) {
|
|
334
|
+
const write = fs.writeFileSync, append = fs.appendFileSync, acknowledged = new Set(), pending = new Map();
|
|
335
|
+
const sentCandidates = new Map();
|
|
336
|
+
candidateHandoff = () => { const groups = new Map(); for (const row of pending.values()) {
|
|
337
|
+
const identity = JSON.stringify(row);
|
|
338
|
+
if (sentCandidates.get(row.path) === identity)
|
|
339
|
+
continue;
|
|
340
|
+
const key = JSON.stringify(row.call), group = groups.get(key) ?? { invocation: row.call, outputs: [] };
|
|
341
|
+
group.outputs.push({ path: row.path, ...(row.digest ? { digest: row.digest } : { reason: row.reason }) });
|
|
342
|
+
groups.set(key, group);
|
|
343
|
+
} return [...groups.values()]; };
|
|
344
|
+
candidateHanded = groups => { for (const group of groups)
|
|
345
|
+
for (const output of group.outputs) {
|
|
346
|
+
const row = pending.get(output.path);
|
|
347
|
+
if (row && JSON.stringify(row.call) === JSON.stringify(group.invocation) && row.digest === output.digest && row.reason === output.reason)
|
|
348
|
+
sentCandidates.set(output.path, JSON.stringify(row));
|
|
349
|
+
} };
|
|
350
|
+
const limits = endpoint.limits;
|
|
351
|
+
if (!limits || !['members', 'bytes', 'work', 'argvBytes', 'argvItems'].every(name => Number.isSafeInteger(limits[name]) && limits[name] >= 0))
|
|
352
|
+
fail('producer selection limits absent');
|
|
353
|
+
flushEmissions = (paths = []) => {
|
|
354
|
+
const required = [...new Set(paths)].flatMap(path => { const row = pending.get(path); if (!row)
|
|
355
|
+
return []; if (row.reason)
|
|
356
|
+
fail('required emission unmeasured ' + path + ' ' + row.reason); return [row]; });
|
|
357
|
+
const groups = new Map();
|
|
358
|
+
for (const row of required) {
|
|
359
|
+
const key = JSON.stringify(row.call), group = groups.get(key) ?? [];
|
|
360
|
+
group.push(row);
|
|
361
|
+
groups.set(key, group);
|
|
362
|
+
}
|
|
363
|
+
for (const rows of groups.values()) {
|
|
364
|
+
const outputs = rows.map(({ path, digest }) => ({ path, digest })), held = request({ outputs, invocation: rows[0].call }, 'emission-batch');
|
|
365
|
+
if (held.kind !== 'producer-emission-batch/1' || held.parent !== parentExecution.identity || held.producer !== endpoint.identity || !Array.isArray(held.outputs) || held.outputs.length !== outputs.length)
|
|
366
|
+
fail('emission batch provenance differs');
|
|
367
|
+
for (let i = 0; i < outputs.length; i++) {
|
|
368
|
+
if (held.outputs[i].path !== outputs[i].path || held.outputs[i].digest !== outputs[i].digest || !held.outputs[i].identity)
|
|
369
|
+
fail('emission batch output differs');
|
|
370
|
+
acknowledged.add(outputs[i].digest);
|
|
371
|
+
pending.delete(outputs[i].path);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
flushExecutable = (path, mode, source) => {
|
|
376
|
+
if (!pending.size || (source && executableDigests.has(sha(source))))
|
|
377
|
+
return;
|
|
378
|
+
const required = new Set(), readPaths = new Set(), derivedPaths = new Set();
|
|
379
|
+
let bytes = 0, derivedBytes = 0, work = 0;
|
|
380
|
+
const step = () => { if (++work > limits.work)
|
|
381
|
+
fail('emission selection work exhausted ' + path); };
|
|
382
|
+
const verify = (target) => { step(); const real = realpathSync(target), candidate = pending.get(real); if (candidate?.reason)
|
|
383
|
+
fail('required emission unmeasured ' + real + ' ' + candidate.reason); const raw = readBoundedFile(real, config.inputBytes, real), digest = sha(raw); if (!readPaths.has(real)) {
|
|
384
|
+
readPaths.add(real);
|
|
385
|
+
bytes += raw.length;
|
|
386
|
+
if (bytes > config.inputBytes)
|
|
387
|
+
fail('emission selection input bytes ' + real);
|
|
388
|
+
if (!known.has(real) && !executableDigests.has(digest) && !metadataDigests.has(digest)) {
|
|
389
|
+
derivedPaths.add(real);
|
|
390
|
+
derivedBytes += raw.length;
|
|
391
|
+
if (derivedPaths.size > limits.members || derivedBytes > limits.bytes)
|
|
392
|
+
fail('emission selection derived resources ' + real);
|
|
393
|
+
}
|
|
394
|
+
} if (candidate) {
|
|
395
|
+
if (candidate.digest !== digest)
|
|
396
|
+
fail('observed emission bytes differ ' + real);
|
|
397
|
+
required.add(real);
|
|
398
|
+
} return { real, bytes: raw, digest }; };
|
|
399
|
+
const entry = source ? { real: path, bytes: source, digest: sha(source) } : verify(path);
|
|
400
|
+
// The hint stops at sealed executable content. Root's custodian
|
|
401
|
+
// still independently verifies the complete selected closure.
|
|
402
|
+
if (!executableDigests.has(entry.digest))
|
|
403
|
+
traverseGeneratedClosure({ source: entry.bytes, path: entry.real, mode, libs: new Map(config.libraries.map((l) => [l.name, l.manifest])), native: new Set(config.builtins.map((n) => n.startsWith('node:') ? n : 'node:' + n)), platform: config.platform, nativeImplementations: executableDigests, verify, step });
|
|
404
|
+
flushEmissions([...required]);
|
|
405
|
+
};
|
|
406
|
+
const writtenDigest = (path) => {
|
|
407
|
+
const fd = fs.openSync(path, 'r');
|
|
408
|
+
try {
|
|
409
|
+
const stat = fs.fstatSync(fd), size = stat.size, limit = Math.min(config.inputBytes ?? parentExecution.responseBytes, parentExecution.responseBytes);
|
|
410
|
+
if (!stat.isFile() || !Number.isSafeInteger(size) || size < 0 || size > limit)
|
|
411
|
+
throw Error('emission bytes before hash ' + path);
|
|
412
|
+
const bytes = Buffer.alloc(size), tail = Buffer.alloc(1);
|
|
413
|
+
let got = 0;
|
|
414
|
+
while (got < size) {
|
|
415
|
+
const n = fs.readSync(fd, bytes, got, size - got, got);
|
|
416
|
+
if (!n)
|
|
417
|
+
break;
|
|
418
|
+
got += n;
|
|
419
|
+
}
|
|
420
|
+
if (got !== size || fs.readSync(fd, tail, 0, 1, size) !== 0)
|
|
421
|
+
throw Error('emission changed during bounded read ' + path);
|
|
422
|
+
return sha(bytes);
|
|
423
|
+
}
|
|
424
|
+
finally {
|
|
425
|
+
fs.closeSync(fd);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
const observeWrite = (operation) => function (file, ...args) {
|
|
429
|
+
const call = invocation([...process.execArgv, ...process.argv.slice(1)], process.cwd(), process.env);
|
|
430
|
+
if (typeof file !== 'string' && !(file instanceof URL))
|
|
431
|
+
fail('emission write route unsupported');
|
|
432
|
+
const named = file instanceof URL ? fileURLToPath(file) : resolve(file);
|
|
433
|
+
const result = Reflect.apply(operation, this, [file, ...args]);
|
|
434
|
+
const actual = realpathSync(named);
|
|
435
|
+
let digest, reason;
|
|
436
|
+
try {
|
|
437
|
+
digest = writtenDigest(actual);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
reason = error instanceof Error ? error.message : String(error);
|
|
441
|
+
}
|
|
442
|
+
// Unstable operational data is an unmeasured candidate, never a
|
|
443
|
+
// successful emission. Actual executable use must close it first.
|
|
444
|
+
if (digest && (executableDigests.has(digest) || acknowledged.has(digest))) {
|
|
445
|
+
pending.delete(actual);
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
pending.set(actual, { path: actual, digest, reason, call });
|
|
449
|
+
if (Buffer.byteLength(JSON.stringify([...pending.values()].map(({ path, digest, reason }) => ({ path, digest, reason })))) > parentExecution.responseBytes)
|
|
450
|
+
fail('pending emission provenance bytes');
|
|
451
|
+
return result;
|
|
452
|
+
};
|
|
453
|
+
fs.writeFileSync = observeWrite(write);
|
|
454
|
+
fs.appendFileSync = observeWrite(append);
|
|
455
|
+
}
|
|
456
|
+
syncBuiltinESMExports();
|
|
457
|
+
// Exact libraries are already verified by Root. Node supplies resolution, then
|
|
458
|
+
// the actual target is checked BEFORE it executes. Computed ordinary code is
|
|
459
|
+
// still screened by the custodian's strict generated-code gate.
|
|
460
|
+
if (typeof registerHooks !== 'function')
|
|
461
|
+
fail('runtime hooks absent');
|
|
462
|
+
registerHooks({ resolve(specifier, context, next) {
|
|
463
|
+
if (red)
|
|
464
|
+
throw Error(red);
|
|
465
|
+
const builtin = specifier.startsWith('node:') ? specifier : 'node:' + specifier;
|
|
466
|
+
if (builtinModules.includes(specifier) || specifier.startsWith('node:')) {
|
|
467
|
+
if (!native.has(builtin))
|
|
468
|
+
fail('native ABI undeclared ' + specifier);
|
|
469
|
+
return next(builtin, context);
|
|
470
|
+
}
|
|
471
|
+
let selected = context, programParent;
|
|
472
|
+
if (context.parentURL?.startsWith('file:') && context.parentURL !== evalParent?.url) {
|
|
473
|
+
const parent = realpathSync(fileURLToPath(context.parentURL)), expected = known.get(parent);
|
|
474
|
+
if (programs.some((p) => p.kind !== 'native-binary' && p.members.some((m) => m.path === parent))) {
|
|
475
|
+
if (!expected || sha(readBoundedFile(parent, config.inputBytes, parent)) !== expected)
|
|
476
|
+
fail('program parent bytes differ');
|
|
477
|
+
try {
|
|
478
|
+
programParent = selectNativeProgramParent(programs.filter((p) => p.kind !== 'native-binary'), { path: parent, digest: expected });
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (!programParent && !specifier.startsWith('.') && !specifier.startsWith('/') && !specifier.includes(':')) {
|
|
486
|
+
const name = specifier.startsWith('@') ? specifier.split('/').slice(0, 2).join('/') : specifier.split('/')[0], lib = library.get(name);
|
|
487
|
+
if (!lib)
|
|
488
|
+
fail('library undeclared ' + name);
|
|
489
|
+
selected = { ...context, parentURL: pathToFileURL(lib.manifest).href };
|
|
490
|
+
}
|
|
491
|
+
const got = next(specifier, selected);
|
|
492
|
+
if (!got.url.startsWith('file:'))
|
|
493
|
+
fail('module scheme undeclared ' + got.url);
|
|
494
|
+
sealFile(fileURLToPath(got.url), invocation([...process.execArgv, ...process.argv.slice(1)], process.cwd(), process.env));
|
|
495
|
+
return got;
|
|
496
|
+
}, load(url, context, nextLoad) {
|
|
497
|
+
if (!url.startsWith('file:'))
|
|
498
|
+
return nextLoad(url, context);
|
|
499
|
+
const actual = realpathSync(fileURLToPath(url));
|
|
500
|
+
if (statSync(actual).size > (config.inputBytes ?? endpoint.responseBytes))
|
|
501
|
+
fail('ambiguous input bytes before load');
|
|
502
|
+
const call = invocation([...process.execArgv, ...process.argv.slice(1)], process.cwd(), process.env);
|
|
503
|
+
// Seal controlling package interpretation before Node reads it. Its returned
|
|
504
|
+
// default is deliberately ignored: only Node's actual load result supplies mode.
|
|
505
|
+
const ambiguous = extname(actual) === '';
|
|
506
|
+
if (ambiguous)
|
|
507
|
+
modeOf(actual, call);
|
|
508
|
+
const before = readBoundedFile(actual, config.inputBytes, actual), digest = sha(before), loaded = nextLoad(url, context);
|
|
509
|
+
if (ambiguous && !['module', 'commonjs'].includes(loaded.format))
|
|
510
|
+
fail('ambiguous Node interpretation unsupported ' + loaded.format);
|
|
511
|
+
if (loaded.source === null || loaded.source === undefined)
|
|
512
|
+
fail('ambiguous Node source absent before execution');
|
|
513
|
+
const source = typeof loaded.source === 'string' ? Buffer.from(loaded.source, 'utf8') : loaded.source instanceof ArrayBuffer ? Buffer.from(loaded.source) : Buffer.from(loaded.source.buffer, loaded.source.byteOffset, loaded.source.byteLength);
|
|
514
|
+
if (sha(source) !== digest || sha(readBoundedFile(actual, config.inputBytes, actual)) !== digest)
|
|
515
|
+
fail('ambiguous Node source differs before execution');
|
|
516
|
+
const processEntry = process.argv[1] && realpathSync(resolve(process.argv[1])) === actual;
|
|
517
|
+
sealFile(actual, call, Boolean(processEntry) && ambiguous, ambiguous ? loaded : undefined);
|
|
518
|
+
return loaded;
|
|
519
|
+
} });
|
|
520
|
+
if (process.argv[1])
|
|
521
|
+
sealFile(resolve(process.argv[1]), invocation([...process.execArgv, ...process.argv.slice(1)], process.cwd(), process.env), true);
|
|
522
|
+
process.once('beforeExit', () => {
|
|
523
|
+
if (red)
|
|
524
|
+
throw Error(red);
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
/** An optional observer does nothing until fixed custody is installed. */
|
|
528
|
+
const ignoreEmission = (..._args) => { };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { type NativeBinaryProjection } from './native-binary.ts';
|
|
2
|
+
import { type GeneratedProducer, type LiteralSnapshot, type DescendantInvocation, type ProducerLimits, type ProducerCandidates } from './generated.ts';
|
|
3
|
+
import { type NativeEvalActual } from './node-launch.ts';
|
|
4
|
+
export interface ProducerEndpoint {
|
|
5
|
+
socket: string;
|
|
6
|
+
token: string;
|
|
7
|
+
coordinate: string;
|
|
8
|
+
implementation: string;
|
|
9
|
+
identity: string;
|
|
10
|
+
timeoutMs: number;
|
|
11
|
+
deadlineMs: number;
|
|
12
|
+
responseBytes: number;
|
|
13
|
+
interpretations: readonly string[];
|
|
14
|
+
limits: Readonly<ProducerLimits>;
|
|
15
|
+
}
|
|
16
|
+
/** Refusal transport is bounded too; an undersized channel closes without a
|
|
17
|
+
* frame while the parent retains the named refusal. No truncation is success. */
|
|
18
|
+
export declare function boundedRefusalFrame(endpoint: ProducerEndpoint, why: string): string | undefined;
|
|
19
|
+
export interface EntryRequest {
|
|
20
|
+
candidates?: readonly ProducerCandidates[];
|
|
21
|
+
kind: 'file' | 'eval';
|
|
22
|
+
path?: string;
|
|
23
|
+
interpretation?: string;
|
|
24
|
+
mode: 'module' | 'commonjs';
|
|
25
|
+
argv?: readonly string[];
|
|
26
|
+
rootPrefix?: readonly string[];
|
|
27
|
+
invocation: DescendantInvocation;
|
|
28
|
+
}
|
|
29
|
+
export interface NativeProgramGrant {
|
|
30
|
+
readonly identity: string;
|
|
31
|
+
readonly program: NativeBinaryProjection;
|
|
32
|
+
}
|
|
33
|
+
export interface NativeProgramRequest {
|
|
34
|
+
alias: string;
|
|
35
|
+
path: string;
|
|
36
|
+
invocation: DescendantInvocation;
|
|
37
|
+
}
|
|
38
|
+
export interface EmissionBatchRequest {
|
|
39
|
+
outputs: readonly {
|
|
40
|
+
path: string;
|
|
41
|
+
digest: string;
|
|
42
|
+
}[];
|
|
43
|
+
invocation: DescendantInvocation;
|
|
44
|
+
}
|
|
45
|
+
export interface EmissionRequest {
|
|
46
|
+
path: string;
|
|
47
|
+
invocation: DescendantInvocation;
|
|
48
|
+
}
|
|
49
|
+
export interface InterpretationRequest {
|
|
50
|
+
path: string;
|
|
51
|
+
interpretation: string;
|
|
52
|
+
invocation: DescendantInvocation;
|
|
53
|
+
}
|
|
54
|
+
export declare function startProducerCustodian(handle: GeneratedProducer, snapshot: LiteralSnapshot, nativeImplementations: ReadonlySet<string>, directory: string, inputNativePrograms?: readonly NativeProgramGrant[]): Promise<{
|
|
55
|
+
endpoint: ProducerEndpoint;
|
|
56
|
+
readonly contexts: any[];
|
|
57
|
+
retention: () => {
|
|
58
|
+
bytes: number;
|
|
59
|
+
contexts: any[];
|
|
60
|
+
values: any[][];
|
|
61
|
+
};
|
|
62
|
+
assertClosed: () => void;
|
|
63
|
+
refusals: string[];
|
|
64
|
+
usage: () => Readonly<{
|
|
65
|
+
members: number;
|
|
66
|
+
bytes: number;
|
|
67
|
+
work: number;
|
|
68
|
+
retainedContents: number;
|
|
69
|
+
codeBytes: number;
|
|
70
|
+
limits: {
|
|
71
|
+
members: number;
|
|
72
|
+
bytes: number;
|
|
73
|
+
work: number;
|
|
74
|
+
argvBytes: number;
|
|
75
|
+
argvItems: number;
|
|
76
|
+
};
|
|
77
|
+
}>;
|
|
78
|
+
close: () => Promise<void>;
|
|
79
|
+
}>;
|
|
80
|
+
/** Asynchronous client for original async native APIs. Original sync APIs use
|
|
81
|
+
* the fixed client executable; both address this SAME custodian/counters. */
|
|
82
|
+
export declare function requestRetainedEvalContext(endpoint: ProducerEndpoint, actual: NativeEvalActual): Promise<any>;
|
|
83
|
+
export declare function requestProducerEntry(endpoint: ProducerEndpoint, request: EntryRequest): Promise<any>;
|
|
84
|
+
export declare function requestProducerInterpretation(endpoint: ProducerEndpoint, request: InterpretationRequest): Promise<any>;
|
|
85
|
+
export declare function requestNativeProgram(endpoint: ProducerEndpoint, request: NativeProgramRequest): Promise<any>;
|
|
86
|
+
export declare function requestProducerEmission(endpoint: ProducerEndpoint, request: EmissionRequest): Promise<any>;
|
|
87
|
+
export declare function requestProducerEmissionBatch(endpoint: ProducerEndpoint, request: EmissionBatchRequest): Promise<any>;
|
|
88
|
+
/** Bound's guarded host constructs this factory, not a world. The transport path
|
|
89
|
+
* is selected by the sealed native preload configuration, not a grant in data.
|
|
90
|
+
* A shared context file cannot be multiplexed across simultaneous bindings. */
|
|
91
|
+
export declare function createProducerJobCustody(input: {
|
|
92
|
+
root: import('./reading.ts').RootStanding;
|
|
93
|
+
coordinate: string;
|
|
94
|
+
implementationBytes: Uint8Array;
|
|
95
|
+
snapshot: LiteralSnapshot;
|
|
96
|
+
nativeImplementations: ReadonlySet<string>;
|
|
97
|
+
directory: string;
|
|
98
|
+
contextFile: string;
|
|
99
|
+
nativeBinaries?: readonly {
|
|
100
|
+
coordinate: string;
|
|
101
|
+
privateBinding: {
|
|
102
|
+
coordinate: string;
|
|
103
|
+
path: string;
|
|
104
|
+
digest: string;
|
|
105
|
+
};
|
|
106
|
+
}[];
|
|
107
|
+
programSelections?: readonly {
|
|
108
|
+
coordinate: string;
|
|
109
|
+
runtimeBinding: {
|
|
110
|
+
coordinate: string;
|
|
111
|
+
path: string;
|
|
112
|
+
digest: string;
|
|
113
|
+
members: readonly {
|
|
114
|
+
coordinate: string;
|
|
115
|
+
path: string;
|
|
116
|
+
}[];
|
|
117
|
+
};
|
|
118
|
+
}[];
|
|
119
|
+
}): import('./node-jobs.ts').RootJobCustody;
|
|
120
|
+
/** Fixed Root preload reports a local before-entry refusal to the SAME parent
|
|
121
|
+
* custodian. A caught child error therefore cannot become a success receipt. */
|
|
122
|
+
export declare function reportProducerViolation(endpoint: ProducerEndpoint, why: string): Promise<never>;
|