2026-09-22 17:22:48 -07:00
|
|
|
import { compileInspectionTemplates, type InspectionTemplate } from "./inspection.js";
|
2026-09-21 01:09:31 -07:00
|
|
|
import path from "node:path";
|
|
|
|
|
import { loadCheckedArtifactWorld } from "./receipts.js";
|
2026-09-21 21:49:52 -07:00
|
|
|
import { contentArtifactUrl, loadArtifactWorld, type LoadedArtifactWorld } from "./files.js";
|
2026-09-21 11:23:26 -07:00
|
|
|
import { selectArtifactImplementation, type ArtifactImplementation, type ComponentArtifact } from "./artifacts.js";
|
|
|
|
|
import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
|
|
|
|
|
import {
|
|
|
|
|
publicOperationContract,
|
|
|
|
|
publicQueryContract,
|
|
|
|
|
type PublicOperationContract,
|
|
|
|
|
type PublicQueryContract,
|
|
|
|
|
} from "./client-contracts.js";
|
2026-09-21 14:03:14 -07:00
|
|
|
import { resultInterfaces } from "./projections.js";
|
2026-10-07 14:44:59 -07:00
|
|
|
import type { CandidateMetadataScope } from "./metadata.js";
|
2026-09-21 11:23:26 -07:00
|
|
|
|
|
|
|
|
/** Activation is assigned by graph authority, never by a package artifact. */
|
|
|
|
|
export interface ActiveExecutionIdentity {
|
|
|
|
|
readonly workspace: string;
|
|
|
|
|
readonly world: string;
|
|
|
|
|
readonly plan: string;
|
|
|
|
|
readonly epoch: string;
|
|
|
|
|
}
|
|
|
|
|
/** Host-owned execution selection. The lifecycle manager fills its checked inputs. */
|
|
|
|
|
export interface ExecutionGroupIdentity {
|
|
|
|
|
readonly logicalId: string;
|
|
|
|
|
readonly executionDigest: string;
|
|
|
|
|
}
|
|
|
|
|
export interface ClientContractCatalog {
|
2026-09-22 17:22:48 -07:00
|
|
|
readonly inspections: readonly InspectionTemplate[];
|
2026-09-21 14:03:14 -07:00
|
|
|
readonly resultInterfaces: Readonly<Record<string, readonly string[]>>;
|
2026-09-21 11:23:26 -07:00
|
|
|
readonly operations: readonly PublicOperationContract[];
|
|
|
|
|
readonly queries: readonly PublicQueryContract[];
|
|
|
|
|
readonly components: readonly InstalledComponent[];
|
|
|
|
|
readonly presentations: readonly { id: string; subject: string; packageRevision: string; component: string }[];
|
2026-09-22 16:19:05 -07:00
|
|
|
readonly moduleBindings: readonly {
|
|
|
|
|
atom: string;
|
|
|
|
|
interface: string;
|
|
|
|
|
member: string;
|
|
|
|
|
packageRevision: string;
|
|
|
|
|
id: string;
|
|
|
|
|
}[];
|
2026-09-21 11:23:26 -07:00
|
|
|
}
|
|
|
|
|
/** Explicit projection prevents accidental spreading of filesystem roots or private bindings. */
|
|
|
|
|
export function clientContractCatalog(
|
|
|
|
|
plan: ExecutionPlan,
|
|
|
|
|
installation: ExecutionInstallation,
|
|
|
|
|
requestedPackages: readonly string[],
|
|
|
|
|
): ClientContractCatalog {
|
|
|
|
|
if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest)
|
|
|
|
|
throw new ExecutionPlanError(
|
|
|
|
|
"CATALOG_PLAN_MISMATCH",
|
|
|
|
|
plan.digest,
|
|
|
|
|
"Installation and public catalog must use the same checked plan",
|
|
|
|
|
);
|
|
|
|
|
const packages = requestedPackages.map((revision) => {
|
|
|
|
|
const pkg = plan.definition.workspace.packageImports.find((p) => p.revisionId === revision);
|
|
|
|
|
if (!pkg)
|
|
|
|
|
throw new ExecutionPlanError("UNINSTALLED_PACKAGE", revision, "Requested catalog package is not installed");
|
|
|
|
|
return pkg;
|
|
|
|
|
});
|
|
|
|
|
// Interface metadata contains no authority or private implementation configuration.
|
|
|
|
|
// A later admission optimization can narrow this set without changing operation identities.
|
|
|
|
|
const operations = plan.definition.workspace.interfaceImports
|
|
|
|
|
.filter((i) => !i.template)
|
|
|
|
|
.flatMap((iface) =>
|
|
|
|
|
iface.members.flatMap((member) =>
|
|
|
|
|
member.operations
|
|
|
|
|
.filter((o) => o.mode === "call")
|
|
|
|
|
.map((operation) => publicOperationContract(iface, operation)),
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-09-22 17:22:48 -07:00
|
|
|
const inspection = compileInspectionTemplates(plan);
|
2026-09-21 11:23:26 -07:00
|
|
|
return {
|
2026-09-22 17:22:48 -07:00
|
|
|
inspections: inspection.templates,
|
2026-09-21 14:03:14 -07:00
|
|
|
resultInterfaces: Object.fromEntries(
|
|
|
|
|
plan.definition.operations.map((operation) => [operation.key, resultInterfaces(plan, operation.key)]),
|
|
|
|
|
),
|
2026-09-21 11:23:26 -07:00
|
|
|
operations,
|
2026-09-22 17:22:48 -07:00
|
|
|
queries: [
|
|
|
|
|
...inspection.contracts,
|
|
|
|
|
...packages.flatMap((pkg) => (pkg.checkedQueries ?? []).map((q) => publicQueryContract(pkg, q.declaration.id))),
|
|
|
|
|
],
|
2026-09-21 11:23:26 -07:00
|
|
|
components: installation.components.filter((c) => requestedPackages.includes(c.packageRevision)),
|
2026-09-22 16:19:05 -07:00
|
|
|
moduleBindings: plan.definition.workspace.conformances.flatMap((c) =>
|
|
|
|
|
(c.moduleBindings ?? [])
|
|
|
|
|
.filter((b) => requestedPackages.includes(b.packageRevisionId))
|
|
|
|
|
.map((b) => ({
|
|
|
|
|
atom: c.atomId,
|
|
|
|
|
interface: c.interfaceRevisionId,
|
|
|
|
|
member: b.memberId,
|
|
|
|
|
packageRevision: b.packageRevisionId,
|
|
|
|
|
id: b.moduleId,
|
|
|
|
|
})),
|
|
|
|
|
),
|
2026-09-21 11:23:26 -07:00
|
|
|
presentations: (plan.definition.presentations ?? [])
|
|
|
|
|
.filter((p) => requestedPackages.includes(p.packageRevisionId))
|
|
|
|
|
.map((p) => ({ id: p.id, subject: p.subject, packageRevision: p.packageRevisionId, component: p.componentId })),
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-09-21 01:09:31 -07:00
|
|
|
|
|
|
|
|
export interface PackageInstallationSelection {
|
|
|
|
|
readonly browser?: { readonly id?: string };
|
|
|
|
|
readonly service?: { readonly id?: string };
|
|
|
|
|
/** Including a module is optional; executing it still requires host trust policy. */
|
|
|
|
|
readonly module?: { readonly id?: string };
|
|
|
|
|
}
|
|
|
|
|
export interface ExecutionInstallation {
|
|
|
|
|
readonly codec: "camino-execution-installation-v1";
|
|
|
|
|
readonly plan: string;
|
|
|
|
|
readonly world: string;
|
|
|
|
|
readonly packages: readonly {
|
|
|
|
|
readonly revision: string;
|
|
|
|
|
readonly root: string;
|
2026-09-21 11:23:26 -07:00
|
|
|
readonly service?: ArtifactImplementation;
|
2026-09-21 01:09:31 -07:00
|
|
|
readonly module?: ArtifactImplementation;
|
2026-10-07 21:14:55 -07:00
|
|
|
/** Checked candidate only; the qualified host verifies its import surface and SDK. */
|
|
|
|
|
readonly restricted?: ArtifactImplementation;
|
2026-09-22 18:11:54 -07:00
|
|
|
readonly browserImplementation?: ArtifactImplementation;
|
2026-09-21 01:09:31 -07:00
|
|
|
readonly browserURL?: string;
|
|
|
|
|
readonly browserRegistry?: string;
|
|
|
|
|
}[];
|
|
|
|
|
readonly routes: Readonly<Record<string, { readonly path: string; readonly mediaType: string }>>;
|
|
|
|
|
readonly coreURL?: string;
|
|
|
|
|
readonly engineURL?: string;
|
2026-09-21 11:23:26 -07:00
|
|
|
readonly components: readonly InstalledComponent[];
|
|
|
|
|
}
|
|
|
|
|
/** Safe to expose in a presentation catalog: no source paths, state slots, or host configuration. */
|
|
|
|
|
export interface InstalledComponent {
|
2026-09-22 16:19:05 -07:00
|
|
|
readonly role: {
|
|
|
|
|
readonly interface: string;
|
|
|
|
|
readonly member: string;
|
|
|
|
|
readonly subject: string;
|
|
|
|
|
readonly contract: string;
|
2026-09-25 11:30:37 -07:00
|
|
|
readonly typeArguments?: readonly unknown[];
|
|
|
|
|
readonly requiredInterfaces?: readonly string[];
|
2026-09-22 16:19:05 -07:00
|
|
|
};
|
2026-09-21 11:23:26 -07:00
|
|
|
readonly packageRevision: string;
|
|
|
|
|
readonly id: string;
|
|
|
|
|
readonly subject: string;
|
|
|
|
|
readonly url: string;
|
|
|
|
|
readonly exportName: string;
|
2026-09-21 15:24:05 -07:00
|
|
|
readonly styles: readonly string[];
|
2026-09-21 11:23:26 -07:00
|
|
|
readonly compatibility: ComponentArtifact["compatibility"];
|
2026-09-27 16:34:53 -07:00
|
|
|
/** Owned and interpreted by the React artifact consumer. */
|
|
|
|
|
readonly preparation?: unknown;
|
2026-09-21 11:23:26 -07:00
|
|
|
readonly sharedModules: Readonly<Record<keyof ComponentArtifact["sharedModules"], string>>;
|
2026-09-21 01:09:31 -07:00
|
|
|
}
|
2026-09-21 21:49:52 -07:00
|
|
|
/** Build-time validation uses the compiler selected by the Nix derivation. */
|
|
|
|
|
export async function checkExecutionInstallation(
|
2026-09-21 01:09:31 -07:00
|
|
|
plan: ExecutionPlan,
|
|
|
|
|
roots: Readonly<Record<string, string>>,
|
|
|
|
|
generator: string,
|
|
|
|
|
selections: Readonly<Record<string, PackageInstallationSelection>> = {},
|
|
|
|
|
): Promise<ExecutionInstallation> {
|
|
|
|
|
const loaded = await loadCheckedArtifactWorld(plan, roots, generator);
|
2026-09-21 21:49:52 -07:00
|
|
|
return assembleExecutionInstallation(plan, roots, loaded, selections);
|
|
|
|
|
}
|
|
|
|
|
/** Load immutable artifacts without regenerating bindings with the host compiler.
|
|
|
|
|
* Format, coverage, content and runtime ABI checks still apply. This grants no
|
|
|
|
|
* authority to activate a world or run optional modules in process. */
|
|
|
|
|
export async function loadExecutionInstallation(
|
|
|
|
|
plan: ExecutionPlan,
|
|
|
|
|
roots: Readonly<Record<string, string>>,
|
|
|
|
|
selections: Readonly<Record<string, PackageInstallationSelection>> = {},
|
2026-10-07 14:44:59 -07:00
|
|
|
metadata?: CandidateMetadataScope,
|
2026-09-21 21:49:52 -07:00
|
|
|
): Promise<ExecutionInstallation> {
|
2026-10-07 14:44:59 -07:00
|
|
|
return assembleExecutionInstallation(plan, roots, await loadArtifactWorld(plan, roots, metadata), selections);
|
2026-09-21 21:49:52 -07:00
|
|
|
}
|
|
|
|
|
function assembleExecutionInstallation(
|
|
|
|
|
plan: ExecutionPlan,
|
|
|
|
|
roots: Readonly<Record<string, string>>,
|
|
|
|
|
loaded: LoadedArtifactWorld,
|
|
|
|
|
selections: Readonly<Record<string, PackageInstallationSelection>>,
|
|
|
|
|
): ExecutionInstallation {
|
2026-09-21 01:09:31 -07:00
|
|
|
const fail = (where: string, message: string): never => {
|
|
|
|
|
throw new ExecutionPlanError("INSTALLATION_INVALID", where, message);
|
|
|
|
|
};
|
|
|
|
|
for (const revision of Object.keys(selections))
|
|
|
|
|
if (!loaded.manifests.some((manifest) => manifest.packageRevisionId === revision))
|
|
|
|
|
fail(revision, "Selection names a package outside this checked world");
|
|
|
|
|
const routes: Record<string, { path: string; mediaType: string }> = Object.create(null);
|
|
|
|
|
const packages: ExecutionInstallation["packages"][number][] = [];
|
2026-09-21 11:23:26 -07:00
|
|
|
const components: InstalledComponent[] = [];
|
|
|
|
|
const sharedComponentModules = new Map<string, string>();
|
2026-09-21 01:09:31 -07:00
|
|
|
let coreURL: string | undefined, engineURL: string | undefined;
|
|
|
|
|
for (const manifest of loaded.manifests) {
|
|
|
|
|
const revision = manifest.packageRevisionId;
|
|
|
|
|
const root = roots[revision]!;
|
|
|
|
|
const selection = selections[revision] ?? {};
|
|
|
|
|
const exports = plan.definition.packages.find((pkg) => pkg.revision === revision)!.exports;
|
2026-09-21 11:23:26 -07:00
|
|
|
const service =
|
|
|
|
|
exports.length || selection.service
|
|
|
|
|
? selectArtifactImplementation(manifest, {
|
|
|
|
|
...selection.service,
|
|
|
|
|
target: "server",
|
|
|
|
|
compatibility: "quixos-service-v1",
|
|
|
|
|
exports: exports.map((entry) => entry.id),
|
|
|
|
|
})
|
|
|
|
|
: undefined;
|
|
|
|
|
for (const component of manifest.components ?? []) {
|
|
|
|
|
const sharedModules = Object.fromEntries(
|
|
|
|
|
Object.entries(component.sharedModules).map(([name, filePath]) => {
|
|
|
|
|
const file = component.files.find((file) => file.path === filePath)!;
|
|
|
|
|
const url = contentArtifactUrl(file),
|
|
|
|
|
previous = sharedComponentModules.get(name);
|
|
|
|
|
if (previous && previous !== url) fail(component.id, `Components disagree on shared module ${name}`);
|
|
|
|
|
sharedComponentModules.set(name, url);
|
|
|
|
|
return [name, url];
|
|
|
|
|
}),
|
|
|
|
|
) as InstalledComponent["sharedModules"];
|
|
|
|
|
for (const file of component.files) {
|
|
|
|
|
const url = contentArtifactUrl(file);
|
|
|
|
|
if (routes[url] && routes[url].mediaType !== file.mediaType) fail(url, "Conflicting artifact media types");
|
|
|
|
|
routes[url] ??= { path: path.join(root, file.path), mediaType: file.mediaType };
|
|
|
|
|
}
|
|
|
|
|
components.push({
|
2026-09-27 16:34:53 -07:00
|
|
|
...(() => {
|
|
|
|
|
const pkg = plan.definition.workspace.packageImports.find((entry) => entry.revisionId === revision);
|
|
|
|
|
const artifact = pkg?.buildArtifacts?.["org.quixos.react.preparation/1"] as
|
|
|
|
|
| { modules: { module: string }[] }
|
|
|
|
|
| undefined;
|
|
|
|
|
const preparation = artifact?.modules.find((entry) => entry.module === component.id);
|
|
|
|
|
return preparation ? { preparation } : {};
|
|
|
|
|
})(),
|
2026-09-22 16:19:05 -07:00
|
|
|
role: (() => {
|
|
|
|
|
const module = plan.definition.packages
|
|
|
|
|
.find((p) => p.revision === revision)!
|
|
|
|
|
.modules!.find((m) => m.id === component.id)!;
|
|
|
|
|
return {
|
|
|
|
|
interface: module.interfaceRevisionId,
|
|
|
|
|
member: module.memberId,
|
|
|
|
|
subject: component.subject,
|
|
|
|
|
contract: module.contract,
|
2026-09-25 11:30:37 -07:00
|
|
|
...(module.typeArguments ? { typeArguments: module.typeArguments } : {}),
|
|
|
|
|
...(module.requiredInterfaces ? { requiredInterfaces: module.requiredInterfaces } : {}),
|
2026-09-22 16:19:05 -07:00
|
|
|
};
|
|
|
|
|
})(),
|
2026-09-21 11:23:26 -07:00
|
|
|
packageRevision: revision,
|
|
|
|
|
id: component.id,
|
|
|
|
|
subject: component.subject,
|
|
|
|
|
compatibility: component.compatibility,
|
|
|
|
|
exportName: component.exportName,
|
2026-09-21 15:24:05 -07:00
|
|
|
styles: component.files.filter((file) => file.mediaType === "text/css").map(contentArtifactUrl),
|
2026-09-21 11:23:26 -07:00
|
|
|
sharedModules,
|
|
|
|
|
url: contentArtifactUrl(component.files.find((file) => file.path === component.path)!),
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-09-21 01:09:31 -07:00
|
|
|
const module = selection.module
|
|
|
|
|
? selectArtifactImplementation(manifest, {
|
|
|
|
|
...selection.module,
|
|
|
|
|
target: "server",
|
|
|
|
|
compatibility: "quixos-js-module-v1",
|
|
|
|
|
exports: exports.map((entry) => entry.id),
|
|
|
|
|
})
|
|
|
|
|
: undefined;
|
2026-10-07 21:14:55 -07:00
|
|
|
const restrictedCandidates = manifest.implementations.filter(
|
|
|
|
|
(entry) => entry.compatibility === "quixos-restricted-module-v1",
|
|
|
|
|
);
|
|
|
|
|
if (restrictedCandidates.length > 1) fail(revision, "Ambiguous restricted module candidates");
|
|
|
|
|
const restricted = restrictedCandidates[0];
|
2026-09-21 01:09:31 -07:00
|
|
|
const browserExports = exports.filter(
|
2026-09-28 20:29:21 -07:00
|
|
|
(entry) =>
|
|
|
|
|
entry.execution.kind !== "remote" &&
|
|
|
|
|
entry.execution.targets.includes("target:org.quixos.execution.javascript@1"),
|
2026-09-21 01:09:31 -07:00
|
|
|
);
|
|
|
|
|
if (!browserExports.length) {
|
|
|
|
|
if (selection.browser) fail(revision, "Package has no browser exports to select");
|
2026-10-07 21:14:55 -07:00
|
|
|
packages.push({
|
|
|
|
|
revision,
|
|
|
|
|
root,
|
|
|
|
|
...(service ? { service } : {}),
|
|
|
|
|
...(module ? { module } : {}),
|
|
|
|
|
...(restricted ? { restricted } : {}),
|
|
|
|
|
});
|
2026-09-21 01:09:31 -07:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const browser = selectArtifactImplementation(manifest, {
|
|
|
|
|
...selection.browser,
|
2026-09-28 20:29:21 -07:00
|
|
|
target: "target:org.quixos.execution.javascript@1",
|
2026-09-21 01:09:31 -07:00
|
|
|
compatibility: "quixos-js-module-v1",
|
|
|
|
|
exports: browserExports.map((entry) => entry.id),
|
|
|
|
|
});
|
|
|
|
|
const entry = browser.files.find((file) => file.path === browser.path)!;
|
|
|
|
|
const shared = (name: string) => {
|
|
|
|
|
const files = browser.files.filter((file) => file.path.endsWith("/" + name));
|
|
|
|
|
if (files.length !== 1) fail(revision, "Expected one shared " + name);
|
|
|
|
|
return contentArtifactUrl(files[0]!);
|
|
|
|
|
};
|
|
|
|
|
const core = shared("core.mjs"),
|
|
|
|
|
engine = shared("engine.mjs");
|
|
|
|
|
if ((coreURL && coreURL !== core) || (engineURL && engineURL !== engine))
|
|
|
|
|
fail(revision, "Browser packages disagree on the shared runtime identity");
|
|
|
|
|
coreURL = core;
|
|
|
|
|
engineURL = engine;
|
|
|
|
|
for (const file of browser.files) {
|
|
|
|
|
const url = contentArtifactUrl(file);
|
|
|
|
|
if (routes[url] && routes[url].mediaType !== file.mediaType) fail(url, "Conflicting artifact media types");
|
|
|
|
|
routes[url] ??= { path: path.join(root, file.path), mediaType: file.mediaType };
|
|
|
|
|
}
|
|
|
|
|
packages.push({
|
|
|
|
|
revision,
|
|
|
|
|
root,
|
2026-09-21 11:23:26 -07:00
|
|
|
...(service ? { service } : {}),
|
2026-09-21 01:09:31 -07:00
|
|
|
...(module ? { module } : {}),
|
2026-10-07 21:14:55 -07:00
|
|
|
...(restricted ? { restricted } : {}),
|
2026-09-22 18:11:54 -07:00
|
|
|
browserImplementation: browser,
|
2026-09-21 01:09:31 -07:00
|
|
|
browserURL: contentArtifactUrl(entry),
|
|
|
|
|
browserRegistry: browser.registryExport,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
codec: "camino-execution-installation-v1",
|
|
|
|
|
plan: plan.digest,
|
|
|
|
|
world: loaded.digest,
|
|
|
|
|
packages,
|
2026-09-21 11:23:26 -07:00
|
|
|
components,
|
2026-09-21 01:09:31 -07:00
|
|
|
routes,
|
|
|
|
|
...(coreURL ? { coreURL, engineURL } : {}),
|
|
|
|
|
};
|
|
|
|
|
}
|