import { standardNativeBackends, type NativeExecutionBackend } from "./native-backends.js"; import type { WorkspaceRevision, ExecutionContract, ValueType, Binding, DependencyBinding, EdgeTraversal, InterfaceRevisionId, AtomId, OperationId, } from "../capability-model/types.js"; import { validateWorkspaceRevision } from "../capability-model/validation.js"; import { canonicalJson, contentDigest } from "../capability-model/evolution.js"; import { resolveForwardedBinding } from "../capability-model/forwarding.js"; import { linkQueries, type LinkedQuery } from "../query/link.js"; import { type BindingSchema } from "../bindings/index.js"; import { unsupportedQueryOperator } from "./query-operators.js"; import { packageBuildContexts } from "./build-contract.js"; export class ExecutionPlanError extends Error { constructor( readonly code: string, readonly path: string, message: string, ) { super(`${code} ${path}: ${message}`); this.name = "ExecutionPlanError"; } } export const hexDigest = (value: unknown): string => contentDigest(value).slice("sha256:".length); const sorted = (values: readonly T[], key: (value: T) => string): T[] => [...values].sort((a, b) => (key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0)); export const publicOperationKey = (atom: string, iface: string, operation: string): string => JSON.stringify([atom, iface, operation]); export type DataDependency = | { kind: "state"; slot: string; via?: EdgeTraversal } | { kind: "edge"; edge: string; projection: string; via?: EdgeTraversal } | { kind: "operation"; operation: string; port: string; via?: EdgeTraversal } | { kind: "query"; query: string; port: string }; export interface PlannedOperation { key: string; atomId: string; interfaceRevisionId: string; operationId: string; scope: "object" | "class"; execution: ExecutionContract; /** Targets available for this complete installed implementation/dependency closure. */ availableTargets: string[]; input: ValueType; output: ValueType; /** Concrete target after public forwarding. Never chosen by a runtime caller. */ binding: Exclude; dependencies: DataDependency[]; } export interface PlannedQuery { id: string; query: LinkedQuery; /** Query-selected getter entrypoints; dependency traversal supplies canonical inputs. */ operations: string[]; /** Local query roots that admission must support, in addition to selected wire outputs. */ admittedOperations: string[]; } export interface PlanPackage { modules?: (NonNullable[number] & { contract: string; subject?: import("../capability-model/types.js").ObjectExpectation; typeArguments?: readonly unknown[]; requiredInterfaces?: readonly string[]; })[]; controllers?: NonNullable; components?: { id: string; displayName: string; subject: InterfaceRevisionId; runtime: "react" }[]; revision: string; source: WorkspaceRevision["packageImports"][number]["source"]; sourceDigest: string; bindingDigest: string; exports: { id: string; execution: ExecutionContract }[]; } export interface ExecutionPlanDefinition { presentations?: NonNullable; codec: "camino-execution-plan-v1"; workspace: WorkspaceRevision; bindingSchema: BindingSchema; packages: PlanPackage[]; owners: { id: string; atom: string; interface: string; initialization: unknown; storage: unknown }[]; operations: PlannedOperation[]; queries: PlannedQuery[]; profiles: { slot: string; profile: string }[]; versions: { value: "camino-value-v1"; reference: "camino-ref-v1"; ports: "quixos-transaction-ports-v1"; crdt: "camino-automerge-3.4.1-structured-v1"; }; } export interface ExecutionPlan { definition: ExecutionPlanDefinition; digest: string; } export const executionVersions: ExecutionPlanDefinition["versions"] = { value: "camino-value-v1", reference: "camino-ref-v1", ports: "quixos-transaction-ports-v1", crdt: "camino-automerge-3.4.1-structured-v1", }; /** Selection only: the coordinator separately verifies the caller's grant and receiver witness. */ export function resolvePlannedOperation( plan: ExecutionPlan, atom: string, iface: string, operation: string, ): PlannedOperation { const key = publicOperationKey(atom, iface, operation); const selected = plan.definition.operations.find((entry) => entry.key === key); if (!selected) throw new ExecutionPlanError("UNINSTALLED_OPERATION", key, "No installed public operation binding"); return selected; } /** Bound argument/state codec subset. A parsed type alone does not mean a runtime implements it. */ function checkValue(type: ValueType, path: string, allowChange = false): void { switch (type.kind) { case "variant": for (const [label, entry] of Object.entries(type.cases)) checkValue(entry.value, `${path}.${label}`, allowChange); return; case "scalar": case "object-ref": return; case "builtin": if (type.name === "unit") return; break; case "optional": case "list": checkValue(type.value, path, allowChange); return; case "record": for (const [name, field] of Object.entries(type.fields)) checkValue(field, `${path}.${name}`, allowChange); return; case "change": if (allowChange) { checkValue(type.value, path); return; } break; } throw new ExecutionPlanError("UNSUPPORTED_PORTABLE_CODEC", path, `Portable execution does not support ${type.kind}`); } /** Compile declarations into a closed dispatch/dependency graph before artifact selection. */ export function compileExecutionPlan( input: WorkspaceRevision, backends: readonly NativeExecutionBackend[] = standardNativeBackends, ): ExecutionPlan { const workspace = structuredClone(input); const issues = validateWorkspaceRevision(workspace); if (issues.length) throw new ExecutionPlanError( "INVALID_WORKSPACE", workspace.id, issues.map((i) => `${i.path}: ${i.message}`).join("\n"), ); const interfaces = new Map(workspace.interfaceImports.map((i) => [i.revisionId, i])); const packages = new Map(workspace.packageImports.map((p) => [p.revisionId, p])); const attachments = [...workspace.sharedAttachments, ...workspace.conformances.flatMap((c) => c.privateAttachments)]; for (const pkg of workspace.packageImports) for (const entry of pkg.exports) if (entry.execution && entry.execution.kind !== "remote") { checkValue(entry.inputType, `${pkg.revisionId}/${entry.id}/input`, true); checkValue(entry.outputType, `${pkg.revisionId}/${entry.id}/output`, true); } for (const attachment of attachments) if (attachment.kind === "state" && attachment.storagePolicy.kind === "crdt-document") checkValue(attachment.valueType, attachment.id); const operationMap = new Map(); const queries = linkQueries(workspace); const queryKey = (pkg: string, id: string) => JSON.stringify([pkg, id]); const queryMap = new Map(queries.map((q) => [queryKey(q.packageRevisionId, q.checked.declaration.id), q])); const declaration = (iface: InterfaceRevisionId, operation: OperationId) => interfaces .get(iface) ?.members.flatMap((m) => m.operations) .find((o) => o.id === operation); const queryOperations = (query: LinkedQuery) => query.fields.map((f) => publicOperationKey(f.atomId, f.interfaceRevisionId, f.getter)); const targetAtoms = (binding: DependencyBinding, atom: AtomId): AtomId[] => { if (binding.kind === "interface" && binding.receiver === "input") return workspace.atoms .filter((candidate) => workspace.conformances.some( (conformance) => conformance.atomId === candidate.id && conformance.interfaceRevisionId === binding.interfaceRevisionId, ), ) .map((candidate) => candidate.id); if (!("via" in binding) || !binding.via) return [atom]; const edge = attachments.find((a) => a.id === binding.via!.edgeTypeId); if (!edge || edge.kind !== "edge") throw new ExecutionPlanError("MISSING_EDGE", atom, "Missing traversal edge"); const endpoint = edge.endpoints.find((e) => e.projectionId !== binding.via!.projectionId)!; const constraint = endpoint.constraint; return workspace.atoms .filter((a) => constraint.kind === "atom" ? constraint.atomId === a.id : workspace.conformances.some( (c) => c.atomId === a.id && c.interfaceRevisionId === constraint.interfaceRevisionId, ), ) .map((a) => a.id); }; const add = (atom: AtomId, iface: InterfaceRevisionId, operation: OperationId): PlannedOperation => { const key = publicOperationKey(atom, iface, operation), existing = operationMap.get(key); if (existing) return existing; const publicContract = declaration(iface, operation); if (!publicContract) throw new ExecutionPlanError("MISSING_OPERATION", key, "Missing public contract"); const { binding } = resolveForwardedBinding(workspace, atom, iface, operation); const planned: PlannedOperation = { key, atomId: atom, interfaceRevisionId: iface, operationId: operation, scope: publicContract.scope === "class" ? "class" : "object", execution: publicContract.execution ?? { kind: "remote" }, availableTargets: [], input: publicContract.inputType, output: publicContract.outputType, binding, dependencies: [], }; operationMap.set(key, planned); const portable = planned.execution.kind !== "remote"; if (portable) { checkValue(planned.input, `${key}/input`, true); checkValue(planned.output, `${key}/output`, true); } if (binding.kind === "state" || binding.kind === "journal" || binding.kind === "subscription") planned.dependencies.push({ kind: "state", slot: binding.slotId }); else if (binding.kind === "edge") planned.dependencies.push({ kind: "edge", edge: binding.edgeTypeId, projection: binding.projectionId }); else if (binding.kind === "package") { const entry = packages.get(binding.packageRevisionId)?.exports.find((e) => e.id === binding.exportId); if (!entry) throw new ExecutionPlanError("MISSING_EXPORT", key, "Unresolved package export"); for (const dependency of binding.dependencies) { const port = entry.dependencyPorts.find((p) => p.id === dependency.portId)!; const target = dependency.binding; const via = "via" in target && target.via ? { via: target.via } : {}; if ("via" in target && target.via) planned.dependencies.push({ kind: "edge", edge: target.via.edgeTypeId, projection: target.via.projectionId }); if (target.kind === "state") planned.dependencies.push({ kind: "state", slot: target.slotId, ...via }); else if (target.kind === "edge") planned.dependencies.push({ kind: "edge", edge: target.edgeTypeId, projection: target.projectionId, ...via }); else if (target.kind === "interface" && port.requirement.kind === "interface") { const selected = port.requirement.operations ?? interfaces.get(target.interfaceRevisionId)!.members.flatMap((m) => m.operations.map((o) => o.id)); for (const targetAtom of targetAtoms(target, atom)) for (const selectedOperation of selected) { const operation = add(targetAtom, target.interfaceRevisionId, selectedOperation); planned.dependencies.push({ kind: "operation", operation: operation.key, port: port.id, ...via }); } } else if (target.kind === "query") { const query = queryMap.get(queryKey(target.packageRevisionId, target.queryId)); if (!query) throw new ExecutionPlanError("MISSING_QUERY", key, `Unresolved query port ${port.id}`); planned.dependencies.push({ kind: "query", query: queryKey(target.packageRevisionId, target.queryId), port: port.id, }); } } } planned.dependencies = sorted(planned.dependencies, canonicalJson); return planned; }; for (const conformance of workspace.conformances) for (const entry of conformance.operationBindings) add(conformance.atomId, conformance.interfaceRevisionId, entry.operationId); // Check the transitive closure, retaining a dependency path for diagnostics. for (const root of operationMap.values()) { if (root.execution.kind === "remote") continue; const requested = root.execution, visited = new Set(); const walk = (key: string, path: string[]) => { if (visited.has(key)) return; visited.add(key); const node = operationMap.get(key)!; if (node.execution.kind === "remote" || (requested.kind === "snapshot" && node.execution.kind !== "snapshot")) throw new ExecutionPlanError( "NONPORTABLE_DEPENDENCY", path.join(" -> "), "Execution effect is not allowed by the root contract", ); for (const dependency of node.dependencies) { if (dependency.kind === "operation") walk(dependency.operation, [...path, dependency.port, dependency.operation]); if (dependency.kind === "query") { // An admitted presentation window is not an authoritative mutation read set. if (requested.kind === "transaction") throw new ExecutionPlanError( "QUERY_IN_TRANSACTION", [...path, dependency.port].join(" -> "), "Capture explicit object references as mutation inputs; local presentation queries cannot define transaction membership", ); const query = queryMap.get(dependency.query)!; if (query.checked.declaration.placement?.kind !== "local") throw new ExecutionPlanError( "REMOTE_QUERY_DEPENDENCY", path.join(" -> "), "Portable code requires a local query", ); for (const operation of queryOperations(query)) walk(operation, [...path, dependency.port, operation]); } } }; walk(root.key, [root.key]); } // Additional implementation targets are opportunities, not new public requirements. // Start with provider support and remove targets until the complete installed // dependency closure agrees. This also handles recursive call graphs without // mistaking an unfinished DFS for proof of availability. for (const node of operationMap.values()) { if (node.execution.kind === "remote") continue; const binding = node.binding; const provider = binding.kind === "package" ? packages.get(binding.packageRevisionId)!.exports.find((entry) => entry.id === binding.exportId)!.execution : { kind: node.execution.kind, targets: backends.filter((backend) => backend.supports(binding)).map((backend) => backend.target), }; if (provider && provider.kind !== "remote") node.availableTargets = [...provider.targets].sort(); } let targetsChanged = true; while (targetsChanged) { targetsChanged = false; for (const node of operationMap.values()) { const available = node.availableTargets.filter((target) => node.dependencies.every((dependency) => { if (dependency.kind === "operation") return operationMap.get(dependency.operation)!.availableTargets.includes(target); if (dependency.kind === "query") { const query = queryMap.get(dependency.query)!; return ( node.execution.kind === "snapshot" && query.checked.declaration.placement?.kind === "local" && queryOperations(query).every((key) => operationMap.get(key)!.availableTargets.includes(target)) ); } return true; }), ); if (available.length !== node.availableTargets.length) { node.availableTargets = available; targetsChanged = true; } } } for (const node of operationMap.values()) if (node.execution.kind !== "remote" && !node.availableTargets.includes("server")) throw new ExecutionPlanError( "MISSING_SERVER_IMPLEMENTATION", node.key, "Every graph operation requires an authoritative implementation across its dependency closure", ); const plannedQueries: PlannedQuery[] = queries.map((query) => ({ id: queryKey(query.packageRevisionId, query.checked.declaration.id), query, operations: queryOperations(query).sort(), admittedOperations: [], })); for (const query of plannedQueries) { for (const field of query.query.fields) { const operation = operationMap.get(publicOperationKey(field.atomId, field.interfaceRevisionId, field.getter))!; const effect = query.query.checked.effects.find( (effect) => effect.interfaceRevisionId === field.interfaceRevisionId && effect.memberId === field.memberId, ); if ( operation.execution.kind === "remote" && (query.query.checked.declaration.placement?.kind === "replica" || effect?.uses.some((use) => use !== "select")) ) throw new ExecutionPlanError( "QUERY_REMOTE_MEMBERSHIP_UNSUPPORTED", query.id, "Remote getters may enrich selected values after native snapshot closure; they cannot determine membership, order or canonical-input admission. Expose an accepted canonical value or split the service result into a separate query.", ); const member = interfaces.get(field.interfaceRevisionId)?.members.find((member) => member.id === field.memberId); if (member?.kind === "relationship" && operation.binding.kind !== "edge") throw new ExecutionPlanError( "QUERY_NON_NATIVE_RELATION", query.id, "This query runtime traverses native graph edges. Expose an edge relationship, or return a computed service value through a value getter.", ); } const unsupported = unsupportedQueryOperator(query.query.checked); if (unsupported) throw new ExecutionPlanError( "QUERY_OPERATOR_UNSUPPORTED", query.id, `This execution world does not support ${unsupported}. Use a bounded portable getter over admitted inputs, or a server service returning the computed value. Query placement will not change automatically.`, ); const placement = query.query.checked.declaration.placement; if (placement?.kind !== "local") continue; const source = plannedQueries.find( (q) => q.id === queryKey(query.query.packageRevisionId, placement.sourceQueryId), ); if (!source || source.query.checked.declaration.placement?.kind !== "replica") throw new ExecutionPlanError("MISSING_REPLICA_SOURCE", query.id, "Local query source must be a replica query"); const localRoot = query.query.checked.declaration.root, sourceRoot = source.query.checked.declaration.root; if (localRoot.kind !== sourceRoot.kind || localRoot.interface !== sourceRoot.interface) throw new ExecutionPlanError( "REPLICA_ROOT_MISMATCH", query.id, "Local and replica queries must use the same root contract", ); if (localRoot.kind === "workspace-interface") { const sourceWindows = source.query.checked.selection .flatMap((root) => root.selection) .filter((s) => s.name === "objects"); for (const selection of query.query.checked.selection .flatMap((root) => root.selection) .filter((s) => s.name === "objects")) { if ( !sourceWindows.some( (window) => canonicalJson({ arguments: window.arguments, conditions: window.conditions }) === canonicalJson({ arguments: selection.arguments, conditions: selection.conditions }), ) ) throw new ExecutionPlanError( "REPLICA_WINDOW_MISMATCH", query.id, "Local objects must observe the identical confirmed source window; use availableObjects for a local candidate view", ); } } const traversals = (linked: LinkedQuery) => { const paths = new Map }>(); const entry = (key: string) => { let value = paths.get(key); if (!value) { value = { complete: false, requiresComplete: false, windows: new Set() }; paths.set(key, value); } return value; }; const visit = (selections: LinkedQuery["checked"]["selection"], parents: string[]) => { for (const selection of selections) { for (const stage of selection.relational?.stages ?? []) { if (stage.operation.kind === "source") entry(JSON.stringify([...parents, stage.operation.memberId])).requiresComplete = true; } const member = selection.interfaceRevisionId ? interfaces.get(selection.interfaceRevisionId)?.members.find((m) => m.id === selection.memberId) : undefined; const path = member?.kind === "relationship" ? [...parents, member.id] : parents; if (member?.kind === "relationship") { const many = member.cardinality === "many" || member.cardinality === "many-unique"; const complete = !selection.predicate && (!many || !!selection.arguments.all); const key = JSON.stringify(path); const value = entry(key); value.complete ||= complete; if (selection.arguments.first) value.windows.add(canonicalJson({ arguments: selection.arguments, conditions: selection.conditions })); else value.requiresComplete = true; } visit(selection.selection, path); } }; visit(linked.checked.selection, []); return paths; }; const admittedTraversals = traversals(source.query); for (const [traversal, needed] of traversals(query.query)) { if (!admittedTraversals.has(traversal)) throw new ExecutionPlanError( "REPLICA_TRAVERSAL_MISSING", query.id, `Replica source does not admit relationship path ${traversal}`, ); if ( !admittedTraversals.get(traversal)!.complete && (needed.requiresComplete || !needed.windows.size || [...needed.windows].some((window) => !admittedTraversals.get(traversal)!.windows.has(window))) ) throw new ExecutionPlanError( "REPLICA_RELATION_INCOMPLETE", query.id, `Local relationship ${traversal} needs either complete source candidates or the identical confirmed first window; a window cannot supply unseen matches`, ); } // The admission executor follows these canonical dependencies on the admitted roots. // They do not change source candidate membership or make local take/filters authoritative. source.admittedOperations.push(...query.operations); for (const operation of query.operations) { const node = operationMap.get(operation)!; if (node.execution.kind !== "snapshot" || !node.availableTargets.some((target) => target !== "server")) throw new ExecutionPlanError( "NONPORTABLE_QUERY_FIELD", `${query.id}/${operation}`, "Local query getter needs an installed local snapshot implementation", ); } } for (const query of plannedQueries) query.admittedOperations = [...new Set(query.admittedOperations)].sort(); const schema: BindingSchema = { format: "quixos-bindings", version: 1, interfaces: sorted( workspace.interfaceImports.filter((i) => !i.template), (i) => i.revisionId, ), interfaceTemplates: sorted( workspace.interfaceImports.filter((i) => i.template), (i) => i.revisionId, ), packages: sorted(workspace.packageImports, (p) => p.revisionId), }; const normalizedSchema = JSON.parse(canonicalJson(schema)) as BindingSchema; const buildContexts = packageBuildContexts(normalizedSchema); const definition: ExecutionPlanDefinition = { codec: "camino-execution-plan-v1", workspace, bindingSchema: normalizedSchema, ...(workspace.presentations?.length ? { presentations: sorted(workspace.presentations, (p) => p.id) } : {}), packages: sorted(workspace.packageImports, (p) => p.revisionId).map((p) => { const contract = buildContexts[p.revisionId]!.contract; return { ...contract.definition.package, source: p.source, sourceDigest: hexDigest({ revision: p.revisionId, source: p.source }), bindingDigest: contract.digest, }; }), owners: sorted(workspace.conformances, (c) => c.id ?? "").map((c) => { if (!c.id) throw new ExecutionPlanError( "OWNER_ID_REQUIRED", `${c.atomId}/${c.interfaceRevisionId}`, "Installed state owners require explicit stable identities", ); return { id: c.id, atom: c.atomId, interface: c.interfaceRevisionId, initialization: c.initialization ?? [], storage: c.privateAttachments, }; }), operations: sorted([...operationMap.values()], (o) => o.key), queries: sorted(plannedQueries, (q) => q.id), profiles: sorted( attachments.filter((a) => a.kind === "state" && a.storagePolicy.kind === "crdt-document"), (a) => a.id, ).map((a) => ({ slot: a.id, profile: "camino-automerge-3.4.1-structured-v1" })), versions: { ...executionVersions }, }; const normalized = JSON.parse(canonicalJson(definition)) as ExecutionPlanDefinition; return { definition: normalized, digest: hexDigest(normalized) }; }