import { constants } from "node:fs"; import { lstat, open, realpath } from "node:fs/promises"; import path from "node:path"; import { createHash } from "node:crypto"; import { artifactPath, parsePackageArtifacts, checkExecutionWorld, checkedWorldDigest, type ArtifactReader, type ArtifactFile, type PackageArtifacts, type CheckedWorld, } from "./artifacts.js"; import { type ExecutionPlan, ExecutionPlanError } from "./plan.js"; import { readCandidateMetadata, type CandidateMetadataScope } from "./metadata.js"; const maxFileBytes = 256 * 1024 * 1024; const metadataLimit = 16 * 1024 * 1024; const fail = (where: string, message: string): never => { throw new ExecutionPlanError("ARTIFACT_FILE_INVALID", where, message); }; /** Roots must be trusted immutable output directories. Refuse symlinks, including * interior directory links. Hash verification is still required before serving bytes. */ async function artifactFile(root: string, relative: string): Promise { artifactPath(relative); const components = relative.split("/"); let resolved = root; for (let index = 0; index < components.length; index++) { resolved = path.join(resolved, components[index]!); const stat = await lstat(resolved); if (stat.isSymbolicLink() || (index < components.length - 1 ? !stat.isDirectory() : !stat.isFile())) fail(relative, "Only real directories and regular files are allowed"); } return resolved; } export async function artifactFileReader(roots: Readonly>): Promise { const directories = new Map(); for (const [revision, root] of Object.entries(roots)) { if (!path.isAbsolute(root) || path.normalize(root) !== root || (await realpath(root)) !== root) fail(revision, "Output root must be a canonical absolute directory"); const stat = await lstat(root); if (!stat.isDirectory() || stat.isSymbolicLink()) fail(root, "Output root is not a directory"); directories.set(revision, root); } return async (revision, relative, limit) => { const root = directories.get(revision); if (!root) fail(revision, "Unknown package output"); if (!Number.isSafeInteger(limit) || limit < 0 || limit > maxFileBytes) fail(relative, "Invalid file byte limit"); const resolved = await artifactFile(root!, relative); const handle = await open(resolved, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK); try { const stat = await handle.stat(); if (!stat.isFile() || stat.size > limit) fail(relative, "File exceeds its declared bound"); // A fixed allocation and one extra byte detect growth without readFile's unbounded allocation. const buffer = Buffer.alloc(stat.size + 1); let offset = 0; while (offset < buffer.length) { const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset); if (!bytesRead) break; offset += bytesRead; } if (offset !== stat.size) fail(relative, "File changed while reading"); return buffer.subarray(0, offset); } finally { await handle.close(); } }; } export interface LoadedArtifactWorld { world: CheckedWorld; digest: string; manifests: PackageArtifacts[]; read: ArtifactReader; } /** Immutable-output loader. No module code is imported into the coordinator. */ export async function loadArtifactWorld( plan: ExecutionPlan, roots: Readonly>, metadata?: CandidateMetadataScope, ): Promise { const read = await artifactFileReader(roots), manifests: PackageArtifacts[] = []; const expected = plan.definition.packages.map((p) => p.revision).sort(); if (JSON.stringify(Object.keys(roots).sort()) !== JSON.stringify(expected)) fail("world", "Output roots must exactly match the installed plan"); for (const revision of expected) { const file = "share/quixos/package-artifacts.json"; const input = metadata ? await readCandidateMetadata(await artifactFile(roots[revision]!, file), metadata, metadataLimit) : { bytes: await read(revision, file, metadataLimit), releaseInput: () => {} }; let manifest: PackageArtifacts; try { manifest = parsePackageArtifacts(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.bytes))); } finally { input.releaseInput(); } if (manifest.packageRevisionId !== revision) fail(revision, "Output belongs to another package revision"); manifests.push(manifest); } const world = await checkExecutionWorld(plan, manifests, read); return { world, digest: checkedWorldDigest(world), manifests, read }; } export interface BrowserArtifact { url: string; implementation: string; packageRevisionId: string; registryExport: string; exports: readonly string[]; } export interface ArtifactResponse { status: 200 | 304; headers: Record; 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): 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 { readonly entries: readonly BrowserArtifact[]; private readonly routes = new Map(); private readonly read: ArtifactReader; constructor(loaded: LoadedArtifactWorld) { this.read = loaded.read; const entries: BrowserArtifact[] = []; for (const manifest of loaded.manifests) for (const implementation of manifest.implementations) { if ( implementation.target !== "target:org.quixos.execution.javascript@1" || implementation.compatibility !== "quixos-js-module-v1" ) continue; const prefix = `/__quixos/artifacts/${loaded.digest}/${implementation.contentDigest}/`; for (const file of implementation.files) { if (!/^[a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+$/.test(file.mediaType)) fail(file.path, "Invalid asset MIME type"); const url = prefix + file.path.split("/").map(encodeURIComponent).join("/"); const previous = this.routes.get(url); if ( previous && (previous.file.digest !== file.digest || previous.file.bytes !== file.bytes || previous.file.mediaType !== file.mediaType) ) 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("/"), implementation: implementation.id, packageRevisionId: manifest.packageRevisionId, registryExport: implementation.registryExport!, exports: Object.freeze([...implementation.exports]), }); } this.entries = Object.freeze(entries.map((entry) => Object.freeze(entry))); } async response(url: string, ifNoneMatch?: string): Promise { const route = this.routes.get(url); if (!route) return undefined; const bytes = await this.read(route.revision, route.file.path, route.file.bytes); if (bytes.length !== route.file.bytes || createHash("sha256").update(bytes).digest("hex") !== route.file.digest) fail(url, "Browser asset changed after world validation"); const etag = `"sha256-${route.file.digest}"`; const headers = { "content-type": route.file.mediaType, "cache-control": "private, max-age=31536000, immutable", etag, "x-content-type-options": "nosniff", }; return ifNoneMatch === etag ? { status: 304, headers, body: new Uint8Array() } : { status: 200, headers, body: bytes }; } }