Reuse checked bootstrap catalogs and coordinate execution deadlines
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { compileInspectionTemplates, type InspectionTemplate } from "./inspection.js";
|
||||
import { compileInspectionTemplates, type InspectionTemplate, type InspectionKernel } from "./inspection.js";
|
||||
import path from "node:path";
|
||||
import { loadCheckedArtifactWorld } from "./receipts.js";
|
||||
import { contentArtifactUrl, loadArtifactWorld, type LoadedArtifactWorld } from "./files.js";
|
||||
@@ -45,6 +45,7 @@ export function clientContractCatalog(
|
||||
plan: ExecutionPlan,
|
||||
installation: ExecutionInstallation,
|
||||
requestedPackages: readonly string[],
|
||||
checkedKernel?: InspectionKernel,
|
||||
): ClientContractCatalog {
|
||||
if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest)
|
||||
throw new ExecutionPlanError(
|
||||
@@ -69,7 +70,7 @@ export function clientContractCatalog(
|
||||
.map((operation) => publicOperationContract(iface, operation)),
|
||||
),
|
||||
);
|
||||
const inspection = compileInspectionTemplates(plan);
|
||||
const inspection = compileInspectionTemplates(plan, checkedKernel);
|
||||
return {
|
||||
inspections: inspection.templates,
|
||||
resultInterfaces: Object.fromEntries(
|
||||
@@ -98,6 +99,75 @@ export function clientContractCatalog(
|
||||
};
|
||||
}
|
||||
|
||||
/** Candidate-owned compiler output, never graph state or authorization. Compile
|
||||
* once, then project each requested package set without hashing/recompiling the
|
||||
* plan. Reserve bounded metadata before compilation; disposal releases both the
|
||||
* catalog and its charge. Callers must supply the candidate's checked inputs. */
|
||||
export function createClientContractCatalog(
|
||||
plan: ExecutionPlan,
|
||||
installation: ExecutionInstallation,
|
||||
metadata: CandidateMetadataScope,
|
||||
checkedKernel?: InspectionKernel,
|
||||
) {
|
||||
const revisions = plan.definition.workspace.packageImports.map((pkg) => pkg.revisionId);
|
||||
const installed = new Set<string>(revisions);
|
||||
let catalog: ClientContractCatalog | undefined;
|
||||
let release: (() => void) | undefined;
|
||||
let closed = false;
|
||||
const freeze = (value: unknown, seen = new WeakSet<object>()) => {
|
||||
if (!value || typeof value !== "object" || seen.has(value)) return;
|
||||
seen.add(value);
|
||||
for (const child of Object.values(value)) freeze(child, seen);
|
||||
Object.freeze(value);
|
||||
};
|
||||
return {
|
||||
select(requestedPackages: readonly string[]): ClientContractCatalog {
|
||||
if (closed) throw Error("CANDIDATE_METADATA_CLOSED");
|
||||
for (const revision of requestedPackages)
|
||||
if (!installed.has(revision))
|
||||
throw new ExecutionPlanError("UNINSTALLED_PACKAGE", revision, "Requested catalog package is not installed");
|
||||
if (!catalog) {
|
||||
// Like other candidate admission charges, this is an allocation estimate,
|
||||
// not a V8 heap limit. The process/cgroup ceiling bounds transient work.
|
||||
const reservation = metadata.reserve(32 * 1024 * 1024);
|
||||
try {
|
||||
const compiled = clientContractCatalog(plan, installation, revisions, checkedKernel);
|
||||
const bytes = Buffer.byteLength(JSON.stringify(compiled));
|
||||
if (bytes > 8 * 1024 * 1024) throw Error("CANDIDATE_CATALOG_LIMIT");
|
||||
freeze(compiled);
|
||||
// No await between reservations: replacing the transient upper bound
|
||||
// with a smaller retained estimate cannot race another admission.
|
||||
reservation();
|
||||
release = metadata.reserve(Math.max(64 * 1024, bytes * 4));
|
||||
catalog = compiled;
|
||||
} catch (error) {
|
||||
reservation();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const requested = new Set(requestedPackages);
|
||||
return {
|
||||
inspections: catalog.inspections,
|
||||
resultInterfaces: catalog.resultInterfaces,
|
||||
operations: catalog.operations,
|
||||
queries: [
|
||||
...catalog.queries.filter((query) => !installed.has(query.package)),
|
||||
...requestedPackages.flatMap((revision) => catalog!.queries.filter((query) => query.package === revision)),
|
||||
],
|
||||
components: catalog.components.filter((component) => requested.has(component.packageRevision)),
|
||||
moduleBindings: catalog.moduleBindings.filter((binding) => requested.has(binding.packageRevision)),
|
||||
presentations: catalog.presentations.filter((presentation) => requested.has(presentation.packageRevision)),
|
||||
};
|
||||
},
|
||||
close() {
|
||||
closed = true;
|
||||
catalog = undefined;
|
||||
release?.();
|
||||
release = undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface PackageInstallationSelection {
|
||||
readonly browser?: { readonly id?: string };
|
||||
readonly service?: { readonly id?: string };
|
||||
|
||||
Reference in New Issue
Block a user