2026-09-13 22:07:18 -07:00
import fs from "node:fs/promises" ;
import path from "node:path" ;
import { execFile as callback , spawn } from "node:child_process" ;
2026-09-14 10:26:11 -07:00
import { createWriteStream } from "node:fs" ;
2026-09-13 22:07:18 -07:00
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" });
2026-09-14 10:26:11 -07:00
export const checkerIdentity = () => process . env . QUIXOS_CHECK_GENERATOR ?? path . resolve ( path . dirname ( fileURLToPath ( import . meta . url )), "../.." );
2026-09-13 22:07:18 -07:00
/** Checking snapshots jj, but never publishes or activates the working copy. */
export async function snapshotCommit ( root : string ) : Promise < string > {
const run = async (... args : string []) => ( await execFile ( "jj" , args , { cwd : root , env : environment ()})). stdout . trim ();
await run ( "status" );
// jj resolve --list exits 1 on a clean revision. Query structured revision
// metadata instead of depending on diagnostic wording or swallowing errors.
if ( await run ( "--ignore-working-copy" , "log" , "--no-graph" , "-r" , "@" , "-T" , "conflict" ) !== "false" ) throw new Error ( "Resolve source conflicts before verification" );
const commit = await run ( "--ignore-working-copy" , "log" , "--no-graph" , "-r" , "@" , "-T" , "commit_id" );
if ( ! /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/ . test ( commit )) throw new Error ( "Verification requires an exact jj commit" );
await execFile ( "git" , [ "diff" , "--exit-code" , "--no-ext-diff" , "--no-textconv" , commit , "--" ], { cwd : root , env : environment ()});
const { stdout } = await execFile ( "git" , [ "ls-files" , "--others" , "--exclude-standard" , "-z" ], { cwd : root });
if ( stdout ) throw new Error ( "Source contains files not captured by jj; inspect jj tracking before verification" );
return commit ;
}
/** Use Git's actual committed tree, never dirty overlays labelled as old pins. */
export async function checkoutCommit ( root : string , commit : string , destination : string ) {
await fs . mkdir ( destination , { recursive : true });
const archive = await execFile ( "git" , [ "archive" , "--format=tar" , commit ], { cwd : root , encoding : "buffer" , maxBuffer : 128 * 1024 * 1024 });
await new Promise < void >(( resolve , reject ) => {
const child = spawn ( "tar" , [ "-xf" , "-" , "-C" , destination ], { stdio : [ "pipe" , "ignore" , "pipe" ]});
let error = "" ;
child . stderr . on ( "data" , chunk => { error += chunk ;});
child . on ( "error" , reject );
child . on ( "close" , code => code === 0 ? resolve () : reject ( new Error ( `Cannot extract committed source: ${ error } ` )));
child . stdin . on ( "error" , reject );
child . stdin . end ( archive . stdout );
});
}
/** Shared by provisional checks, template publication and host activation.
* The Nix derivation is the cached check; its metadata is internal provenance,
* not a certificate authored or approved by the workspace agent.
*/
export async function buildCheckedPackage ( source : string , schema : string , packageRevisionId : string ) : Promise < string > {
const generator = process . env . QUIXOS_CHECK_GENERATOR ?? path . resolve ( path . dirname ( fileURLToPath ( import . meta . url )), "../.." );
if ( ! /^\/nix\/store\/[^/]+$/ . test ( generator )) throw new Error ( "Run verification with the installed Quixos tooling (its exact Nix checker is required)" );
const builder = path . join ( generator , "share/checked-package.nix" );
await fs . access ( builder );
return await new Promise (( resolve , reject ) => {
const child = spawn ( "nix" , [ "build" , "--impure" , "--file" , builder ,
"--argstr" , "source" , source , "--argstr" , "schema" , schema ,
"--argstr" , "generator" , generator , "--argstr" , "packageRevisionId" , packageRevisionId ,
"--no-link" , "--print-out-paths" , "-L" ], { env : environment (), stdio : [ "ignore" , "pipe" , "inherit" ]});
let output = "" ;
child . stdout . on ( "data" , chunk => { output += chunk ;});
child . on ( "error" , reject );
child . on ( "close" , code => {
const artifact = output . trim ();
if ( code !== 0 || ! /^\/nix\/store\/[a-z0-9]{32}-[^\s/]+$/ . test ( artifact )) reject ( new Error ( `Checked Nix build failed ( ${ code } ); see build diagnostics above` ));
else resolve ( artifact );
});
});
}
2026-09-14 10:26:11 -07:00
/** 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 ());
}));
});
}