Keep checked browser runtime versions independent of authority upgrades

This commit is contained in:
Timothy J. Aveni
2026-10-07 17:03:01 -07:00
parent 391f02f78a
commit 61534d3fb5
3 changed files with 24 additions and 7 deletions
+18 -4
View File
@@ -10,10 +10,16 @@ import {
type CandidateMetadataScope,
} from "./metadata.js";
const rootPattern = /^\/nix\/store\/[a-z0-9]{32}-[^/\s]+$/;
async function readJson(root: string, file: string, scope: CandidateMetadataScope): Promise<any> {
async function readJson(root: string, file: string, scope: CandidateMetadataScope) {
const input = await readCandidateMetadata(join(root, file), scope, 32 * 1024 * 1024);
try {
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.bytes));
return {
value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.bytes)),
releaseTree: input.releaseTree,
};
} catch (error) {
input.releaseTree();
throw error;
} finally {
input.releaseInput();
}
@@ -30,6 +36,7 @@ export async function loadCheckedInstallationCandidate(
if (!rootPattern.test(root) || !(await lstat(root)).isDirectory())
throw Error("Candidate must be an immutable Nix output directory");
const scope = (resources.budget ?? candidateMetadataBudget).acquire(resources.owner ?? root);
let releaseValidation = () => {};
try {
// Sequential reads avoid partially rejected Promise.all() work allocating
// after the failed candidate's reservation has been released.
@@ -43,8 +50,13 @@ export async function loadCheckedInstallationCandidate(
"kernel.json",
"queries.json",
"graph-execution.json",
])
values.push(await readJson(root, file, scope));
]) {
const parsed = await readJson(root, file, scope);
values.push(parsed.value);
// candidate.json duplicates the checked plan for validation. Nothing in
// the installed candidate retains this input tree after validation.
if (file === "candidate.json") releaseValidation = parsed.releaseTree;
}
const [candidate, plan, artifacts, source, entryRequest, kernel, queries, graph] = values;
const { executionContracts: _legacyContracts, ...workspace } = candidate;
if (
@@ -149,6 +161,8 @@ export async function loadCheckedInstallationCandidate(
} catch (error) {
scope.release();
throw error;
} finally {
releaseValidation();
}
}
+3 -3
View File
@@ -104,7 +104,7 @@ export async function readCandidateMetadata(
path: string,
scope: CandidateMetadataScope,
limit: number,
): Promise<{ bytes: Uint8Array; releaseInput: () => void }> {
): Promise<{ bytes: Uint8Array; releaseInput: () => void; releaseTree: () => void }> {
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
let releaseInput: (() => void) | undefined;
try {
@@ -123,8 +123,8 @@ export async function readCandidateMetadata(
if (offset !== stat.size) throw Error("CANDIDATE_METADATA_CHANGED");
const content = bytes.subarray(0, offset);
// No JSON.parse/decoded tree exists until its shape-based charge succeeds.
scope.charge(jsonAllocation(content));
return { bytes: content, releaseInput };
const releaseTree = scope.reserve(jsonAllocation(content));
return { bytes: content, releaseInput, releaseTree };
} catch (error) {
releaseInput?.();
throw error;