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
+150
View File
@@ -0,0 +1,150 @@
import { lstat, readFile } from "node:fs/promises";
import path from "node:path";
import {
parseQuixosLockDocument,
type QuixosLockDiagnostic,
type QuixosLockParseResult,
} from "./parser.js";
import type {
LockedResource,
QuixosLockDocument,
QuixosRepositoryLock,
} from "./types.js";
export type QuixosLockSourceReader = (relativePath: string) => Promise<string>;
const diagnostic = (
code: string,
message: string,
fileName: string,
path?: string,
): QuixosLockDiagnostic => ({
phase: "resolution",
code,
message,
fileName,
line: 1,
column: 0,
path,
});
export const resolveQuixosLock = async (
rootSource: string,
readSource: QuixosLockSourceReader,
rootFileName = "quixos.lock",
): Promise<QuixosLockParseResult> => {
const root = parseQuixosLockDocument(rootSource, rootFileName);
if (!root.ok) return root;
if (root.document.kind !== "root") {
return {
ok: false,
diagnostics: [diagnostic(
"expected-root-lock",
"The entrypoint must be a root Quixos lock, not a fragment",
rootFileName,
)],
};
}
const diagnostics: QuixosLockDiagnostic[] = [];
const resources: LockedResource[] = [];
const sourceFiles = [rootFileName];
const visited = new Set<string>();
const active: string[] = [];
const addResources = (document: QuixosLockDocument, fileName: string) => {
for (const resource of document.resources) {
const duplicate = resources.find((entry) =>
entry.kind === resource.kind && entry.binding === resource.binding);
if (duplicate) {
diagnostics.push(diagnostic(
"duplicate-resource-binding",
`Duplicate ${resource.kind} binding ${resource.binding} across imported lock files`,
fileName,
`${resource.kind}.${resource.binding}`,
));
} else {
resources.push(resource);
}
}
};
const visit = async (importPath: string) => {
if (active.includes(importPath)) {
diagnostics.push(diagnostic(
"import-cycle",
`Lock import cycle: ${[...active, importPath].join(" -> ")}`,
importPath,
));
return;
}
if (visited.has(importPath)) return;
active.push(importPath);
let source: string;
try {
source = await readSource(importPath);
} catch (cause) {
diagnostics.push(diagnostic(
"import-read-failed",
`Could not read lock import ${importPath}: ${cause instanceof Error ? cause.message : String(cause)}`,
importPath,
));
active.pop();
return;
}
const parsed = parseQuixosLockDocument(source, importPath);
if (!parsed.ok) {
diagnostics.push(...parsed.diagnostics);
active.pop();
return;
}
if (parsed.document.kind !== "fragment") {
diagnostics.push(diagnostic(
"imported-root-lock",
`Imported file ${importPath} must begin with "quixos-lock fragment"`,
importPath,
));
active.pop();
return;
}
visited.add(importPath);
sourceFiles.push(importPath);
addResources(parsed.document, importPath);
for (const nested of parsed.document.imports) await visit(nested);
active.pop();
};
addResources(root.document, rootFileName);
for (const importPath of root.document.imports) await visit(importPath);
if (diagnostics.length) return { ok: false, diagnostics };
const lock: QuixosRepositoryLock = {
formatVersion: 1,
quixos: root.document.quixos,
resources,
sourceFiles,
};
return { ok: true, lock, diagnostics: [] };
};
export const loadQuixosLock = async (fileName: string): Promise<QuixosLockParseResult> => {
const absoluteRoot = path.resolve(fileName);
const repositoryRoot = path.dirname(absoluteRoot);
const rootName = path.basename(absoluteRoot);
const rootSource = await readFile(absoluteRoot, "utf8");
return await resolveQuixosLock(rootSource, async (relativePath) => {
let absoluteImport = repositoryRoot;
const segments = relativePath.split("/");
for (const [index, segment] of segments.entries()) {
absoluteImport = path.join(absoluteImport, segment);
const metadata = await lstat(absoluteImport);
if (metadata.isSymbolicLink()) {
throw new Error("imports must not traverse symbolic links");
}
const final = index === segments.length - 1;
if ((!final && !metadata.isDirectory()) || (final && !metadata.isFile())) {
throw new Error("imports must be ordinary files beneath ordinary directories");
}
}
return await readFile(absoluteImport, "utf8");
}, rootName);
};