Project source-free capability contracts and keep exact source provenance in package assembly. Reuse compiled groups across unrelated graph and dependency implementation edits. Keep React public inputs separate from workspace query lowering and retain explicit type-companion dependencies. Allow independent execution-package checks, scope resource compilation to declared dependencies, and memoize source closure traversal. Document generic-specialization limits and measured Reader rebuild behavior. Verified protocol, artifact, package, React, and managed activation checks; retained-Git independent/workspace artifact equality and incompatible type dependency rejection; nix fmt -- --check and lint. One-off Reader measurements recompiled 1 of 13 groups for a child edit and 0 for a workspace QX default edit.
562 lines
25 KiB
TypeScript
562 lines
25 KiB
TypeScript
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 = <T>(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<Binding, { kind: "interface" }>;
|
|
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<WorkspaceRevision["packageImports"][number]["modules"]>[number] & {
|
|
contract: string;
|
|
subject?: import("../capability-model/types.js").ObjectExpectation;
|
|
typeArguments?: readonly unknown[];
|
|
requiredInterfaces?: readonly string[];
|
|
})[];
|
|
controllers?: NonNullable<WorkspaceRevision["packageImports"][number]["controllers"]>;
|
|
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<WorkspaceRevision["presentations"]>;
|
|
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<string, PlannedOperation>();
|
|
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<string>();
|
|
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<string, { complete: boolean; requiresComplete: boolean; windows: Set<string> }>();
|
|
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) };
|
|
}
|