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
93 changes: 71 additions & 22 deletions .github/workflows/package-image-row.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ permissions:

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
# The MeshLLM Depot project owns the persistent remote BuildKit cache.
DEPOT_PROJECT_ID: mzm95zcv7p

jobs:
package:
name: Produce native package
permissions:
contents: read
id-token: write
runs-on: ${{ fromJSON(fromJSON(inputs.row_json).runner_labels) }}
outputs:
package_base_image: ${{ steps.bases.outputs.package_base_image }}
Expand All @@ -44,7 +47,10 @@ jobs:
with:
name: ${{ fromJSON(inputs.row_json).upstream_artifact_id }}
path: artifacts/upstream
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
- name: Set up Depot CLI
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1
with:
version: 2.101.77
- name: Resolve exact package and runtime base images
id: bases
env:
Expand Down Expand Up @@ -74,8 +80,10 @@ jobs:
echo "runtime_base_image=$runtime_base_image"
} >> "$GITHUB_OUTPUT"
- name: Build native package from verified upstream product bundle
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
id: package
uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1
with:
project: ${{ env.DEPOT_PROJECT_ID }}
context: .
file: docker/Dockerfile.mesh-llm
target: native-package-artifact
Expand All @@ -87,26 +95,60 @@ jobs:
BACKEND_VERSION=${{ fromJSON(inputs.row_json).backend_version }}
TARGET_ARCH=${{ fromJSON(inputs.row_json).arch }}
MESH_LLM_VERSION=${{ fromJSON(inputs.row_json).mesh_version }}
# Package QA and artifact upload require the package bytes on the runner.
outputs: type=local,dest=artifacts/native-package
cache-from: type=gha,scope=package-${{ fromJSON(inputs.row_json).artifact_id }}
cache-to: type=gha,mode=max,scope=package-${{ fromJSON(inputs.row_json).artifact_id }}
- name: Namespace and require BuildKit package provenance
# Preserve a BuildKit attestation when the exporter can provide one.
provenance: mode=max
- name: Namespace exact package build provenance
env:
ARTIFACT_ID: ${{ fromJSON(inputs.row_json).artifact_id }}
DEPOT_BUILD_ID: ${{ steps.package.outputs.build-id }}
DEPOT_PROJECT_ID: ${{ steps.package.outputs.project-id }}
PACKAGE_FILE: ${{ fromJSON(inputs.row_json).package_file }}
shell: bash
run: |
set -euo pipefail
test -s artifacts/native-package/provenance.json
package_sha="$(sha256sum "artifacts/native-package/$PACKAGE_FILE" | awk '{ print $1 }')"
jq -e --arg name "$PACKAGE_FILE" --arg digest "$package_sha" '
._type == "https://in-toto.io/Statement/v1" and
(.subject | length) == 1 and
.subject[0].name == $name and
.subject[0].digest.sha256 == $digest
' artifacts/native-package/provenance.json >/dev/null
mv artifacts/native-package/provenance.json \
"artifacts/native-package/$ARTIFACT_ID.buildkit-provenance.json"
exporter_provenance="artifacts/native-package/provenance.json"
provenance="artifacts/native-package/$ARTIFACT_ID.buildkit-provenance.json"
has_exact_subject() {
jq -e --arg name "$PACKAGE_FILE" --arg digest "$package_sha" '
._type == "https://in-toto.io/Statement/v1" and
(.subject | length) == 1 and
.subject[0].name == $name and
.subject[0].digest.sha256 == $digest
' "$1" >/dev/null
}
# Depot's local exporter can emit an image-stage statement rather than
# one for the exported package. Preserve an exact statement; otherwise
# bind the exact package bytes to the remote Depot build.
if has_exact_subject "$exporter_provenance" 2>/dev/null; then
mv "$exporter_provenance" "$provenance"
else
rm -f "$exporter_provenance"
[[ "$DEPOT_BUILD_ID" =~ ^[a-z0-9]+$ ]]
[[ "$DEPOT_PROJECT_ID" =~ ^[a-z0-9]+$ ]]
jq_args=(
--arg name "$PACKAGE_FILE"
--arg digest "$package_sha"
--arg project_id "$DEPOT_PROJECT_ID"
--arg build_id "$DEPOT_BUILD_ID"
)
jq -n "${jq_args[@]}" '
{
_type: "https://in-toto.io/Statement/v1",
subject: [{name: $name, digest: {sha256: $digest}}],
predicateType: "https://meshllm.cloud/depot-build-receipt/v1",
predicate: {
builder: {
id: ("https://depot.dev/projects/" + $project_id + "/builds/" + $build_id)
},
build: {project_id: $project_id, build_id: $build_id}
}
}
' > "$provenance"
fi
has_exact_subject "$provenance"
- name: Verify and install package
shell: bash
run: |
Expand Down Expand Up @@ -149,6 +191,7 @@ jobs:
needs: package
permissions:
contents: read
id-token: write
runs-on: ${{ fromJSON(fromJSON(inputs.row_json).runner_labels) }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
Expand All @@ -158,7 +201,10 @@ jobs:
with:
name: ${{ fromJSON(inputs.row_json).native_package_artifact_name }}
path: artifacts/native-package
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
- name: Set up Depot CLI
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1
with:
version: 2.101.77
- name: Read immutable product inputs
id: product
shell: bash
Expand All @@ -181,12 +227,14 @@ jobs:
echo "runtime_sha=$(jq -r .runtime_sha256 "$provenance")"
} >> "$GITHUB_OUTPUT"
- name: Build the final runtime image once
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1
with:
project: ${{ env.DEPOT_PROJECT_ID }}
context: .
file: docker/Dockerfile.mesh-llm
target: runtime
platforms: ${{ fromJSON(inputs.row_json).platform }}
# Runtime QA runs Docker commands on the runner, so only this dry-run image is loaded.
load: true
tags: mesh-packaging-local:${{ fromJSON(inputs.row_json).artifact_id }}
build-args: |
Expand All @@ -200,8 +248,6 @@ jobs:
MESH_LLM_HOST_SHA=${{ steps.product.outputs.host_sha }}
MESH_LLM_RUNTIME_ID=${{ steps.product.outputs.runtime_id }}
MESH_LLM_RUNTIME_SHA=${{ steps.product.outputs.runtime_sha }}
cache-from: type=gha,scope=image-${{ fromJSON(inputs.row_json).artifact_id }}
cache-to: type=gha,mode=max,scope=image-${{ fromJSON(inputs.row_json).artifact_id }}
- name: Test the exact locally loaded final image
env:
IMAGE_REF: mesh-packaging-local:${{ fromJSON(inputs.row_json).artifact_id }}
Expand Down Expand Up @@ -252,7 +298,10 @@ jobs:
with:
name: ${{ fromJSON(inputs.row_json).native_package_artifact_name }}
path: artifacts/native-package
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
- name: Set up Depot CLI
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1
with:
version: 2.101.77
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
Expand Down Expand Up @@ -281,12 +330,14 @@ jobs:
} >> "$GITHUB_OUTPUT"
- name: Build and push one run-scoped staging image
id: stage
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1
with:
project: ${{ env.DEPOT_PROJECT_ID }}
context: .
file: docker/Dockerfile.mesh-llm
target: runtime
platforms: ${{ fromJSON(inputs.row_json).platform }}
# Push directly from Depot; downstream QA pulls the exact emitted digest.
push: true
tags: ${{ inputs.image_name }}:staging-${{ github.run_id }}-${{ github.run_attempt }}-${{ fromJSON(inputs.row_json).artifact_id }}
build-args: |
Expand All @@ -300,8 +351,6 @@ jobs:
MESH_LLM_HOST_SHA=${{ steps.product.outputs.host_sha }}
MESH_LLM_RUNTIME_ID=${{ steps.product.outputs.runtime_id }}
MESH_LLM_RUNTIME_SHA=${{ steps.product.outputs.runtime_sha }}
cache-from: type=gha,scope=image-${{ fromJSON(inputs.row_json).artifact_id }}
cache-to: type=gha,mode=max,scope=image-${{ fromJSON(inputs.row_json).artifact_id }}
- name: Pull and test the exact staged digest
env:
IMAGE_REF: ${{ inputs.image_name }}@${{ steps.stage.outputs.digest }}
Expand Down
12 changes: 12 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@
immutable version-tag conflict rejection, convenience-tag rollback evidence,
zero Docker builds in promotion, and a complete deterministic release index.

