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
+15
View File
@@ -99,6 +99,13 @@ export interface ArtifactResponse {
headers: Record<string, string>;
body: Uint8Array;
}
/** Shared runtime imports use content identity, independent of the importing package/world.
* The file remains covered by every importing implementation's checked manifest. */
export function contentArtifactUrl(file: Pick<ArtifactFile, "path" | "digest">): string {
if (!/^[0-9a-f]{64}$/.test(file.digest)) fail(file.path, "Invalid content digest");
artifactPath(file.path);
return `/__quixos/artifacts/content/${file.digest}/${encodeURIComponent(path.basename(file.path))}`;
}
/** This registry is placed behind the workspace gateway's existing authentication.
* Lookup uses an explicit URL table; request paths never become filesystem paths. */
export class BrowserArtifactGateway {
@@ -124,6 +131,14 @@ export class BrowserArtifactGateway {
)
fail(url, "Conflicting browser asset route");
this.routes.set(url, { revision: manifest.packageRevisionId, file: Object.freeze({ ...file }) });
// Exact shared-file names are a packaging convention, never a path lookup capability.
if (file.path.startsWith("share/quixos/shared/")) {
const contentUrl = contentArtifactUrl(file);
const existing = this.routes.get(contentUrl);
if (existing && (existing.file.bytes !== file.bytes || existing.file.mediaType !== file.mediaType))
fail(contentUrl, "Conflicting shared asset metadata");
this.routes.set(contentUrl, { revision: manifest.packageRevisionId, file: Object.freeze({ ...file }) });
}
}
entries.push({
url: prefix + implementation.path.split("/").map(encodeURIComponent).join("/"),
+4
View File
@@ -2,3 +2,7 @@ export * from "./plan.js";
export * from "./artifacts.js";
export * from "./ports.js";
export * from "./files.js";
export * from "./receipts.js";
export { generatePortableTypeScriptBindings } from "../bindings/portable.js";
+5 -2
View File
@@ -3,7 +3,8 @@
export const transactionPortAbi = "quixos-transaction-ports-v1";
export type PortAction =
| { kind: "state"; primitive: "read" | "write" | "edit" }
| { kind: "edge"; primitive: "resolve" | "connect" | "disconnect" }
| { kind: "edge"; primitive: "resolve" | "disconnect" }
| { kind: "edge"; primitive: "connect"; key: string }
| { kind: "interface"; operationId: string; key?: string }
| { kind: "query" }
| { kind: "creation"; key: string };
@@ -19,6 +20,7 @@ export interface PortInvocation {
}
export interface PackageInvocation {
abi: typeof transactionPortAbi;
workspace: string;
frame: string;
invocation: string;
exportId: string;
@@ -66,7 +68,8 @@ export function checkPortInvocation(value: unknown): PortInvocation {
action.kind === "state"
? exact(["kind", "primitive"]) && ["read", "write", "edit"].includes(action.primitive as string)
: action.kind === "edge"
? exact(["kind", "primitive"]) && ["resolve", "connect", "disconnect"].includes(action.primitive as string)
? (exact(["kind", "primitive"]) && ["resolve", "disconnect"].includes(action.primitive as string)) ||
(exact(["kind", "primitive", "key"]) && action.primitive === "connect" && identifier(action.key))
: action.kind === "interface"
? (exact(["kind", "operationId"]) || (exact(["kind", "operationId", "key"]) && identifier(action.key))) &&
identifier(action.operationId)
+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;
}