diff --git a/nix/checked-candidate.nix b/nix/checked-candidate.nix index 4ccb7a6..9446844 100644 --- a/nix/checked-candidate.nix +++ b/nix/checked-candidate.nix @@ -7,6 +7,7 @@ generator, contractOnly ? false, workspaceId ? null, + platformSourceJSON ? null, system ? builtins.currentSystem, }: let @@ -71,9 +72,16 @@ let else { }; execution = (configuration.backend or "typescript") == "execution"; - runtime = builtins.getFlake ( - "git+${root.lock.quixos.repository}?rev=${root.lock.quixos.commit}&ref=refs/tags/quixos-reachability/${root.lock.quixos.commit}" - ); + platformSource = if platformSourceJSON == null then null else builtins.fromJSON platformSourceJSON; + runtime = + if platformSource == null then + builtins.getFlake ( + "git+${root.lock.quixos.repository}?rev=${root.lock.quixos.commit}&ref=refs/tags/quixos-reachability/${root.lock.quixos.commit}" + ) + else + assert platformSource.repository == root.lock.quixos.repository; + assert platformSource.commit == root.lock.quixos.commit; + builtins.getFlake "path:${builtins.storePath platformSource.directory}"; runtimePackages = runtime.packages.${system}; resourceClosure = includeBuild: node: diff --git a/src/capability-language/checked-build.ts b/src/capability-language/checked-build.ts index e7d21a0..922079d 100644 --- a/src/capability-language/checked-build.ts +++ b/src/capability-language/checked-build.ts @@ -113,6 +113,7 @@ export async function buildImmutableCandidate( contractOnly = false, observe?: (phase: "nixEvaluationMs" | "nixBuildMs", ms: number) => void, workspaceId?: string, + platformSource?: { repository: string; commit: string; directory: string }, ): Promise { if (workspaceId !== undefined && (kind !== "workspace" || !/^[a-zA-Z0-9][a-zA-Z0-9:._-]{0,255}$/.test(workspaceId))) throw new Error("Instance binding requires a valid Workspace identity and a Workspace root"); @@ -121,6 +122,13 @@ export async function buildImmutableCandidate( 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"); + if ( + platformSource && + (Object.keys(platformSource).sort().join(",") !== "commit,directory,repository" || + !/^[a-f0-9]{40}$/.test(platformSource.commit) || + !/^\/nix\/store\/[a-z0-9]{32}-[^/\s]+$/.test(platformSource.directory)) + ) + throw Error("Invalid immutable platform source input"); 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"); @@ -204,6 +212,7 @@ export async function buildImmutableCandidate( "contractOnly", contractOnly ? "true" : "false", ...(workspaceId === undefined ? [] : ["--argstr", "workspaceId", workspaceId]), + ...(platformSource ? ["--argstr", "platformSourceJSON", JSON.stringify(platformSource)] : []), "--add-root", gcRoot, "--indirect", diff --git a/src/capability-language/source-check-driver.ts b/src/capability-language/source-check-driver.ts index 4a4169a..e627287 100644 --- a/src/capability-language/source-check-driver.ts +++ b/src/capability-language/source-check-driver.ts @@ -6,5 +6,13 @@ const [repository, commit, log, workspaceId] = process.argv.slice(2); if (!repository || !commit || !log || !workspaceId || process.argv.length !== 6) throw Error("source-check-driver REPOSITORY COMMIT LOG WORKSPACE_ID"); process.stdout.write( - (await buildImmutableCandidate({ repository, commit }, "workspace", log, false, undefined, workspaceId)) + "\n", + (await buildImmutableCandidate( + { repository, commit }, + "workspace", + log, + false, + undefined, + workspaceId, + process.env.QUIXOS_CHECK_PLATFORM_SOURCE ? JSON.parse(process.env.QUIXOS_CHECK_PLATFORM_SOURCE) : undefined, + )) + "\n", ); diff --git a/src/resource-lock/cli.ts b/src/resource-lock/cli.ts index 3562ed8..2d220ff 100644 --- a/src/resource-lock/cli.ts +++ b/src/resource-lock/cli.ts @@ -1,5 +1,20 @@ #!/usr/bin/env node import { loadQuixosLock } from "./loader.js"; +import { parseQuixosLockDocument } from "./parser.js"; +import { readFile, stat } from "node:fs/promises"; + +// A trusted fetcher can inspect the root's declared platform pin without +// following authored import paths or evaluating Nix. The input is a fetched +// Git blob, not a filesystem tree provided by an author. +if (process.argv[2] === "--platform-only") { + const filename = process.argv[3]; + if (process.argv.length !== 4 || !filename || (await stat(filename)).size > 1024 * 1024) + throw Error("INVALID_PLATFORM_SOURCE_LOCK"); + const result = parseQuixosLockDocument(await readFile(filename, "utf8")); + if (!result.ok || result.document.kind !== "root") throw Error("INVALID_PLATFORM_SOURCE_LOCK"); + console.log(JSON.stringify({ repository: result.document.quixos.repository, commit: result.document.quixos.commit })); + process.exit(0); +} const fileName = process.argv[2]; if (!fileName || process.argv.length !== 3) {