Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Production Readiness TODO

- [x] Validate the Linux glibc metadata upstream `325e4bc` added.
Final result: extraction rejects a `runtime.platform.min_glibc` that is not a
`major.minor` version or that is attached to a non-Linux runtime, and records
the declared floor in `upstream-provenance.json` as `runtime_min_glibc`. The
field stays optional: absence makes no claim, and host and runtime always ship
in the same product bundle, so a runtime predating the field is always paired
with a host predating the check that would skip it. Packaging does not
re-derive the floor from ELF headers; upstream's
`verify_linux_min_glibc_consistency` owns that, and the runtime manifest is
already digest-bound to the verified producer.
QA: archive fixtures accept a declared `2.35` floor and surface it in
provenance, reject `2.35.1`, `2`, `""`, `two.35`, a number, and a boolean,
reject a floor on a macOS runtime, and accept all three shapes a pre-field
release can take (no platform block, a platform block without the key, and an
explicit null).

- [ ] Keep no-driver package QA reliable under hosted-runner load while still
enforcing bounded SIGINT shutdown. QA: the shared Linux smoke and Homebrew
formula use the same 30-second bound, focused regression tests pass, the full
Expand Down
10 changes: 10 additions & 0 deletions docs/native-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ plan downloads that schema at the immutable upstream source SHA and byte-checks
it against this checkout before any archive, package, or image job starts.
Contract changes update both copies in the same cross-repository change; a
release must not proceed with unexplained schema drift.

The per-runtime `manifest.json` stays outside that schema, but two of its claims
are checked at extraction. Every runtime file outside `lib/`, `tools/`,
`manifest.json`, and `README.md` must be declared in `runtime.files` with a
matching SHA-256. And `runtime.platform.min_glibc`, which upstream added so a
host can refuse a Linux runtime its glibc cannot load, must be a `major.minor`
version attached to a Linux runtime; provenance records it as
`runtime_min_glibc`. Both checks are conditional. A runtime that declares
neither makes no claim and verifies as before, which is what keeps releases
through v0.76.2 packageable.
`packaging/native/build-package.sh` stages that verified bundle and produces
exactly one package with version, distro, architecture, backend, and backend
version in its filename.
Expand Down
38 changes: 34 additions & 4 deletions scripts/upstream-archive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,47 @@ function filesBelow(root: string, current = root): string[] {
});
}