- [ ] Route package and image BuildKit execution through Depot remote builders.
Final result: native package, dry-image, and staging-image builds use the
MeshLLM Depot project and its persistent cache; package bytes return only for
required package QA/upload, dry images load only for runner-side QA, and
staging images push directly from Depot before digest-bound QA and promotion.
Operational prerequisite: configure a GitHub Actions OIDC trust relationship
for GitHub organization `Mesh-LLM` and repository `mesh-packaging` in the
Depot project; do not replace OIDC with a long-lived repository token.
QA: release-workflow tests reject hosted Buildx and `type=gha` cache use in
these paths; matrix validation, YAML/actionlint checks, Dockerfile checks,
and the relevant GitHub Actions workflow pass.

- [x] Consume upstream-produced Node addon artifacts instead of compiling addon
source in mesh-packaging.
Final result: the upstream release owns five platform-native addon producers;
Expand Down
59 changes: 42 additions & 17 deletions tests/release-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { test } from "node:test";

const release = readFileSync(resolve(".github/workflows/images-release.yml"), "utf8");
const row = readFileSync(resolve(".github/workflows/package-image-row.yml"), "utf8");
const pinnedBuildPushAction =
"uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7";
const pinnedDepotSetupAction =
"uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1";
const pinnedDepotBuildPushAction =
"uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1";

