@tiangong-lca/cli 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +8 -5
  2. package/assets/runtime/cli-runtime-descriptor.schema.json +138 -0
  3. package/assets/runtime/cli-runtime-expectation.schema.json +38 -0
  4. package/assets/runtime/runtime-bootstrap-lock.schema.json +254 -0
  5. package/assets/runtime/runtime-command-result.schema.json +95 -0
  6. package/assets/runtime/runtime-manifest.schema.json +380 -0
  7. package/dist/src/cli.js +5 -1
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/lib/dataset-import-lca.js +2 -2
  10. package/dist/src/lib/dataset-import-lca.js.map +1 -1
  11. package/dist/src/lib/runtime/archive-writer.d.ts +7 -0
  12. package/dist/src/lib/runtime/archive-writer.js +89 -0
  13. package/dist/src/lib/runtime/archive-writer.js.map +1 -0
  14. package/dist/src/lib/runtime/archive.d.ts +2 -0
  15. package/dist/src/lib/runtime/archive.js +122 -0
  16. package/dist/src/lib/runtime/archive.js.map +1 -0
  17. package/dist/src/lib/runtime/command.d.ts +8 -0
  18. package/dist/src/lib/runtime/command.js +57 -0
  19. package/dist/src/lib/runtime/command.js.map +1 -0
  20. package/dist/src/lib/runtime/descriptor.d.ts +11 -0
  21. package/dist/src/lib/runtime/descriptor.js +126 -0
  22. package/dist/src/lib/runtime/descriptor.js.map +1 -0
  23. package/dist/src/lib/runtime/download.d.ts +9 -0
  24. package/dist/src/lib/runtime/download.js +118 -0
  25. package/dist/src/lib/runtime/download.js.map +1 -0
  26. package/dist/src/lib/runtime/exec-command.d.ts +7 -0
  27. package/dist/src/lib/runtime/exec-command.js +83 -0
  28. package/dist/src/lib/runtime/exec-command.js.map +1 -0
  29. package/dist/src/lib/runtime/execute.d.ts +11 -0
  30. package/dist/src/lib/runtime/execute.js +125 -0
  31. package/dist/src/lib/runtime/execute.js.map +1 -0
  32. package/dist/src/lib/runtime/files.d.ts +7 -0
  33. package/dist/src/lib/runtime/files.js +91 -0
  34. package/dist/src/lib/runtime/files.js.map +1 -0
  35. package/dist/src/lib/runtime/host.d.ts +8 -0
  36. package/dist/src/lib/runtime/host.js +37 -0
  37. package/dist/src/lib/runtime/host.js.map +1 -0
  38. package/dist/src/lib/runtime/leases.d.ts +12 -0
  39. package/dist/src/lib/runtime/leases.js +72 -0
  40. package/dist/src/lib/runtime/leases.js.map +1 -0
  41. package/dist/src/lib/runtime/managed-command.d.ts +6 -0
  42. package/dist/src/lib/runtime/managed-command.js +99 -0
  43. package/dist/src/lib/runtime/managed-command.js.map +1 -0
  44. package/dist/src/lib/runtime/manager.d.ts +33 -0
  45. package/dist/src/lib/runtime/manager.js +196 -0
  46. package/dist/src/lib/runtime/manager.js.map +1 -0
  47. package/dist/src/lib/runtime/manifest-types.d.ts +73 -0
  48. package/dist/src/lib/runtime/manifest-types.js +4 -0
  49. package/dist/src/lib/runtime/manifest-types.js.map +1 -0
  50. package/dist/src/lib/runtime/manifest-values.d.ts +15 -0
  51. package/dist/src/lib/runtime/manifest-values.js +124 -0
  52. package/dist/src/lib/runtime/manifest-values.js.map +1 -0
  53. package/dist/src/lib/runtime/manifest.d.ts +7 -0
  54. package/dist/src/lib/runtime/manifest.js +220 -0
  55. package/dist/src/lib/runtime/manifest.js.map +1 -0
  56. package/dist/src/lib/runtime/process.d.ts +3 -0
  57. package/dist/src/lib/runtime/process.js +55 -0
  58. package/dist/src/lib/runtime/process.js.map +1 -0
  59. package/dist/src/lib/runtime/storage.d.ts +8 -0
  60. package/dist/src/lib/runtime/storage.js +142 -0
  61. package/dist/src/lib/runtime/storage.js.map +1 -0
  62. package/dist/src/lib/runtime/types.d.ts +45 -0
  63. package/dist/src/lib/runtime/types.js +9 -0
  64. package/dist/src/lib/runtime/types.js.map +1 -0
  65. package/dist/src/main.js +13 -1
  66. package/dist/src/main.js.map +1 -1
  67. package/dist/src/runtime.d.ts +15 -0
  68. package/dist/src/runtime.js +22 -0
  69. package/dist/src/runtime.js.map +1 -0
  70. 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,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>;
