diff --git a/Dockerfile b/Dockerfile index 8ec1e6c2c..4385fce29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ ARG APPRISE_VERSION=1.12.0 # Build stage for frontend (must be built first for embedding) # Force amd64 platform to avoid slow QEMU emulation during multi-arch builds -FROM --platform=linux/amd64 node:24-alpine@sha256:50c8e8ca1d27439048670df5883f32d57cf81cff6233222c893fd0d9884cbd81 AS frontend-builder +FROM --platform=linux/amd64 node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1 AS frontend-builder WORKDIR /app/frontend-modern diff --git a/deploy/provider-msp/Dockerfile.control-plane b/deploy/provider-msp/Dockerfile.control-plane index 2f2002055..cde348f41 100644 --- a/deploy/provider-msp/Dockerfile.control-plane +++ b/deploy/provider-msp/Dockerfile.control-plane @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1.7 -FROM --platform=linux/amd64 node:24-alpine@sha256:50c8e8ca1d27439048670df5883f32d57cf81cff6233222c893fd0d9884cbd81 AS frontend-builder +FROM --platform=linux/amd64 node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1 AS frontend-builder WORKDIR /app/frontend-modern COPY frontend-modern/package*.json ./ RUN --mount=type=cache,id=pulse-control-plane-npm-cache,target=/root/.npm \ diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 013b563c0..eaa6eb250 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -5538,6 +5538,20 @@ a valid pinned SSH signature and the extracted request expression with absent and synthetic email. It does not establish installed onboarding, server acceptance or legal identity. See the qualification evidence below. +### Node 24 amd64 release-builder digest parity + +The canonical Pulse release Dockerfile and provider-MSP control-plane Dockerfile +pin the same immutable amd64 Node 24 Alpine frontend-builder image. The +`TestNode24FrontendBuilderDigestIsAligned` installability test binds both +build inputs to the current dependency digest. The canonical release-build +metadata proof `TestDockerBuildUsesCanonicalReleaseLdflags` and hosted +provider-MSP control proof `TestProviderMSPControlPlaneDockerfileBuildsReleaseLicenseBinary` +also require that exact digest while retaining their release metadata and +license-build assertions. These are source-contract checks, not an image build. +The hosted release build must verify the landed exact source, and provider-MSP +rollout and installed acceptance must be observed separately before this refresh +is considered operationally complete. + ### Pinned release action consumer compatibility The grouped release actions use immutable revisions recorded in diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index 4417d5955..5b83d8470 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -1641,7 +1641,7 @@ func TestDockerBuildUsesCanonicalReleaseLdflags(t *testing.T) { } dockerfile := string(dockerfileBytes) dockerRequired := []string{ - `FROM --platform=linux/amd64 node:24-alpine@sha256:`, + "FROM --platform=linux/amd64 node:24-alpine@sha256:" + node24Amd64FrontendDigest + " AS frontend-builder", `FROM --platform=linux/amd64 golang:1.26.8-alpine@sha256:`, `FROM backend-builder AS release-assets-builder`, `AS agent_runtime`, diff --git a/scripts/installtests/dependency_update_test.go b/scripts/installtests/dependency_update_test.go index ba77d0ebb..453cc8d82 100644 --- a/scripts/installtests/dependency_update_test.go +++ b/scripts/installtests/dependency_update_test.go @@ -1,6 +1,7 @@ package installtests import ( + "os" "regexp" "testing" "time" @@ -31,6 +32,28 @@ func assertDigestPinnedDockerStage(t *testing.T, dockerfile, prefix, suffix stri } } +const node24Amd64FrontendDigest = "ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1" + +func TestNode24FrontendBuilderDigestIsAligned(t *testing.T) { + pattern := regexp.MustCompile(`(?m)^FROM --platform=linux/amd64 node:24-alpine@sha256:([0-9a-f]{64}) AS frontend-builder$`) + for _, path := range []string{ + repoFile("Dockerfile"), + repoFile("deploy", "provider-msp", "Dockerfile.control-plane"), + } { + contents, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read Node 24 builder Dockerfile %s: %v", path, err) + } + matches := pattern.FindStringSubmatch(string(contents)) + if len(matches) != 2 { + t.Fatalf("%s must pin its amd64 Node 24 frontend builder by a full immutable digest", path) + } + if matches[1] != node24Amd64FrontendDigest { + t.Fatalf("%s Node 24 frontend builder digest is %s; expected %s", path, matches[1], node24Amd64FrontendDigest) + } + } +} + // TestGovernedContainerBaseSupportWindow turns the upstream lifecycle date // into an advance operational signal. Digest automation keeps a selected line // patched, but it cannot move a deliberately governed major/minor tag. diff --git a/scripts/installtests/provider_msp_deploy_test.go b/scripts/installtests/provider_msp_deploy_test.go index 113218d23..cfe375ec9 100644 --- a/scripts/installtests/provider_msp_deploy_test.go +++ b/scripts/installtests/provider_msp_deploy_test.go @@ -303,7 +303,7 @@ func TestProviderMSPControlPlaneDockerfileBuildsReleaseLicenseBinary(t *testing. text := string(dockerfileBytes) assertContainsAll(t, text, "# syntax=docker/dockerfile:1.7", - "FROM --platform=linux/amd64 node:24-alpine@sha256:", + "FROM --platform=linux/amd64 node:24-alpine@sha256:"+node24Amd64FrontendDigest+" AS frontend-builder", "npm ci", "npm run build", "FROM --platform=$BUILDPLATFORM golang:1.26.8-alpine@sha256:",