Build checked portable package artifacts and shared runtime assets

This commit is contained in:
Timothy J. Aveni
2026-09-20 00:42:19 -07:00
parent 60b1bdb39f
commit ab41e67dc5
8 changed files with 216 additions and 2 deletions
+102
View File
@@ -0,0 +1,102 @@
import { createHash } from "node:crypto";
import { generatePortableTypeScriptBindings } from "../bindings/portable.js";
import { type ExecutionPlan, ExecutionPlanError, hexDigest } from "./plan.js";
import { type ArtifactReader, type PackageArtifacts } from "./artifacts.js";
import { loadArtifactWorld, type LoadedArtifactWorld } from "./files.js";
export interface ArtifactReceipt {
schemaVersion: 1;
packageRevisionId: string;
bindingDigest: string;
sourceDigest: string;
manifestDigest: string;
generatorDigest: string;
generated: { target: string; path: string; digest: string; bytes: number }[];
}
const hashBytes = (bytes: Uint8Array) => createHash("sha256").update(bytes).digest("hex");
/** Deterministic expected receipt. A trusted build must also typecheck and bundle these exact sources.
* This is consistency evidence inside a trusted Nix build path, not remote build attestation. */
export function expectedArtifactReceipt(
plan: ExecutionPlan,
manifest: PackageArtifacts,
generatorDigest: string,
): { receipt: ArtifactReceipt; sources: Map<string, Uint8Array> } {
if (!/^[0-9a-f]{64}$/.test(generatorDigest)) throw new Error("Invalid trusted generator digest");
const pkg = plan.definition.packages.find((p) => p.revision === manifest.packageRevisionId);
const schemaPackage = plan.definition.bindingSchema.packages.find((p) => p.revisionId === manifest.packageRevisionId);
if (
!pkg ||
!schemaPackage ||
manifest.bindingDigest !== pkg.bindingDigest ||
manifest.sourceDigest !== pkg.sourceDigest
)
throw new ExecutionPlanError(
"INVALID_RECEIPT",
manifest.packageRevisionId,
"Receipt package/schema/source mismatch",
);
const available = new Set(manifest.implementations.map((i) => i.target));
const targets = [
...new Set(
schemaPackage.exports.flatMap((e) => (e.execution && e.execution.kind !== "remote" ? e.execution.targets : [])),
),
]
.filter((t) => available.has(t))
.sort();
const sources = new Map<string, Uint8Array>();
const generated = targets.map((target) => {
if (!/^[a-zA-Z0-9_-]+$/.test(target)) throw new Error("Unsupported generated target filename");
const path = `share/quixos/generated/${target}.ts`;
const bytes = new TextEncoder().encode(
generatePortableTypeScriptBindings(plan.definition.bindingSchema, pkg.revision, { target }),
);
sources.set(path, bytes);
return { target, path, digest: hashBytes(bytes), bytes: bytes.length };
});
return {
receipt: {
schemaVersion: 1,
packageRevisionId: pkg.revision,
bindingDigest: pkg.bindingDigest,
sourceDigest: pkg.sourceDigest,
manifestDigest: hexDigest(manifest),
generatorDigest,
generated,
},
sources,
};
}
export async function checkArtifactReceipt(
plan: ExecutionPlan,
manifest: PackageArtifacts,
receipt: unknown,
generatorDigest: string,
read: ArtifactReader,
): Promise<void> {
const expected = expectedArtifactReceipt(plan, manifest, generatorDigest);
if (hexDigest(receipt) !== hexDigest(expected.receipt))
throw new ExecutionPlanError(
"INVALID_RECEIPT",
manifest.packageRevisionId,
"Receipt differs from the trusted compiler/schema/artifact expectation",
);
for (const file of expected.receipt.generated) {
const actual = await read(manifest.packageRevisionId, file.path, file.bytes);
if (actual.length !== file.bytes || hashBytes(actual) !== file.digest)
throw new ExecutionPlanError("INVALID_RECEIPT", file.path, "Generated bindings were replaced or omitted");
}
}
/** Activation uses this entrypoint with its trusted generator identity, not the manifest-only loader. */
export async function loadCheckedArtifactWorld(
plan: ExecutionPlan,
roots: Readonly<Record<string, string>>,
generatorDigest: string,
): Promise<LoadedArtifactWorld> {
const loaded = await loadArtifactWorld(plan, roots);
for (const manifest of loaded.manifests) {
const bytes = await loaded.read(manifest.packageRevisionId, "share/quixos/build-receipt.json", 16 * 1024 * 1024);
const receipt: unknown = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
await checkArtifactReceipt(plan, manifest, receipt, generatorDigest, loaded.read);
}
return loaded;
}