Expose authoritative lifecycle deletion through capability bindings

This commit is contained in:
Timothy J. Aveni
2026-09-20 22:48:09 -07:00
parent d80464d285
commit 115976cacd
15 changed files with 1837 additions and 1698 deletions
+1
View File
@@ -195,6 +195,7 @@ export const runtimeContracts = (workspace: WorkspaceRevision): RuntimeContract[
}
};
const binding = (parent: GraphNode, value: Binding, atomId: string, context: unknown) => {
if (value.kind === "lifecycle") return;
if (value.kind !== "package") {
dependency(parent, value, atomId);
return;
+14 -1
View File
@@ -125,7 +125,7 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
if (!revision) continue;
for (const entry of conformance.operationBindings) {
const operation = operations(entry.operationId, revision);
if (!operation?.execution || operation.execution.kind === "remote") continue;
if (!operation) continue;
const path = `${conformance.atomId}/${revision.revisionId}/${entry.operationId}`;
let binding;
try {
@@ -142,6 +142,19 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
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 === "package") {
const provider = packages.get(binding.packageRevisionId)?.exports.find((e) => e.id === binding.exportId);
supports(operation.execution, provider?.execution, path);
+1
View File
@@ -358,6 +358,7 @@ export interface RelationshipMaterialization {
}
export type Binding =
| { kind: "lifecycle"; primitive: "delete" }
| {
kind: "interface";
interfaceRevisionId: InterfaceRevisionId;
+21 -1
View File
@@ -70,6 +70,7 @@ export type CapabilityValidationIssueCode =
| "invalid-attachment"
| "invalid-state-binding"
| "invalid-edge-binding"
| "invalid-lifecycle-binding"
| "invalid-package-binding"
| "invalid-forwarding-binding"
| "invalid-dependency-binding"
@@ -1696,6 +1697,24 @@ const validateConformances = (
);
continue;
}
if (binding.kind === "lifecycle") {
if (
binding.primitive !== "delete" ||
operationEntry.member.kind !== "operation" ||
operation.mode !== "call" ||
operation.inputType.kind !== "builtin" ||
operation.inputType.name !== "unit" ||
operation.outputType.kind !== "builtin" ||
operation.outputType.name !== "unit"
)
issue(
issues,
"invalid-lifecycle-binding",
bindingPath,
"Deletion implements an object call from unit to unit",
);
continue;
}
if (binding.kind === "state") {
const attachment = findAttachment(indexes, "state", binding.slotId);
if (!attachment || attachment.attachment.kind !== "state") {
@@ -2324,6 +2343,7 @@ export const resolveOperationPlan = (
attachment: owned as OwnedAttachment & { attachment: EdgeDefinition },
};
}
if (binding.kind === "lifecycle") return undefined; // Old runtime has no lifecycle provider.
const packageRevision = plan.packages.get(binding.packageRevisionId);
const packageExport = packageRevision?.exports.find(
(entry): entry is PackageOperationExport | PackageFunctionExport =>
@@ -2469,7 +2489,7 @@ export const computeCapabilityClosure = (
attachments.add(binding.edgeTypeId);
} else if (binding.kind === "interface") {
queued.push({ atomId: root.atomId, interfaceRevisionId: binding.interfaceRevisionId });
} else {
} else if (binding.kind === "package") {
packages.add(binding.packageRevisionId);
includeDependencies(root.atomId, binding.dependencies);
const packageRevision = plan.packages.get(binding.packageRevisionId);