Files
quixos-protocol/src/capability-language/assembly.ts
T
Timothy J. Aveni 01ca965c7f Make workspace authoring converge through immutable Nix candidates
Coordinate registered resource edits bottom-up into retained exact remote sources.
Use one Nix-owned source graph for provisional checking, template publication,
explicit baseline upgrades and host activation; retain independent runtime pins.

Add scoped contract inspection, historical recovery, derived worklists, crash-safe
locks, named dependency adoption and plain-QX structural editing. Repair TODO
ownership and template instantiation, and document the supported agent workflow.

Validated with protocol and command suites, real jj/Nix convergence and cache
checks, TS/React installed-command acceptance, and fresh TODO first-edit acceptance.
No live deployment or public publication performed. Props projection generation
and a one-command rich feature generator remain explicitly outside this delivery.
2026-09-14 12:25:47 -07:00

387 lines
14 KiB
TypeScript

import { readFile, realpath } from "node:fs/promises";
import path from "node:path";
import { loadQxSources, resolveQxSources } from "./source-loader.js";
import {
capabilityId,
compileWorkspaceRevision,
validateMigrationCatalog,
contentDigest,
type AtomDefinition,
type CompiledWorkspaceRevision,
type InterfaceRevision,
type PackageRevision,
type SourceRevision,
type WorkspaceRevision,
} from "../capability-model/index.js";
import {
loadQuixosLock,
type GitSource,
type LockedResource,
type QuixosRepositoryLock,
} from "../resource-lock/index.js";
import {
compileCapabilityResourceSource,
compileCapabilitySource,
type CapabilityImportEnvironment,
type CapabilityResource,
type CapabilityResourceImport,
} from "./parser.js";
export type CapabilityRepositorySnapshot = {
directory: string;
};
export type CapabilityRepositoryResolver = (
source: GitSource,
kind: LockedResource["kind"],
) => Promise<CapabilityRepositorySnapshot>;
export type ResolvedCapabilityResource = {
key: string;
kind: LockedResource["kind"];
source: GitSource;
directory: string;
lock: QuixosRepositoryLock;
resource: CapabilityResource;
dependencies: ReadonlyMap<string, ResolvedCapabilityResource>;
};
export type CompiledWorkspaceRepository = {
workspace: WorkspaceRevision;
plan: CompiledWorkspaceRevision;
lock: QuixosRepositoryLock;
resources: ResolvedCapabilityResource[];
directResources: ReadonlyMap<string, ResolvedCapabilityResource>;
};
export type CompiledCapabilityResourceRepository = {
resource: CapabilityResource;
lock: QuixosRepositoryLock;
resources: ResolvedCapabilityResource[];
directResources: ReadonlyMap<string, ResolvedCapabilityResource>;
};
const sourceKey = (kind: LockedResource["kind"], source: GitSource) =>
`${kind}\0${source.repository}\0${source.commit.toLowerCase()}`;
const bindingKey = (kind: LockedResource["kind"], binding: string) =>
`${kind}\0${binding}`;
const importsKey = (entry: CapabilityResourceImport | LockedResource) =>
bindingKey(entry.kind, entry.binding);
const assertImportsMatchLock = (
label: string,
imports: readonly CapabilityResourceImport[],
resources: readonly LockedResource[],
) => {
const authored = [...imports].map(importsKey).sort();
const locked = [...resources].map(importsKey).sort();
if (
authored.length !== locked.length ||
authored.some((entry, index) => entry !== locked[index])
) {
throw new Error(
`${label} imports do not match quixos.lock:\n` +
`authored: ${authored.join(", ") || "none"}\n` +
`locked: ${locked.join(", ") || "none"}`,
);
}
};
const exactRevisions = <Revision extends {
revisionId: string;
source: SourceRevision;
}>(revisions: readonly 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 exactAtoms = (atoms: readonly AtomDefinition[]) => {
const seen = new Set<string>();
return atoms.filter((atom) => {
if (seen.has(atom.id)) return false;
seen.add(atom.id);
return true;
});
};
const diagnosticsMessage = (
label: string,
diagnostics: readonly {
fileName: string;
line: number;
column: number;
phase: string;
code: string;
message: string;
path?: string;
}[],
) => `${label} did not compile:\n${diagnostics.map((entry) => {
const location = entry.line > 0
? `${entry.fileName}:${entry.line}:${entry.column + 1}`
: `${entry.fileName}${entry.path ? `:${entry.path}` : ""}`;
return `${location}: ${entry.phase} ${entry.code}: ${entry.message}`;
}).join("\n")}`;
const revisionFor = (node: ResolvedCapabilityResource) =>
node.resource.revision;
const resourceClosure = (
roots: readonly ResolvedCapabilityResource[],
): ResolvedCapabilityResource[] => {
const result: ResolvedCapabilityResource[] = [];
const seen = new Set<string>();
const visit = (node: ResolvedCapabilityResource) => {
if (seen.has(node.key)) return;
seen.add(node.key);
for (const dependency of node.dependencies.values()) visit(dependency);
result.push(node);
};
for (const root of roots) visit(root);
return result;
};
const environmentFor = (
direct: readonly [LockedResource, ResolvedCapabilityResource][],
closure: readonly ResolvedCapabilityResource[],
): CapabilityImportEnvironment => ({
interfaces: new Map(direct.flatMap(([locked, node]) =>
node.resource.kind === "interface"
? [[locked.binding, node.resource.revision] as const]
: [])),
packages: new Map(direct.flatMap(([locked, node]) =>
node.resource.kind === "package"
? [[locked.binding, node.resource.revision] as const]
: [])),
interfaceClosure: exactRevisions(closure.flatMap((node) =>
node.resource.kind === "interface" ? [node.resource.revision] : [])),
packageClosure: exactRevisions(closure.flatMap((node) =>
node.resource.kind === "package" ? [node.resource.revision] : [])),
externalAtoms: exactAtoms(closure.flatMap((node) => node.resource.externalAtoms)),
});
const createResourceGraphResolver = (
quixosCommit: string,
resolveResource: CapabilityRepositoryResolver,
) => {
const resolved = new Map<string, Promise<ResolvedCapabilityResource>>();
const active: string[] = [];
const visit = async (
locked: LockedResource,
suppliedSnapshot?: CapabilityRepositorySnapshot,
): Promise<ResolvedCapabilityResource> => {
const key = sourceKey(locked.kind, locked.source);
const cached = resolved.get(key);
if (cached) {
if (active.includes(key)) {
throw new Error(`Resource dependency cycle: ${[...active, key].join(" -> ")}`);
}
return await cached;
}
const pending = (async () => {
active.push(key);
try {
const snapshot = suppliedSnapshot ?? await resolveResource(locked.source, locked.kind);
const lockResult = await loadQuixosLock(path.join(snapshot.directory, "quixos.lock"));
if (!lockResult.ok) {
throw new Error(diagnosticsMessage(`${locked.kind} ${locked.binding} lock`, lockResult.diagnostics));
}
if (lockResult.lock.quixos.policy) {
throw new Error(
`${locked.kind} ${locked.binding} lock declares workspace Quixos policy ` +
`${lockResult.lock.quixos.policy}; resource locks may only declare their exact authored-against commit`,
);
}
if (lockResult.lock.quixos.commit.toLowerCase() !== quixosCommit.toLowerCase()) {
throw new Error(
`${locked.kind} ${locked.binding} selects Quixos ${lockResult.lock.quixos.commit}, ` +
`but the repository graph selects ${quixosCommit}`,
);
}
const directPairs: Array<[LockedResource, ResolvedCapabilityResource]> = [];
for (const dependency of lockResult.lock.resources) {
directPairs.push([dependency, await visit(dependency)]);
}
const dependencyClosure = resourceClosure(
directPairs.map(([, node]) => node),
);
const environment = environmentFor(directPairs, dependencyClosure);
const manifestName = locked.kind === "interface" ? "interface.qx" : "package.qx";
const manifestPath = path.join(snapshot.directory, manifestName);
const sourceText = await readFile(manifestPath, "utf8");
const compiled = compileCapabilityResourceSource(sourceText, {
source: locked.source,
fileName: manifestPath,
environment,
});
if (!compiled.ok) {
throw new Error(diagnosticsMessage(`${locked.kind} ${locked.binding}`, compiled.diagnostics));
}
if (compiled.resource.kind !== locked.kind) {
throw new Error(
`${manifestPath} declares ${compiled.resource.kind}, not ${locked.kind}`,
);
}
assertImportsMatchLock(manifestPath, compiled.resource.imports, lockResult.lock.resources);
if (compiled.resource.kind === "package") {
let catalogText: string | undefined;
try { catalogText = await readFile(path.join(snapshot.directory, "quixos.migrations.json"), "utf8"); }
catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; }
if (catalogText !== undefined) {
const catalog = validateMigrationCatalog(JSON.parse(catalogText), new Set(compiled.resource.revision.exports.map((entry) => entry.id)));
const root = await realpath(snapshot.directory);
for (const migration of catalog.migrations) {
const implementation = await realpath(path.join(root, migration.implementation.file));
if (!implementation.startsWith(`${root}${path.sep}`)) throw new Error("Migration implementation escapes its package");
if (contentDigest(await readFile(implementation, "utf8")) !== migration.implementation.digest) throw new Error(`Migration implementation digest mismatch: ${migration.id}`);
}
compiled.resource.revision.migrationCatalog = catalog;
}
}
return {
key,
kind: locked.kind,
source: locked.source,
directory: snapshot.directory,
lock: lockResult.lock,
resource: compiled.resource,
dependencies: new Map(directPairs.map(([dependency, node]) => [
bindingKey(dependency.kind, dependency.binding),
node,
])),
};
} finally {
active.pop();
}
})();
resolved.set(key, pending);
return await pending;
};
return {
visit,
nodes: async () => await Promise.all(resolved.values()),
};
};
export const compileCapabilityResourceRepository = async (options: {
rootDirectory: string;
kind: LockedResource["kind"];
source: GitSource;
resolveResource: CapabilityRepositoryResolver;
}): Promise<CompiledCapabilityResourceRepository> => {
const lockResult = await loadQuixosLock(path.join(options.rootDirectory, "quixos.lock"));
if (!lockResult.ok) {
throw new Error(diagnosticsMessage("Resource lock", lockResult.diagnostics));
}
const resolver = createResourceGraphResolver(
lockResult.lock.quixos.commit,
options.resolveResource,
);
const root = await resolver.visit({
kind: options.kind,
binding: "<root>",
source: options.source,
}, { directory: options.rootDirectory });
return {
resource: root.resource,
lock: root.lock,
resources: await resolver.nodes(),
directResources: root.dependencies,
};
};
export const compileWorkspaceRepository = async (options: {
rootDirectory: string;
resolveResource: CapabilityRepositoryResolver;
workspaceId?: string;
workspaceRevisionId?: string;
sourceRootCommit?: string;
/** An editor's proposed source snapshot; locks and dependency revisions remain exact. */
readSource?: (name: string) => Promise<string>;
}): Promise<CompiledWorkspaceRepository> => {
const rootLockResult = await loadQuixosLock(
path.join(options.rootDirectory, "quixos.lock"),
);
if (!rootLockResult.ok) {
throw new Error(diagnosticsMessage("Workspace lock", rootLockResult.diagnostics));
}
const rootLock = rootLockResult.lock;
const resolver = createResourceGraphResolver(rootLock.quixos.commit, options.resolveResource);
const directPairs: Array<[LockedResource, ResolvedCapabilityResource]> = [];
for (const locked of rootLock.resources) {
directPairs.push([locked, await resolver.visit(locked)]);
}
const nodes = await resolver.nodes();
const environment = environmentFor(directPairs, nodes);
const workspacePath = path.join(options.rootDirectory, "workspace.qx");
const sources = options.readSource ? await resolveQxSources(options.readSource) : await loadQxSources(options.rootDirectory);
const compiled = compileCapabilitySource(sources.source, workspacePath, environment);
if (!compiled.ok) {
throw new Error(diagnosticsMessage("Workspace", compiled.diagnostics.map((diagnostic) =>
diagnostic.line > 0 ? { ...diagnostic, ...sources.originalPosition(diagnostic.line, diagnostic.column) } : diagnostic)));
}
assertImportsMatchLock(workspacePath, compiled.imports, rootLock.resources);
const availableInterfaceIds = new Set(
nodes.flatMap((node) => node.resource.kind === "interface"
? [node.resource.revision.revisionId]
: []),
);
const availableAtomIds = new Set(compiled.workspace.atoms.map((atom) => atom.id));
for (const node of nodes) {
for (const requirement of node.resource.externalInterfaces) {
if (!availableInterfaceIds.has(requirement.revisionId)) {
throw new Error(
`${node.kind} ${node.resource.revision.displayName} requires external interface ` +
`${requirement.binding} (${requirement.revisionId}), but the workspace resource graph does not provide it`,
);
}
}
for (const atom of node.resource.externalAtoms) {
if (!availableAtomIds.has(atom.id)) {
throw new Error(
`${node.kind} ${node.resource.revision.displayName} requires external atom ` +
`${atom.displayName} (${atom.id}), but the workspace does not define it`,
);
}
}
}
const workspace: WorkspaceRevision = {
...compiled.workspace,
...(options.workspaceId
? { workspaceId: capabilityId.workspace(options.workspaceId) }
: {}),
...(options.workspaceRevisionId
? { id: capabilityId.workspaceRevision(options.workspaceRevisionId) }
: options.sourceRootCommit ? { id: capabilityId.workspaceRevision(`workspace-revision:${options.workspaceId ?? compiled.workspace.workspaceId}:${options.sourceRootCommit}`) } : {}),
...(options.sourceRootCommit
? { sourceRootCommit: options.sourceRootCommit }
: {}),
};
const checked = compileWorkspaceRevision(workspace);
if (!checked.ok) {
throw new Error(
`Instantiated workspace did not compile:\n${checked.issues.map((entry) =>
`${entry.path}: ${entry.message}`).join("\n")}`,
);
}
return {
workspace,
plan: checked.plan,
lock: rootLock,
resources: nodes,
directResources: new Map(directPairs.map(([locked, node]) => [
bindingKey(locked.kind, locked.binding),
node,
])),
};
};