Add local-first execution contracts to the capability language

This commit is contained in:
Timothy J. Aveni
2026-09-19 22:09:40 -07:00
parent 757531bb63
commit 03ac7f8557
24 changed files with 3872 additions and 2760 deletions
+106 -16
View File
@@ -13,6 +13,7 @@ import {
cardinalityValueType,
compileWorkspaceRevision,
valueType,
type ExecutionContract,
type AtomId,
type BoundDependency,
type CompiledWorkspaceRevision,
@@ -57,6 +58,7 @@ import { specializeQueryTemplate } from "../query/templates.js";
import { QuixosCapabilityLexer } from "./generated/QuixosCapabilityLexer.js";
import {
QuixosCapabilityParser,
type ExecutionClauseContext,
type AttachmentDeclContext,
type ConformanceDeclContext,
type DependencyBindingBlockContext,
@@ -361,6 +363,38 @@ const lowerValueType = (state: LoweringState, context: ValueTypeContext | null):
return valueType.unit;
};
const lowerSelectedOperations = (
state: LoweringState,
context: DependencyPortContext,
target: InterfaceSymbol | undefined,
): { operations?: InterfaceOperation["id"][] } => {
const selected = context.selectedOperations();
if (!selected) return {};
const operations = selected.memberOperationRef().flatMap((reference) => {
const name = identifier(reference.identifier());
const member = target && requireSymbol(state, target.members, name, reference, "interface member");
const operation =
member &&
requireSymbol(state, member.operations, text(reference.operationName()), reference, "interface operation");
return operation ? [operation] : [];
});
if (new Set(operations).size !== operations.length)
loweringIssue(state, context, "duplicate-operation-selection", "Interface operation selection contains duplicates");
return { operations };
};
const lowerExecution = (context: ExecutionClauseContext | null): { execution?: ExecutionContract } => {
if (!context) return {};
return {
execution: context.REMOTE()
? { kind: "remote" }
: {
kind: context.SNAPSHOT() ? "snapshot" : "transaction",
targets: context.identifierList()!.identifier().map(identifier),
},
};
};
const valueOperation = (displayName: string, id: InterfaceOperation["id"], value: ValueType): InterfaceOperation => {
switch (displayName) {
case "get":
@@ -456,13 +490,15 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
const operations: InterfaceOperation[] = [];
for (const operation of context.valueMemberOperation()) {
if (operation.GET()) {
operations.push(
valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
);
operations.push({
...valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
});
} else if (operation.SET()) {
operations.push(
valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
);
operations.push({
...valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
});
} else {
operations.push(
valueOperation("watch-start", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
@@ -475,7 +511,11 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
...(context.queryReadContract()
? {
queryRead: {
execution: context.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
execution: context.queryReadContract()!.RPC()
? ("rpc-permitted" as const)
: context.queryReadContract()!.PORTABLE()
? ("portable" as const)
: ("native" as const),
},
}
: {}),
@@ -540,6 +580,8 @@ const lowerRelationshipMember = (
),
);
}
if (operation.executionClause())
Object.assign(operations[operations.length - 1]!, lowerExecution(operation.executionClause()));
}
return {
kind: "relationship",
@@ -547,7 +589,11 @@ const lowerRelationshipMember = (
...(context.queryReadContract()
? {
queryRead: {
execution: context.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
execution: context.queryReadContract()!.RPC()
? ("rpc-permitted" as const)
: context.queryReadContract()!.PORTABLE()
? ("portable" as const)
: ("native" as const),
},
}
: {}),
@@ -577,6 +623,7 @@ const lowerOperationMember = (state: LoweringState, context: OperationMemberCont
outputType,
mode: "call",
...(context.STATIC() ? { scope: "class" as const } : {}),
...lowerExecution(context.executionClause()),
},
],
};
@@ -699,7 +746,11 @@ const lowerInterfaceTemplate = (
...(value.queryReadContract()
? {
queryRead: {
execution: value.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
execution: value.queryReadContract()!.RPC()
? ("rpc-permitted" as const)
: value.queryReadContract()!.PORTABLE()
? ("portable" as const)
: ("native" as const),
},
}
: {}),
@@ -708,8 +759,14 @@ const lowerInterfaceTemplate = (
valueType: type,
operations: value.valueMemberOperation().flatMap((operation) => {
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
if (operation.GET()) return [signature("get", id, valueType.unit, type)];
if (operation.SET()) return [signature("set", id, type, valueType.unit)];
if (operation.GET())
return [
{ ...signature("get", id, valueType.unit, type), ...lowerExecution(operation.executionClause()) },
];
if (operation.SET())
return [
{ ...signature("set", id, type, valueType.unit), ...lowerExecution(operation.executionClause()) },
];
return [
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type),
signature(
@@ -742,6 +799,7 @@ const lowerInterfaceTemplate = (
outputType,
),
...(operation.STATIC() ? { scope: "class" as const } : {}),
...lowerExecution(operation.executionClause()),
},
],
};
@@ -762,7 +820,11 @@ const lowerInterfaceTemplate = (
...(relationship.queryReadContract()
? {
queryRead: {
execution: relationship.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
execution: relationship.queryReadContract()!.RPC()
? ("rpc-permitted" as const)
: relationship.queryReadContract()!.PORTABLE()
? ("portable" as const)
: ("native" as const),
},
}
: {}),
@@ -773,9 +835,17 @@ const lowerInterfaceTemplate = (
ordered: Boolean(relationship.ORDERED()),
operations: relationship.relationshipOperation().flatMap((operation) => {
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
if (operation.RESOLVE()) return [signature("resolve", id, valueType.unit, resolved)];
if (operation.RESOLVE())
return [
{ ...signature("resolve", id, valueType.unit, resolved), ...lowerExecution(operation.executionClause()) },
];
if (operation.CONNECT() || operation.DISCONNECT())
return [signature(operation.CONNECT() ? "connect" : "disconnect", id, targetType, valueType.unit)];
return [
{
...signature(operation.CONNECT() ? "connect" : "disconnect", id, targetType, valueType.unit),
...lowerExecution(operation.executionClause()),
},
];
return [
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", resolved),
signature(
@@ -933,6 +1003,7 @@ const lowerDependencyPort = (state: LoweringState, context: DependencyPortContex
requirement: {
kind: "interface",
interfaceRevisionId: target.revisionId,
...lowerSelectedOperations(state, context, target),
},
}
: undefined;
@@ -1036,6 +1107,7 @@ const lowerPackageOperation = (
inputType: lowerValueType(state, context.valueType(0)),
outputType: lowerValueType(state, context.valueType(1)),
mode: text(context.operationMode()) as InterfaceOperationMode,
...lowerExecution(context.executionClause()),
eventType: event ? lowerValueType(state, event.valueType()) : undefined,
receiverRequirement: lowerReceiver(state, context.receiverRequirement()),
dependencyPorts: ports,
@@ -1052,6 +1124,7 @@ const lowerPackageFunction = (
const alias = identifier(context.identifier());
const entry: PackageExport = {
kind: "function",
...lowerExecution(context.executionClause()),
id: capabilityId.packageExport(stringValue(context.stringLiteral())),
displayName: alias,
inputType: lowerValueType(state, context.valueType(0)),
@@ -1143,6 +1216,7 @@ const lowerGenericPackageExport = (
requirement: {
kind: "interface",
application: state.types.interfaceByName(identifier(port.identifier(1)), port.typeArguments()),
...lowerSelectedOperations(state, port, state.interfaces.get(identifier(port.identifier(1)))),
},
};
const name = identifier(port.identifier(1)),
@@ -1186,6 +1260,7 @@ const lowerGenericPackageExport = (
displayName: identifier(context.identifier()),
parameters,
kind: operation ? "operation" : "function",
...lowerExecution(context.executionClause()),
inputType: state.types.expression(context.valueType(0)!),
outputType: state.types.expression(context.valueType(1)!),
dependencyPorts,
@@ -1275,7 +1350,22 @@ const lowerPackage = (
};
const assigned = new Set<string>();
for (const clause of query.queryClause()) {
if (clause.FRAGMENTS()) declaration.fragments.push(stringValue(clause.stringLiteral()!));
if (clause.REPLICA() || clause.LOCAL()) {
if (declaration.placement)
loweringIssue(state, clause, "duplicate-query-placement", "Declare query placement once");
else if (clause.REPLICA()) declaration.placement = { kind: "replica" };
else {
const name = identifier(clause.identifier(0));
const sourceQueryId = requireSymbol(
state,
state.packages.get(alias)!.queries!,
name,
clause,
"replica query",
);
if (sourceQueryId) declaration.placement = { kind: "local", sourceQueryId };
}
} else if (clause.FRAGMENTS()) declaration.fragments.push(stringValue(clause.stringLiteral()!));
else if (clause.VIEW()) {
const name = identifier(clause.identifier(0));
if (parameters.length) {
@@ -1952,7 +2042,7 @@ const interfaceSymbolFor = (revision: InterfaceRevision): InterfaceSymbol => ({
revisionId: revision.revisionId,
contractAvailable: true,
members: new Map(
revision.members.map((member) => [
(revision.template?.members ?? revision.members).map((member) => [
member.displayName,
{
memberId: member.id,