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.
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {execFile as callback, spawn} from "node:child_process";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import {promisify} from "node:util";
|
||||
import {fileURLToPath} from "node:url";
|
||||
const execFile = promisify(callback);
|
||||
const environment = () => ({...process.env, QUIXOS_JJ_NO_CHECKPOINT: "1", GIT_TERMINAL_PROMPT: "0"});
|
||||
export const checkerIdentity = () => process.env.QUIXOS_CHECK_GENERATOR ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
|
||||
/** Checking snapshots jj, but never publishes or activates the working copy. */
|
||||
export async function snapshotCommit(root: string): Promise<string> {
|
||||
@@ -60,3 +62,34 @@ export async function buildCheckedPackage(source: string, schema: string, packag
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Exact remote source DAG; the Nix checker owns schema construction and all
|
||||
* nested fetches. Keep build noise in a named log, with a bounded failure tail. */
|
||||
export async function buildImmutableCandidate(source: { repository: string; commit: string }, kind: "workspace" | "interface" | "package", logFile: string, contractOnly = false): Promise<string> {
|
||||
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(source.commit)) throw new Error("An immutable candidate requires an exact commit");
|
||||
const url = new URL(source.repository);
|
||||
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error("Candidate origin must be credential-free HTTPS");
|
||||
const generator = process.env.QUIXOS_CHECK_GENERATOR ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
if (!/^\/nix\/store\/[^/]+$/.test(generator)) throw new Error("Use the installed Quixos checker");
|
||||
const builder = path.join(generator, "share/checked-candidate.nix");
|
||||
await fs.access(builder);
|
||||
const log = createWriteStream(logFile, { flags: "wx", mode: 0o600 });
|
||||
await new Promise<void>((resolve, reject) => { log.once("open", () => resolve()); log.once("error", reject); });
|
||||
return await new Promise((resolve, reject) => {
|
||||
let output = "", tail = "", failure: Error | undefined;
|
||||
const child = spawn("nix", ["build", "--impure", "--file", builder,
|
||||
"--argstr", "repository", source.repository, "--argstr", "commit", source.commit,
|
||||
"--argstr", "kind", kind, "--argstr", "generator", generator,
|
||||
"--arg", "contractOnly", contractOnly ? "true" : "false",
|
||||
"--no-link", "--print-out-paths", "-L"], { env: environment(), stdio: ["ignore", "pipe", "pipe"] });
|
||||
log.on("error", error => { failure = error; child.kill(); });
|
||||
child.stdout.on("data", chunk => { output += chunk; });
|
||||
child.stderr.on("data", chunk => { log.write(chunk); tail = (tail + String(chunk)).slice(-6000); });
|
||||
child.on("error", error => { failure = error; });
|
||||
child.on("close", code => log.end(() => {
|
||||
if (failure) reject(failure);
|
||||
else if (code !== 0 || !/^\/nix\/store\/[a-z0-9]{32}-[^\s/]+$/.test(output.trim())) reject(new Error(`Candidate Nix check failed (${code}). Full log: ${logFile}\n${tail}`));
|
||||
else resolve(output.trim());
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user