@tiangong-lca/cli 0.1.9 → 0.1.11
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 +10 -5
- package/assets/runtime/cli-runtime-descriptor.schema.json +138 -0
- package/assets/runtime/cli-runtime-expectation.schema.json +38 -0
- package/assets/runtime/runtime-bootstrap-lock.schema.json +254 -0
- package/assets/runtime/runtime-command-result.schema.json +95 -0
- package/assets/runtime/runtime-manifest.schema.json +383 -0
- package/dist/src/cli.js +5 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-import-lca.js +2 -2
- package/dist/src/lib/dataset-import-lca.js.map +1 -1
- package/dist/src/lib/runtime/archive-writer.d.ts +7 -0
- package/dist/src/lib/runtime/archive-writer.js +89 -0
- package/dist/src/lib/runtime/archive-writer.js.map +1 -0
- package/dist/src/lib/runtime/archive.d.ts +2 -0
- package/dist/src/lib/runtime/archive.js +122 -0
- package/dist/src/lib/runtime/archive.js.map +1 -0
- package/dist/src/lib/runtime/command.d.ts +8 -0
- package/dist/src/lib/runtime/command.js +57 -0
- package/dist/src/lib/runtime/command.js.map +1 -0
- package/dist/src/lib/runtime/descriptor.d.ts +11 -0
- package/dist/src/lib/runtime/descriptor.js +126 -0
- package/dist/src/lib/runtime/descriptor.js.map +1 -0
- package/dist/src/lib/runtime/download.d.ts +9 -0
- package/dist/src/lib/runtime/download.js +118 -0
- package/dist/src/lib/runtime/download.js.map +1 -0
- package/dist/src/lib/runtime/exec-command.d.ts +7 -0
- package/dist/src/lib/runtime/exec-command.js +86 -0
- package/dist/src/lib/runtime/exec-command.js.map +1 -0
- package/dist/src/lib/runtime/execute.d.ts +12 -0
- package/dist/src/lib/runtime/execute.js +132 -0
- package/dist/src/lib/runtime/execute.js.map +1 -0
- package/dist/src/lib/runtime/files.d.ts +7 -0
- package/dist/src/lib/runtime/files.js +91 -0
- package/dist/src/lib/runtime/files.js.map +1 -0
- package/dist/src/lib/runtime/host-context-protocol.d.ts +20 -0
- package/dist/src/lib/runtime/host-context-protocol.js +83 -0
- package/dist/src/lib/runtime/host-context-protocol.js.map +1 -0
- package/dist/src/lib/runtime/host-context-server.d.ts +6 -0
- package/dist/src/lib/runtime/host-context-server.js +81 -0
- package/dist/src/lib/runtime/host-context-server.js.map +1 -0
- package/dist/src/lib/runtime/host-context.d.ts +23 -0
- package/dist/src/lib/runtime/host-context.js +122 -0
- package/dist/src/lib/runtime/host-context.js.map +1 -0
- package/dist/src/lib/runtime/host.d.ts +8 -0
- package/dist/src/lib/runtime/host.js +37 -0
- package/dist/src/lib/runtime/host.js.map +1 -0
- package/dist/src/lib/runtime/leases.d.ts +12 -0
- package/dist/src/lib/runtime/leases.js +72 -0
- package/dist/src/lib/runtime/leases.js.map +1 -0
- package/dist/src/lib/runtime/managed-command.d.ts +6 -0
- package/dist/src/lib/runtime/managed-command.js +99 -0
- package/dist/src/lib/runtime/managed-command.js.map +1 -0
- package/dist/src/lib/runtime/manager.d.ts +33 -0
- package/dist/src/lib/runtime/manager.js +196 -0
- package/dist/src/lib/runtime/manager.js.map +1 -0
- package/dist/src/lib/runtime/manifest-types.d.ts +82 -0
- package/dist/src/lib/runtime/manifest-types.js +5 -0
- package/dist/src/lib/runtime/manifest-types.js.map +1 -0
- package/dist/src/lib/runtime/manifest-values.d.ts +15 -0
- package/dist/src/lib/runtime/manifest-values.js +124 -0
- package/dist/src/lib/runtime/manifest-values.js.map +1 -0
- package/dist/src/lib/runtime/manifest.d.ts +8 -0
- package/dist/src/lib/runtime/manifest.js +239 -0
- package/dist/src/lib/runtime/manifest.js.map +1 -0
- package/dist/src/lib/runtime/process.d.ts +4 -0
- package/dist/src/lib/runtime/process.js +69 -0
- package/dist/src/lib/runtime/process.js.map +1 -0
- package/dist/src/lib/runtime/storage.d.ts +8 -0
- package/dist/src/lib/runtime/storage.js +142 -0
- package/dist/src/lib/runtime/storage.js.map +1 -0
- package/dist/src/lib/runtime/types.d.ts +45 -0
- package/dist/src/lib/runtime/types.js +9 -0
- package/dist/src/lib/runtime/types.js.map +1 -0
- package/dist/src/lib/runtime/work-directory.d.ts +2 -0
- package/dist/src/lib/runtime/work-directory.js +16 -0
- package/dist/src/lib/runtime/work-directory.js.map +1 -0
- package/dist/src/main.js +13 -1
- package/dist/src/main.js.map +1 -1
- package/dist/src/runtime.d.ts +16 -0
- package/dist/src/runtime.js +23 -0
- package/dist/src/runtime.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { CliError } from '../errors.js';
|
|
5
|
+
export function runtimeError(code, message) {
|
|
6
|
+
throw new CliError(message, { code, exitCode: 69 });
|
|
7
|
+
}
|
|
8
|
+
export function contentHash(value) {
|
|
9
|
+
return createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
/** Hash one descriptor without loading the whole executable or following a selected symlink. */
|
|
12
|
+
export function hashRuntimeFile(file, label) {
|
|
13
|
+
const before = fs.lstatSync(file, { bigint: true });
|
|
14
|
+
if (before.isSymbolicLink() || !before.isFile() || before.size > 512 * 1024 * 1024) {
|
|
15
|
+
runtimeError('RUNTIME_FILE_INVALID', 'Runtime files must be bounded regular files.');
|
|
16
|
+
}
|
|
17
|
+
const fd = fs.openSync(file, 'r');
|
|
18
|
+
try {
|
|
19
|
+
const opened = fs.fstatSync(fd, { bigint: true });
|
|
20
|
+
if (opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size) {
|
|
21
|
+
runtimeError('RUNTIME_FILE_CHANGED', 'Runtime file changed before it could be inspected.');
|
|
22
|
+
}
|
|
23
|
+
const hash = createHash('sha256');
|
|
24
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
25
|
+
let bytes = 0;
|
|
26
|
+
while (true) {
|
|
27
|
+
const read = fs.readSync(fd, buffer, 0, buffer.length, null);
|
|
28
|
+
if (read === 0)
|
|
29
|
+
break;
|
|
30
|
+
bytes += read;
|
|
31
|
+
if (bytes > before.size)
|
|
32
|
+
runtimeError('RUNTIME_FILE_CHANGED', 'Runtime file grew during inspection.');
|
|
33
|
+
hash.update(buffer.subarray(0, read));
|
|
34
|
+
}
|
|
35
|
+
const after = fs.fstatSync(fd, { bigint: true });
|
|
36
|
+
const selected = fs.lstatSync(file, { bigint: true });
|
|
37
|
+
if (BigInt(bytes) !== before.size ||
|
|
38
|
+
after.size !== before.size ||
|
|
39
|
+
after.mtimeNs !== before.mtimeNs ||
|
|
40
|
+
selected.dev !== before.dev ||
|
|
41
|
+
selected.ino !== before.ino ||
|
|
42
|
+
selected.isSymbolicLink() ||
|
|
43
|
+
!selected.isFile()) {
|
|
44
|
+
runtimeError('RUNTIME_FILE_CHANGED', 'Runtime file changed during inspection.');
|
|
45
|
+
}
|
|
46
|
+
return Object.freeze({ path: label, bytes, sha256: hash.digest('hex') });
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
fs.closeSync(fd);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function assertInventoryBudget(count, bytes) {
|
|
53
|
+
if (count > 50_000 || bytes > 2 * 1024 * 1024 * 1024) {
|
|
54
|
+
runtimeError('RUNTIME_INVENTORY_LIMIT', 'Runtime inventory exceeds its bounded file or byte budget.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function listRuntimeFiles(root) {
|
|
58
|
+
const files = [
|
|
59
|
+
hashRuntimeFile(path.join(root, 'package.json'), 'package.json'),
|
|
60
|
+
];
|
|
61
|
+
let totalBytes = files[0].bytes;
|
|
62
|
+
const directoryPath = (relative) => {
|
|
63
|
+
const directory = path.join(root, relative);
|
|
64
|
+
const stat = fs.lstatSync(directory);
|
|
65
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
66
|
+
runtimeError('RUNTIME_DIRECTORY_INVALID', 'Runtime directories cannot be symlinks.');
|
|
67
|
+
return directory;
|
|
68
|
+
};
|
|
69
|
+
// The initial subtree skips this container; inspect it before following dist/src.
|
|
70
|
+
directoryPath('dist');
|
|
71
|
+
const walk = (relative) => {
|
|
72
|
+
const directory = directoryPath(relative);
|
|
73
|
+
for (const name of fs.readdirSync(directory).sort()) {
|
|
74
|
+
const item = `${relative}/${name}`;
|
|
75
|
+
const file = path.join(root, item);
|
|
76
|
+
const child = fs.lstatSync(file);
|
|
77
|
+
if (child.isDirectory())
|
|
78
|
+
walk(item);
|
|
79
|
+
else {
|
|
80
|
+
const fact = hashRuntimeFile(file, item);
|
|
81
|
+
files.push(fact);
|
|
82
|
+
totalBytes += fact.bytes;
|
|
83
|
+
assertInventoryBudget(files.length, totalBytes);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
for (const directory of ['bin', 'dist/src', 'assets'])
|
|
88
|
+
walk(directory);
|
|
89
|
+
return Object.freeze(files.sort((left, right) => (left.path < right.path ? -1 : 1)));
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../../../src/lib/runtime/files.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { CliError } from '../errors.js';\nimport type { RuntimeFileFact } from './types.js';\n\nexport function runtimeError(code: string, message: string): never {\n throw new CliError(message, { code, exitCode: 69 });\n}\n\nexport function contentHash(value: unknown): string {\n return createHash('sha256').update(JSON.stringify(value)).digest('hex');\n}\n\n/** Hash one descriptor without loading the whole executable or following a selected symlink. */\nexport function hashRuntimeFile(file: string, label: string): RuntimeFileFact {\n const before = fs.lstatSync(file, { bigint: true });\n if (before.isSymbolicLink() || !before.isFile() || before.size > 512 * 1024 * 1024) {\n runtimeError('RUNTIME_FILE_INVALID', 'Runtime files must be bounded regular files.');\n }\n const fd = fs.openSync(file, 'r');\n try {\n const opened = fs.fstatSync(fd, { bigint: true });\n if (opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size) {\n runtimeError('RUNTIME_FILE_CHANGED', 'Runtime file changed before it could be inspected.');\n }\n const hash = createHash('sha256');\n const buffer = Buffer.allocUnsafe(1024 * 1024);\n let bytes = 0;\n while (true) {\n const read = fs.readSync(fd, buffer, 0, buffer.length, null);\n if (read === 0) break;\n bytes += read;\n if (bytes > before.size)\n runtimeError('RUNTIME_FILE_CHANGED', 'Runtime file grew during inspection.');\n hash.update(buffer.subarray(0, read));\n }\n const after = fs.fstatSync(fd, { bigint: true });\n const selected = fs.lstatSync(file, { bigint: true });\n if (\n BigInt(bytes) !== before.size ||\n after.size !== before.size ||\n after.mtimeNs !== before.mtimeNs ||\n selected.dev !== before.dev ||\n selected.ino !== before.ino ||\n selected.isSymbolicLink() ||\n !selected.isFile()\n ) {\n runtimeError('RUNTIME_FILE_CHANGED', 'Runtime file changed during inspection.');\n }\n return Object.freeze({ path: label, bytes, sha256: hash.digest('hex') });\n } finally {\n fs.closeSync(fd);\n }\n}\n\nexport function assertInventoryBudget(count: number, bytes: number): void {\n if (count > 50_000 || bytes > 2 * 1024 * 1024 * 1024) {\n runtimeError(\n 'RUNTIME_INVENTORY_LIMIT',\n 'Runtime inventory exceeds its bounded file or byte budget.',\n );\n }\n}\n\nexport function listRuntimeFiles(root: string): readonly RuntimeFileFact[] {\n const files: RuntimeFileFact[] = [\n hashRuntimeFile(path.join(root, 'package.json'), 'package.json'),\n ];\n let totalBytes = files[0]!.bytes;\n const directoryPath = (relative: string): string => {\n const directory = path.join(root, relative);\n const stat = fs.lstatSync(directory);\n if (stat.isSymbolicLink() || !stat.isDirectory())\n runtimeError('RUNTIME_DIRECTORY_INVALID', 'Runtime directories cannot be symlinks.');\n return directory;\n };\n // The initial subtree skips this container; inspect it before following dist/src.\n directoryPath('dist');\n const walk = (relative: string): void => {\n const directory = directoryPath(relative);\n for (const name of fs.readdirSync(directory).sort()) {\n const item = `${relative}/${name}`;\n const file = path.join(root, item);\n const child = fs.lstatSync(file);\n if (child.isDirectory()) walk(item);\n else {\n const fact = hashRuntimeFile(file, item);\n files.push(fact);\n totalBytes += fact.bytes;\n assertInventoryBudget(files.length, totalBytes);\n }\n }\n };\n for (const directory of ['bin', 'dist/src', 'assets']) walk(directory);\n return Object.freeze(files.sort((left, right) => (left.path < right.path ? -1 : 1)));\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RuntimeHostContext, TrustedRuntimeManifest } from './manifest-types.js';
|
|
2
|
+
export declare const HOST_CONTEXT_TIMEOUT_MS = 30000;
|
|
3
|
+
export type HostContextBinding = Readonly<{
|
|
4
|
+
nonce: string;
|
|
5
|
+
parent_pid: number;
|
|
6
|
+
child_pid: number;
|
|
7
|
+
}>;
|
|
8
|
+
export type HostContextMessage = Readonly<Record<string, string | number>>;
|
|
9
|
+
export type HostContextSendCallback = (error: Error | null) => void;
|
|
10
|
+
export declare function hostContextError(reason: string): Error;
|
|
11
|
+
export declare function hostControlMessage(kind: string, binding: HostContextBinding): HostContextMessage;
|
|
12
|
+
export declare function readHostRequest(value: unknown, parentPid: number, childPid: number): HostContextBinding;
|
|
13
|
+
export declare function assertHostControl(value: unknown, kind: string, binding: HostContextBinding): void;
|
|
14
|
+
export declare function hostContextMessage(context: RuntimeHostContext, binding: HostContextBinding): HostContextMessage;
|
|
15
|
+
export declare function readHostContextMessage(value: unknown, binding: HostContextBinding): {
|
|
16
|
+
manifest: TrustedRuntimeManifest;
|
|
17
|
+
cacheDir: string;
|
|
18
|
+
cwd: string;
|
|
19
|
+
entry: string;
|
|
20
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { copyTrustedRuntimeManifestBytes, trustRuntimeManifest } from './manifest.js';
|
|
2
|
+
import { RUNTIME_HOST_CONTEXT_PROTOCOL } from './manifest-types.js';
|
|
3
|
+
export const HOST_CONTEXT_TIMEOUT_MS = 30_000;
|
|
4
|
+
const bindingKeys = ['schema', 'kind', 'nonce', 'parent_pid', 'child_pid'];
|
|
5
|
+
const contextKeys = ['manifest_base64', 'manifest_sha256', 'cache_dir', 'cwd', 'entry'];
|
|
6
|
+
export function hostContextError(reason) {
|
|
7
|
+
return Object.assign(new Error(`Runtime host context ${reason}.`), {
|
|
8
|
+
code: 'RUNTIME_HOST_CONTEXT',
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function messageRecord(value, keys) {
|
|
12
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
13
|
+
throw hostContextError('message must be an object');
|
|
14
|
+
const record = value;
|
|
15
|
+
if (Object.keys(record).length !== keys.length || keys.some((key) => !Object.hasOwn(record, key)))
|
|
16
|
+
throw hostContextError('message has unexpected fields');
|
|
17
|
+
return record;
|
|
18
|
+
}
|
|
19
|
+
export function hostControlMessage(kind, binding) {
|
|
20
|
+
return { schema: RUNTIME_HOST_CONTEXT_PROTOCOL, kind, ...binding };
|
|
21
|
+
}
|
|
22
|
+
export function readHostRequest(value, parentPid, childPid) {
|
|
23
|
+
const message = messageRecord(value, bindingKeys);
|
|
24
|
+
if (message.schema !== RUNTIME_HOST_CONTEXT_PROTOCOL ||
|
|
25
|
+
message.kind !== 'request' ||
|
|
26
|
+
typeof message.nonce !== 'string' ||
|
|
27
|
+
!/^[0-9a-f]{64}$/u.test(message.nonce) ||
|
|
28
|
+
message.parent_pid !== parentPid ||
|
|
29
|
+
message.child_pid !== childPid)
|
|
30
|
+
throw hostContextError('request is not bound to this child');
|
|
31
|
+
return Object.freeze({ nonce: message.nonce, parent_pid: parentPid, child_pid: childPid });
|
|
32
|
+
}
|
|
33
|
+
export function assertHostControl(value, kind, binding) {
|
|
34
|
+
assertBinding(messageRecord(value, bindingKeys), kind, binding);
|
|
35
|
+
}
|
|
36
|
+
function assertBinding(message, kind, binding) {
|
|
37
|
+
if (message.schema !== RUNTIME_HOST_CONTEXT_PROTOCOL ||
|
|
38
|
+
message.kind !== kind ||
|
|
39
|
+
message.nonce !== binding.nonce ||
|
|
40
|
+
message.parent_pid !== binding.parent_pid ||
|
|
41
|
+
message.child_pid !== binding.child_pid)
|
|
42
|
+
throw hostContextError('message belongs to another handshake');
|
|
43
|
+
}
|
|
44
|
+
export function hostContextMessage(context, binding) {
|
|
45
|
+
return {
|
|
46
|
+
...hostControlMessage('context', binding),
|
|
47
|
+
manifest_base64: copyTrustedRuntimeManifestBytes(context.manifest).toString('base64'),
|
|
48
|
+
manifest_sha256: context.manifest.sha256,
|
|
49
|
+
cache_dir: context.cacheDir,
|
|
50
|
+
cwd: context.cwd,
|
|
51
|
+
entry: context.entry,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function readHostContextMessage(value, binding) {
|
|
55
|
+
const message = messageRecord(value, [...bindingKeys, ...contextKeys]);
|
|
56
|
+
assertBinding(message, 'context', binding);
|
|
57
|
+
if (typeof message.manifest_base64 !== 'string' ||
|
|
58
|
+
message.manifest_base64.length === 0 ||
|
|
59
|
+
message.manifest_base64.length > 4 * Math.ceil((32 * 1024 * 1024) / 3) ||
|
|
60
|
+
typeof message.manifest_sha256 !== 'string')
|
|
61
|
+
throw hostContextError('manifest exceeds its wire bound');
|
|
62
|
+
const bytes = Buffer.from(message.manifest_base64, 'base64');
|
|
63
|
+
if (bytes.toString('base64') !== message.manifest_base64)
|
|
64
|
+
throw hostContextError('manifest encoding is not canonical');
|
|
65
|
+
const stringField = (key) => {
|
|
66
|
+
const value = message[key];
|
|
67
|
+
if (typeof value !== 'string' ||
|
|
68
|
+
!value ||
|
|
69
|
+
value.length > 32768 ||
|
|
70
|
+
value
|
|
71
|
+
.split('')
|
|
72
|
+
.some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127))
|
|
73
|
+
throw hostContextError('selection fields are invalid');
|
|
74
|
+
return value;
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
manifest: trustRuntimeManifest(bytes, message.manifest_sha256),
|
|
78
|
+
cacheDir: stringField('cache_dir'),
|
|
79
|
+
cwd: stringField('cwd'),
|
|
80
|
+
entry: stringField('entry'),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=host-context-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-context-protocol.js","sourceRoot":"","sources":["../../../../src/lib/runtime/host-context-protocol.ts"],"names":[],"mappings":"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","sourcesContent":["import { copyTrustedRuntimeManifestBytes, trustRuntimeManifest } from './manifest.js';\nimport { RUNTIME_HOST_CONTEXT_PROTOCOL } from './manifest-types.js';\nimport type { RuntimeHostContext, TrustedRuntimeManifest } from './manifest-types.js';\n\nexport const HOST_CONTEXT_TIMEOUT_MS = 30_000;\nexport type HostContextBinding = Readonly<{ nonce: string; parent_pid: number; child_pid: number }>;\nexport type HostContextMessage = Readonly<Record<string, string | number>>;\nexport type HostContextSendCallback = (error: Error | null) => void;\nconst bindingKeys = ['schema', 'kind', 'nonce', 'parent_pid', 'child_pid'];\nconst contextKeys = ['manifest_base64', 'manifest_sha256', 'cache_dir', 'cwd', 'entry'];\n\nexport function hostContextError(reason: string): Error {\n return Object.assign(new Error(`Runtime host context ${reason}.`), {\n code: 'RUNTIME_HOST_CONTEXT',\n });\n}\n\nfunction messageRecord(value: unknown, keys: readonly string[]): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n throw hostContextError('message must be an object');\n const record = value as Record<string, unknown>;\n if (Object.keys(record).length !== keys.length || keys.some((key) => !Object.hasOwn(record, key)))\n throw hostContextError('message has unexpected fields');\n return record;\n}\n\nexport function hostControlMessage(kind: string, binding: HostContextBinding): HostContextMessage {\n return { schema: RUNTIME_HOST_CONTEXT_PROTOCOL, kind, ...binding };\n}\n\nexport function readHostRequest(\n value: unknown,\n parentPid: number,\n childPid: number,\n): HostContextBinding {\n const message = messageRecord(value, bindingKeys);\n if (\n message.schema !== RUNTIME_HOST_CONTEXT_PROTOCOL ||\n message.kind !== 'request' ||\n typeof message.nonce !== 'string' ||\n !/^[0-9a-f]{64}$/u.test(message.nonce) ||\n message.parent_pid !== parentPid ||\n message.child_pid !== childPid\n )\n throw hostContextError('request is not bound to this child');\n return Object.freeze({ nonce: message.nonce, parent_pid: parentPid, child_pid: childPid });\n}\n\nexport function assertHostControl(value: unknown, kind: string, binding: HostContextBinding): void {\n assertBinding(messageRecord(value, bindingKeys), kind, binding);\n}\n\nfunction assertBinding(\n message: Record<string, unknown>,\n kind: string,\n binding: HostContextBinding,\n): void {\n if (\n message.schema !== RUNTIME_HOST_CONTEXT_PROTOCOL ||\n message.kind !== kind ||\n message.nonce !== binding.nonce ||\n message.parent_pid !== binding.parent_pid ||\n message.child_pid !== binding.child_pid\n )\n throw hostContextError('message belongs to another handshake');\n}\n\nexport function hostContextMessage(\n context: RuntimeHostContext,\n binding: HostContextBinding,\n): HostContextMessage {\n return {\n ...hostControlMessage('context', binding),\n manifest_base64: copyTrustedRuntimeManifestBytes(context.manifest).toString('base64'),\n manifest_sha256: context.manifest.sha256,\n cache_dir: context.cacheDir,\n cwd: context.cwd,\n entry: context.entry,\n };\n}\n\nexport function readHostContextMessage(\n value: unknown,\n binding: HostContextBinding,\n): {\n manifest: TrustedRuntimeManifest;\n cacheDir: string;\n cwd: string;\n entry: string;\n} {\n const message = messageRecord(value, [...bindingKeys, ...contextKeys]);\n assertBinding(message, 'context', binding);\n if (\n typeof message.manifest_base64 !== 'string' ||\n message.manifest_base64.length === 0 ||\n message.manifest_base64.length > 4 * Math.ceil((32 * 1024 * 1024) / 3) ||\n typeof message.manifest_sha256 !== 'string'\n )\n throw hostContextError('manifest exceeds its wire bound');\n const bytes = Buffer.from(message.manifest_base64, 'base64');\n if (bytes.toString('base64') !== message.manifest_base64)\n throw hostContextError('manifest encoding is not canonical');\n const stringField = (key: 'cache_dir' | 'cwd' | 'entry') => {\n const value = message[key];\n if (\n typeof value !== 'string' ||\n !value ||\n value.length > 32768 ||\n value\n .split('')\n .some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)\n )\n throw hostContextError('selection fields are invalid');\n return value;\n };\n return {\n manifest: trustRuntimeManifest(bytes, message.manifest_sha256),\n cacheDir: stringField('cache_dir'),\n cwd: stringField('cwd'),\n entry: stringField('entry'),\n };\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
import type { RuntimeHostContext } from './manifest-types.js';
|
|
3
|
+
/** One IPC handshake, owned by the same process that holds the execution lease. */
|
|
4
|
+
export declare function serveRuntimeHostContext(child: ChildProcess, context: RuntimeHostContext, onFailure: (error: Error) => void): {
|
|
5
|
+
finish(): Error | undefined;
|
|
6
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { HOST_CONTEXT_TIMEOUT_MS, assertHostControl, hostContextError, hostContextMessage, hostControlMessage, readHostRequest, } from './host-context-protocol.js';
|
|
2
|
+
/** One IPC handshake, owned by the same process that holds the execution lease. */
|
|
3
|
+
export function serveRuntimeHostContext(child, context, onFailure) {
|
|
4
|
+
let phase = 'request';
|
|
5
|
+
let binding;
|
|
6
|
+
let readySent = false, clientClosed = false;
|
|
7
|
+
const cleanup = () => {
|
|
8
|
+
clearTimeout(timer);
|
|
9
|
+
child.off('message', message);
|
|
10
|
+
child.off('disconnect', disconnected);
|
|
11
|
+
};
|
|
12
|
+
const fail = (error) => {
|
|
13
|
+
if (phase === 'failed' || phase === 'complete')
|
|
14
|
+
return;
|
|
15
|
+
phase = 'failed';
|
|
16
|
+
cleanup();
|
|
17
|
+
onFailure(error instanceof Error ? error : hostContextError('transport failed'));
|
|
18
|
+
};
|
|
19
|
+
const complete = () => {
|
|
20
|
+
if (readySent && clientClosed) {
|
|
21
|
+
phase = 'complete';
|
|
22
|
+
cleanup();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const send = (frame, ready = false) => {
|
|
26
|
+
if (!child.connected || !child.send)
|
|
27
|
+
throw hostContextError('channel is unavailable');
|
|
28
|
+
child.send(frame, (error) => {
|
|
29
|
+
if (error) {
|
|
30
|
+
fail(error);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (ready && phase === 'ready') {
|
|
34
|
+
readySent = true;
|
|
35
|
+
complete();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const message = (value) => {
|
|
40
|
+
try {
|
|
41
|
+
if (phase === 'request') {
|
|
42
|
+
if (!child.pid)
|
|
43
|
+
throw hostContextError('child identity is unavailable');
|
|
44
|
+
binding = readHostRequest(value, process.pid, child.pid);
|
|
45
|
+
phase = 'accept';
|
|
46
|
+
send(hostContextMessage(context, binding));
|
|
47
|
+
}
|
|
48
|
+
else if (phase === 'accept') {
|
|
49
|
+
assertHostControl(value, 'accept', binding);
|
|
50
|
+
phase = 'ready';
|
|
51
|
+
send(hostControlMessage('ready', binding), true);
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
throw hostContextError('message is repeated or out of order');
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
fail(error);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const disconnected = () => {
|
|
61
|
+
if (phase === 'ready') {
|
|
62
|
+
clientClosed = true;
|
|
63
|
+
complete();
|
|
64
|
+
}
|
|
65
|
+
else
|
|
66
|
+
fail(hostContextError('channel closed before acceptance'));
|
|
67
|
+
};
|
|
68
|
+
const timer = setTimeout(() => fail(hostContextError('handshake timed out')), HOST_CONTEXT_TIMEOUT_MS);
|
|
69
|
+
child.on('message', message);
|
|
70
|
+
child.on('disconnect', disconnected);
|
|
71
|
+
return {
|
|
72
|
+
finish() {
|
|
73
|
+
const error = phase === 'complete' ? undefined : hostContextError('child closed before acceptance');
|
|
74
|
+
if (error)
|
|
75
|
+
phase = 'failed';
|
|
76
|
+
cleanup();
|
|
77
|
+
return error;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=host-context-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-context-server.js","sourceRoot":"","sources":["../../../../src/lib/runtime/host-context-server.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChildProcess } from 'node:child_process';\nimport type { RuntimeHostContext } from './manifest-types.js';\nimport {\n HOST_CONTEXT_TIMEOUT_MS,\n assertHostControl,\n hostContextError,\n hostContextMessage,\n hostControlMessage,\n readHostRequest,\n type HostContextBinding,\n type HostContextMessage,\n} from './host-context-protocol.js';\n\n/** One IPC handshake, owned by the same process that holds the execution lease. */\nexport function serveRuntimeHostContext(\n child: ChildProcess,\n context: RuntimeHostContext,\n onFailure: (error: Error) => void,\n): { finish(): Error | undefined } {\n let phase: 'request' | 'accept' | 'ready' | 'complete' | 'failed' = 'request';\n let binding: HostContextBinding | undefined;\n let readySent = false,\n clientClosed = false;\n const cleanup = () => {\n clearTimeout(timer);\n child.off('message', message);\n child.off('disconnect', disconnected);\n };\n const fail = (error: unknown) => {\n if (phase === 'failed' || phase === 'complete') return;\n phase = 'failed';\n cleanup();\n onFailure(error instanceof Error ? error : hostContextError('transport failed'));\n };\n const complete = () => {\n if (readySent && clientClosed) {\n phase = 'complete';\n cleanup();\n }\n };\n const send = (frame: HostContextMessage, ready = false) => {\n if (!child.connected || !child.send) throw hostContextError('channel is unavailable');\n child.send(frame, (error) => {\n if (error) {\n fail(error);\n return;\n }\n if (ready && phase === 'ready') {\n readySent = true;\n complete();\n }\n });\n };\n const message = (value: unknown) => {\n try {\n if (phase === 'request') {\n if (!child.pid) throw hostContextError('child identity is unavailable');\n binding = readHostRequest(value, process.pid, child.pid);\n phase = 'accept';\n send(hostContextMessage(context, binding));\n } else if (phase === 'accept') {\n assertHostControl(value, 'accept', binding!);\n phase = 'ready';\n send(hostControlMessage('ready', binding!), true);\n } else throw hostContextError('message is repeated or out of order');\n } catch (error) {\n fail(error);\n }\n };\n const disconnected = () => {\n if (phase === 'ready') {\n clientClosed = true;\n complete();\n } else fail(hostContextError('channel closed before acceptance'));\n };\n const timer = setTimeout(\n () => fail(hostContextError('handshake timed out')),\n HOST_CONTEXT_TIMEOUT_MS,\n );\n child.on('message', message);\n child.on('disconnect', disconnected);\n return {\n finish() {\n const error =\n phase === 'complete' ? undefined : hostContextError('child closed before acceptance');\n if (error) phase = 'failed';\n cleanup();\n return error;\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type RuntimeHostContext } from './manifest-types.js';
|
|
2
|
+
import { readHostContextMessage, type HostContextMessage, type HostContextSendCallback } from './host-context-protocol.js';
|
|
3
|
+
type Listener = (value?: unknown) => void;
|
|
4
|
+
export type RuntimeHostProcess = {
|
|
5
|
+
pid: number;
|
|
6
|
+
ppid: number;
|
|
7
|
+
execPath: string;
|
|
8
|
+
cwd(): string;
|
|
9
|
+
connected?: boolean;
|
|
10
|
+
send?: (message: HostContextMessage, callback: HostContextSendCallback) => unknown;
|
|
11
|
+
disconnect?: () => void;
|
|
12
|
+
on(event: 'message' | 'disconnect', listener: Listener): unknown;
|
|
13
|
+
off(event: 'message' | 'disconnect', listener: Listener): unknown;
|
|
14
|
+
};
|
|
15
|
+
declare function validateSelection(input: ReturnType<typeof readHostContextMessage>, child: RuntimeHostProcess): RuntimeHostContext;
|
|
16
|
+
declare function receiveFromProcess(child: RuntimeHostProcess): Promise<RuntimeHostContext>;
|
|
17
|
+
/** Receive only the owning manager's one-use IPC handoff, before loading task input. */
|
|
18
|
+
export declare function receiveRuntimeHostContext(): Promise<RuntimeHostContext>;
|
|
19
|
+
export declare const runtimeHostContextInternals: {
|
|
20
|
+
receiveFromProcess: typeof receiveFromProcess;
|
|
21
|
+
validateSelection: typeof validateSelection;
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import { componentKey } from './manifest.js';
|
|
5
|
+
import { inspectRuntimeHost } from './host.js';
|
|
6
|
+
import { inspectRuntimeComponents } from './manager.js';
|
|
7
|
+
import { openRuntimeCache, cachePath } from './storage.js';
|
|
8
|
+
import { runtimeWorkDirectory, assertRuntimeWorkDirectoryOutsideInstall, } from './work-directory.js';
|
|
9
|
+
import { RUNTIME_HOST_CONTEXT_PROTOCOL } from './manifest-types.js';
|
|
10
|
+
import { HOST_CONTEXT_TIMEOUT_MS, assertHostControl, hostContextError, hostControlMessage, readHostContextMessage, } from './host-context-protocol.js';
|
|
11
|
+
const claimed = new WeakSet();
|
|
12
|
+
function validateSelection(input, child) {
|
|
13
|
+
if (!path.isAbsolute(input.cacheDir) || !path.isAbsolute(input.cwd))
|
|
14
|
+
throw hostContextError('requires absolute cache and work directories');
|
|
15
|
+
const cacheDir = openRuntimeCache(input.cacheDir, false);
|
|
16
|
+
const cwd = runtimeWorkDirectory(input.cwd);
|
|
17
|
+
if (cwd !== fs.realpathSync(child.cwd()))
|
|
18
|
+
throw hostContextError('work directory changed');
|
|
19
|
+
assertRuntimeWorkDirectoryOutsideInstall(cwd, cacheDir);
|
|
20
|
+
const host = inspectRuntimeHost();
|
|
21
|
+
const launch = input.manifest.manifest.launches.find((item) => item.platform === host.platform && item.id === input.entry);
|
|
22
|
+
if (launch?.context_protocol !== RUNTIME_HOST_CONTEXT_PROTOCOL)
|
|
23
|
+
throw hostContextError('launch did not select this protocol');
|
|
24
|
+
if (inspectRuntimeComponents(input.manifest, { cacheDir, host }).status !== 'ready')
|
|
25
|
+
throw hostContextError('selected components changed');
|
|
26
|
+
const component = input.manifest.manifest.components.find((item) => item.platform === host.platform && item.id === launch.executable.component);
|
|
27
|
+
const executable = cachePath(cacheDir, `components/${componentKey(component)}/root/${launch.executable.path}`);
|
|
28
|
+
const expected = fs.statSync(executable, { bigint: true }), actual = fs.statSync(child.execPath, { bigint: true });
|
|
29
|
+
if (expected.dev !== actual.dev || expected.ino !== actual.ino)
|
|
30
|
+
throw hostContextError('executable is not the declared managed Node');
|
|
31
|
+
return Object.freeze({ ...input, cacheDir, cwd, host });
|
|
32
|
+
}
|
|
33
|
+
function receiveFromProcess(child) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
if (!child.connected || !child.send || !child.disconnect) {
|
|
36
|
+
reject(hostContextError('requires an inherited manager IPC channel'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (claimed.has(child)) {
|
|
40
|
+
reject(hostContextError('handshake was already consumed'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
claimed.add(child);
|
|
44
|
+
const binding = Object.freeze({
|
|
45
|
+
nonce: randomBytes(32).toString('hex'),
|
|
46
|
+
parent_pid: child.ppid,
|
|
47
|
+
child_pid: child.pid,
|
|
48
|
+
});
|
|
49
|
+
let context;
|
|
50
|
+
let ready = false, settled = false, closing = false;
|
|
51
|
+
const disconnect = () => {
|
|
52
|
+
if (closing || !child.connected)
|
|
53
|
+
return;
|
|
54
|
+
closing = true;
|
|
55
|
+
child.disconnect();
|
|
56
|
+
};
|
|
57
|
+
const cleanup = () => {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
child.off('message', message);
|
|
60
|
+
child.off('disconnect', disconnected);
|
|
61
|
+
};
|
|
62
|
+
const fail = (error) => {
|
|
63
|
+
if (settled)
|
|
64
|
+
return;
|
|
65
|
+
settled = true;
|
|
66
|
+
cleanup();
|
|
67
|
+
reject(error instanceof Error ? error : hostContextError('transport failed'));
|
|
68
|
+
try {
|
|
69
|
+
disconnect();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* Preserve the original failure; the owning manager drains the child. */
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const send = (value) => child.send(value, (error) => {
|
|
76
|
+
if (error)
|
|
77
|
+
fail(error);
|
|
78
|
+
});
|
|
79
|
+
const message = (value) => {
|
|
80
|
+
try {
|
|
81
|
+
if (!context) {
|
|
82
|
+
context = validateSelection(readHostContextMessage(value, binding), child);
|
|
83
|
+
send(hostControlMessage('accept', binding));
|
|
84
|
+
}
|
|
85
|
+
else if (!ready) {
|
|
86
|
+
assertHostControl(value, 'ready', binding);
|
|
87
|
+
ready = true;
|
|
88
|
+
disconnect();
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
throw hostContextError('message is repeated or out of order');
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
fail(error);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const disconnected = () => {
|
|
98
|
+
if (!ready) {
|
|
99
|
+
fail(hostContextError('manager closed before confirming acceptance'));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
settled = true;
|
|
103
|
+
cleanup();
|
|
104
|
+
resolve(context);
|
|
105
|
+
};
|
|
106
|
+
const timer = setTimeout(() => fail(hostContextError('handshake timed out')), HOST_CONTEXT_TIMEOUT_MS);
|
|
107
|
+
child.on('message', message);
|
|
108
|
+
child.on('disconnect', disconnected);
|
|
109
|
+
try {
|
|
110
|
+
send(hostControlMessage('request', binding));
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
fail(error);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/** Receive only the owning manager's one-use IPC handoff, before loading task input. */
|
|
118
|
+
export function receiveRuntimeHostContext() {
|
|
119
|
+
return receiveFromProcess(process);
|
|
120
|
+
}
|
|
121
|
+
export const runtimeHostContextInternals = { receiveFromProcess, validateSelection };
|
|
122
|
+
//# sourceMappingURL=host-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-context.js","sourceRoot":"","sources":["../../../../src/lib/runtime/host-context.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { randomBytes } from 'node:crypto';\nimport { componentKey } from './manifest.js';\nimport { inspectRuntimeHost } from './host.js';\nimport { inspectRuntimeComponents } from './manager.js';\nimport { openRuntimeCache, cachePath } from './storage.js';\nimport {\n runtimeWorkDirectory,\n assertRuntimeWorkDirectoryOutsideInstall,\n} from './work-directory.js';\nimport { RUNTIME_HOST_CONTEXT_PROTOCOL, type RuntimeHostContext } from './manifest-types.js';\nimport {\n HOST_CONTEXT_TIMEOUT_MS,\n assertHostControl,\n hostContextError,\n hostControlMessage,\n readHostContextMessage,\n type HostContextMessage,\n type HostContextSendCallback,\n} from './host-context-protocol.js';\n\ntype Listener = (value?: unknown) => void;\nexport type RuntimeHostProcess = {\n pid: number;\n ppid: number;\n execPath: string;\n cwd(): string;\n connected?: boolean;\n send?: (message: HostContextMessage, callback: HostContextSendCallback) => unknown;\n disconnect?: () => void;\n on(event: 'message' | 'disconnect', listener: Listener): unknown;\n off(event: 'message' | 'disconnect', listener: Listener): unknown;\n};\nconst claimed = new WeakSet<RuntimeHostProcess>();\n\nfunction validateSelection(\n input: ReturnType<typeof readHostContextMessage>,\n child: RuntimeHostProcess,\n): RuntimeHostContext {\n if (!path.isAbsolute(input.cacheDir) || !path.isAbsolute(input.cwd))\n throw hostContextError('requires absolute cache and work directories');\n const cacheDir = openRuntimeCache(input.cacheDir, false);\n const cwd = runtimeWorkDirectory(input.cwd);\n if (cwd !== fs.realpathSync(child.cwd())) throw hostContextError('work directory changed');\n assertRuntimeWorkDirectoryOutsideInstall(cwd, cacheDir);\n const host = inspectRuntimeHost();\n const launch = input.manifest.manifest.launches.find(\n (item) => item.platform === host.platform && item.id === input.entry,\n );\n if (launch?.context_protocol !== RUNTIME_HOST_CONTEXT_PROTOCOL)\n throw hostContextError('launch did not select this protocol');\n if (inspectRuntimeComponents(input.manifest, { cacheDir, host }).status !== 'ready')\n throw hostContextError('selected components changed');\n const component = input.manifest.manifest.components.find(\n (item) => item.platform === host.platform && item.id === launch.executable.component,\n )!;\n const executable = cachePath(\n cacheDir,\n `components/${componentKey(component)}/root/${launch.executable.path}`,\n );\n const expected = fs.statSync(executable, { bigint: true }),\n actual = fs.statSync(child.execPath, { bigint: true });\n if (expected.dev !== actual.dev || expected.ino !== actual.ino)\n throw hostContextError('executable is not the declared managed Node');\n return Object.freeze({ ...input, cacheDir, cwd, host });\n}\n\nfunction receiveFromProcess(child: RuntimeHostProcess): Promise<RuntimeHostContext> {\n return new Promise((resolve, reject) => {\n if (!child.connected || !child.send || !child.disconnect) {\n reject(hostContextError('requires an inherited manager IPC channel'));\n return;\n }\n if (claimed.has(child)) {\n reject(hostContextError('handshake was already consumed'));\n return;\n }\n claimed.add(child);\n const binding = Object.freeze({\n nonce: randomBytes(32).toString('hex'),\n parent_pid: child.ppid,\n child_pid: child.pid,\n });\n let context: RuntimeHostContext | undefined;\n let ready = false,\n settled = false,\n closing = false;\n const disconnect = () => {\n if (closing || !child.connected) return;\n closing = true;\n child.disconnect!();\n };\n const cleanup = () => {\n clearTimeout(timer);\n child.off('message', message);\n child.off('disconnect', disconnected);\n };\n const fail = (error: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(error instanceof Error ? error : hostContextError('transport failed'));\n try {\n disconnect();\n } catch {\n /* Preserve the original failure; the owning manager drains the child. */\n }\n };\n const send = (value: HostContextMessage) =>\n child.send!(value, (error) => {\n if (error) fail(error);\n });\n const message = (value: unknown) => {\n try {\n if (!context) {\n context = validateSelection(readHostContextMessage(value, binding), child);\n send(hostControlMessage('accept', binding));\n } else if (!ready) {\n assertHostControl(value, 'ready', binding);\n ready = true;\n disconnect();\n } else throw hostContextError('message is repeated or out of order');\n } catch (error) {\n fail(error);\n }\n };\n const disconnected = () => {\n if (!ready) {\n fail(hostContextError('manager closed before confirming acceptance'));\n return;\n }\n settled = true;\n cleanup();\n resolve(context!);\n };\n const timer = setTimeout(\n () => fail(hostContextError('handshake timed out')),\n HOST_CONTEXT_TIMEOUT_MS,\n );\n child.on('message', message);\n child.on('disconnect', disconnected);\n try {\n send(hostControlMessage('request', binding));\n } catch (error) {\n fail(error);\n }\n });\n}\n\n/** Receive only the owning manager's one-use IPC handoff, before loading task input. */\nexport function receiveRuntimeHostContext(): Promise<RuntimeHostContext> {\n return receiveFromProcess(process);\n}\nexport const runtimeHostContextInternals = { receiveFromProcess, validateSelection };\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RuntimeHost, TrustedRuntimeManifest } from './manifest-types.js';
|
|
2
|
+
declare function compareVersions(left: string, right: string): number;
|
|
3
|
+
export declare function inspectRuntimeHost(): RuntimeHost;
|
|
4
|
+
export declare function assertRuntimeHost(value: TrustedRuntimeManifest, host: RuntimeHost): void;
|
|
5
|
+
export declare const runtimeHostInternals: {
|
|
6
|
+
compareVersions: typeof compareVersions;
|
|
7
|
+
};
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { runtimePlatform } from './descriptor.js';
|
|
3
|
+
import { assertTrustedManifest } from './manifest.js';
|
|
4
|
+
import { runtimeError } from './files.js';
|
|
5
|
+
function compareVersions(left, right) {
|
|
6
|
+
const a = left.split(/[.-]/u).slice(0, 3).map(Number), b = right.split('.').map(Number);
|
|
7
|
+
if (a.some((value) => !Number.isSafeInteger(value) || value < 0))
|
|
8
|
+
return -1;
|
|
9
|
+
for (let i = 0; i < 3; i++) {
|
|
10
|
+
const delta = (a[i] ?? 0) - (b[i] ?? 0);
|
|
11
|
+
if (delta !== 0)
|
|
12
|
+
return delta;
|
|
13
|
+
}
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
export function inspectRuntimeHost() {
|
|
17
|
+
const platform = runtimePlatform(process.platform, process.arch);
|
|
18
|
+
let glibc = null;
|
|
19
|
+
if (process.platform === 'linux') {
|
|
20
|
+
// Project only the ABI header; never serialize a diagnostic report or its environment.
|
|
21
|
+
const report = process.report.getReport();
|
|
22
|
+
if (typeof report.header?.glibcVersionRuntime === 'string')
|
|
23
|
+
glibc = report.header.glibcVersionRuntime;
|
|
24
|
+
}
|
|
25
|
+
return Object.freeze({ platform, osRelease: os.release(), glibc });
|
|
26
|
+
}
|
|
27
|
+
export function assertRuntimeHost(value, host) {
|
|
28
|
+
assertTrustedManifest(value);
|
|
29
|
+
const minimum = value.manifest.minimum_hosts[host.platform];
|
|
30
|
+
if (!minimum ||
|
|
31
|
+
compareVersions(host.osRelease, minimum.os_release) < 0 ||
|
|
32
|
+
(host.platform.startsWith('linux-') &&
|
|
33
|
+
(!host.glibc || !minimum.glibc || compareVersions(host.glibc, minimum.glibc) < 0)))
|
|
34
|
+
runtimeError('RUNTIME_HOST_UNSUPPORTED', 'The selected release does not support this host OS/architecture/ABI.');
|
|
35
|
+
}
|
|
36
|
+
export const runtimeHostInternals = { compareVersions };
|
|
37
|
+
//# sourceMappingURL=host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../../../src/lib/runtime/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,SAAS,eAAe,CAAC,IAAY,EAAE,KAAa;IAClD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EACnD,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC;IAC5E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACxC,IAAI,KAAK,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;IAChC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AACD,MAAM,UAAU,kBAAkB;IAChC,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,IAAI,KAAK,GAAkB,IAAI,CAAC;IAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,uFAAuF;QACvF,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,EAAoD,CAAC;QAC5F,IAAI,OAAO,MAAM,CAAC,MAAM,EAAE,mBAAmB,KAAK,QAAQ;YACxD,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC;IAC9C,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AACrE,CAAC;AACD,MAAM,UAAU,iBAAiB,CAAC,KAA6B,EAAE,IAAiB;IAChF,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5D,IACE,CAAC,OAAO;QACR,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;QACvD,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC;YACjC,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAEpF,YAAY,CACV,0BAA0B,EAC1B,sEAAsE,CACvE,CAAC;AACN,CAAC;AACD,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,eAAe,EAAE,CAAC","sourcesContent":["import os from 'node:os';\nimport { runtimePlatform } from './descriptor.js';\nimport { assertTrustedManifest } from './manifest.js';\nimport { runtimeError } from './files.js';\nimport type { RuntimeHost, TrustedRuntimeManifest } from './manifest-types.js';\n\nfunction compareVersions(left: string, right: string): number {\n const a = left.split(/[.-]/u).slice(0, 3).map(Number),\n b = right.split('.').map(Number);\n if (a.some((value) => !Number.isSafeInteger(value) || value < 0)) return -1;\n for (let i = 0; i < 3; i++) {\n const delta = (a[i] ?? 0) - (b[i] ?? 0);\n if (delta !== 0) return delta;\n }\n return 0;\n}\nexport function inspectRuntimeHost(): RuntimeHost {\n const platform = runtimePlatform(process.platform, process.arch);\n let glibc: string | null = null;\n if (process.platform === 'linux') {\n // Project only the ABI header; never serialize a diagnostic report or its environment.\n const report = process.report.getReport() as { header?: { glibcVersionRuntime?: unknown } };\n if (typeof report.header?.glibcVersionRuntime === 'string')\n glibc = report.header.glibcVersionRuntime;\n }\n return Object.freeze({ platform, osRelease: os.release(), glibc });\n}\nexport function assertRuntimeHost(value: TrustedRuntimeManifest, host: RuntimeHost): void {\n assertTrustedManifest(value);\n const minimum = value.manifest.minimum_hosts[host.platform];\n if (\n !minimum ||\n compareVersions(host.osRelease, minimum.os_release) < 0 ||\n (host.platform.startsWith('linux-') &&\n (!host.glibc || !minimum.glibc || compareVersions(host.glibc, minimum.glibc) < 0))\n )\n runtimeError(\n 'RUNTIME_HOST_UNSUPPORTED',\n 'The selected release does not support this host OS/architecture/ABI.',\n );\n}\nexport const runtimeHostInternals = { compareVersions };\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type RuntimeLease = Readonly<{
|
|
2
|
+
schema: 'tiangong-lca.runtime-lease.v1';
|
|
3
|
+
id: string;
|
|
4
|
+
owner: string;
|
|
5
|
+
components: readonly string[];
|
|
6
|
+
}>;
|
|
7
|
+
export declare function runtimeLeaseKey(id: string): string;
|
|
8
|
+
export declare function withRuntimeLeaseLock<T>(root: string, operation: () => Promise<T> | T): Promise<T>;
|
|
9
|
+
export declare function readRuntimeLease(root: string, key: string): RuntimeLease;
|
|
10
|
+
export declare function acquireRuntimeLease(root: string, id: string, owner: string, components: readonly string[]): Promise<RuntimeLease>;
|
|
11
|
+
export declare function releaseRuntimeLease(root: string, id: string, owner: string): Promise<boolean>;
|
|
12
|
+
export declare function leasedRuntimeKeys(root: string): Set<string>;
|