diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 8e6c7e3..b4b7f96 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -1,52 +1,73 @@ name: Docker Release -# Reusable matrix-based image build/push. +# Reusable image build/release via stevedore (https://github.com/blairham/stevedore). # -# The caller owns the service catalog. It passes `matrix` as a JSON string -# in the GitHub Actions matrix include shape: +# This REPLACED the matrix-based implementation on main — that snapshot lives +# on the frozen `pre-stevedore` tag, which every existing caller is pinned to. +# The pin comes off caller-by-caller as each repo adds a `.stevedore.yaml` and +# switches its `uses:` back to @main; the tag is deleted when the migration +# completes (see README "How to use"). # -# { -# "include": [ -# { -# "name": "trader-tools", # required: ECR repo basename + tag service segment -# "ecr_repo": "trader-tools", # required: full ECR repo path (e.g. "trading/oms") -# "dockerfile": "Dockerfile", # optional: defaults to "Dockerfile" -# "target": "production", # optional: docker --target value -# "build_args": "KEY=VAL\nK2=V2" # optional: newline-separated --build-arg entries -# } -# ] -# } +# Unlike the matrix implementation, the CALLER PASSES NO SERVICE MATRIX. +# stevedore reads the repo's .stevedore.yaml, resolves per-service ECR versions +# itself (`aws ecr describe-images`, highest X.Y.Z + bump — ECR stays the +# version record, platform-gitops#1201), does change detection against the +# `refs/releases/image/` marker refs (the same family the matrix +# implementation advanced — it fetches and re-pushes them itself), and builds +# only the images whose sources changed. No git tags or GitHub Releases are +# minted, same as before. # -# Version resolution reads ECR (highest X.Y.Z tag + 1) — the artifact -# store is the version record (platform-gitops#1201). Each successful -# build pushes the image, advances the `refs/releases/image/` -# marker ref (the change-detection baseline for discover-services), -# and notifies Dispatch directly (signed POST via notify-dispatch; -# the EventBridge ECR-push backstop covers a missed call). No git tags -# or GitHub Releases are minted — the historical `image//X.Y.Z` -# tags remain in repos as the frozen fallback baseline only. +# SINGLE JOB BY DESIGN (pinpredict/.github#39): all of a repo's images build on +# one runner sharing one BuildKit instance, so a build-once Dockerfile (e.g. +# trading#1967 — a shared compile stage with no per-image build args) compiles +# ONCE and every image reuses it, even with --parallel (BuildKit dedups +# identical in-flight stages). Fanning out per image (stevedore plan → matrix) +# would give every image a cold cache and re-run the shared stage per runner — +# only adopt matrix mode for repos whose images have nothing in common. +# +# Layer cache: exports STEVEDORE_CACHE_FROM/STEVEDORE_CACHE_TO (one type=gha +# scope per calling repo — GHA cache is repo-scoped already). Configs opt in +# per image with '{{ index .Env "STEVEDORE_CACHE_FROM" }}' entries, which +# render empty (and are skipped, stevedore >= v0.0.5) outside CI so local +# builds are unaffected. +# +# Remaining gap vs the matrix implementation: no Dispatch publish notification +# yet (blairham/stevedore#6) — the EventBridge ECR-push backstop still covers +# deploy correlation. Callers should keep `no-push: true` (the default) or stay +# on the pre-stevedore pin for real publishes until that is wired. on: workflow_call: inputs: - matrix: - description: "JSON matrix of services to build (GitHub Actions matrix `include` shape)" - required: true + command: + description: "stevedore subcommand (release, build, check)." + required: false type: string - private-modules: - description: >- - Mint a short-lived, read-only token for the org-wide pinpredict-argocd - App (BOOTSTRAP_APP_ID / BOOTSTRAP_APP_PRIVATE_KEY) and expose it to the - build as BuildKit secret `id=gh_token`. Opt-in for services whose - Dockerfile fetches a private pinpredict module instead of vendoring it - (e.g. a Go repo that requires github.com/pinpredict/ppkit) via - `RUN --mount=type=secret,id=gh_token ... go mod download`. Handed to - buildx as a `--secret`, never a `--build-arg`, so it never lands in an - image layer or `docker history`. Default false — a no-op for every - existing caller. + default: "release" + changed-since: + description: "git ref to diff against for change detection (e.g. the PR base or the pushed-before SHA)." + required: false + type: string + default: "" + no-push: + description: "Build without pushing (validation only). Default true until the Dispatch notification gap closes." required: false type: boolean - default: false + default: true + parallel: + description: "Build up to N images concurrently." + required: false + type: number + default: 4 + stevedore-version: + description: "stevedore release to use." + required: false + type: string + # v0.0.5 = plan/matrix mode (v0.0.4) + skip-empty cache entries + # (blairham/stevedore#8), which keeps LOCAL builds of cache-wired + # configs working; in CI the cache env below renders the entries + # non-empty either way. + default: "v0.0.5" env: AWS_REGION: us-east-1 @@ -57,180 +78,80 @@ concurrency: cancel-in-progress: false jobs: - build: - if: ${{ fromJson(inputs.matrix).include[0] != null }} + stevedore: runs-on: ubuntu-latest permissions: - id-token: write - contents: write - strategy: - fail-fast: false - matrix: ${{ fromJson(inputs.matrix) }} + contents: write # push refs/releases/image/* marker refs on publish + id-token: write # AWS OIDC (ECR) + cosign keyless signing steps: - uses: actions/checkout@v6 with: - fetch-depth: 0 - fetch-tags: true + fetch-depth: 0 # history for versioning and change detection (stevedore fetches marker refs itself) - # Three-shot retry on the AWS AssumeRole to survive a - # first-colocation race with Crossplane minting the per-service - # xp--gha-push role (pinpredict/trading#616). Empty - # `matrix.role` for un-migrated services falls back to the central - # xp-trading-gha-push role in secrets.AWS_ROLE_ARN. See the action - # for the full incident writeup. - name: Configure AWS credentials uses: pinpredict/.github/actions/configure-aws-with-retry@main with: - role-to-assume: ${{ matrix.role || secrets.AWS_ROLE_ARN }} + # Single-job builds push every image under the central role. The + # per-service xp--gha-push roles are a matrix-mode feature + # (stevedore plan + release --only) — see the workflow header. + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} - name: Log in to Amazon ECR id: ecr-login uses: aws-actions/amazon-ecr-login@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - # Opt-in (private-modules: true): mint a short-lived, read-only token for - # the org-wide pinpredict-argocd App so the build can fetch private - # pinpredict modules without vendoring. contents:read across the org (the - # App is installed on all repos); consumed by buildx as a BuildKit - # `--secret` below — never a build-arg — so it never lands in an image - # layer or `docker history`. Skipped (and the secret omitted) for every - # caller that leaves private-modules at its default false. - - name: Mint private-module read token - id: private-module-token - if: ${{ inputs.private-modules }} - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.BOOTSTRAP_APP_ID }} - private-key: ${{ secrets.BOOTSTRAP_APP_PRIVATE_KEY }} - owner: pinpredict - permission-contents: read - - - name: Resolve version, build, push, tag - id: release + - name: Compose args + id: args env: - NAME: ${{ matrix.name }} - ECR_REPO: ${{ matrix.ecr_repo }} - DOCKERFILE: ${{ matrix.dockerfile || 'Dockerfile' }} - TARGET: ${{ matrix.target }} - BUILD_ARGS: ${{ matrix.build_args }} - ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }} - SHORT_SHA: ${{ github.sha }} - # Empty unless private-modules minted a token above; gates the - # BuildKit --secret so it's a no-op for every other caller. - GH_PRIVATE_TOKEN: ${{ steps.private-module-token.outputs.token }} + NO_PUSH: ${{ inputs.no-push }} + PARALLEL: ${{ inputs.parallel }} + CHANGED_SINCE: ${{ inputs.changed-since }} run: | set -euo pipefail - SHORT_SHA="${SHORT_SHA:0:7}" - - # The ECR repo is the version record: next version is a patch - # bump of the highest X.Y.Z tag already pushed (the - - # sibling tags are filtered out). A missing repo or an empty - # tag list both mean first release. - latest=$(aws ecr describe-images \ - --repository-name "${ECR_REPO}" \ - --filter tagStatus=TAGGED \ - --query 'imageDetails[].imageTags[]' --output text 2>/dev/null \ - | tr '[:space:]' '\n' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \ - | sort -V | tail -n1 || true) - - if [ -z "$latest" ]; then - major=0; minor=0; patch=0 - else - IFS='.' read -r major minor patch <<< "$latest" - fi - patch=$((patch + 1)) - - # ECR tags are immutable — probe-and-bump past any candidate - # that already exists (covers races that slip the workflow - # concurrency group and repos with gaps above the max semver). - while aws ecr describe-images \ - --repository-name "${ECR_REPO}" \ - --image-ids imageTag="${major}.${minor}.${patch}" >/dev/null 2>&1; do - echo "Image ${ECR_REPO}:${major}.${minor}.${patch} exists in ECR — bumping" - patch=$((patch + 1)) - done - version="${major}.${minor}.${patch}" - - echo "Building ${ECR_REPO} v${version} from ${DOCKERFILE}${TARGET:+ (target=${TARGET})}" - - buildx_args=( - --platform linux/amd64 - --push - --provenance=false - --cache-from "type=gha,scope=${NAME}" - --cache-to "type=gha,mode=max,scope=${NAME}" - -t "${ECR_REGISTRY}/${ECR_REPO}:${version}" - -t "${ECR_REGISTRY}/${ECR_REPO}:${version}-${SHORT_SHA}" - -f "${DOCKERFILE}" - # Stamp the resolved version into the build so images can - # surface what they actually are at runtime (e.g. Vite-baked - # `VITE_APP_VERSION` for SPA bundles, /api/version fallback - # for backend services). Dockerfiles that don't declare - # `ARG APP_VERSION` silently ignore it. - --build-arg "APP_VERSION=${version}" - # Stamp the git short-SHA too. Unlike the per-repo `0.0.X` - # version (a build-ID), the commit is identical iff the code is - # identical — the reliable "is this the same build?" signal for - # services on parallel lanes (e.g. trader-tools stable vs dev). - # Dockerfiles that don't declare `ARG GIT_SHA` silently ignore it. - --build-arg "GIT_SHA=${SHORT_SHA}" - ) - if [ -n "${TARGET}" ]; then - buildx_args+=(--target "${TARGET}") - fi - if [ -n "${BUILD_ARGS}" ]; then - while IFS= read -r ba; do - [ -z "$ba" ] && continue - buildx_args+=(--build-arg "$ba") - done <<< "${BUILD_ARGS}" - fi - - # Private-module fetch (inputs.private-modules): hand the minted App - # token to the build as a BuildKit secret, readable inside the - # Dockerfile at /run/secrets/gh_token via - # `RUN --mount=type=secret,id=gh_token`. Only added when the token - # step ran (env non-empty) — a no-op for every other caller. - if [ -n "${GH_PRIVATE_TOKEN:-}" ]; then - buildx_args+=(--secret "id=gh_token,env=GH_PRIVATE_TOKEN") - fi - - docker buildx build "${buildx_args[@]}" . - - # Advance the per-service release marker — one mutable ref, - # not a tag, so it is invisible to Kargo's tag enumeration and - # the GitHub create-webhook. discover-services diffs against - # it to decide whether the service needs a rebuild. - git push --force origin "${GITHUB_SHA}:refs/releases/image/${NAME}" \ - || echo "::warning::Marker ref push failed for refs/releases/image/${NAME} — next run falls back to the legacy tag baseline" - - echo "version=${version}" >> "$GITHUB_OUTPUT" - - # The publish signal (platform-gitops#1201). Never fails the - # release; the EventBridge ECR-push backstop covers a missed - # notification. - - name: Notify Dispatch - if: steps.release.outcome == 'success' - uses: pinpredict/.github/actions/notify-dispatch@main + args="--parallel ${PARALLEL}" + [ "${NO_PUSH}" = "true" ] && args="${args} --no-push" + [ -n "${CHANGED_SINCE}" ] && args="${args} --changed-since ${CHANGED_SINCE}" + echo "value=${args}" >> "$GITHUB_OUTPUT" + + # buildx's type=gha cache backend reads ACTIONS_CACHE_URL / + # ACTIONS_RUNTIME_TOKEN, which GitHub only hands to actions — this step + # re-exports them to the job env so stevedore's shelled-out buildx sees + # them. + - name: Expose GitHub runtime for buildx gha cache + uses: crazy-max/ghaction-github-runtime@v3 + + - name: Stevedore + # uses: refs cannot be expressions — pin the action; the `version` + # input below selects the binary that actually runs. + uses: blairham/stevedore@v0.0.5 + env: + # ECR host, e.g. 123456789.dkr.ecr.us-east-1.amazonaws.com — the + # .stevedore.yaml repositories template this as {{ .Env.REGISTRY }}. + REGISTRY: ${{ steps.ecr-login.outputs.registry }} + # One layer-cache scope per calling repo (GHA cache is already + # repo-scoped). Configs opt in per image via + # '{{ index .Env "STEVEDORE_CACHE_FROM" }}' — repos without cache + # entries simply ignore these. + STEVEDORE_CACHE_FROM: "type=gha,scope=stevedore" + STEVEDORE_CACHE_TO: "type=gha,mode=max,scope=stevedore" with: - kind: image - service: ${{ matrix.name }} - version: ${{ steps.release.outputs.version }} - secret: ${{ secrets.CI_WEBHOOK_SECRET }} - - # Kept as the aggregate fail-gate (stable check name for branch - # protection). The GitHub release it used to create was keyed off the - # legacy image/* git tag; Dispatch's Slack cards carry the per-service - # changelog now (platform-gitops#1201). - release-summary: - needs: build - if: ${{ !cancelled() && needs.build.result != 'skipped' }} - runs-on: ubuntu-latest - steps: - - name: Fail if any build failed - if: needs.build.result != 'success' - run: | - echo "::error::One or more service builds failed (build job result: ${{ needs.build.result }})" - exit 1 + command: ${{ inputs.command }} + version: ${{ inputs.stevedore-version }} + args: ${{ steps.args.outputs.value }} + # cosign/syft/grype install is skipped — enable per config once + # sign/sbom/scan are turned on in .stevedore.yaml. + install-cosign: "false" + install-syft: "false" + install-grype: "false" + +# ── How a repo calls it ────────────────────────────────────────────────────── +# +# jobs: +# images: +# permissions: { contents: write, id-token: write } +# uses: pinpredict/.github/.github/workflows/docker-release.yml@main +# with: +# changed-since: ${{ github.event.before }} # or the PR base SHA +# no-push: true # flip to false to publish +# secrets: inherit diff --git a/AGENTS.md b/AGENTS.md index 1f06a8b..898ed7b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,7 +28,7 @@ Static lint: `actionlint.yml` runs on every PR that touches `.github/workflows/* ### Per-service IAM push role naming -Both `docker-release.yml` and `chart-release.yml` assume the deterministic role `arn:aws:iam::784682930591:role/xp--gha-push` (rendered by the Service XR composition in `platform-gitops`). If `matrix.role` is empty (un-migrated service/chart), the workflow falls back to `secrets.AWS_ROLE_ARN`. **Do not break this fallback** — some repos still rely on it. +`chart-release.yml` (and the frozen `docker-release.yml@pre-stevedore` that existing callers pin) assume the deterministic role `arn:aws:iam::784682930591:role/xp--gha-push` (rendered by the Service XR composition in `platform-gitops`). If `matrix.role` is empty (un-migrated service/chart), the workflow falls back to `secrets.AWS_ROLE_ARN`. **Do not break this fallback** — some repos still rely on it. The stevedore `docker-release.yml` on main builds all of a repo's images in one job under the central `secrets.AWS_ROLE_ARN`; per-service push roles are a stevedore matrix-mode feature (`plan` + `release --only`), deliberately not used for build-once repos (#39). Both workflows use a three-shot retry pattern (try / sleep 30 / retry / sleep 60 / retry) on `configure-aws-credentials` to survive the race where Crossplane is still creating the per-service role on first colocation. Incident reference: pinpredict/trading#616 (2-second race). Keep the retry pattern when editing AWS auth steps. @@ -36,7 +36,7 @@ Both workflows use a three-shot retry pattern (try / sleep 30 / retry / sleep 60 - **ECR is the version record** (platform-gitops#1201): both release workflows resolve the next version as the highest strict-`X.Y.Z` tag in the service's ECR repo (image or `charts/` OCI) plus one, then probe-and-bump past any existing candidate (ECR tags are immutable). Git tags are never read for versioning. - **Release marker refs**: `refs/releases/image/` and `refs/releases/chart/` — one mutable ref per service, force-advanced to the released SHA on every successful push. They are the change-detection baseline for `discover-services` and chart-release's prepare job (legacy `image|chart//X.Y.Z` tag is the fallback until a service releases once with the marker in place). Not tags, so they don't feed Kargo's tag enumeration or the GitHub `create` webhook. Readers must fetch them explicitly (`+refs/releases/*:refs/releases/*`). -- **Dispatch publish notification**: after each successful push, both release workflows call `actions/notify-dispatch` — a signed POST (service, version, full SHA, run URL) through the public webhook-forwarder `/dispatch/ci` route, HMAC'd with the org `CI_WEBHOOK_SECRET` (reaches reusable workflows via `secrets: inherit`). Warn-only on failure: the EventBridge ECR-push backstop covers a missed call. +- **Dispatch publish notification**: after each successful push, `chart-release.yml` and the pinned `docker-release.yml@pre-stevedore` call `actions/notify-dispatch` — the stevedore `docker-release.yml` on main does not yet (blairham/stevedore#6; its `no-push` defaults `true` until that closes). The notification is — a signed POST (service, version, full SHA, run URL) through the public webhook-forwarder `/dispatch/ci` route, HMAC'd with the org `CI_WEBHOOK_SECRET` (reaches reusable workflows via `secrets: inherit`). Warn-only on failure: the EventBridge ECR-push backstop covers a missed call. - **Chart sha alias**: chart-release aliases the pushed OCI chart as `X.Y.Z-` (via `aws ecr put-image` on the same manifest — allowed under tag immutability) so the ECR-push backstop can recover commit provenance for charts, mirroring the image tag pair. Semver-prerelease form, so Kargo chart Warehouses ignore it. - **No `image/*` / `chart/*` git tags and no GitHub Releases are minted** (platform-gitops#1201). The historical tags remain in service repos as the *frozen* fallback change-detection baseline — do not prune them until every service has released once with marker refs (the readers fall back to them). Do not reintroduce tag minting: it spams Kargo's git tag enumeration and the GitHub `create` webhook. - Per-service config tag (Kargo freight for `-config` Warehouse): `vX.Y.Z+` — semver **build metadata** form (`+`), not pre-release (`-`). The `+` form passes `semverConstraint: ">=0.0.0"` cleanly; the pre-release form would require `>=0.0.0-0`. **This tag family stays** — it is genuine Kargo freight. @@ -53,7 +53,7 @@ It posts a sticky PR comment with hook output and fails the job (so engineers se ## Editing playbook -- **Changing the docker matrix shape** in `discover-services/action.yml` means changing the corresponding consumer in `docker-release.yml` and (usually) the caller's `ci.yml` skeleton in `README.md` — keep all three in sync in one PR. +- **Changing the docker matrix shape** in `discover-services/action.yml`: its docker-release consumer now lives only on the frozen `@pre-stevedore` tag (never retarget it) — pinned callers' `ci.yml` files still read the matrix output, so treat the shape as frozen until the stevedore migration completes. - **Bumping action versions** (`actions/checkout`, `aws-actions/*`, etc.) — bump both workflows together if they share the action; mismatched versions across the two release workflows have caused subtle behavior splits before. - **Adding a new composite action** under `actions/`: add a row to the `## What's here` table in `README.md` and a usage block under `#### Language setup composites — usage` if it's a setup composite. - **Force `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true`** is set on workflows that use older marketplace actions still pinned to Node 20 — leave it in place when copying steps. diff --git a/README.md b/README.md index 35afe7a..9dba99d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Why `.github` and not a dedicated `github-actions` repo: `.github` is *the* GitH | File | Purpose | |---|---| -| `docker-release.yml` | Matrix-based image build + push to ECR. Version = highest `X.Y.Z` tag in the ECR repo + 1 (ECR is the version record — platform-gitops#1201); advances the `refs/releases/image/` marker ref and notifies Dispatch. No git tags or GitHub Releases. Caller passes a `matrix` input in the standard `{include:[...]}` shape. Optional `private-modules: true` mints a short-lived read-only `pinpredict-argocd` App token and exposes it to the build as BuildKit secret `id=gh_token` (`RUN --mount=type=secret,id=gh_token …`) — for Dockerfiles that fetch a private pinpredict module (e.g. `github.com/pinpredict/ppkit`) instead of vendoring it. Default false. | +| `docker-release.yml` | Image build + release via [stevedore](https://github.com/blairham/stevedore), driven by the caller repo's `.stevedore.yaml` — **no `matrix` input**. Single job by design (pinpredict/.github#39): one runner + one BuildKit, so a build-once Dockerfile compiles once for every image. Version = highest `X.Y.Z` tag in the ECR repo + 1 (ECR is the version record — platform-gitops#1201, resolved by stevedore); advances the `refs/releases/image/` marker refs. No git tags or GitHub Releases. Exports `STEVEDORE_CACHE_FROM/TO` (one `type=gha` scope) for configs that opt into layer caching. `no-push` defaults `true` until the Dispatch notification gap (blairham/stevedore#6) closes. Existing callers are pinned to `@pre-stevedore` (the frozen matrix implementation, which also carried the `private-modules` BuildKit-secret input) — see the tagging exception below. | | `chart-release.yml` | Auto-discovers `charts/*/`, skips charts unchanged since their `refs/releases/chart/` marker ref, resolves the next version from the ECR OCI repo, packages, pushes (+ `X.Y.Z-` provenance alias), advances the marker, notifies Dispatch. No git tags or GitHub Releases. No caller inputs. | | `tag-config.yml` | Tags merges to main that touch `.platform/services/.yaml` with `vX.Y.Z+` (per-service Kargo `-config` Warehouse freight), then dispatches `service-config-tag` to platform-gitops so missing pointer files get seeded. | | `actionlint.yml` | Lints GitHub Actions workflow YAML with [`actionlint`](https://github.com/rhysd/actionlint) at a pinned version. Self-runs on this repo when PRs/pushes touch `.github/workflows/**` or `actions/**/action.yml`; callers reuse it via `uses: pinpredict/.github/.github/workflows/actionlint.yml@main`. | @@ -70,7 +70,7 @@ Why `.github` and not a dedicated `github-actions` repo: `.github` is *the* GitH Pin callers to `@main`. We own all consumers, so version pinning adds overhead without safety benefit at this team size — `@main` gives the "edit once, propagate everywhere" property that's the whole point of centralizing. If blast radius ever bites, we add a tag selectively for the workflows that broke; we don't pre-tag everything. -**Current exception — the `pre-stevedore` tag.** `docker-release.yml` is being rebuilt on [stevedore](https://github.com/blairham/stevedore), which is exactly the "blast radius bites" case the rule reserves. Callers of `docker-release.yml` (and only that workflow) are temporarily pinned to `@pre-stevedore`, a frozen snapshot of main — not a maintained version line, no fixes land on it. Everything else (chart-release, composites, tag-config) stays on `@main`. The pin comes off caller-by-caller as each repo migrates, and the tag is deleted when the migration completes. +**Current exception — the `pre-stevedore` tag.** `docker-release.yml` on main is now the [stevedore](https://github.com/blairham/stevedore) implementation — the rebuild that is exactly the "blast radius bites" case the rule reserves. Callers of `docker-release.yml` (and only that workflow) are temporarily pinned to `@pre-stevedore`, the frozen snapshot of the old matrix implementation — not a maintained version line, no fixes land on it. Everything else (chart-release, composites, tag-config) stays on `@main`. A repo migrates by adding a `.stevedore.yaml`, dropping the `matrix` input from its `ci.yml`, and pointing `uses:` back to `@main`; the tag is deleted when the migration completes. For workflows that touch secrets/OIDC, pin to an immutable SHA only if a security audit later requires it. @@ -132,13 +132,16 @@ jobs: with: github-token: ${{ steps.pg-read-token.outputs.token }} + # Stevedore reads .stevedore.yaml and does its own change detection — + # no matrix input. Migrating repos: swap @pre-stevedore back to @main + # and drop the matrix wiring. docker-release: needs: [detect, test] - if: needs.detect.outputs.docker_matrix != '{"include":[]}' permissions: { id-token: write, contents: write } uses: pinpredict/.github/.github/workflows/docker-release.yml@main with: - matrix: ${{ needs.detect.outputs.docker_matrix }} + changed-since: ${{ github.event.before }} + no-push: true # flip to false once the Dispatch notification lands secrets: inherit chart-release: