Skip to content
Merged
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
25 changes: 23 additions & 2 deletions .github/workflows/images-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ on:
description: Release version (for example 0.73.1)
required: true
type: string
expected_mesh_sha:
description: Optional expected immutable upstream tag SHA (40 lowercase hexadecimal characters)
required: false
default: ''
type: string
dry_run:
description: Build and test everything without publishing
required: true
Expand Down Expand Up @@ -115,6 +120,7 @@ jobs:
INPUT_REPOSITORY: ${{ inputs.mesh_repository }}
INPUT_REF: ${{ inputs.mesh_ref }}
INPUT_VERSION: ${{ inputs.mesh_version }}
INPUT_EXPECTED_MESH_SHA: ${{ inputs.expected_mesh_sha }}
INPUT_DRY_RUN: ${{ inputs.dry_run }}
INPUT_PUBLISH_IMAGES: ${{ inputs.publish_images }}
INPUT_PUBLISH_RELEASE_ASSETS: ${{ inputs.publish_release_assets }}
Expand All @@ -130,6 +136,7 @@ jobs:
repository="${DISPATCH_REPOSITORY:-${INPUT_REPOSITORY:-Mesh-LLM/mesh-llm}}"
ref="${DISPATCH_REF:-${INPUT_REF:-}}"
version="${DISPATCH_VERSION:-${INPUT_VERSION:-}}"
expected_mesh_sha="${INPUT_EXPECTED_MESH_SHA:-}"
dry_run="${DISPATCH_DRY_RUN:-${INPUT_DRY_RUN:-true}}"
publish_images="${DISPATCH_PUBLISH_IMAGES:-${INPUT_PUBLISH_IMAGES:-false}}"
publish_release_assets="${DISPATCH_PUBLISH_RELEASE_ASSETS:-${INPUT_PUBLISH_RELEASE_ASSETS:-false}}"
Expand Down Expand Up @@ -165,6 +172,20 @@ jobs:
mesh_sha="$(git ls-remote "https://github.com/$repository.git" "refs/tags/$ref" | awk 'NR == 1 { print $1 }')"
fi
[[ "$mesh_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "could not resolve immutable upstream tag SHA" >&2; exit 1; }
validate_expected_mesh_sha() {
local expected="$1"
local resolved="$2"
[[ -z "$expected" ]] && return 0
[[ "$expected" =~ ^[0-9a-f]{40}$ ]] || {
echo "expected_mesh_sha must be 40 lowercase hexadecimal characters" >&2
return 1
}
[[ "$expected" == "$resolved" ]] || {
echo "resolved upstream tag SHA $resolved does not match expected_mesh_sha $expected" >&2
return 1
}
}
validate_expected_mesh_sha "$expected_mesh_sha" "$mesh_sha"

{
echo "mesh_repository=$repository"
Expand Down Expand Up @@ -574,8 +595,8 @@ jobs:
jq -e --slurpfile results staged-results.json '
(.include | length) == ($results[0] | length) and
([.include[].artifact_id] | sort) == ([$results[0][].artifact_id] | sort) and
all(.include[] as $row;
any($results[0][]; .artifact_id == $row.artifact_id and
all(.include[];
. as $row | any($results[0][]; .artifact_id == $row.artifact_id and
.platform == $row.platform and .arch == $row.arch and
.backend == $row.backend and .backend_version == $row.backend_version and
.package_file == $row.package_file and
Expand Down
11 changes: 11 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Production Readiness TODO

- [x] Repair exact-image index assembly after the v0.75.0 publication failure.
Final result: the release workflow uses jq's valid `all(generator; condition)`
form and retains the selected matrix row as an explicit binding while matching
every staged, digest-tested result. Manual recovery dispatches may additionally
pin the expected immutable upstream tag SHA and fail before build fan-out if it
is malformed or differs from the resolved tag.
QA: the workflow regression executes the embedded jq filter against exact and
mismatched fixtures, and directly exercises empty, matching, mismatching, and
malformed expected SHA values; the full TypeScript suite, actionlint, release
matrix consistency check, and `git diff --check` pass.

- [x] Add measured Depot Registry pull-through base-image support.
Final result: trusted image rows may substitute configured Depot mirrors for
Ubuntu, CUDA, ROCm, and Arch bases while retaining the original tag or digest;
Expand Down
68 changes: 67 additions & 1 deletion tests/release-workflow.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { resolve } from "node:path";
import { test } from "node:test";

Expand Down Expand Up @@ -32,6 +34,27 @@ test("manual dispatch uses typed components and exact selectors", () => {
assert.match(release, /scripts\/release-plan\.ts/);
});

test("manual dispatch can fail closed on an expected immutable upstream SHA", () => {
assert.match(release, /expected_mesh_sha:[\s\S]*required: false[\s\S]*type: string/);
const functionMatch = release.match(
/(validate_expected_mesh_sha\(\) \{[\s\S]*?^ \})/m,
);
assert.ok(functionMatch, "expected SHA validator function is missing");
const validator = functionMatch[1].replace(/^ {10}/gm, "");
const sha = "a".repeat(40);
const run = (expected: string, resolved = sha) => spawnSync(
"bash",
["-c", `${validator}\nvalidate_expected_mesh_sha "$1" "$2"`, "test", expected, resolved],
{ encoding: "utf8" },
);

assert.equal(run("", sha).status, 0);
assert.equal(run(sha, sha).status, 0);
assert.notEqual(run("b".repeat(40), sha).status, 0);
assert.notEqual(run("A".repeat(40), sha).status, 0);
assert.notEqual(run("short", sha).status, 0);
});

test("each path builds once on Depot and QA binds the same identity", () => {
const packageJob = section(row, " package:", " dry-image:");
const dry = section(row, " dry-image:", " stage-image:");
Expand Down Expand Up @@ -182,6 +205,49 @@ test("promotion consumes the canonical tested index without rebuilding", () => {
assert.doesNotMatch(promotion, /build-push-action|Dockerfile|docker buildx build/);
});

test("image index matrix binding executes for exact staged results", (t) => {
const index = section(release, " image-index:", " promote-images:");
const filterMatch = index.match(
/jq -e --slurpfile results staged-results\.json '\n([\s\S]*?)\n ' package-matrix\.json/,
);
assert.ok(filterMatch, "image index matrix-binding jq filter is missing");
const filter = filterMatch[1].replace(/^ {12}/gm, "");
const directory = mkdtempSync(resolve(tmpdir(), "image-index-workflow-test-"));
t.after(() => rmSync(directory, { recursive: true, force: true }));
const matrixPath = resolve(directory, "package-matrix.json");
const resultsPath = resolve(directory, "staged-results.json");
const matrixRow = {
artifact_id: "ubuntu-cpu-amd64",
platform: "linux/amd64",
arch: "amd64",
backend: "cpu",
backend_version: "",
package_file: "mesh-llm-0.75.0-ubuntu-amd64-cpu.deb",
package_base_image: "ubuntu:24.04",
runtime_base_image: "ubuntu:24.04",
tags: "ghcr.io/mesh-llm/mesh-llm:0.75.0-ubuntu-amd64-cpu\nghcr.io/mesh-llm/mesh-llm:ubuntu-amd64-cpu",
};
const resultRow = {
...matrixRow,
package_base_image: `ubuntu@sha256:${"a".repeat(64)}`,
runtime_base_image: `ubuntu@sha256:${"a".repeat(64)}`,
tags: matrixRow.tags.split("\n"),
};
writeFileSync(matrixPath, JSON.stringify({ include: [matrixRow] }));
writeFileSync(resultsPath, JSON.stringify([resultRow]));

const exact = spawnSync("jq", ["-e", "--slurpfile", "results", resultsPath, filter, matrixPath], {
encoding: "utf8",
});
assert.equal(exact.status, 0, exact.stderr);

writeFileSync(resultsPath, JSON.stringify([{ ...resultRow, backend: "vulkan" }]));
const mismatch = spawnSync("jq", ["-e", "--slurpfile", "results", resultsPath, filter, matrixPath], {
encoding: "utf8",
});
assert.notEqual(mismatch.status, 0);
});

test("Node packaging consumes safe upstream addon artifacts without compiling", () => {
const addons = section(release, " node-sdk-addon:", " node-sdk-preflight:");
assert.match(addons, /scripts\/upstream-node-addon\.ts/);
Expand Down