import type { ExecutionContract, InterfaceOperation, WorkspaceRevision } from "./types.js"; import type { CapabilityValidationIssue } from "./validation.js"; import { resolveForwardedBinding } from "./forwarding.js"; /** Validate declared effects and targets; this is not a JavaScript purity proof. */ export function validateExecutionContracts(workspace: WorkspaceRevision): CapabilityValidationIssue[] { const issues: CapabilityValidationIssue[] = []; const fail = (path: string, message: string) => issues.push({ code: "invalid-execution-contract", path, message }); const interfaces = new Map(workspace.interfaceImports.map((i) => [i.revisionId, i])); const packages = new Map(workspace.packageImports.map((p) => [p.revisionId, p])); const operations = (id: InterfaceOperation["id"], revision: WorkspaceRevision["interfaceImports"][number]) => (revision.template?.members ?? revision.members).flatMap((m) => m.operations).find((o) => o.id === id); const check = (contract: ExecutionContract | undefined, path: string) => { if (!contract || contract.kind === "remote") return; if ( !["snapshot", "transaction"].includes(contract.kind) || !Array.isArray(contract.targets) || !contract.targets.length || contract.targets.some((t) => typeof t !== "string" || !t.trim()) || new Set(contract.targets).size !== contract.targets.length ) fail(path, "Portable execution requires a nonempty set of distinct target names"); }; const supports = ( requested: ExecutionContract | undefined, provided: ExecutionContract | undefined, path: string, ) => { if (!requested || requested.kind === "remote") return; if (!provided || provided.kind === "remote") { fail(path, "Portable execution depends on a remote-only operation"); return; } if (requested.kind === "snapshot" && provided.kind !== "snapshot") fail(path, "Snapshot execution cannot perform a transaction"); }; for (const revision of workspace.interfaceImports) for (const member of revision.template?.members ?? revision.members) { for (const operation of member.operations) { check(operation.execution, `${revision.revisionId}/${operation.id}`); if (operation.execution && operation.execution.kind !== "remote" && operation.mode !== "call") fail( `${revision.revisionId}/${operation.id}`, "Watch lifecycle operations are remote; portable reads use snapshots", ); } if (member.kind !== "operation" && member.kind !== "module" && member.queryRead?.execution === "portable") { const getter = member.operations.find((o) => o.displayName === (member.kind === "value" ? "get" : "resolve")); if (getter?.execution?.kind !== "snapshot") fail(`${revision.revisionId}/${member.id}`, "Queryable portable members require a snapshot getter"); } } for (const revision of workspace.packageImports) { for (const entry of [...revision.exports, ...(revision.genericExports ?? [])]) { const path = `${revision.revisionId}/${entry.id}`; check(entry.execution, path); for (const port of entry.dependencyPorts) { const requirement = port.requirement; const portPath = `${path}/${port.id}`; if (requirement.kind === "interface") { const contract = interfaces.get( "interfaceRevisionId" in requirement ? requirement.interfaceRevisionId : requirement.application.definitionId, ); const selected = requirement.operations; if (requirement.receiver === "input") { if (!entry.execution || entry.execution.kind === "remote" || !selected?.length) fail(portPath, "Reference-selected calls require portable execution and an explicit selection"); for (const id of selected ?? []) { const operation = contract && operations(id, contract); if (!operation?.execution || operation.execution.kind === "remote" || operation.scope === "class") fail(portPath, "Reference-selected dependencies require portable object operations"); } } if (selected && (!selected.length || new Set(selected).size !== selected.length)) fail(portPath, "Selected interface operations must be nonempty and distinct"); for (const id of selected ?? []) if (contract && !operations(id, contract)) fail(portPath, `Unknown selected operation ${id}`); if (entry.execution && entry.execution.kind !== "remote") { if (!selected) fail(portPath, "Portable interface ports must select their required operations"); for (const id of selected ?? []) if (contract) supports(entry.execution, operations(id, contract)?.execution, portPath); } } if (!entry.execution || entry.execution.kind === "remote") continue; if (requirement.kind === "state" || requirement.kind === "edge") { const read = requirement.kind === "state" ? "read" : "resolve"; for (const primitive of requirement.primitives) { if (primitive.startsWith("watch")) fail(portPath, "Portable operations cannot acquire watch lifecycle primitives"); else if (entry.execution.kind === "snapshot" && primitive !== read) fail(portPath, "Snapshot operations cannot acquire write primitives"); } } else if (requirement.kind === "query") { const query = packages.get(requirement.packageRevisionId)?.queries?.find((q) => q.id === requirement.queryId); if (query?.placement?.kind !== "local") fail(portPath, "Portable operations require a local query dependency"); } else if (requirement.kind === "creation") { if (entry.execution.kind !== "transaction") fail(portPath, "Object creation requires transaction execution"); } else if (requirement.kind === "reflection") { if (requirement.selectors.some((selector) => selector !== "presentations")) fail(portPath, "Only checked presentation metadata is portable"); } else if (requirement.kind !== "interface") fail(portPath, `Portable execution does not support ${requirement.kind} dependencies`); } } for (const query of revision.queries ?? []) if (query.placement?.kind === "local") { const sourceQueryId = query.placement.sourceQueryId; const source = revision.queries?.find((q) => q.id === sourceQueryId); if (source?.placement?.kind !== "replica") fail(`${revision.revisionId}/${query.id}`, "Local queries must name a replica query in the same package"); } } for (const conformance of workspace.conformances) { const revision = interfaces.get(conformance.interfaceRevisionId); if (!revision) continue; for (const entry of conformance.operationBindings) { const operation = operations(entry.operationId, revision); if (!operation) continue; const path = `${conformance.atomId}/${revision.revisionId}/${entry.operationId}`; let binding; try { binding = resolveForwardedBinding( workspace, conformance.atomId, revision.revisionId, entry.operationId, ).binding; } catch { continue; } // Ordinary binding validation reports unresolved references and cycles. if (entry.binding.kind === "interface") { const target = interfaces.get(entry.binding.interfaceRevisionId); if (target) supports(operation.execution, operations(entry.binding.operationId, target)?.execution, path); } if (binding.kind === "lifecycle") { if ( operation.execution?.kind !== "transaction" || operation.execution.targets.length !== 1 || operation.execution.targets[0] !== "server" ) fail( path, "Deletion requires an explicitly server-only transaction; partial replicas cannot enumerate incoming references", ); continue; } if (!operation.execution || operation.execution.kind === "remote") continue; if (binding.kind === "journal") { const expected = binding.primitive === "publish" ? "transaction" : "snapshot"; if (operation.execution.kind !== expected) fail(path, "Native journal operation requires the matching effect"); continue; } if (binding.kind === "subscription") { const expected = binding.primitive === "status" ? "snapshot" : "transaction"; if ( operation.execution.kind !== expected || (binding.primitive !== "status" && operation.execution.targets.some((target) => target !== "server")) ) fail(path, "Subscription controls require authoritative execution; status is a portable snapshot"); continue; } if (binding.kind === "package") { const provider = packages.get(binding.packageRevisionId)?.exports.find((e) => e.id === binding.exportId); supports(operation.execution, provider?.execution, path); } else { const read = binding.kind === "state" ? "read" : "resolve"; if (binding.primitive.startsWith("watch")) fail(path, "Portable operations cannot bind watch lifecycle primitives"); else if (operation.execution.kind === "snapshot" && binding.primitive !== read) fail(path, "Snapshot operations cannot bind write primitives"); } } } return issues; }