function declaredRuntimeFileDigests(runtimeDir: string): Record<string, unknown> {
type RuntimeManifest = { runtime?: { files?: unknown; platform?: { os?: unknown; min_glibc?: unknown } } };

function readRuntimeManifest(runtimeDir: string): RuntimeManifest {
const raw = readFileSync(resolve(runtimeDir, "manifest.json"), "utf8");
let parsed: unknown;
try {
parsed = JSON.parse(raw);
return JSON.parse(raw) as RuntimeManifest;
} catch {
throw new Error("native runtime manifest is not valid JSON");
}
const files = (parsed as { runtime?: { files?: unknown } })?.runtime?.files;
}

function declaredRuntimeFileDigests(runtimeDir: string): Record<string, unknown> {
const files = readRuntimeManifest(runtimeDir)?.runtime?.files;
if (!files || typeof files !== "object" || Array.isArray(files)) return {};
return files as Record<string, unknown>;
}

/**
* Upstream 325e4bc added `platform.min_glibc` so a host can refuse a Linux
* runtime its glibc cannot load. Packaging copies the runtime manifest
* unchanged, so the least it owes the field is refusing to ship a floor that is
* malformed or attached to the wrong OS.
*
* An absent field makes no claim and is accepted. Releases predating the field,
* v0.76.2 among them, pair that runtime with a host that predates the check, and
* host and runtime always travel together in one product bundle.
*/
export function validateRuntimeMinGlibc(runtimeDir: string): string | null {
const platform = readRuntimeManifest(runtimeDir)?.runtime?.platform;
if (!platform || typeof platform !== "object") return null;
const declared = platform.min_glibc;
if (declared === undefined || declared === null) return null;
if (typeof declared !== "string" || !/^\d+\.\d+$/.test(declared)) {
throw new Error(`native runtime platform min_glibc must be a major.minor version: ${JSON.stringify(declared)}`);
}
if (platform.os !== "linux") {
throw new Error(`native runtime platform min_glibc is only meaningful on linux: ${JSON.stringify(platform.os)}`);
}
return declared;
}

/**
* Accepts only the extra runtime files the producer declared, and proves their
* bytes. The archive listing already bounded these to `licenses/<name>`; this
Expand Down Expand Up @@ -269,12 +297,14 @@ export async function verifyAndExtract(input: Inputs) {
const runtimeManifestSha256 = await sha256File(resolve(runtime, "manifest.json"));
if (runtimeManifestSha256 !== productManifest.runtime.manifest_sha256) throw new Error("product runtime manifest digest does not match");
await verifyDeclaredRuntimeFiles(runtime);
const runtimeMinGlibc = validateRuntimeMinGlibc(runtime);
const provenance = resolve(input.outputDir, "upstream-provenance.json");
writeFileSync(provenance, `${JSON.stringify({
archive: archiveName,
flavor: input.flavor,
host_sha256: productManifest.host?.sha256,
runtime_id: productManifest.runtime?.id,
runtime_min_glibc: runtimeMinGlibc,
runtime_sha256: productManifest.runtime?.sha256,
sha256: actual,
source_url: input.sourceUrl,
Expand Down
61 changes: 57 additions & 4 deletions tests/upstream-archive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
validateArchiveEntries,
validateArchiveEntryTypes,
validateProductManifest,
validateRuntimeMinGlibc,
verifyAndExtract,
verifyDeclaredRuntimeFiles,
} from "../scripts/upstream-archive.ts";
Expand Down Expand Up @@ -50,6 +51,10 @@ type FixtureOptions = {
// runtime manifest claims about them. `declare: false` stages the bytes
// without declaring them; `digest` overrides the declared digest.
licenses?: { name: string; contents: string; declare?: boolean; digest?: string }[];
// Written into the runtime manifest's platform block verbatim, so a test can
// stage a malformed or wrongly-attached floor. `undefined` writes no platform
// block at all, matching releases that predate the field.
platform?: Record<string, unknown>;
};

async function fixture(t: { after(callback: () => void): void }, options: FixtureOptions = {}) {
Expand All @@ -64,20 +69,25 @@ async function fixture(t: { after(callback: () => void): void }, options: Fixtur
mkdirSync(resolve(runtime, "lib"), { recursive: true });
writeFileSync(resolve(runtime, "README.md"), "runtime\n");
writeFileSync(resolve(runtime, "lib/libllama.so"), "runtime\n");
if (options.licenses?.length) {
mkdirSync(resolve(runtime, "licenses"), { recursive: true });
if (options.licenses?.length || options.platform) {
const files: Record<string, string> = {
"lib/libllama.so": await sha256File(resolve(runtime, "lib/libllama.so")),
};
for (const license of options.licenses) {
if (options.licenses?.length) mkdirSync(resolve(runtime, "licenses"), { recursive: true });
for (const license of options.licenses ?? []) {
const relative = `licenses/${license.name}`;
writeFileSync(resolve(runtime, relative), license.contents);
if (license.declare === false) continue;
files[relative] = license.digest ?? await sha256File(resolve(runtime, relative));
}
writeFileSync(resolve(runtime, "manifest.json"), `${JSON.stringify({
build: { primary_library: "lib/libllama.so" },
runtime: { files, id: "linux-cpu", libraries: ["lib/libllama.so"] },
runtime: {
files,
id: "linux-cpu",
libraries: ["lib/libllama.so"],
...(options.platform ? { platform: options.platform } : {}),
},
}, null, 2)}\n`);
} else {
writeFileSync(resolve(runtime, "manifest.json"), "{}\n");
Expand Down Expand Up @@ -116,6 +126,7 @@ test("verifies checksum, layout, extraction, and provenance", async (t) => {
flavor: "cpu",
host_sha256: data.hostSha256,
runtime_id: "linux-cpu",
runtime_min_glibc: null,
runtime_sha256: data.runtimeSha256,
sha256: result.sha256,
source_url: "https://example.test/archive",
Expand Down Expand Up @@ -194,6 +205,48 @@ test("rejects runtime license bytes that drifted from the declared digest", asyn
);
});

const extractTo = async (data: { directory: string; archive: string; checksum: string }, name: string) => {
const outputDir = resolve(data.directory, name);
await verifyAndExtract({ archive: data.archive, checksum: data.checksum, outputDir, sourceUrl: "https://example.test/archive", version: "0.73.1", flavor: "cpu" });
return resolve(outputDir, "native-runtimes/linux-cpu");
};

test("records a declared Linux glibc floor in provenance", async (t) => {
const data = await fixture(t, { platform: { arch: "x86_64", min_glibc: "2.35", os: "linux" } });
const outputDir = resolve(data.directory, "output");
const result = await verifyAndExtract({ archive: data.archive, checksum: data.checksum, outputDir, sourceUrl: "https://example.test/archive", version: "0.73.1", flavor: "cpu" });
assert.equal(JSON.parse(readFileSync(result.provenance, "utf8")).runtime_min_glibc, "2.35");
assert.equal(validateRuntimeMinGlibc(resolve(outputDir, "native-runtimes/linux-cpu")), "2.35");
});

test("rejects a malformed glibc floor", async (t) => {
for (const min_glibc of ["2.35.1", "2", "", "two.35", 2.35, true]) {
const data = await fixture(t, { platform: { arch: "x86_64", min_glibc, os: "linux" } });
await assert.rejects(
verifyAndExtract({ archive: data.archive, checksum: data.checksum, outputDir: resolve(data.directory, "output"), sourceUrl: "https://example.test/archive", version: "0.73.1", flavor: "cpu" }),
/min_glibc must be a major\.minor version/,
`accepted ${JSON.stringify(min_glibc)}`,
);
}
});

test("rejects a glibc floor attached to a non-Linux runtime", async (t) => {
const data = await fixture(t, { platform: { arch: "aarch64", min_glibc: "2.35", os: "macos" } });
await assert.rejects(
verifyAndExtract({ archive: data.archive, checksum: data.checksum, outputDir: resolve(data.directory, "output"), sourceUrl: "https://example.test/archive", version: "0.73.1", flavor: "cpu" }),
/min_glibc is only meaningful on linux/,
);
});

test("accepts runtimes that predate the glibc floor", async (t) => {
// v0.76.2 and earlier ship no platform.min_glibc. Absence makes no claim, and
// the host in the same bundle predates the check that would reject it.
for (const platform of [undefined, { arch: "x86_64", os: "linux" }, { arch: "x86_64", min_glibc: null, os: "linux" }]) {
const data = await fixture(t, platform ? { platform } : {});
assert.equal(validateRuntimeMinGlibc(await extractTo(data, "output")), null);
}
});

test("makes no declaration demand of runtimes that ship no extra files", async (t) => {
const data = await fixture(t);
const outputDir = resolve(data.directory, "output");
Expand Down