diff --git a/src/execution-world/execution-groups.ts b/src/execution-world/execution-groups.ts index 2b07126..ad4b8dd 100644 --- a/src/execution-world/execution-groups.ts +++ b/src/execution-world/execution-groups.ts @@ -6,7 +6,7 @@ import type { PersistentAttachment, } from "../capability-model/types.js"; import { type ExecutionInstallation, type ExecutionGroupIdentity } from "./installation.js"; -import { type ExecutionPlan, hexDigest, ExecutionPlanError } from "./plan.js"; +import { type ExecutionPlan, hexDigest, hexDigestAsync, ExecutionPlanError } from "./plan.js"; export interface ExecutionEnvironment { /** Explicit host placement. Merely including an optional module does not select it. */ @@ -23,12 +23,12 @@ export interface InstalledExecutionGroup extends ExecutionGroupIdentity { } /** Conservative outbound closure. Runtime graph values, reverse callers, source * provenance and the workspace epoch are deliberately absent from this identity. */ -export function compileExecutionGroups( +export async function compileExecutionGroups( plan: ExecutionPlan, installation: ExecutionInstallation, environments: Readonly>, -): InstalledExecutionGroup[] { - if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest) +): Promise { + if (installation.plan !== plan.digest || (await hexDigestAsync(plan.definition)) !== plan.digest) throw new ExecutionPlanError("GROUP_PLAN_MISMATCH", plan.digest, "Expected one checked installation"); const workspace = plan.definition.workspace; const packages = new Map(workspace.packageImports.map((p) => [p.revisionId as string, p])); @@ -82,178 +82,175 @@ export function compileExecutionGroups( }; const bindingContract = (value: DependencyBinding) => value.kind === "query" ? { ...value, packageRevisionId: logical(value.packageRevisionId) } : value; - return installation.packages - .filter((p) => !!implementations.get(p.revision)) - .map((root) => { - const consumed = new Map(), - visited = new Set(), - visitedQueries = new Set(), - consumedPackages = new Set(); - const remember = (key: string, value: unknown) => consumed.set(key, value); - const storage = (id: string) => { - const value = attachments.get(id); - if (!value) throw new ExecutionPlanError("GROUP_STORAGE_MISSING", id, "Uninstalled consumed storage"); - remember(`storage:${id}`, attachmentContract(value)); - }; - // Deletion may visit related objects and their owned state. Conservatively - // include the connected schema component, including possible future edges. - const lifecycle = (atom: string) => { - const visited = new Set(), - pending = [atom]; - const atoms = ( - constraint: Extract["endpoints"][number]["constraint"], - ) => - constraint.kind === "atom" - ? [constraint.atomId as string] - : workspace.conformances - .filter((c) => c.interfaceRevisionId === constraint.interfaceRevisionId) - .map((c) => c.atomId as string); - while (pending.length) { - const current = pending.pop()!; - if (visited.has(current)) continue; - visited.add(current); - for (const owner of workspace.conformances.filter((c) => c.atomId === current)) - for (const attachment of owner.privateAttachments) storage(attachment.id); - for (const attachment of attachments.values()) - if (attachment.kind === "edge" && attachment.endpoints.some((e) => atoms(e.constraint).includes(current))) { - storage(attachment.id); - pending.push(...attachment.endpoints.flatMap((e) => atoms(e.constraint))); - } - } - }; - const targets = (binding: DependencyBinding, atom: string): string[] => { - if (binding.kind === "interface" && binding.receiver === "input") - return workspace.conformances - .filter((c) => c.interfaceRevisionId === binding.interfaceRevisionId) - .map((c) => c.atomId); - if (!("via" in binding) || !binding.via) return [atom]; - storage(binding.via.edgeTypeId); - const edge = attachments.get(binding.via.edgeTypeId); - if (!edge || edge.kind !== "edge") - throw new ExecutionPlanError("GROUP_EDGE_MISSING", atom, "Missing traversal edge"); - const endpoint = edge.endpoints.find((e) => e.projectionId !== binding.via!.projectionId)!; - const constraint = endpoint.constraint; - return constraint.kind === "atom" - ? [constraint.atomId] + const groups: InstalledExecutionGroup[] = []; + for (const root of installation.packages.filter((p) => !!implementations.get(p.revision))) { + const consumed = new Map(), + visited = new Set(), + visitedQueries = new Set(), + consumedPackages = new Set(); + const remember = (key: string, value: unknown) => consumed.set(key, value); + const storage = (id: string) => { + const value = attachments.get(id); + if (!value) throw new ExecutionPlanError("GROUP_STORAGE_MISSING", id, "Uninstalled consumed storage"); + remember(`storage:${id}`, attachmentContract(value)); + }; + // Deletion may visit related objects and their owned state. Conservatively + // include the connected schema component, including possible future edges. + const lifecycle = (atom: string) => { + const visited = new Set(), + pending = [atom]; + const atoms = (constraint: Extract["endpoints"][number]["constraint"]) => + constraint.kind === "atom" + ? [constraint.atomId as string] : workspace.conformances .filter((c) => c.interfaceRevisionId === constraint.interfaceRevisionId) - .map((c) => c.atomId); - }; - const query = (revision: string, id: string) => { - const key = JSON.stringify([revision, id]); - if (visitedQueries.has(key)) return; - visitedQueries.add(key); - const value = plan.definition.queries.find((q) => q.id === key); - if (!value) throw new ExecutionPlanError("GROUP_QUERY_MISSING", key, "Missing consumed query"); - remember(`query:${JSON.stringify([logical(revision), id])}`, value.query.checked.definitionDigest); - for (const operation of value.operations) visit(operation); - }; - const dependencies = (entry: PackageExport, bindings: readonly BoundDependency[], atom: string) => { - for (const bound of bindings) { - const target = bound.binding, - port = entry.dependencyPorts.find((p) => p.id === bound.portId)!; - if ("via" in target && target.via) storage(target.via.edgeTypeId); - if (target.kind === "reflection") { - for (const selector of target.selectors) - remember("reflection:" + selector, portableReflectionValue(workspace, selector)); - } else if (target.kind === "state") storage(target.slotId); - else if (target.kind === "edge") storage(target.edgeTypeId); - else if (target.kind === "query") query(target.packageRevisionId, target.queryId); - else if (target.kind === "interface" && port.requirement.kind === "interface") { - const iface = workspace.interfaceImports.find((i) => i.revisionId === target.interfaceRevisionId)!; - for (const targetAtom of targets(target, atom)) - for (const op of port.requirement.operations ?? - iface.members.flatMap((m) => m.operations.map((o) => o.id))) - visit(JSON.stringify([targetAtom, target.interfaceRevisionId, op])); - } else if (target.kind === "creation") { - for (const seed of target.owners) { - const owner = workspace.conformances.find( - (c) => c.atomId === target.atomId && c.interfaceRevisionId === seed.interfaceRevisionId, - )!; - remember(`birth:${owner.id}`, { - id: owner.id, - atom: owner.atomId, - interface: owner.interfaceRevisionId, - initialization: owner.initialization ?? [], - }); - for (const attachment of owner.privateAttachments) storage(attachment.id); - } - } else - throw new ExecutionPlanError( - "GROUP_DEPENDENCY_UNSUPPORTED", - bound.portId, - "Cannot establish execution reuse for this dependency", - ); - } - }; - const visit = (key: string) => { - if (visited.has(key)) return; - visited.add(key); - const operation = operations.get(key); - if (!operation) throw new ExecutionPlanError("GROUP_OPERATION_MISSING", key, "Missing consumed operation"); - const binding = operation.binding; - if (binding.kind === "package") { - const pkg = packages.get(binding.packageRevisionId)!; - const implementation = implementations.get(pkg.revisionId); - if (!implementation) - throw new ExecutionPlanError( - "GROUP_IMPLEMENTATION_MISSING", - pkg.revisionId, - "Consumed package has no selected server implementation", - ); - consumedPackages.add(pkg.packageId); - remember(`implementation:${pkg.packageId}`, { - digest: implementation.contentDigest, - environment: environment(pkg.revisionId), - }); - const entry = pkg.exports.find((e) => e.id === binding.exportId)!; - const bindings = binding.dependencies.map((d) => ({ portId: d.portId, binding: bindingContract(d.binding) })); - const acceptance = (binding.acceptanceDependencies ?? []).map((d) => ({ - portId: d.portId, - binding: bindingContract(d.binding), - })); - const contract = { - input: operation.input, - output: operation.output, - execution: operation.execution, - scope: operation.scope, - binding: { - package: pkg.packageId, - export: binding.exportId, - dependencies: bindings, - acceptanceDependencies: acceptance, - }, - }; - // Deduplicate aliases of the same bound export, rather than keying its owner by reverse callers. - remember(`binding:${hexDigest(contract)}`, contract); - dependencies(entry, binding.dependencies, operation.atomId); - if (entry.acceptanceExportId) - dependencies( - pkg.exports.find((e) => e.id === entry.acceptanceExportId)!, - binding.acceptanceDependencies ?? [], - operation.atomId, - ); - } else { - remember(`operation:${key}`, operation); - if (binding.kind === "state") storage(binding.slotId); - if (binding.kind === "edge") storage(binding.edgeTypeId); - if (binding.kind === "lifecycle") lifecycle(operation.atomId); - } - }; - for (const operation of plan.definition.operations) - if (operation.binding.kind === "package" && operation.binding.packageRevisionId === root.revision) - visit(operation.key); - const own = implementations.get(root.revision)!; - const exports = packages.get(root.revision)!.exports.map(({ displayName: _name, dependencyPorts, ...entry }) => ({ - ...entry, - dependencyPorts: dependencyPorts.map(({ displayName: _name, ...port }) => port), - })); - const entries = [...consumed].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)); - return { - logicalId: logical(root.revision), - packageRevision: root.revision, - executionDigest: `sha256:${hexDigest({ implementation: own.contentDigest, exports, environment: environment(root.revision), versions: plan.definition.versions, consumed: entries })}`, - consumedPackages: [...consumedPackages].sort(), - }; + .map((c) => c.atomId as string); + while (pending.length) { + const current = pending.pop()!; + if (visited.has(current)) continue; + visited.add(current); + for (const owner of workspace.conformances.filter((c) => c.atomId === current)) + for (const attachment of owner.privateAttachments) storage(attachment.id); + for (const attachment of attachments.values()) + if (attachment.kind === "edge" && attachment.endpoints.some((e) => atoms(e.constraint).includes(current))) { + storage(attachment.id); + pending.push(...attachment.endpoints.flatMap((e) => atoms(e.constraint))); + } + } + }; + const targets = (binding: DependencyBinding, atom: string): string[] => { + if (binding.kind === "interface" && binding.receiver === "input") + return workspace.conformances + .filter((c) => c.interfaceRevisionId === binding.interfaceRevisionId) + .map((c) => c.atomId); + if (!("via" in binding) || !binding.via) return [atom]; + storage(binding.via.edgeTypeId); + const edge = attachments.get(binding.via.edgeTypeId); + if (!edge || edge.kind !== "edge") + throw new ExecutionPlanError("GROUP_EDGE_MISSING", atom, "Missing traversal edge"); + const endpoint = edge.endpoints.find((e) => e.projectionId !== binding.via!.projectionId)!; + const constraint = endpoint.constraint; + return constraint.kind === "atom" + ? [constraint.atomId] + : workspace.conformances + .filter((c) => c.interfaceRevisionId === constraint.interfaceRevisionId) + .map((c) => c.atomId); + }; + const query = (revision: string, id: string) => { + const key = JSON.stringify([revision, id]); + if (visitedQueries.has(key)) return; + visitedQueries.add(key); + const value = plan.definition.queries.find((q) => q.id === key); + if (!value) throw new ExecutionPlanError("GROUP_QUERY_MISSING", key, "Missing consumed query"); + remember(`query:${JSON.stringify([logical(revision), id])}`, value.query.checked.definitionDigest); + for (const operation of value.operations) visit(operation); + }; + const dependencies = (entry: PackageExport, bindings: readonly BoundDependency[], atom: string) => { + for (const bound of bindings) { + const target = bound.binding, + port = entry.dependencyPorts.find((p) => p.id === bound.portId)!; + if ("via" in target && target.via) storage(target.via.edgeTypeId); + if (target.kind === "reflection") { + for (const selector of target.selectors) + remember("reflection:" + selector, portableReflectionValue(workspace, selector)); + } else if (target.kind === "state") storage(target.slotId); + else if (target.kind === "edge") storage(target.edgeTypeId); + else if (target.kind === "query") query(target.packageRevisionId, target.queryId); + else if (target.kind === "interface" && port.requirement.kind === "interface") { + const iface = workspace.interfaceImports.find((i) => i.revisionId === target.interfaceRevisionId)!; + for (const targetAtom of targets(target, atom)) + for (const op of port.requirement.operations ?? iface.members.flatMap((m) => m.operations.map((o) => o.id))) + visit(JSON.stringify([targetAtom, target.interfaceRevisionId, op])); + } else if (target.kind === "creation") { + for (const seed of target.owners) { + const owner = workspace.conformances.find( + (c) => c.atomId === target.atomId && c.interfaceRevisionId === seed.interfaceRevisionId, + )!; + remember(`birth:${owner.id}`, { + id: owner.id, + atom: owner.atomId, + interface: owner.interfaceRevisionId, + initialization: owner.initialization ?? [], + }); + for (const attachment of owner.privateAttachments) storage(attachment.id); + } + } else + throw new ExecutionPlanError( + "GROUP_DEPENDENCY_UNSUPPORTED", + bound.portId, + "Cannot establish execution reuse for this dependency", + ); + } + }; + const visit = (key: string) => { + if (visited.has(key)) return; + visited.add(key); + const operation = operations.get(key); + if (!operation) throw new ExecutionPlanError("GROUP_OPERATION_MISSING", key, "Missing consumed operation"); + const binding = operation.binding; + if (binding.kind === "package") { + const pkg = packages.get(binding.packageRevisionId)!; + const implementation = implementations.get(pkg.revisionId); + if (!implementation) + throw new ExecutionPlanError( + "GROUP_IMPLEMENTATION_MISSING", + pkg.revisionId, + "Consumed package has no selected server implementation", + ); + consumedPackages.add(pkg.packageId); + remember(`implementation:${pkg.packageId}`, { + digest: implementation.contentDigest, + environment: environment(pkg.revisionId), + }); + const entry = pkg.exports.find((e) => e.id === binding.exportId)!; + const bindings = binding.dependencies.map((d) => ({ portId: d.portId, binding: bindingContract(d.binding) })); + const acceptance = (binding.acceptanceDependencies ?? []).map((d) => ({ + portId: d.portId, + binding: bindingContract(d.binding), + })); + const contract = { + input: operation.input, + output: operation.output, + execution: operation.execution, + scope: operation.scope, + binding: { + package: pkg.packageId, + export: binding.exportId, + dependencies: bindings, + acceptanceDependencies: acceptance, + }, + }; + // Deduplicate aliases of the same bound export, rather than keying its owner by reverse callers. + remember(`binding:${hexDigest(contract)}`, contract); + dependencies(entry, binding.dependencies, operation.atomId); + if (entry.acceptanceExportId) + dependencies( + pkg.exports.find((e) => e.id === entry.acceptanceExportId)!, + binding.acceptanceDependencies ?? [], + operation.atomId, + ); + } else { + remember(`operation:${key}`, operation); + if (binding.kind === "state") storage(binding.slotId); + if (binding.kind === "edge") storage(binding.edgeTypeId); + if (binding.kind === "lifecycle") lifecycle(operation.atomId); + } + }; + for (const operation of plan.definition.operations) + if (operation.binding.kind === "package" && operation.binding.packageRevisionId === root.revision) + visit(operation.key); + const own = implementations.get(root.revision)!; + const exports = packages.get(root.revision)!.exports.map(({ displayName: _name, dependencyPorts, ...entry }) => ({ + ...entry, + dependencyPorts: dependencyPorts.map(({ displayName: _name, ...port }) => port), + })); + const entries = [...consumed].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)); + groups.push({ + logicalId: logical(root.revision), + packageRevision: root.revision, + executionDigest: `sha256:${await hexDigestAsync({ implementation: own.contentDigest, exports, environment: environment(root.revision), versions: plan.definition.versions, consumed: entries })}`, + consumedPackages: [...consumedPackages].sort(), }); + } + return groups; } diff --git a/src/execution-world/installation.ts b/src/execution-world/installation.ts index 0cb526a..b5eb379 100644 --- a/src/execution-world/installation.ts +++ b/src/execution-world/installation.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { loadCheckedArtifactWorld } from "./receipts.js"; import { contentArtifactUrl, loadArtifactWorld, type LoadedArtifactWorld } from "./files.js"; import { selectArtifactImplementation, type ArtifactImplementation, type ComponentArtifact } from "./artifacts.js"; -import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js"; +import { ExecutionPlanError, hexDigestAsync, type ExecutionPlan } from "./plan.js"; import { publicOperationContract, publicQueryContract, @@ -41,13 +41,13 @@ export interface ClientContractCatalog { }[]; } /** Explicit projection prevents accidental spreading of filesystem roots or private bindings. */ -export function clientContractCatalog( +export async function clientContractCatalog( plan: ExecutionPlan, installation: ExecutionInstallation, requestedPackages: readonly string[], checkedKernel?: InspectionKernel, -): ClientContractCatalog { - if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest) +): Promise { + if (installation.plan !== plan.digest || (await hexDigestAsync(plan.definition)) !== plan.digest) throw new ExecutionPlanError( "CATALOG_PLAN_MISMATCH", plan.digest, @@ -112,6 +112,7 @@ export function createClientContractCatalog( const revisions = plan.definition.workspace.packageImports.map((pkg) => pkg.revisionId); const installed = new Set(revisions); let catalog: ClientContractCatalog | undefined; + let preparing: Promise | undefined; let release: (() => void) | undefined; let closed = false; const freeze = (value: unknown, seen = new WeakSet()) => { @@ -121,7 +122,7 @@ export function createClientContractCatalog( Object.freeze(value); }; return { - select(requestedPackages: readonly string[]): ClientContractCatalog { + async select(requestedPackages: readonly string[]): Promise { if (closed) throw Error("CANDIDATE_METADATA_CLOSED"); for (const revision of requestedPackages) if (!installed.has(revision)) @@ -129,22 +130,29 @@ export function createClientContractCatalog( 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; - } + preparing ??= (async () => { + const reservation = metadata.reserve(32 * 1024 * 1024); + try { + const compiled = await clientContractCatalog(plan, installation, revisions, checkedKernel); + if (closed) throw Error("CANDIDATE_METADATA_CLOSED"); + 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; + } + })().finally(() => { + preparing = undefined; + }); + await preparing; } + if (closed || !catalog) throw Error("CANDIDATE_METADATA_CLOSED"); const requested = new Set(requestedPackages); return { inspections: catalog.inspections, diff --git a/src/execution-world/public-reflection.ts b/src/execution-world/public-reflection.ts index c40ef0b..68f08a1 100644 --- a/src/execution-world/public-reflection.ts +++ b/src/execution-world/public-reflection.ts @@ -1,22 +1,17 @@ import { compileInspectionTemplates, type InspectionKernel } from "./inspection.js"; -import { hexDigest, type ExecutionPlan } from "./plan.js"; -import { clientContractCatalog, type ExecutionInstallation } from "./installation.js"; +import { hexDigestAsync, type ExecutionPlan } from "./plan.js"; +import type { ClientContractCatalog } from "./installation.js"; import { publicOperationContract, publicQueryContract } from "./client-contracts.js"; /** Explicit public projection of checked inputs. Never serialize conformance - * storage or package ports, or recompile a historical Candidate at runtime. */ -export function publicDefinitionSnapshot( + * storage or package ports, or recompile a historical Candidate at runtime. + * The caller supplies that Candidate's checked, cached public catalog. */ +export async function publicDefinitionSnapshot( plan: ExecutionPlan, - installation: ExecutionInstallation, kernel: InspectionKernel, + catalog: ClientContractCatalog, ) { const workspace = plan.definition.workspace; - const catalog = clientContractCatalog( - plan, - installation, - installation.packages.map((pkg) => pkg.revision), - kernel, - ); const rows = { atoms: workspace.atoms.map((atom) => ({ id: atom.id, name: atom.displayName, documentation: atom.documentation })), interfaces: workspace.interfaceImports.map((iface) => ({ @@ -95,5 +90,5 @@ export function publicDefinitionSnapshot( { id: string; [key: string]: unknown }[] >; for (const entries of Object.values(publicRows)) entries.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); - return { codec: "camino-definitions-v1" as const, id: hexDigest(publicRows), rows: publicRows }; + return { codec: "camino-definitions-v1" as const, id: await hexDigestAsync(publicRows), rows: publicRows }; } diff --git a/test/execution-groups.test.ts b/test/execution-groups.test.ts index 22cc215..938dceb 100644 --- a/test/execution-groups.test.ts +++ b/test/execution-groups.test.ts @@ -45,7 +45,7 @@ const prepare = async (workspace: WorkspaceRevision) => { test("execution reuse ignores world/source/display changes and unrelated packages", async () => { const workspace = await loadReaderWorkspace(), original = await prepare(workspace); - const before = original.groups()[0]!.executionDigest; + const before = (await original.groups())[0]!.executionDigest; workspace.sourceRootCommit = "f".repeat(40); workspace.packageImports[0]!.displayName = "Renamed"; workspace.packageImports[0]!.source.commit = "e".repeat(40); @@ -59,13 +59,13 @@ test("execution reuse ignores world/source/display changes and unrelated package const after = await prepare(workspace); assert.notEqual(after.plan.digest, original.plan.digest); assert.equal( - after.groups().find((g) => g.logicalId === workspace.packageImports[0]!.packageId)!.executionDigest, + (await after.groups()).find((g) => g.logicalId === workspace.packageImports[0]!.packageId)!.executionDigest, before, ); original.environments[workspace.packageImports[0]!.revisionId]!.credentialGeneration = "config:2"; - assert.notEqual(original.groups()[0]!.executionDigest, before); + assert.notEqual((await original.groups())[0]!.executionDigest, before); original.environments[workspace.packageImports[0]!.revisionId]!.placement = "module"; - assert.notEqual(original.groups()[0]!.executionDigest, before); + assert.notEqual((await original.groups())[0]!.executionDigest, before); }); test("outbound provider changes invalidate consumers while caller changes preserve providers", async () => { @@ -108,15 +108,15 @@ test("outbound provider changes invalidate consumers while caller changes preser dependencies: [{ portId, binding: { kind: "state", slotId: id.slot("slot:document:content") } }], }; const prepared = await prepare(workspace); - const values = () => new Map(prepared.groups().map((g) => [g.logicalId, g.executionDigest])); - const before = values(); + const values = async () => new Map((await prepared.groups()).map((g) => [g.logicalId, g.executionDigest])); + const before = await values(); prepared.installation.packages.find((p) => p.revision === "package:reader-runtime@1")!.service!.contentDigest = "c".repeat(64); - const callerChanged = values(); + const callerChanged = await values(); assert.equal(callerChanged.get("provider"), before.get("provider")); assert.notEqual(callerChanged.get("package:reader-runtime"), before.get("package:reader-runtime")); prepared.installation.packages.find((p) => p.revision === revision)!.service!.contentDigest = "d".repeat(64); - const providerChanged = values(); + const providerChanged = await values(); assert.notEqual(providerChanged.get("provider"), callerChanged.get("provider")); assert.notEqual(providerChanged.get("package:reader-runtime"), callerChanged.get("package:reader-runtime")); }); diff --git a/test/execution-world.test.ts b/test/execution-world.test.ts index 9000ab4..471a62c 100644 --- a/test/execution-world.test.ts +++ b/test/execution-world.test.ts @@ -7,6 +7,7 @@ import { checkExecutionWorld, checkedWorldDigest, implementationDigest, + clientContractCatalog, hexDigest, parsePackageArtifacts, artifactPath, @@ -386,7 +387,13 @@ test("public reflection contains nominal contracts and callable descriptors with routes: {}, }; const kernel = compileKernelProgram(plan) as import("../src/execution-world/inspection.js").InspectionKernel; - const snapshot = publicDefinitionSnapshot(plan, installation, kernel); + const catalog = await clientContractCatalog( + plan, + installation, + installation.packages.map((pkg) => pkg.revision), + kernel, + ); + const snapshot = await publicDefinitionSnapshot(plan, kernel, catalog); assert.ok(snapshot.rows.interfaces.length); assert.ok(snapshot.rows.queries.length); assert.ok(snapshot.rows.conformances.length); @@ -401,13 +408,19 @@ test("public reflection contains nominal contracts and callable descriptors with assert.ok(!publicText.includes('"' + privateKey + '"'), privateKey); assert.ok(publicText.includes('"descriptor"')); assert.ok(publicText.includes('"scope":"class"')); - assert.equal(snapshot.id, publicDefinitionSnapshot(plan, installation, kernel).id); + assert.equal(snapshot.id, (await publicDefinitionSnapshot(plan, kernel, catalog)).id); // Historical checked availability must survive a newer host compiler. If // reflection silently regenerates the Kernel, this evidence disappears. const historical = structuredClone(kernel); const installed = historical.operations.find((operation) => !operation.unsupported)!; assert.ok(installed); installed.unsupported = "CHECKED_HISTORICAL_UNSUPPORTED"; - const historicalSnapshot = publicDefinitionSnapshot(plan, installation, historical); + const historicalCatalog = await clientContractCatalog( + plan, + installation, + installation.packages.map((pkg) => pkg.revision), + historical, + ); + const historicalSnapshot = await publicDefinitionSnapshot(plan, historical, historicalCatalog); assert.match(JSON.stringify(historicalSnapshot.rows.conformances), /CHECKED_HISTORICAL_UNSUPPORTED/); }); diff --git a/test/presentations.test.ts b/test/presentations.test.ts index b9d16c5..29a7d34 100644 --- a/test/presentations.test.ts +++ b/test/presentations.test.ts @@ -249,13 +249,13 @@ test("checked component-only artifacts install without a service and reject miss assert.ok(installation.routes[installation.components[0]!.url]); assert.equal(JSON.stringify(installation.components).includes(root), false); assert.equal(JSON.stringify(installation.components).includes("slot:"), false); - const catalog = clientContractCatalog(plan, installation, [pkg.revision]); + const catalog = await clientContractCatalog(plan, installation, [pkg.revision]); assert.equal(catalog.presentations[0]!.component, "component:card"); assert.equal(catalog.components[0]!.subject, "interface:Data@1"); assert.equal(JSON.stringify(catalog).includes(root), false); assert.equal(JSON.stringify(catalog).includes("bindingSchema"), false); - assert.throws(() => clientContractCatalog(plan, installation, ["absent"]), /UNINSTALLED_PACKAGE/); - assert.throws( + await assert.rejects(() => clientContractCatalog(plan, installation, ["absent"]), /UNINSTALLED_PACKAGE/); + await assert.rejects( () => clientContractCatalog(plan, { ...installation, plan: "bad" }, [pkg.revision]), /CATALOG_PLAN_MISMATCH/, ); @@ -268,14 +268,16 @@ test("checked component-only artifacts install without a service and reject miss compileKernelProgram(plan) as InspectionKernel, ); try { - assert.throws(() => prepared.select(["absent"]), /UNINSTALLED_PACKAGE/); + await assert.rejects(() => prepared.select(["absent"]), /UNINSTALLED_PACKAGE/); assert.equal(scope.bytes, 0, "Unknown requests do not allocate compiler metadata"); - assert.deepEqual(prepared.select([pkg.revision]), catalog); + const [first, coalesced] = await Promise.all([prepared.select([pkg.revision]), prepared.select([pkg.revision])]); + assert.deepEqual(first, catalog); + assert.equal(first.operations, coalesced.operations, "Concurrent preparation shares one metadata reservation"); const retained = scope.bytes; assert.ok(retained > 0); assert.ok(retained < 32 * 1024 * 1024, "Small catalogs retain a proportional metadata charge"); - const full = prepared.select([pkg.revision]); - const empty = prepared.select([]); + const full = await prepared.select([pkg.revision]); + const empty = await prepared.select([]); assert.deepEqual(empty.components, []); assert.deepEqual(empty.presentations, []); assert.deepEqual(empty.moduleBindings, []); @@ -290,14 +292,14 @@ test("checked component-only artifacts install without a service and reject miss }, }); try { - assert.deepEqual(prepared.select([pkg.revision]), full); + assert.deepEqual(await prepared.select([pkg.revision]), full); assert.equal(scope.bytes, retained, "Package projections do not accumulate cache entries"); } finally { Object.defineProperty(plan, "definition", { configurable: true, value: definition }); } prepared.close(); assert.equal(scope.bytes, 0); - assert.throws(() => prepared.select([pkg.revision]), /CANDIDATE_METADATA_CLOSED/); + await assert.rejects(() => prepared.select([pkg.revision]), /CANDIDATE_METADATA_CLOSED/); } finally { prepared.close(); scope.release(); @@ -305,7 +307,7 @@ test("checked component-only artifacts install without a service and reject miss const tight = new CandidateMetadataBudget({ bytes: 1000, ownerBytes: 1000, candidates: 1 }); const tightScope = tight.acquire("catalog-test"); const refused = createClientContractCatalog(plan, installation, tightScope); - assert.throws(() => refused.select([pkg.revision]), /CANDIDATE_METADATA_LIMIT/); + await assert.rejects(() => refused.select([pkg.revision]), /CANDIDATE_METADATA_LIMIT/); assert.equal(tightScope.bytes, 0); refused.close(); tightScope.release();