@timurproko/a1 0.1.1-dev.1 → 0.1.1-dev.2
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/dist/src/foundation/release/bootstrap.js +37 -5
- package/dist/src/foundation/release/concurrency.d.ts +1 -0
- package/dist/src/foundation/release/concurrency.js +17 -0
- package/dist/src/foundation/release/release-store.d.ts +20 -1
- package/dist/src/foundation/release/release-store.js +43 -9
- package/dist/src/foundation/release/release.js +7 -5
- package/dist/src/foundation/release/update.d.ts +1 -1
- package/dist/src/foundation/release/update.js +5 -3
- package/dist/src/foundation/supervision/main.js +6 -5
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import { CohortStateStore } from "./cohort-state.js";
|
|
|
8
8
|
import { assertLaunchProfileId, resolveProductPaths } from "../lifecycle/index.js";
|
|
9
9
|
import { encodeFrame, LineFrameDecoder } from "../protocol/index.js";
|
|
10
10
|
import { cleanupProvenIdleOwner, processIsAlive } from "./process-cleanup.js";
|
|
11
|
-
import { materializeRelease, readMaterializedRelease, resolveReleaseEntryPoint, verifyMaterializedRelease } from "./release-store.js";
|
|
11
|
+
import { materializeRelease, readCertifiedReleaseManifest, readMaterializedRelease, resolveReleaseEntryPoint, verifyMaterializedRelease } from "./release-store.js";
|
|
12
12
|
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
|
|
13
13
|
export async function runBootstrap(options) {
|
|
14
14
|
const environment = { ...(options.environment ?? process.env) };
|
|
@@ -18,18 +18,43 @@ export async function runBootstrap(options) {
|
|
|
18
18
|
const output = options.output ?? process.stderr;
|
|
19
19
|
const paths = resolveProductPaths(environment);
|
|
20
20
|
await mkdir(paths.runtimeDir, { recursive: true, mode: 0o700 });
|
|
21
|
-
const candidate = await materializeRelease(options.packageRoot, paths.dataDir);
|
|
22
21
|
const stateStore = new CohortStateStore(paths.dataDir);
|
|
23
|
-
await stateStore.recordCandidate(candidate);
|
|
24
22
|
let state = await stateStore.read();
|
|
23
|
+
let endpoint = await readEndpointMetadata(paths.endpointMetadataPath);
|
|
24
|
+
let probe = endpoint ? await probeOwnership(endpoint) : "dead";
|
|
25
|
+
const installedVersion = await readInstalledVersion(options.packageRoot);
|
|
26
|
+
const activeId = state.references.active;
|
|
27
|
+
const active = activeId === null ? undefined : state.releases[activeId];
|
|
28
|
+
if (active?.approval === "approved" && active.packageVersion === installedVersion) {
|
|
29
|
+
const endpointMatches = endpoint?.releaseId === active.releaseId
|
|
30
|
+
&& endpoint.releaseRoot === active.releaseRoot
|
|
31
|
+
&& endpoint.contentDigest === active.contentDigest;
|
|
32
|
+
if (endpointMatches && probe === "live-verified") {
|
|
33
|
+
const retained = await readCertifiedReleaseManifest(active, resolve(paths.dataDir, "releases"));
|
|
34
|
+
return await launchUi(retained, environment);
|
|
35
|
+
}
|
|
36
|
+
if (endpoint === null || probe === "dead") {
|
|
37
|
+
if (endpoint)
|
|
38
|
+
await removeEndpointArtifacts(paths.endpointMetadataPath, paths.endpoint);
|
|
39
|
+
const retained = await readMaterializedRelease(active.releaseRoot);
|
|
40
|
+
await startSupervisor(retained, environment);
|
|
41
|
+
await waitForVerifiedEndpoint(paths.endpointMetadataPath, retained, 8_000);
|
|
42
|
+
return await launchUi(retained, environment);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const candidate = await materializeRelease(options.packageRoot, paths.dataDir, {
|
|
46
|
+
onProgress: progress => output.write(`${PRODUCT_TEXT.diagnostic(`preparing ${progress.fileCount} installed release files; first launch may take a moment.`)}\n`),
|
|
47
|
+
});
|
|
48
|
+
await stateStore.recordCandidate(candidate);
|
|
49
|
+
state = await stateStore.read();
|
|
25
50
|
if (!state.references.active) {
|
|
26
51
|
const diagnosticsPath = await certifyMaterializedRelease(candidate, paths.dataDir);
|
|
27
52
|
await stateStore.approve(candidate.releaseId, diagnosticsPath);
|
|
28
53
|
await stateStore.activate(candidate.releaseId);
|
|
29
54
|
state = await stateStore.read();
|
|
30
55
|
}
|
|
31
|
-
|
|
32
|
-
|
|
56
|
+
endpoint = await readEndpointMetadata(paths.endpointMetadataPath);
|
|
57
|
+
probe = endpoint ? await probeOwnership(endpoint) : "dead";
|
|
33
58
|
let decision = selectCohortLaunch(candidate, state, endpoint, probe);
|
|
34
59
|
if (decision.action === "blocked") {
|
|
35
60
|
await stateStore.blockPending(decision.reason, endpoint?.ownership.liveGenerationIds ?? []);
|
|
@@ -93,6 +118,13 @@ export async function runBootstrap(options) {
|
|
|
93
118
|
await waitForVerifiedEndpoint(paths.endpointMetadataPath, selected, 8_000);
|
|
94
119
|
return await launchUi(selected, environment);
|
|
95
120
|
}
|
|
121
|
+
async function readInstalledVersion(packageRoot) {
|
|
122
|
+
const manifest = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
|
|
123
|
+
if (typeof manifest.version !== "string" || manifest.version.length === 0) {
|
|
124
|
+
throw new Error(PRODUCT_TEXT.diagnostic("package metadata has no version"));
|
|
125
|
+
}
|
|
126
|
+
return manifest.version;
|
|
127
|
+
}
|
|
96
128
|
export async function certifyMaterializedRelease(release, dataDir) {
|
|
97
129
|
await verifyMaterializedRelease(release.releaseRoot, release, resolve(dataDir, "releases"));
|
|
98
130
|
const path = resolve(dataDir, `certification-${release.releaseId}.json`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function mapWithConcurrency<Value, Result>(values: readonly Value[], concurrency: number, operation: (value: Value, index: number) => Promise<Result>): Promise<Result[]>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export async function mapWithConcurrency(values, concurrency, operation) {
|
|
2
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1)
|
|
3
|
+
throw new RangeError("concurrency must be a positive safe integer");
|
|
4
|
+
const results = new Array(values.length);
|
|
5
|
+
let nextIndex = 0;
|
|
6
|
+
const worker = async () => {
|
|
7
|
+
while (true) {
|
|
8
|
+
const index = nextIndex;
|
|
9
|
+
nextIndex += 1;
|
|
10
|
+
if (index >= values.length)
|
|
11
|
+
return;
|
|
12
|
+
results[index] = await operation(values[index], index);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, worker));
|
|
16
|
+
return results;
|
|
17
|
+
}
|
|
@@ -3,8 +3,27 @@ export declare const RELEASE_MANIFEST_FILENAME: string;
|
|
|
3
3
|
export interface MaterializedRelease extends ReleaseIdentity {
|
|
4
4
|
readonly releaseRoot: string;
|
|
5
5
|
}
|
|
6
|
-
export
|
|
6
|
+
export interface MaterializeReleaseOptions {
|
|
7
|
+
readonly onProgress?: (progress: {
|
|
8
|
+
readonly phase: "copying";
|
|
9
|
+
readonly fileCount: number;
|
|
10
|
+
}) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface CertifiedReleaseRecord {
|
|
13
|
+
readonly releaseId: string;
|
|
14
|
+
readonly releaseRoot: string;
|
|
15
|
+
readonly packageVersion?: string;
|
|
16
|
+
readonly contentDigest: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function materializeRelease(packageRoot: string, dataDir: string, options?: MaterializeReleaseOptions): Promise<MaterializedRelease>;
|
|
7
19
|
export declare function readMaterializedRelease(releaseRoot: string): Promise<MaterializedRelease>;
|
|
20
|
+
/**
|
|
21
|
+
* Load metadata for a release whose bytes were already certified by the
|
|
22
|
+
* current parent process or an authenticated live supervisor. Callers must
|
|
23
|
+
* establish one of those preconditions; untrusted releases require full
|
|
24
|
+
* verification.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readCertifiedReleaseManifest(record: CertifiedReleaseRecord, selectedStoreRoot: string): Promise<MaterializedRelease>;
|
|
8
27
|
export declare function verifyMaterializedRelease(releaseRoot: string, expected?: ReleaseIdentity, selectedStoreRoot?: string): Promise<MaterializedRelease>;
|
|
9
28
|
export declare function assertImmutableExecutionRoot(release: MaterializedRelease, dataDir: string): Promise<void>;
|
|
10
29
|
export declare function resolveReleaseEntryPoint(release: MaterializedRelease, entryPoint: string): Promise<string>;
|
|
@@ -3,8 +3,10 @@ import { chmod, copyFile, lstat, mkdir, readFile, realpath, rename, rm, writeFil
|
|
|
3
3
|
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { PRODUCT_PACKAGE_NAME, deriveReleaseIdentity, resolveWithin } from "./release.js";
|
|
5
5
|
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
|
|
6
|
+
import { mapWithConcurrency } from "./concurrency.js";
|
|
7
|
+
const RELEASE_FILE_IO_CONCURRENCY = 32;
|
|
6
8
|
export const RELEASE_MANIFEST_FILENAME = PRODUCT_IDENTITY.manifest.releaseFilename;
|
|
7
|
-
export async function materializeRelease(packageRoot, dataDir) {
|
|
9
|
+
export async function materializeRelease(packageRoot, dataDir, options = {}) {
|
|
8
10
|
const identity = await deriveReleaseIdentity(packageRoot);
|
|
9
11
|
const storeRoot = resolve(dataDir, "releases");
|
|
10
12
|
await mkdir(storeRoot, { recursive: true, mode: 0o700 });
|
|
@@ -12,20 +14,31 @@ export async function materializeRelease(packageRoot, dataDir) {
|
|
|
12
14
|
const existing = await lstat(releaseRoot).catch(() => null);
|
|
13
15
|
if (existing)
|
|
14
16
|
return await verifyMaterializedRelease(releaseRoot, identity);
|
|
17
|
+
options.onProgress?.({ phase: "copying", fileCount: identity.files.length });
|
|
15
18
|
const candidate = resolveWithin(storeRoot, `.candidate-${identity.releaseId}-${randomUUID()}`);
|
|
16
19
|
await mkdir(candidate, { recursive: false, mode: 0o700 });
|
|
17
20
|
try {
|
|
18
|
-
|
|
21
|
+
const directories = [...new Set(identity.files.map(file => dirname(resolveWithin(candidate, file.path))))];
|
|
22
|
+
await mapWithConcurrency(directories, RELEASE_FILE_IO_CONCURRENCY, async (directory) => {
|
|
23
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
24
|
+
});
|
|
25
|
+
await mapWithConcurrency(identity.files, RELEASE_FILE_IO_CONCURRENCY, async (file) => {
|
|
19
26
|
const source = resolveWithin(identity.packageRoot, file.path);
|
|
20
27
|
const destination = resolveWithin(candidate, file.path);
|
|
21
|
-
await mkdir(dirname(destination), { recursive: true, mode: 0o700 });
|
|
22
28
|
await copyFile(source, destination);
|
|
23
29
|
await chmod(destination, file.executable ? 0o500 : 0o400);
|
|
24
|
-
}
|
|
30
|
+
});
|
|
25
31
|
await writeFile(resolve(candidate, RELEASE_MANIFEST_FILENAME), JSON.stringify(identity, null, 2), { mode: 0o400, flag: "wx" });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
try {
|
|
33
|
+
await rename(candidate, releaseRoot);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (!await lstat(releaseRoot).catch(() => null))
|
|
37
|
+
throw error;
|
|
38
|
+
await rm(candidate, { recursive: true, force: true });
|
|
39
|
+
return await verifyMaterializedRelease(releaseRoot, identity);
|
|
40
|
+
}
|
|
41
|
+
return { ...identity, releaseRoot: await realpath(releaseRoot) };
|
|
29
42
|
}
|
|
30
43
|
catch (error) {
|
|
31
44
|
await rm(candidate, { recursive: true, force: true });
|
|
@@ -37,6 +50,25 @@ export async function readMaterializedRelease(releaseRoot) {
|
|
|
37
50
|
const manifest = JSON.parse(await readFile(resolve(canonical, RELEASE_MANIFEST_FILENAME), "utf8"));
|
|
38
51
|
return await verifyMaterializedRelease(canonical, manifest);
|
|
39
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Load metadata for a release whose bytes were already certified by the
|
|
55
|
+
* current parent process or an authenticated live supervisor. Callers must
|
|
56
|
+
* establish one of those preconditions; untrusted releases require full
|
|
57
|
+
* verification.
|
|
58
|
+
*/
|
|
59
|
+
export async function readCertifiedReleaseManifest(record, selectedStoreRoot) {
|
|
60
|
+
const canonical = await realpath(record.releaseRoot);
|
|
61
|
+
assertContained(await realpath(selectedStoreRoot), canonical, "release root is outside the selected release store");
|
|
62
|
+
const manifest = JSON.parse(await readFile(resolveWithin(canonical, RELEASE_MANIFEST_FILENAME), "utf8"));
|
|
63
|
+
validateManifest(manifest);
|
|
64
|
+
if (manifest.releaseId !== record.releaseId || manifest.contentDigest !== record.contentDigest
|
|
65
|
+
|| (record.packageVersion !== undefined && manifest.packageVersion !== record.packageVersion)) {
|
|
66
|
+
throw new Error(`certified release record differs from manifest for ${canonical}`);
|
|
67
|
+
}
|
|
68
|
+
if (canonical.split(sep).at(-1) !== manifest.releaseId)
|
|
69
|
+
throw new Error(`release directory does not match identity ${manifest.releaseId}`);
|
|
70
|
+
return { ...manifest, releaseRoot: canonical };
|
|
71
|
+
}
|
|
40
72
|
export async function verifyMaterializedRelease(releaseRoot, expected, selectedStoreRoot) {
|
|
41
73
|
const canonical = await realpath(releaseRoot);
|
|
42
74
|
if (selectedStoreRoot)
|
|
@@ -50,15 +82,17 @@ export async function verifyMaterializedRelease(releaseRoot, expected, selectedS
|
|
|
50
82
|
if (canonical.split(sep).at(-1) !== manifest.releaseId && !canonical.split(sep).at(-1)?.startsWith(`.candidate-${manifest.releaseId}-`)) {
|
|
51
83
|
throw new Error(`release directory does not match identity ${manifest.releaseId}`);
|
|
52
84
|
}
|
|
53
|
-
|
|
85
|
+
await mapWithConcurrency(manifest.files, RELEASE_FILE_IO_CONCURRENCY, async (file) => {
|
|
54
86
|
await verifyFile(canonical, file);
|
|
87
|
+
});
|
|
55
88
|
const recomputed = digestManifestFiles(manifest.files);
|
|
56
89
|
if (recomputed !== manifest.contentDigest)
|
|
57
90
|
throw new Error(`release content digest mismatch for ${manifest.releaseId}`);
|
|
58
91
|
return { ...manifest, releaseRoot: canonical };
|
|
59
92
|
}
|
|
60
93
|
export async function assertImmutableExecutionRoot(release, dataDir) {
|
|
61
|
-
await
|
|
94
|
+
const storeRoot = await realpath(resolve(dataDir, "releases"));
|
|
95
|
+
assertContained(storeRoot, release.releaseRoot, "release root is outside the selected release store");
|
|
62
96
|
const selectedRoot = process.env[PRODUCT_IDENTITY.environment.releaseRoot];
|
|
63
97
|
if (!selectedRoot)
|
|
64
98
|
throw new Error(PRODUCT_TEXT.diagnostic("persistent process has no immutable release root"));
|
|
@@ -2,6 +2,8 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { lstat, readFile, readdir, realpath } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
|
|
5
|
+
import { mapWithConcurrency } from "./concurrency.js";
|
|
6
|
+
const RELEASE_FILE_IO_CONCURRENCY = 32;
|
|
5
7
|
export const PRODUCT_PACKAGE_NAME = PRODUCT_IDENTITY.packageName;
|
|
6
8
|
/**
|
|
7
9
|
* Derive release execution identity only from installed distribution metadata and
|
|
@@ -23,20 +25,20 @@ export async function deriveReleaseIdentity(packageRoot) {
|
|
|
23
25
|
await collectFiles(canonicalRoot, absolute, paths);
|
|
24
26
|
}
|
|
25
27
|
await collectDependencyClosure(canonicalRoot, canonicalRoot, manifest, paths, new Set());
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
+
const sortedPaths = [...paths].sort();
|
|
29
|
+
const files = await mapWithConcurrency(sortedPaths, RELEASE_FILE_IO_CONCURRENCY, async (path) => {
|
|
28
30
|
const absolute = resolveWithin(canonicalRoot, path);
|
|
29
31
|
const metadata = await lstat(absolute);
|
|
30
32
|
if (!metadata.isFile())
|
|
31
33
|
throw new Error(`release payload is not a regular file: ${path}`);
|
|
32
34
|
const bytes = await readFile(absolute);
|
|
33
|
-
|
|
35
|
+
return {
|
|
34
36
|
path: normalizeRelative(path),
|
|
35
37
|
bytes: bytes.length,
|
|
36
38
|
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
37
39
|
executable: (metadata.mode & 0o111) !== 0,
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
+
};
|
|
41
|
+
});
|
|
40
42
|
const digest = createHash("sha256");
|
|
41
43
|
for (const file of files)
|
|
42
44
|
digest.update(`${file.path}\0${file.bytes}\0${file.sha256}\0${file.executable ? 1 : 0}\n`);
|
|
@@ -50,5 +50,5 @@ export interface UpdateTransactionJournal {
|
|
|
50
50
|
clearCompleted(): Promise<void>;
|
|
51
51
|
}
|
|
52
52
|
export declare function createNpmProcessRunner(platform?: NodeJS.Platform): UpdateProcessRunner;
|
|
53
|
-
export declare function createUpdateLifecycleCoordinator(environment?: NodeJS.ProcessEnv, fileSystem?: UpdateFileSystem): UpdateLifecycleCoordinator;
|
|
53
|
+
export declare function createUpdateLifecycleCoordinator(environment?: NodeJS.ProcessEnv, fileSystem?: UpdateFileSystem, output?: UpdateOutput): UpdateLifecycleCoordinator;
|
|
54
54
|
export declare function runSelfUpdate(options: SelfUpdateOptions): Promise<number>;
|
|
@@ -40,7 +40,7 @@ export function createNpmProcessRunner(platform = process.platform) {
|
|
|
40
40
|
} });
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
|
-
export function createUpdateLifecycleCoordinator(environment = process.env, fileSystem = defaultFileSystem) {
|
|
43
|
+
export function createUpdateLifecycleCoordinator(environment = process.env, fileSystem = defaultFileSystem, output = defaultOutput) {
|
|
44
44
|
const paths = resolveProductPaths(environment);
|
|
45
45
|
const stateStore = new CohortStateStore(paths.dataDir);
|
|
46
46
|
return {
|
|
@@ -106,7 +106,9 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
|
|
|
106
106
|
}
|
|
107
107
|
},
|
|
108
108
|
async activateInstalled(packageRoot, targetVersion, phase) {
|
|
109
|
-
const candidate = await materializeRelease(packageRoot, paths.dataDir
|
|
109
|
+
const candidate = await materializeRelease(packageRoot, paths.dataDir, {
|
|
110
|
+
onProgress: progress => output.stdout(`${PRODUCT_TEXT.diagnostic(`preparing ${progress.fileCount} installed release files; this one-time activation may take a moment.`)}\n`),
|
|
111
|
+
});
|
|
110
112
|
if (candidate.packageVersion !== targetVersion)
|
|
111
113
|
throw new Error(`installed ${PRODUCT_TEXT.displayName} version ${candidate.packageVersion} does not match target ${targetVersion}`);
|
|
112
114
|
await stateStore.recordCandidate(candidate);
|
|
@@ -170,7 +172,7 @@ export async function runSelfUpdate(options) {
|
|
|
170
172
|
}
|
|
171
173
|
const environment = options.environment ?? process.env;
|
|
172
174
|
const paths = resolveProductPaths(environment);
|
|
173
|
-
const lifecycle = options.lifecycle ?? createUpdateLifecycleCoordinator(environment, fileSystem);
|
|
175
|
+
const lifecycle = options.lifecycle ?? createUpdateLifecycleCoordinator(environment, fileSystem, output);
|
|
174
176
|
const transactionStore = options.transactionStore ?? new UpdateTransactionStore(paths.dataDir);
|
|
175
177
|
let transaction = await transactionStore.read();
|
|
176
178
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { assertImmutableExecutionRoot, readCertifiedReleaseManifest } from "../release/index.js";
|
|
4
5
|
import { ControlStore } from "../storage/index.js";
|
|
5
6
|
import { resolveProductPaths } from "./paths.js";
|
|
6
7
|
import { SupervisorServer } from "./server.js";
|
|
@@ -10,11 +11,11 @@ export async function runSupervisor() {
|
|
|
10
11
|
mkdirSync(paths.runtimeDir, { recursive: true, mode: 0o700 });
|
|
11
12
|
const log = (message) => appendFileSync(paths.supervisorLogPath, `${new Date().toISOString()} ${message}\n`);
|
|
12
13
|
const releaseRoot = process.env[PRODUCT_IDENTITY.environment.releaseRoot];
|
|
13
|
-
|
|
14
|
+
const releaseId = process.env[PRODUCT_IDENTITY.environment.releaseId];
|
|
15
|
+
const contentDigest = process.env[PRODUCT_IDENTITY.environment.releaseDigest];
|
|
16
|
+
if (!releaseRoot || !releaseId || !contentDigest)
|
|
14
17
|
throw new Error(PRODUCT_TEXT.diagnostic("supervisor must be launched from a verified immutable release"));
|
|
15
|
-
const release = await
|
|
16
|
-
if (process.env[PRODUCT_IDENTITY.environment.releaseId] !== release.releaseId)
|
|
17
|
-
throw new Error(PRODUCT_TEXT.diagnostic("selected release identity does not match its verified manifest"));
|
|
18
|
+
const release = await readCertifiedReleaseManifest({ releaseRoot, releaseId, contentDigest }, resolve(paths.dataDir, "releases"));
|
|
18
19
|
await assertImmutableExecutionRoot(release, paths.dataDir);
|
|
19
20
|
const bootNonce = randomUUID();
|
|
20
21
|
const store = new ControlStore(paths.databasePath, bootNonce);
|