Build workspace agent, capability graph, and versioned cutovers

This commit is contained in:
2026-09-05 12:33:52 -07:00
parent c4d42a0ac5
commit ce793cc54f
55 changed files with 5600 additions and 2529 deletions
+473 -90
View File
@@ -27,6 +27,7 @@ import {
type InterfaceOperation,
type InterfaceOperationMode,
type InterfaceRevision,
type AtomDefinition,
type PackageExport,
type PackageReceiverRequirement,
type PackageRevision,
@@ -43,15 +44,19 @@ import { QuixosCapabilityParser,
type ConformanceDeclContext,
type DependencyBindingBlockContext,
type DependencyPortContext,
type DocumentContext,
type EdgeDeclContext,
type EdgeEndpointContext,
type InterfaceDeclContext,
type ExternalAtomDeclContext,
type ExternalInterfaceDeclContext,
type InterfaceResourceDeclContext,
type PackageConstructorExportContext,
type PackageDeclContext,
type PackageResourceDeclContext,
type PackageFunctionExportContext,
type PackageOperationExportContext,
type OperationMemberContext,
type SourceBlockContext,
type ResourceImportDeclContext,
type ResourcePreambleContext,
type StateDeclContext,
type TargetConstraintContext,
type ValueMemberContext,
@@ -61,6 +66,50 @@ import { QuixosCapabilityParser,
type WorkspaceDeclContext,
} from "./generated/QuixosCapabilityParser.js";
export type CapabilityResourceKind = "interface" | "package";
export type CapabilityResourceImport = {
kind: CapabilityResourceKind;
binding: string;
};
export type CapabilityExternalInterface = {
binding: string;
revisionId: InterfaceRevision["revisionId"];
};
export type CapabilityImportEnvironment = {
interfaces?: ReadonlyMap<string, InterfaceRevision>;
packages?: ReadonlyMap<string, PackageRevision>;
/** Complete resolved closure; direct maps above provide local authoring names. */
interfaceClosure?: readonly InterfaceRevision[];
packageClosure?: readonly PackageRevision[];
/** External nominal atoms required by imported resources. */
externalAtoms?: readonly AtomDefinition[];
/** External nominal interfaces supplied by the importing workspace. */
externalInterfaces?: readonly CapabilityExternalInterface[];
};
export type CapabilityResource =
| {
kind: "interface";
imports: CapabilityResourceImport[];
externalAtoms: AtomDefinition[];
externalInterfaces: CapabilityExternalInterface[];
revision: InterfaceRevision;
}
| {
kind: "package";
imports: CapabilityResourceImport[];
externalAtoms: AtomDefinition[];
externalInterfaces: CapabilityExternalInterface[];
revision: PackageRevision;
};
export type CapabilityResourceCompileResult =
| { ok: true; resource: CapabilityResource; diagnostics: [] }
| { ok: false; diagnostics: CapabilitySourceDiagnostic[] };
export type CapabilitySourceDiagnosticPhase =
| "syntax"
| "lowering"
@@ -81,6 +130,7 @@ export type CapabilitySourceCompileResult =
ok: true;
workspace: WorkspaceRevision;
plan: CompiledWorkspaceRevision;
imports: CapabilityResourceImport[];
diagnostics: [];
}
| {
@@ -90,6 +140,7 @@ export type CapabilitySourceCompileResult =
interface InterfaceSymbol {
revisionId: InterfaceRevision["revisionId"];
contractAvailable: boolean;
members: Map<
string,
{
@@ -220,11 +271,6 @@ const requireSymbol = <Value>(
return value;
};
const lowerSource = (context: SourceBlockContext): SourceRevision => ({
repository: stringValue(context.stringLiteral(0)),
commit: stringValue(context.stringLiteral(1)),
});
const lowerCardinality = (
context: { getText(): string } | null,
): EdgeCardinality => text(context) as EdgeCardinality;
@@ -534,7 +580,8 @@ const lowerOperationMember = (
const lowerInterface = (
state: LoweringState,
context: InterfaceDeclContext,
context: InterfaceResourceDeclContext,
source: SourceRevision,
): InterfaceRevision => {
const alias = identifier(context.identifier());
const members = context.interfaceMember().flatMap((entry) => {
@@ -572,7 +619,7 @@ const lowerInterface = (
interfaceId: capabilityId.interface(stringValue(context.stringLiteral(0))),
revisionId: symbol.revisionId,
displayName: alias,
source: lowerSource(context.sourceBlock()),
source,
members,
};
};
@@ -652,12 +699,21 @@ const lowerDependencyPort = (
context,
"interface",
);
if (target && !target.contractAvailable) {
loweringIssue(
state,
context,
"nominal-interface-port",
`Interface port ${name} needs the full ${targetName} contract; use import interface ${targetName}`,
);
return undefined;
}
return target
? {
id,
displayName: name,
requirement: {
kind: "receiver-interface",
kind: "interface",
interfaceRevisionId: target.revisionId,
},
}
@@ -815,7 +871,8 @@ const lowerPackageConstructor = (
const lowerPackage = (
state: LoweringState,
context: PackageDeclContext,
context: PackageResourceDeclContext,
source: SourceRevision,
): PackageRevision => {
const alias = identifier(context.identifier());
const exports = context.packageExport().map((exportContext) => {
@@ -837,7 +894,7 @@ const lowerPackage = (
packageId: capabilityId.package(stringValue(context.stringLiteral(0))),
revisionId: state.packages.get(alias)!.revisionId,
displayName: alias,
source: lowerSource(context.sourceBlock()),
source,
exports,
};
};
@@ -1064,7 +1121,7 @@ const lowerBoundDependencies = (
binding: { kind: "state", slotId: attachment.attachment.id, ...(via ? { via } : {}) },
}];
}
if (entry.EDGE(0)) {
if (entry.EDGE(0) && !entry.INTERFACE()) {
const attachmentName = identifier(entry.identifier(1));
const projectionName = identifier(entry.identifier(2));
const attachment = requireSymbol(
@@ -1119,13 +1176,18 @@ const lowerBoundDependencies = (
entry,
"interface",
);
const via = entry.VIA()
? traversal(identifier(entry.identifier(2)), identifier(entry.identifier(3)))
: undefined;
if (entry.VIA() && !via) return [];
return interfaceSymbol
? [
{
portId,
binding: {
kind: "receiver-interface",
kind: "interface",
interfaceRevisionId: interfaceSymbol.revisionId,
...(via ? { via } : {}),
},
},
]
@@ -1349,10 +1411,117 @@ const lowerConformance = (
};
};
const interfaceSymbolFor = (revision: InterfaceRevision): InterfaceSymbol => ({
revisionId: revision.revisionId,
contractAvailable: true,
members: new Map(revision.members.map((member) => [
member.displayName,
{
memberId: member.id,
operations: new Map(member.operations.map((operation) => [
operation.displayName,
operation.id,
])),
},
])),
});
const packageSymbolFor = (revision: PackageRevision): PackageSymbol => ({
revisionId: revision.revisionId,
exports: new Map(revision.exports.map((entry) => [
entry.displayName,
{
exportId: entry.id,
ports: new Map(entry.dependencyPorts.map((port) => [
port.displayName,
port.id,
])),
},
])),
});
const resourceImport = (
context: ResourceImportDeclContext,
): CapabilityResourceImport => ({
kind: context.INTERFACE() ? "interface" : "package",
binding: identifier(context.identifier()),
});
const registerImports = (
state: LoweringState,
contexts: readonly ResourceImportDeclContext[],
environment: CapabilityImportEnvironment,
) => contexts.map((context) => {
const imported = resourceImport(context);
if (imported.kind === "interface") {
const revision = environment.interfaces?.get(imported.binding);
if (!revision) {
loweringIssue(
state,
context,
"missing-import",
`No resolved interface is available for lock binding ${imported.binding}`,
);
} else {
declareSymbol(
state,
state.interfaces,
imported.binding,
interfaceSymbolFor(revision),
context,
"interface",
);
}
} else {
const revision = environment.packages?.get(imported.binding);
if (!revision) {
loweringIssue(
state,
context,
"missing-import",
`No resolved package is available for lock binding ${imported.binding}`,
);
} else {
declareSymbol(
state,
state.packages,
imported.binding,
packageSymbolFor(revision),
context,
"package",
);
}
}
return imported;
});
const uniqueExactRevisions = <Revision extends {
revisionId: string;
source: SourceRevision;
}>(revisions: readonly Revision[]): Revision[] => {
const seen = new Set<string>();
return revisions.filter((revision) => {
const key = `${revision.revisionId}\0${revision.source.repository}\0${revision.source.commit}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
};
const uniqueAtoms = (atoms: readonly AtomDefinition[]): AtomDefinition[] => {
const seen = new Set<string>();
return atoms.filter((atom) => {
if (seen.has(atom.id)) return false;
seen.add(atom.id);
return true;
});
};
const lowerWorkspace = (
state: LoweringState,
context: WorkspaceDeclContext,
): WorkspaceRevision => {
environment: CapabilityImportEnvironment,
): { workspace: WorkspaceRevision; imports: CapabilityResourceImport[] } => {
const items = context.workspaceItem();
for (const item of items) {
@@ -1368,41 +1537,14 @@ const lowerWorkspace = (
);
continue;
}
const interfaceContext = item.interfaceDecl();
if (interfaceContext) {
declareSymbol(
state,
state.interfaces,
identifier(interfaceContext.identifier()),
{
revisionId: capabilityId.interfaceRevision(
stringValue(interfaceContext.stringLiteral(1)),
),
members: new Map(),
},
interfaceContext,
"interface",
);
continue;
}
const packageContext = item.packageDecl();
if (packageContext) {
declareSymbol(
state,
state.packages,
identifier(packageContext.identifier()),
{
revisionId: capabilityId.packageRevision(
stringValue(packageContext.stringLiteral(1)),
),
exports: new Map(),
},
packageContext,
"package",
);
}
}
const imports = registerImports(
state,
items.flatMap((item) => item.resourceImportDecl() ?? []),
environment,
);
const atoms = items.flatMap((item) => {
const atom = item.atomDecl();
if (!atom) {
@@ -1422,15 +1564,15 @@ const lowerWorkspace = (
: [];
});
const interfaceImports = items.flatMap((item) => {
const interfaceContext = item.interfaceDecl();
return interfaceContext ? [lowerInterface(state, interfaceContext)] : [];
});
const interfaceImports = uniqueExactRevisions([
...(environment.interfaceClosure ?? []),
...[...(environment.interfaces?.values() ?? [])],
]);
const packageImports = items.flatMap((item) => {
const packageContext = item.packageDecl();
return packageContext ? [lowerPackage(state, packageContext)] : [];
});
const packageImports = uniqueExactRevisions([
...(environment.packageClosure ?? []),
...[...(environment.packages?.values() ?? [])],
]);
const sharedAttachments: PersistentAttachment[] = [];
const privateAttachments = new Map<
@@ -1520,23 +1662,26 @@ const lowerWorkspace = (
});
return {
id: capabilityId.workspaceRevision(stringValue(context.stringLiteral(1))),
workspaceId: capabilityId.workspace(stringValue(context.stringLiteral(0))),
parentRevisionIds: [],
sourceRootCommit: stringValue(context.stringLiteral(2)),
atoms,
sharedAttachments,
interfaceImports,
packageImports,
conformances,
constructors,
imports,
workspace: {
id: capabilityId.workspaceRevision(stringValue(context.stringLiteral(1))),
workspaceId: capabilityId.workspace(stringValue(context.stringLiteral(0))),
parentRevisionIds: [],
sourceRootCommit: stringValue(context.stringLiteral(2)),
atoms,
sharedAttachments,
interfaceImports,
packageImports,
conformances,
constructors,
},
};
};
export const compileCapabilitySource = (
const parseDocument = (
source: string,
fileName = "<memory>",
): CapabilitySourceCompileResult => {
fileName: string,
): { tree: DocumentContext; diagnostics: CapabilitySourceDiagnostic[] } => {
const diagnostics: CapabilitySourceDiagnostic[] = [];
const listener = new SyntaxErrorListener(fileName, diagnostics);
const lexer = new QuixosCapabilityLexer(CharStream.fromString(source));
@@ -1548,36 +1693,274 @@ export const compileCapabilitySource = (
parser.removeErrorListeners();
parser.addErrorListener(listener);
const tree = parser.document();
return { tree, diagnostics };
};
const newLoweringState = (
fileName: string,
diagnostics: CapabilitySourceDiagnostic[],
): LoweringState => ({
fileName,
diagnostics,
atoms: new Map(),
interfaces: new Map(),
packages: new Map(),
attachments: new Map(),
});
const validationDiagnostics = (
fileName: string,
issues: ReturnType<typeof compileWorkspaceRevision> extends infer _Result
? Array<{ code: string; message: string; path: string }>
: never,
): CapabilitySourceDiagnostic[] => issues.map((entry) => ({
phase: "validation",
code: entry.code,
message: entry.message,
fileName,
line: 0,
column: 0,
path: entry.path,
}));
const externalAtomsFrom = (
state: LoweringState,
contexts: readonly ExternalAtomDeclContext[],
): AtomDefinition[] => contexts.map((context) => {
const atom: AtomDefinition = {
id: capabilityId.atom(stringValue(context.stringLiteral())),
displayName: identifier(context.identifier()),
};
declareSymbol(
state,
state.atoms,
atom.displayName,
atom.id,
context,
"external atom",
);
return atom;
});
const externalInterfacesFrom = (
state: LoweringState,
contexts: readonly ExternalInterfaceDeclContext[],
): CapabilityExternalInterface[] => contexts.map((context) => {
const requirement: CapabilityExternalInterface = {
binding: identifier(context.identifier()),
revisionId: capabilityId.interfaceRevision(stringValue(context.stringLiteral())),
};
declareSymbol(
state,
state.interfaces,
requirement.binding,
{
revisionId: requirement.revisionId,
contractAvailable: false,
members: new Map(),
},
context,
"external interface",
);
return requirement;
});
const resourcePreambleParts = (contexts: readonly ResourcePreambleContext[]) => ({
imports: contexts.flatMap((context) => context.resourceImportDecl() ?? []),
atoms: contexts.flatMap((context) => context.externalAtomDecl() ?? []),
interfaces: contexts.flatMap((context) => context.externalInterfaceDecl() ?? []),
});
const resourceValidationWorkspace = (
resource: CapabilityResource,
environment: CapabilityImportEnvironment,
): WorkspaceRevision => {
const resolvedInterfaceIds = new Set([
...(environment.interfaceClosure ?? []).map((revision) => revision.revisionId),
...[...(environment.interfaces?.values() ?? [])].map((revision) => revision.revisionId),
...(resource.kind === "interface" ? [resource.revision.revisionId] : []),
]);
return ({
id: capabilityId.workspaceRevision("workspace-revision:resource-validation"),
workspaceId: capabilityId.workspace("workspace:resource-validation"),
parentRevisionIds: [],
sourceRootCommit: resource.revision.source.commit,
atoms: uniqueAtoms([
...(environment.externalAtoms ?? []),
...resource.externalAtoms,
]),
sharedAttachments: [],
interfaceImports: uniqueExactRevisions([
...(environment.interfaceClosure ?? []),
...[...(environment.interfaces?.values() ?? [])],
...(resource.kind === "interface" ? [resource.revision] : []),
...resource.externalInterfaces
.filter((requirement) => !resolvedInterfaceIds.has(requirement.revisionId))
.map((requirement): InterfaceRevision => ({
interfaceId: capabilityId.interface(`external:${requirement.revisionId}`),
revisionId: requirement.revisionId,
displayName: requirement.binding,
source: {
repository: `https://external.invalid/${encodeURIComponent(requirement.revisionId)}.git`,
commit: "0".repeat(40),
},
members: [],
})),
]),
packageImports: uniqueExactRevisions([
...(environment.packageClosure ?? []),
...[...(environment.packages?.values() ?? [])],
...(resource.kind === "package" ? [resource.revision] : []),
]),
conformances: [],
constructors: [],
});
};
export const compileCapabilityResourceSource = (
sourceText: string,
options: {
source: SourceRevision;
fileName?: string;
environment?: CapabilityImportEnvironment;
},
): CapabilityResourceCompileResult => {
const fileName = options.fileName ?? "<memory>";
const environment = options.environment ?? {};
const { tree, diagnostics } = parseDocument(sourceText, fileName);
if (diagnostics.length > 0) {
return { ok: false, diagnostics };
}
const state: LoweringState = {
fileName,
diagnostics,
atoms: new Map(),
interfaces: new Map(),
packages: new Map(),
attachments: new Map(),
};
const workspace = lowerWorkspace(state, tree.workspaceDecl());
const interfaceContext = tree.interfaceResourceDecl();
const packageContext = tree.packageResourceDecl();
if (!interfaceContext && !packageContext) {
return {
ok: false,
diagnostics: [{
phase: "lowering",
code: "expected-resource",
message: "Expected a standalone interface or package resource document",
fileName,
line: 1,
column: 0,
}],
};
}
const state = newLoweringState(fileName, diagnostics);
const preamble = resourcePreambleParts(
(interfaceContext ?? packageContext)!.resourcePreamble(),
);
const externalAtoms = externalAtomsFrom(state, preamble.atoms);
const imports = registerImports(state, preamble.imports, environment);
const externalInterfaces = externalInterfacesFrom(state, preamble.interfaces);
let resource: CapabilityResource;
if (interfaceContext) {
const alias = identifier(interfaceContext.identifier());
declareSymbol(
state,
state.interfaces,
alias,
{
revisionId: capabilityId.interfaceRevision(
stringValue(interfaceContext.stringLiteral(1)),
),
contractAvailable: true,
members: new Map(),
},
interfaceContext,
"interface",
);
resource = {
kind: "interface",
imports,
externalAtoms,
externalInterfaces,
revision: lowerInterface(state, interfaceContext, options.source),
};
} else {
const context = packageContext!;
const alias = identifier(context.identifier());
declareSymbol(
state,
state.packages,
alias,
{
revisionId: capabilityId.packageRevision(
stringValue(context.stringLiteral(1)),
),
exports: new Map(),
},
context,
"package",
);
resource = {
kind: "package",
imports,
externalAtoms,
externalInterfaces,
revision: lowerPackage(state, context, options.source),
};
}
if (diagnostics.length > 0) {
return { ok: false, diagnostics };
}
const compiled = compileWorkspaceRevision(workspace);
const compiled = compileWorkspaceRevision(
resourceValidationWorkspace(resource, environment),
);
if (!compiled.ok) {
return {
ok: false,
diagnostics: compiled.issues.map((entry) => ({
phase: "validation",
code: entry.code,
message: entry.message,
fileName,
line: 0,
column: 0,
path: entry.path,
})),
diagnostics: validationDiagnostics(fileName, compiled.issues),
};
}
return { ok: true, workspace, plan: compiled.plan, diagnostics: [] };
return { ok: true, resource, diagnostics: [] };
};
export const compileCapabilitySource = (
source: string,
fileName = "<memory>",
environment: CapabilityImportEnvironment = {},
): CapabilitySourceCompileResult => {
const { tree, diagnostics } = parseDocument(source, fileName);
if (diagnostics.length > 0) {
return { ok: false, diagnostics };
}
const workspaceContext = tree.workspaceDecl();
if (!workspaceContext) {
return {
ok: false,
diagnostics: [{
phase: "lowering",
code: "expected-workspace",
message: "Expected a workspace document",
fileName,
line: 1,
column: 0,
}],
};
}
const state = newLoweringState(fileName, diagnostics);
const lowered = lowerWorkspace(state, workspaceContext, environment);
if (diagnostics.length > 0) {
return { ok: false, diagnostics };
}
const compiled = compileWorkspaceRevision(lowered.workspace);
if (!compiled.ok) {
return {
ok: false,
diagnostics: validationDiagnostics(fileName, compiled.issues),
};
}
return {
ok: true,
workspace: lowered.workspace,
plan: compiled.plan,
imports: lowered.imports,
diagnostics: [],
};
};