@@ -0,0 +1,72 @@
1
+ import fs from 'node:fs';
2
+ import { withBatchRunLock } from '../../batch.js';
3
+ import { contentHash, runtimeError } from './files.js';
4
+ import { array, exact, record, sha, text, unique } from './manifest-values.js';
5
+ import { cachePath, readCacheJson, writeOnce } from './storage.js';
6
+ export function runtimeLeaseKey(id) {
7
+ return contentHash(text(id, 'lease id', 256));
8
+ }
9
+ export async function withRuntimeLeaseLock(root, operation) {
10
+ return withBatchRunLock({
11
+ runPath: cachePath(root, 'locks/leases.json'),
12
+ identity: { schema: 'runtime-lease-lock.v1' },
13
+ reason: 'Runtime component lease mutation',
14
+ }, operation);
15
+ }
16
+ export function readRuntimeLease(root, key) {
17
+ sha(key);
18
+ const value = record(readCacheJson(root, `leases/${key}.json`), 'lease');
19
+ exact(value, ['schema', 'id', 'owner', 'components'], 'lease');
20
+ if (value.schema !== 'tiangong-lca.runtime-lease.v1')
21
+ runtimeError('RUNTIME_LEASE_INVALID', 'Unknown runtime lease schema.');
22
+ const id = text(value.id, 'lease id', 256), owner = text(value.owner, 'lease owner', 4096), components = array(value.components, 128, 1).map(sha);
23
+ unique(components, 'lease component');
24
+ if (runtimeLeaseKey(id) !== key)
25
+ runtimeError('RUNTIME_LEASE_INVALID', 'Runtime lease identity changed.');
26
+ return { schema: 'tiangong-lca.runtime-lease.v1', id, owner, components };
27
+ }
28
+ export async function acquireRuntimeLease(root, id, owner, components) {
29
+ const lease = {
30
+ schema: 'tiangong-lca.runtime-lease.v1',
31
+ id: text(id, 'lease id', 256),
32
+ owner: text(owner, 'lease owner', 4096),
33
+ components: [...components].sort(),
34
+ };
35
+ array(components, 128, 1).forEach(sha);
36
+ unique(components, 'lease component');
37
+ return withRuntimeLeaseLock(root, () => {
38
+ const key = runtimeLeaseKey(id), file = cachePath(root, `leases/${key}.json`);
39
+ if (fs.existsSync(file) && contentHash(readRuntimeLease(root, key)) !== contentHash(lease))
40
+ runtimeError('RUNTIME_LEASE_CONFLICT', 'Existing lease pins another owner or component set; release it explicitly before replacement.');
41
+ writeOnce(root, `leases/${key}.json`, Buffer.from(JSON.stringify(lease) + '\n'));
42
+ return Object.freeze(lease);
43
+ });
44
+ }
45
+ export async function releaseRuntimeLease(root, id, owner) {
46
+ return withRuntimeLeaseLock(root, () => {
47
+ const key = runtimeLeaseKey(id), file = cachePath(root, `leases/${key}.json`);
48
+ if (!fs.existsSync(file))
49
+ return false;
50
+ if (readRuntimeLease(root, key).owner !== owner)
51
+ runtimeError('RUNTIME_LEASE_OWNER', 'Only the same explicit lease owner can release a runtime pin.');
52
+ fs.unlinkSync(file);
53
+ return true;
54
+ });
55
+ }
56
+ export function leasedRuntimeKeys(root) {
57
+ const directory = cachePath(root, 'leases');
58
+ if (!fs.existsSync(directory))
59
+ return new Set();
60
+ const names = fs.readdirSync(directory);
61
+ if (names.length > 10_000)
62
+ runtimeError('RUNTIME_LEASE_LIMIT', 'Lease inventory exceeds its bound.');
63
+ const keys = new Set();
64
+ for (const name of names) {
65
+ if (!/^[0-9a-f]{64}\.json$/u.test(name))
66
+ runtimeError('RUNTIME_LEASE_INVALID', 'Unknown lease record prevents cache pruning.');
67
+ for (const key of readRuntimeLease(root, name.slice(0, -5)).components)
68
+ keys.add(key);
69
+ }
70
+ return keys;
71
+ }
72
+ //# sourceMappingURL=leases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leases.js","sourceRoot":"","sources":["../../../../src/lib/runtime/leases.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport { withBatchRunLock } from '../../batch.js';\nimport { contentHash, runtimeError } from './files.js';\nimport { array, exact, record, sha, text, unique } from './manifest-values.js';\nimport { cachePath, readCacheJson, writeOnce } from './storage.js';\n\nexport type RuntimeLease = Readonly<{\n schema: 'tiangong-lca.runtime-lease.v1';\n id: string;\n owner: string;\n components: readonly string[];\n}>;\nexport function runtimeLeaseKey(id: string): string {\n return contentHash(text(id, 'lease id', 256));\n}\nexport async function withRuntimeLeaseLock<T>(\n root: string,\n operation: () => Promise<T> | T,\n): Promise<T> {\n return withBatchRunLock(\n {\n runPath: cachePath(root, 'locks/leases.json'),\n identity: { schema: 'runtime-lease-lock.v1' },\n reason: 'Runtime component lease mutation',\n },\n operation,\n );\n}\nexport function readRuntimeLease(root: string, key: string): RuntimeLease {\n sha(key);\n const value = record(readCacheJson(root, `leases/${key}.json`), 'lease');\n exact(value, ['schema', 'id', 'owner', 'components'], 'lease');\n if (value.schema !== 'tiangong-lca.runtime-lease.v1')\n runtimeError('RUNTIME_LEASE_INVALID', 'Unknown runtime lease schema.');\n const id = text(value.id, 'lease id', 256),\n owner = text(value.owner, 'lease owner', 4096),\n components = array(value.components, 128, 1).map(sha);\n unique(components, 'lease component');\n if (runtimeLeaseKey(id) !== key)\n runtimeError('RUNTIME_LEASE_INVALID', 'Runtime lease identity changed.');\n return { schema: 'tiangong-lca.runtime-lease.v1', id, owner, components };\n}\nexport async function acquireRuntimeLease(\n root: string,\n id: string,\n owner: string,\n components: readonly string[],\n): Promise<RuntimeLease> {\n const lease: RuntimeLease = {\n schema: 'tiangong-lca.runtime-lease.v1',\n id: text(id, 'lease id', 256),\n owner: text(owner, 'lease owner', 4096),\n components: [...components].sort(),\n };\n array(components, 128, 1).forEach(sha);\n unique(components, 'lease component');\n return withRuntimeLeaseLock(root, () => {\n const key = runtimeLeaseKey(id),\n file = cachePath(root, `leases/${key}.json`);\n if (fs.existsSync(file) && contentHash(readRuntimeLease(root, key)) !== contentHash(lease))\n runtimeError(\n 'RUNTIME_LEASE_CONFLICT',\n 'Existing lease pins another owner or component set; release it explicitly before replacement.',\n );\n writeOnce(root, `leases/${key}.json`, Buffer.from(JSON.stringify(lease) + '\\n'));\n return Object.freeze(lease);\n });\n}\nexport async function releaseRuntimeLease(\n root: string,\n id: string,\n owner: string,\n): Promise<boolean> {\n return withRuntimeLeaseLock(root, () => {\n const key = runtimeLeaseKey(id),\n file = cachePath(root, `leases/${key}.json`);\n if (!fs.existsSync(file)) return false;\n if (readRuntimeLease(root, key).owner !== owner)\n runtimeError(\n 'RUNTIME_LEASE_OWNER',\n 'Only the same explicit lease owner can release a runtime pin.',\n );\n fs.unlinkSync(file);\n return true;\n });\n}\nexport function leasedRuntimeKeys(root: string): Set<string> {\n const directory = cachePath(root, 'leases');\n if (!fs.existsSync(directory)) return new Set();\n const names = fs.readdirSync(directory);\n if (names.length > 10_000)\n runtimeError('RUNTIME_LEASE_LIMIT', 'Lease inventory exceeds its bound.');\n const keys = new Set<string>();\n for (const name of names) {\n if (!/^[0-9a-f]{64}\\.json$/u.test(name))\n runtimeError('RUNTIME_LEASE_INVALID', 'Unknown lease record prevents cache pruning.');\n for (const key of readRuntimeLease(root, name.slice(0, -5)).components) keys.add(key);\n }\n return keys;\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { FetchLike } from '../http.js';
2
+ export declare function runManagedRuntimeCommand(operation: string, args: string[], fetchImpl?: FetchLike): Promise<{
3
+ exitCode: number;
4
+ stdout: string;
5
+ stderr: string;
6
+ }>;
@@ -0,0 +1,99 @@
1
+ import { parseArgs } from 'node:util';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { CliError } from '../errors.js';
5
+ import { loadTrustedRuntimeManifest } from './manifest.js';
6
+ import { ensureRuntimeComponents, inspectRuntimeComponents, pruneRuntimeComponents, } from './manager.js';
7
+ import { releaseRuntimeLease } from './leases.js';
8
+ import { defaultRuntimeCache, openRuntimeCache } from './storage.js';
9
+ export async function runManagedRuntimeCommand(operation, args, fetchImpl) {
10
+ let values;
11
+ try {
12
+ values = parseArgs({
13
+ args,
14
+ strict: true,
15
+ allowPositionals: false,
16
+ options: {
17
+ help: { type: 'boolean', short: 'h' },
18
+ json: { type: 'boolean' },
19
+ manifest: { type: 'string' },
20
+ 'manifest-sha256': { type: 'string' },
21
+ 'cache-dir': { type: 'string' },
22
+ lease: { type: 'string' },
23
+ 'lease-owner': { type: 'string' },
24
+ apply: { type: 'boolean' },
25
+ },
26
+ }).values;
27
+ }
28
+ catch {
29
+ throw new CliError('Invalid runtime management arguments.', {
30
+ code: 'RUNTIME_ARGUMENT_INVALID',
31
+ exitCode: 2,
32
+ });
33
+ }
34
+ if (values.help)
35
+ return {
36
+ exitCode: 0,
37
+ stdout: 'Usage: tiangong-lca runtime ensure|status|prune --manifest <file> --manifest-sha256 <trusted-sha256> [--cache-dir <absolute-dir>] [--json]\nensure accepts --lease <id> --lease-owner <non-secret-owner>. prune requires --apply.\nlease-release requires --lease and --lease-owner. No authentication or task state is created.\n',
38
+ stderr: '',
39
+ };
40
+ const cacheDir = values['cache-dir'] ?? defaultRuntimeCache();
41
+ if (operation === 'lease-release') {
42
+ if (!values.lease ||
43
+ !values['lease-owner'] ||
44
+ values.manifest ||
45
+ values['manifest-sha256'] ||
46
+ values.apply)
47
+ throw new CliError('Lease release requires only cache, lease id and owner.', {
48
+ code: 'RUNTIME_ARGUMENT_INVALID',
49
+ exitCode: 2,
50
+ });
51
+ const root = openRuntimeCache(cacheDir, false);
52
+ const released = fs.existsSync(path.join(root, '.runtime-cache.json'))
53
+ ? await releaseRuntimeLease(root, values.lease, values['lease-owner'])
54
+ : false;
55
+ return {
56
+ exitCode: 0,
57
+ stdout: JSON.stringify({ schema: 'tiangong-lca.runtime-lease-release.v1', released }) + '\n',
58
+ stderr: '',
59
+ };
60
+ }
61
+ if (!values.manifest ||
62
+ !values['manifest-sha256'] ||
63
+ Boolean(values.lease) !== Boolean(values['lease-owner']) ||
64
+ ((values.lease || values['lease-owner']) && operation !== 'ensure') ||
65
+ (values.apply && operation !== 'prune'))
66
+ throw new CliError('Select an explicit manifest and independent digest; lease options apply only to ensure.', { code: 'RUNTIME_ARGUMENT_INVALID', exitCode: 2 });
67
+ const trusted = loadTrustedRuntimeManifest(values.manifest, values['manifest-sha256']);
68
+ const options = {
69
+ cacheDir,
70
+ fetchImpl,
71
+ ...(values.lease ? { lease: { id: values.lease, owner: values['lease-owner'] } } : {}),
72
+ };
73
+ if (operation === 'prune') {
74
+ if (!values.apply)
75
+ throw new CliError('Pruning requires an explicit --apply for this manifest component set.', {
76
+ code: 'RUNTIME_PRUNE_APPLY_REQUIRED',
77
+ exitCode: 2,
78
+ });
79
+ return {
80
+ exitCode: 0,
81
+ stdout: JSON.stringify({
82
+ schema: 'tiangong-lca.runtime-prune.v1',
83
+ ...(await pruneRuntimeComponents(trusted, options)),
84
+ }) + '\n',
85
+ stderr: '',
86
+ };
87
+ }
88
+ const report = operation === 'ensure'
89
+ ? await ensureRuntimeComponents(trusted, options)
90
+ : inspectRuntimeComponents(trusted, options);
91
+ return {
92
+ exitCode: report.status === 'ready' ? 0 : 69,
93
+ stdout: values.json
94
+ ? JSON.stringify(report) + '\n'
95
+ : `Runtime ${report.status}: ${report.components.map((item) => `${item.id}@${item.version} ${item.status}`).join(', ')}\n`,
96
+ stderr: '',
97
+ };
98
+ }
99
+ //# sourceMappingURL=managed-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed-command.js","sourceRoot":"","sources":["../../../../src/lib/runtime/managed-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseArgs } from 'node:util';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { CliError } from '../errors.js';\nimport type { FetchLike } from '../http.js';\nimport { loadTrustedRuntimeManifest } from './manifest.js';\nimport {\n ensureRuntimeComponents,\n inspectRuntimeComponents,\n pruneRuntimeComponents,\n} from './manager.js';\nimport { releaseRuntimeLease } from './leases.js';\nimport { defaultRuntimeCache, openRuntimeCache } from './storage.js';\n\nexport async function runManagedRuntimeCommand(\n operation: string,\n args: string[],\n fetchImpl?: FetchLike,\n): Promise<{ exitCode: number; stdout: string; stderr: string }> {\n let values;\n try {\n values = parseArgs({\n args,\n strict: true,\n allowPositionals: false,\n options: {\n help: { type: 'boolean', short: 'h' },\n json: { type: 'boolean' },\n manifest: { type: 'string' },\n 'manifest-sha256': { type: 'string' },\n 'cache-dir': { type: 'string' },\n lease: { type: 'string' },\n 'lease-owner': { type: 'string' },\n apply: { type: 'boolean' },\n },\n }).values;\n } catch {\n throw new CliError('Invalid runtime management arguments.', {\n code: 'RUNTIME_ARGUMENT_INVALID',\n exitCode: 2,\n });\n }\n if (values.help)\n return {\n exitCode: 0,\n stdout:\n 'Usage: tiangong-lca runtime ensure|status|prune --manifest <file> --manifest-sha256 <trusted-sha256> [--cache-dir <absolute-dir>] [--json]\\nensure accepts --lease <id> --lease-owner <non-secret-owner>. prune requires --apply.\\nlease-release requires --lease and --lease-owner. No authentication or task state is created.\\n',\n stderr: '',\n };\n const cacheDir = values['cache-dir'] ?? defaultRuntimeCache();\n if (operation === 'lease-release') {\n if (\n !values.lease ||\n !values['lease-owner'] ||\n values.manifest ||\n values['manifest-sha256'] ||\n values.apply\n )\n throw new CliError('Lease release requires only cache, lease id and owner.', {\n code: 'RUNTIME_ARGUMENT_INVALID',\n exitCode: 2,\n });\n const root = openRuntimeCache(cacheDir, false);\n const released = fs.existsSync(path.join(root, '.runtime-cache.json'))\n ? await releaseRuntimeLease(root, values.lease, values['lease-owner'])\n : false;\n return {\n exitCode: 0,\n stdout: JSON.stringify({ schema: 'tiangong-lca.runtime-lease-release.v1', released }) + '\\n',\n stderr: '',\n };\n }\n if (\n !values.manifest ||\n !values['manifest-sha256'] ||\n Boolean(values.lease) !== Boolean(values['lease-owner']) ||\n ((values.lease || values['lease-owner']) && operation !== 'ensure') ||\n (values.apply && operation !== 'prune')\n )\n throw new CliError(\n 'Select an explicit manifest and independent digest; lease options apply only to ensure.',\n { code: 'RUNTIME_ARGUMENT_INVALID', exitCode: 2 },\n );\n const trusted = loadTrustedRuntimeManifest(values.manifest, values['manifest-sha256']);\n const options = {\n cacheDir,\n fetchImpl,\n ...(values.lease ? { lease: { id: values.lease, owner: values['lease-owner']! } } : {}),\n };\n if (operation === 'prune') {\n if (!values.apply)\n throw new CliError('Pruning requires an explicit --apply for this manifest component set.', {\n code: 'RUNTIME_PRUNE_APPLY_REQUIRED',\n exitCode: 2,\n });\n return {\n exitCode: 0,\n stdout:\n JSON.stringify({\n schema: 'tiangong-lca.runtime-prune.v1',\n ...(await pruneRuntimeComponents(trusted, options)),\n }) + '\\n',\n stderr: '',\n };\n }\n const report =\n operation === 'ensure'\n ? await ensureRuntimeComponents(trusted, options)\n : inspectRuntimeComponents(trusted, options);\n return {\n exitCode: report.status === 'ready' ? 0 : 69,\n stdout: values.json\n ? JSON.stringify(report) + '\\n'\n : `Runtime ${report.status}: ${report.components.map((item) => `${item.id}@${item.version} ${item.status}`).join(', ')}\\n`,\n stderr: '',\n };\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { type DownloadOptions } from './download.js';
2
+ import type { RuntimeComponent, RuntimeHost, TrustedRuntimeManifest } from './manifest-types.js';
3
+ export type RuntimeManagerOptions = DownloadOptions & {
4
+ cacheDir?: string;
5
+ host?: RuntimeHost;
6
+ lease?: {
7
+ id: string;
8
+ owner: string;
9
+ };
10
+ archiveSeeds?: Readonly<Record<string, string>>;
11
+ };
12
+ export type RuntimeComponentStatus = {
13
+ id: string;
14
+ version: string;
15
+ key: string;
16
+ status: 'ready' | 'missing' | 'unverified' | 'corrupt';
17
+ root: string;
18
+ reason: string | null;
19
+ };
20
+ export type RuntimeManagerReport = {
21
+ schema: 'tiangong-lca.runtime-status.v1';
22
+ manifest_sha256: string;
23
+ platform: string;
24
+ status: 'ready' | 'missing' | 'blocked';
25
+ components: RuntimeComponentStatus[];
26
+ };
27
+ export declare function verifyRuntimeComponent(root: string, component: RuntimeComponent, platform: string): void;
28
+ export declare function inspectRuntimeComponents(value: TrustedRuntimeManifest, options?: RuntimeManagerOptions): RuntimeManagerReport;
29
+ export declare function ensureRuntimeComponents(value: TrustedRuntimeManifest, options?: RuntimeManagerOptions): Promise<RuntimeManagerReport>;
30
+ export declare function pruneRuntimeComponents(value: TrustedRuntimeManifest, options?: RuntimeManagerOptions): Promise<{
31
+ removed: string[];
32
+ retained: string[];
33
+ }>;
@@ -0,0 +1,196 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { withBatchRunLock } from '../../batch.js';
5
+ import { contentHash, hashRuntimeFile, runtimeError } from './files.js';
6
+ import { assertTrustedManifest, componentKey } from './manifest.js';
7
+ import { assertRuntimeHost, inspectRuntimeHost } from './host.js';
8
+ import { cachePath, defaultRuntimeCache, ensureRuntimeCache, openRuntimeCache, readCacheJson, writeOnce, } from './storage.js';
9
+ import { downloadRuntimeArchive } from './download.js';
10
+ import { extractRuntimeArchive } from './archive.js';
11
+ import { acquireRuntimeLease, leasedRuntimeKeys, withRuntimeLeaseLock } from './leases.js';
12
+ function receipt(component) {
13
+ return {
14
+ schema: 'tiangong-lca.runtime-component.v1',
15
+ key: componentKey(component),
16
+ archive_sha256: component.archive.sha256,
17
+ content_sha256: component.content_sha256,
18
+ };
19
+ }
20
+ export function verifyRuntimeComponent(root, component, platform) {
21
+ const expected = new Map(component.files.map((file) => [file.path, file]));
22
+ let count = 0;
23
+ const walk = (relative) => {
24
+ const directory = relative ? cachePath(root, relative) : root;
25
+ const stat = fs.lstatSync(directory);
26
+ if (stat.isSymbolicLink() || !stat.isDirectory())
27
+ runtimeError('RUNTIME_COMPONENT_PATH', 'Component directories cannot be links.');
28
+ for (const name of fs.readdirSync(directory)) {
29
+ const file = relative ? `${relative}/${name}` : name;
30
+ const target = cachePath(root, file);
31
+ const item = fs.lstatSync(target);
32
+ if (item.isDirectory())
33
+ walk(file);
34
+ else {
35
+ const expectedFile = expected.get(file);
36
+ if (!expectedFile)
37
+ runtimeError('RUNTIME_COMPONENT_EXTRA', 'Component contains an unregistered file.');
38
+ const actual = hashRuntimeFile(target, file);
39
+ if (actual.bytes !== expectedFile.bytes ||
40
+ actual.sha256 !== expectedFile.sha256 ||
41
+ (platform !== 'win32-x64' && (item.mode & 0o777) !== expectedFile.mode))
42
+ runtimeError('RUNTIME_COMPONENT_CHANGED', 'Component file bytes, digest or executable mode changed.');
43
+ count++;
44
+ }
45
+ }
46
+ };
47
+ walk('');
48
+ if (count !== component.files.length)
49
+ runtimeError('RUNTIME_COMPONENT_MISSING', 'Component inventory is incomplete.');
50
+ }
51
+ function inspectComponent(cache, component, platform) {
52
+ const key = componentKey(component), relative = `components/${key}`, root = cachePath(cache, `${relative}/root`);
53
+ const base = { id: component.id, version: component.version, key, root };
54
+ if (!fs.existsSync(cachePath(cache, relative)))
55
+ return { ...base, status: 'missing', reason: null };
56
+ try {
57
+ if (fs
58
+ .readdirSync(cachePath(cache, relative))
59
+ .some((name) => !['root', 'receipt.json'].includes(name)))
60
+ runtimeError('RUNTIME_COMPONENT_EXTRA', 'Component installation directory contains unknown data.');
61
+ if (!fs.existsSync(cachePath(cache, `${relative}/receipt.json`)))
62
+ return { ...base, status: 'unverified', reason: 'runtime_install_incomplete' };
63
+ if (contentHash(readCacheJson(cache, `${relative}/receipt.json`)) !==
64
+ contentHash(receipt(component)))
65
+ runtimeError('RUNTIME_RECEIPT_CHANGED', 'Component installation receipt does not match the selected manifest.');
66
+ verifyRuntimeComponent(root, component, platform);
67
+ return { ...base, status: 'ready', reason: null };
68
+ }
69
+ catch (error) {
70
+ return {
71
+ ...base,
72
+ status: 'corrupt',
73
+ reason: typeof error.code === 'string'
74
+ ? error.code
75
+ : 'runtime_component_invalid',
76
+ };
77
+ }
78
+ }
79
+ function selected(value, options) {
80
+ assertTrustedManifest(value);
81
+ const host = options.host ?? inspectRuntimeHost();
82
+ assertRuntimeHost(value, host);
83
+ return {
84
+ host,
85
+ components: value.manifest.components.filter((component) => component.platform === host.platform),
86
+ };
87
+ }
88
+ export function inspectRuntimeComponents(value, options = {}) {
89
+ const { host, components } = selected(value, options), cache = openRuntimeCache(options.cacheDir ?? defaultRuntimeCache(), false);
90
+ const states = components.map((component) => inspectComponent(cache, component, host.platform));
91
+ return {
92
+ schema: 'tiangong-lca.runtime-status.v1',
93
+ manifest_sha256: value.sha256,
94
+ platform: host.platform,
95
+ status: states.every((item) => item.status === 'ready')
96
+ ? 'ready'
97
+ : states.some((item) => item.status === 'corrupt' || item.status === 'unverified')
98
+ ? 'blocked'
99
+ : 'missing',
100
+ components: states,
101
+ };
102
+ }
103
+ async function installComponent(cache, component, options, platform) {
104
+ const key = componentKey(component);
105
+ await withBatchRunLock({
106
+ runPath: cachePath(cache, `locks/${key}.json`),
107
+ identity: { schema: 'runtime-component-lock.v1', key },
108
+ reason: 'Runtime component installation',
109
+ }, async () => {
110
+ const state = inspectComponent(cache, component, platform);
111
+ if (state.status === 'ready')
112
+ return;
113
+ const target = cachePath(cache, `components/${key}`);
114
+ if (state.status === 'unverified') {
115
+ // Bootstrap may publish a complete tree before Node can produce the CLI receipt.
116
+ // It is cache data only: adopt after every declared byte/mode and absence of extra files is proved.
117
+ verifyRuntimeComponent(state.root, component, platform);
118
+ writeOnce(cache, `components/${key}/receipt.json`, Buffer.from(JSON.stringify(receipt(component)) + '\n'));
119
+ return;
120
+ }
121
+ if (state.status === 'corrupt')
122
+ runtimeError('RUNTIME_CACHE_CORRUPT', 'A selected cached component is corrupt; preserve active leases and prune it explicitly.');
123
+ const staging = cachePath(cache, `tmp/${key}-${randomUUID()}`);
124
+ fs.mkdirSync(staging, { recursive: true, mode: 0o700 });
125
+ try {
126
+ const archive = path.join(staging, 'component.tar.gz'), seed = options.archiveSeeds?.[key];
127
+ if (seed) {
128
+ const fact = hashRuntimeFile(seed, 'archive');
129
+ if (fact.bytes !== component.archive.bytes || fact.sha256 !== component.archive.sha256)
130
+ runtimeError('RUNTIME_ARCHIVE_SEED', 'Supplied archive seed differs from the trusted component.');
131
+ fs.copyFileSync(seed, archive, fs.constants.COPYFILE_EXCL);
132
+ }
133
+ else
134
+ await downloadRuntimeArchive(component.archive, archive, options);
135
+ await extractRuntimeArchive(archive, path.join(staging, 'root'), component, options.signal);
136
+ verifyRuntimeComponent(path.join(staging, 'root'), component, platform);
137
+ fs.unlinkSync(archive);
138
+ writeOnce(staging, 'receipt.json', Buffer.from(JSON.stringify(receipt(component)) + '\n'));
139
+ fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
140
+ cachePath(cache, `components/${key}`);
141
+ if (fs.existsSync(target))
142
+ runtimeError('RUNTIME_INSTALL_CONFLICT', 'Another component tree appeared outside the installation lock.');
143
+ fs.renameSync(staging, target);
144
+ }
145
+ finally {
146
+ if (fs.existsSync(staging))
147
+ fs.rmSync(staging, { recursive: true, force: true });
148
+ }
149
+ });
150
+ }
151
+ export async function ensureRuntimeComponents(value, options = {}) {
152
+ const { host, components } = selected(value, options);
153
+ options.signal?.throwIfAborted();
154
+ const cache = await ensureRuntimeCache(options.cacheDir ?? defaultRuntimeCache());
155
+ if (options.lease)
156
+ await acquireRuntimeLease(cache, options.lease.id, options.lease.owner, components.map(componentKey));
157
+ for (const component of components) {
158
+ options.signal?.throwIfAborted();
159
+ await installComponent(cache, component, options, host.platform);
160
+ }
161
+ return inspectRuntimeComponents(value, { ...options, cacheDir: cache, host });
162
+ }
163
+ export async function pruneRuntimeComponents(value, options = {}) {
164
+ const { components } = selected(value, options);
165
+ const cache = openRuntimeCache(options.cacheDir ?? defaultRuntimeCache(), false);
166
+ if (!fs.existsSync(cachePath(cache, '.runtime-cache.json')))
167
+ return { removed: [], retained: [] };
168
+ return withRuntimeLeaseLock(cache, async () => {
169
+ const pinned = leasedRuntimeKeys(cache), removed = [], retained = [];
170
+ for (const component of components) {
171
+ const key = componentKey(component);
172
+ if (pinned.has(key)) {
173
+ retained.push(key);
174
+ continue;
175
+ }
176
+ await withBatchRunLock({
177
+ runPath: cachePath(cache, `locks/${key}.json`),
178
+ identity: { schema: 'runtime-component-lock.v1', key },
179
+ reason: 'Explicit unused runtime pruning',
180
+ }, () => {
181
+ const target = cachePath(cache, `components/${key}`);
182
+ if (!fs.existsSync(target))
183
+ return;
184
+ if (fs.readdirSync(target).some((name) => !['root', 'receipt.json'].includes(name)))
185
+ runtimeError('RUNTIME_PRUNE_UNOWNED', 'Unknown files must be preserved instead of pruned.');
186
+ if (contentHash(readCacheJson(cache, `components/${key}/receipt.json`)) !==
187
+ contentHash(receipt(component)))
188
+ runtimeError('RUNTIME_PRUNE_UNOWNED', 'Only an installation with its exact ownership receipt can be pruned.');
189
+ fs.rmSync(target, { recursive: true, force: false });
190
+ removed.push(key);
191
+ });
192
+ }
193
+ return { removed, retained };
194
+ });
195
+ }
196
+ //# sourceMappingURL=manager.js.map