function section(source: string, start: string, end?: string): string {
const from = source.indexOf(start);
Expand All @@ -30,11 +32,13 @@ test("manual dispatch uses typed components and exact selectors", () => {
assert.match(release, /scripts\/release-plan\.ts/);
});

test("each path builds one final image and QA binds the same identity", () => {
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:");
const stage = section(row, " stage-image:");
assert.equal(dry.split(pinnedBuildPushAction).length - 1, 1);
assert.equal(stage.split(pinnedBuildPushAction).length - 1, 1);
assert.equal(packageJob.split(pinnedDepotBuildPushAction).length - 1, 1);
assert.equal(dry.split(pinnedDepotBuildPushAction).length - 1, 1);
assert.equal(stage.split(pinnedDepotBuildPushAction).length - 1, 1);
assert.match(dry, /target: runtime[\s\S]*load: true/);
assert.match(dry, /docker image inspect --format '\{\{\.Id\}\}' "\$IMAGE_REF"/);
assert.match(stage, /push: true[\s\S]*staging-\$\{\{ github\.run_id \}\}/);
Expand All @@ -44,17 +48,38 @@ test("each path builds one final image and QA binds the same identity", () => {
assert.doesNotMatch(row, /runtime-qa|type=cacheonly/);
});

test("package and image BuildKit work uses the Depot project cache", () => {
const packageJob = section(row, " package:", " dry-image:");
const dry = section(row, " dry-image:", " stage-image:");
const stage = section(row, " stage-image:");
assert.match(row, /DEPOT_PROJECT_ID: mzm95zcv7p/);
assert.equal(row.split(pinnedDepotSetupAction).length - 1, 3);
for (const job of [packageJob, dry, stage]) {
assert.match(job, new RegExp(pinnedDepotSetupAction.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
assert.match(job, new RegExp(pinnedDepotBuildPushAction.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
assert.match(job, /project: \$\{\{ env\.DEPOT_PROJECT_ID \}\}/);
assert.doesNotMatch(job, /docker\/(?:setup-buildx-action|build-push-action)/);
}
assert.match(packageJob, /id: package[\s\S]*outputs: type=local,dest=artifacts\/native-package/);
assert.match(packageJob, /provenance: mode=max/);
assert.match(packageJob, /DEPOT_BUILD_ID: \$\{\{ steps\.package\.outputs\.build-id \}\}/);
assert.match(packageJob, /DEPOT_PROJECT_ID: \$\{\{ steps\.package\.outputs\.project-id \}\}/);
assert.match(packageJob, /https:\/\/meshllm\.cloud\/depot-build-receipt\/v1/);
assert.match(dry, /load: true/);
assert.doesNotMatch(stage, /outputs: type=local|load: true/);
assert.match(stage, /push:\s+true/);
assert.match(stage, /tags: \$\{\{ inputs\.image_name \}\}:staging-/);
assert.match(stage, /IMAGE_REF: \$\{\{ inputs\.image_name \}\}@\$\{\{ steps\.stage\.outputs\.digest \}\}/);
assert.doesNotMatch(row, /cache-(?:from|to): type=gha/);
});

test("new reusable and image-index actions use immutable commits", () => {
assert.doesNotMatch(row, /uses: [^\s]+@v\d+/);
for (const action of [
"actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7",
"actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8",
"actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7",
"docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4",
"docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4",
pinnedBuildPushAction.slice("uses: ".length),
]) {
assert.ok(row.includes(action), `row workflow is missing immutable ${action}`);
const externalActionReferences = [...row.matchAll(
/^\s*(?:-\s+)?uses:\s*([^@\s]+)@([^\s#]+)/gm,
)];
assert.ok(externalActionReferences.length > 0);
for (const [, action, ref] of externalActionReferences) {
assert.match(ref, /^[0-9a-f]{40}$/, `${action} must use an immutable commit SHA`);
}
const index = section(release, " image-index:", " promote-images:");
for (const action of [
Expand All @@ -73,8 +98,8 @@ test("dry validation cannot write to a registry", () => {
assert.doesNotMatch(caller, /secrets:\s*inherit/);
const packageJob = section(row, " package:", " dry-image:");
const dry = section(row, " dry-image:", " stage-image:");
assert.match(packageJob, /permissions:\s+contents: read/);
assert.match(dry, /permissions:\s+contents: read/);
assert.match(packageJob, /permissions:\s+contents: read\s+id-token: write/);
assert.match(dry, /permissions:\s+contents: read\s+id-token: write/);
assert.doesNotMatch(dry, /packages: write|docker\/login-action|push: true/);
const stage = section(row, " stage-image:");
assert.match(stage, /environment: release/);
Expand Down
Loading