chore(ci): add auto-bump-images + pkg/** path filter to all build-*-controller workflows (Closes #2006)

TBD-A69. PR #2005 fixed build-organization-controller.yaml only. The
other six controller workflows (application, blueprint, continuum,
environment, sandbox, useraccess) had the same gaps that caused the
#1997 18h deploy gap:

- application-controller: missing pkg/** in path filter (auto-bump
  already present from earlier work).
- blueprint, continuum, environment, useraccess: missing BOTH pkg/**
  path filter AND auto-bump pipeline (permissions promotion +
  values.yaml bump + commit/push + blueprint-release dispatch).
- sandbox: already complete (pkg/** + auto-bump to platform/sandbox
  chart) — left untouched.

Each updated workflow inherits the canonical shape from
build-organization-controller.yaml (PR #2005):

  1. `core/controllers/pkg/**` added to BOTH push.paths and
     pull_request.paths. Without this, a fix that only touches the
     shared HTTP-client tree (gitea/keycloak/kc-mappers) silently
     fails to rebuild the controller image.
  2. `permissions.contents: write` + `actions: write` so the build
     job can push the values.yaml bump and dispatch the downstream
     chart re-publish.
  3. An awk-scoped `Bump controllers.<who>.image.tag in values.yaml`
     step that updates ONLY the targeted controller's tag (verified
     locally — sibling tags remain untouched).
  4. A commit/push step that bumps
     products/catalyst/chart/values.yaml (or
     products/continuum/chart/values.yaml for continuum, which has
     its own chart).
  5. A `gh workflow run blueprint-release.yaml` dispatch so the
     bot-pushed commit fires the downstream chart re-publish
     (GitHub Actions silently filters bot pushes from path-trigger
     workflows otherwise).

Adds two new files to lock the shape in:

  - `scripts/check-controller-workflow-uniformity.sh` — a CI
    regression test that grep-asserts every controller workflow has
    the canonical pkg/** filter + auto-bump pipeline. Fails loudly
    if any new controller workflow ships without the canonical shape,
    or if an existing one regresses.
  - `.github/workflows/check-controller-workflow-uniformity.yaml` —
    push-on-touch + pull_request-on-touch event-driven wrapper that
    runs the script. Mirrors the shape of check-vendor-coupling.yaml.

Verified locally:
  - YAML syntax valid for all 7 controller workflows + the new check
    workflow.
  - Regression script passes on all 7 controller workflows.
  - Simulated awk bumps against products/catalyst/chart/values.yaml
    and products/continuum/chart/values.yaml — each script bumps
    ONLY the targeted controller's tag, sibling tags untouched.

No chart bumps. No Go/chart changes. CI-workflow-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hatiyildiz 2026-05-20 01:23:14 +02:00
parent 4568298c9b
commit 354ddd5711
7 changed files with 513 additions and 4 deletions

View File

@ -20,6 +20,15 @@ on:
paths:
- 'core/controllers/application/**'
- 'core/controllers/internal/**'
# core/controllers/pkg/** is the shared HTTP-client tree (gitea,
# keycloak, kc-mappers, …) consumed by every Group C controller's
# Containerfile via `COPY core/controllers/pkg`. Without this path
# entry a change to the shared pkg/ tree rebuilds the image only
# if the same PR also happens to touch files under application/ —
# which silently held the t38 #1997 gitea-405 fix in main for
# ~12h. Uniform pattern across every build-*-controller.yaml
# (TBD-A69 #2006).
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- '.github/workflows/build-application-controller.yaml'
@ -28,6 +37,7 @@ on:
paths:
- 'core/controllers/application/**'
- 'core/controllers/internal/**'
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- '.github/workflows/build-application-controller.yaml'

View File

@ -21,6 +21,15 @@ on:
paths:
- 'core/controllers/blueprint/**'
- 'core/controllers/internal/**'
# core/controllers/pkg/** is the shared HTTP-client tree (gitea,
# keycloak, kc-mappers, …) consumed by every Group C controller's
# Containerfile via `COPY core/controllers/pkg`. Without this path
# entry a change to the shared pkg/ tree rebuilds the image only
# if the same PR also happens to touch files under blueprint/ —
# which silently held the t38 #1997 gitea-405 fix in main for
# ~12h. Uniform pattern across every build-*-controller.yaml
# (TBD-A69 #2006).
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- '.github/workflows/build-blueprint-controller.yaml'
@ -29,6 +38,7 @@ on:
paths:
- 'core/controllers/blueprint/**'
- 'core/controllers/internal/**'
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- '.github/workflows/build-blueprint-controller.yaml'
@ -42,10 +52,19 @@ jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
# contents: write — the deploy step below pushes a values.yaml SHA
# bump back to main so the bp-catalyst-platform chart picks up the
# newly-built image without an operator manually editing the file
# (per `feedback_no_mvp_no_workarounds.md` rule 1: target-state,
# never "manual follow-up bump"). Pre-#2006 this workflow shipped
# without auto-bump — same deploy-gap class as #1997.
contents: write
packages: write
# id-token write is required by cosign keyless signing (Sigstore).
id-token: write
# actions: write — required for `gh workflow run` to dispatch the
# downstream blueprint-release chart re-publish workflow.
actions: write
outputs:
sha_short: ${{ steps.vars.outputs.sha_short }}
digest: ${{ steps.build.outputs.digest }}
@ -133,3 +152,67 @@ jobs:
--predicate <(echo '{"sbom":"in-toto-spdx attached at build time"}') \
--type spdx \
"${IMAGE}@${DIGEST}"
# Auto-bump the chart values.yaml tag so the next Sovereign chart
# rollout picks up this image without a manual edit. Per
# `feedback_no_mvp_no_workarounds.md` rule 1 (target-state, no
# operator-action gates) and `feedback_inviolable_principles.md`
# (event-driven, never cron). Mirrors the pattern in
# build-application-controller.yaml + build-organization-controller.yaml.
# Added as part of TBD-A69 (#2006) — pre-#2006 this workflow shipped
# without auto-bump, so the same deploy-gap class as #1997 was live
# for every blueprint-controller code fix.
- name: Bump controllers.blueprint.image.tag in values.yaml
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
env:
SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
run: |
VALUES="products/catalyst/chart/values.yaml"
# awk: find ` blueprint:` under `controllers:`, then update
# the next `tag: "..."` line. Stops at the next top-level key
# so we don't accidentally bump a sibling controller's tag.
awk -v sha="${SHA_SHORT}" '
/^controllers:/ { in_ctrls=1 }
in_ctrls && /^ blueprint:/ { print; in_bp=1; next }
in_ctrls && /^ [a-z]/ && !/^ blueprint:/ { in_bp=0 }
in_bp && /^ tag:/ { sub(/"[^"]*"/, "\"" sha "\""); in_bp=0 }
{ print }
' "${VALUES}" > "${VALUES}.tmp" && mv "${VALUES}.tmp" "${VALUES}"
echo "values.yaml after bump:"
grep -A4 "^ blueprint:" "${VALUES}" | head -10
- name: Commit and push values.yaml bump
id: deploy_commit
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
env:
SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet products/catalyst/chart/values.yaml; then
echo "no values.yaml change — already pinned to ${SHA_SHORT}"
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git add products/catalyst/chart/values.yaml
git commit -m "deploy: bump blueprint-controller image to ${SHA_SHORT}"
# Pull-rebase to avoid races with parallel build commits.
git pull --rebase --autostash origin main || true
git push origin HEAD:main
echo "pushed=true" >> "$GITHUB_OUTPUT"
# GitHub Actions does NOT trigger workflows from bot pushes by
# default (anti-recursion safeguard). Without this dispatch the
# rebuilt image is NEVER baked into a new chart version, so
# Sovereigns keep installing the previous chart with the previous
# image tag (`feedback_no_mvp_no_workarounds.md` rule 1 violation).
- name: Dispatch blueprint-release for chart re-publish
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && steps.deploy_commit.outputs.pushed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run blueprint-release.yaml \
--repo "${GITHUB_REPOSITORY}" \
--ref main \
-f blueprint=catalyst \
-f tree=products

View File

@ -20,6 +20,15 @@ on:
paths:
- 'core/controllers/continuum/**'
- 'core/controllers/internal/**'
# core/controllers/pkg/** is the shared HTTP-client tree (gitea,
# keycloak, kc-mappers, …) consumed by every Group C controller's
# Containerfile via `COPY core/controllers/pkg`. Without this path
# entry a change to the shared pkg/ tree rebuilds the image only
# if the same PR also happens to touch files under continuum/ —
# which silently held the t38 #1997 gitea-405 fix in main for
# ~12h. Uniform pattern across every build-*-controller.yaml
# (TBD-A69 #2006).
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- 'products/continuum/**'
@ -29,6 +38,7 @@ on:
paths:
- 'core/controllers/continuum/**'
- 'core/controllers/internal/**'
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- 'products/continuum/**'
@ -43,10 +53,19 @@ jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
# contents: write — the deploy step below pushes a values.yaml SHA
# bump back to main so the products/continuum chart picks up the
# newly-built image without an operator manually editing the file
# (per `feedback_no_mvp_no_workarounds.md` rule 1: target-state,
# never "manual follow-up bump"). Pre-#2006 this workflow shipped
# without auto-bump — same deploy-gap class as #1997.
contents: write
packages: write
# id-token write is required by cosign keyless signing (Sigstore).
id-token: write
# actions: write — required for `gh workflow run` to dispatch the
# downstream blueprint-release chart re-publish workflow.
actions: write
outputs:
sha_short: ${{ steps.vars.outputs.sha_short }}
digest: ${{ steps.build.outputs.digest }}
@ -184,6 +203,72 @@ jobs:
--type spdx \
"${IMAGE}@${DIGEST}"
# Auto-bump the chart values.yaml tag so the next Sovereign chart
# rollout picks up this image without a manual edit. Per
# `feedback_no_mvp_no_workarounds.md` rule 1 (target-state, no
# operator-action gates) and `feedback_inviolable_principles.md`
# (event-driven, never cron). Unlike sibling controllers that ship
# in the catalyst chart, continuum-controller has its own
# standalone chart at products/continuum/chart/values.yaml whose
# top-level `continuum.image.tag` is what gets stamped.
# Added as part of TBD-A69 (#2006) — pre-#2006 this workflow shipped
# without auto-bump, so the same deploy-gap class as #1997 was live
# for every continuum-controller code fix.
- name: Bump continuum.image.tag in products/continuum/chart/values.yaml
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
env:
SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
run: |
VALUES="products/continuum/chart/values.yaml"
# awk: find top-level `continuum:`, then update the next
# `tag: "..."` line under its `image:` sub-block. Stops at the
# next top-level key so we don't accidentally bump an unrelated
# tag.
awk -v sha="${SHA_SHORT}" '
/^continuum:/ { in_cont=1 }
in_cont && /^[a-z]/ && !/^continuum:/ { in_cont=0 }
in_cont && /^ tag:/ { sub(/"[^"]*"/, "\"" sha "\""); in_cont=0 }
{ print }
' "${VALUES}" > "${VALUES}.tmp" && mv "${VALUES}.tmp" "${VALUES}"
echo "values.yaml after bump:"
grep -A4 "^continuum:" "${VALUES}" | head -10
- name: Commit and push values.yaml bump
id: deploy_commit
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
env:
SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet products/continuum/chart/values.yaml; then
echo "no values.yaml change — already pinned to ${SHA_SHORT}"
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git add products/continuum/chart/values.yaml
git commit -m "deploy: bump continuum-controller image to ${SHA_SHORT}"
# Pull-rebase to avoid races with parallel build commits.
git pull --rebase --autostash origin main || true
git push origin HEAD:main
echo "pushed=true" >> "$GITHUB_OUTPUT"
# GitHub Actions does NOT trigger workflows from bot pushes by
# default (anti-recursion safeguard). Without this dispatch the
# rebuilt image is NEVER baked into a new chart version, so
# Sovereigns keep installing the previous chart with the previous
# image tag (`feedback_no_mvp_no_workarounds.md` rule 1 violation).
- name: Dispatch blueprint-release for chart re-publish
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && steps.deploy_commit.outputs.pushed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run blueprint-release.yaml \
--repo "${GITHUB_REPOSITORY}" \
--ref main \
-f blueprint=continuum \
-f tree=products
notify:
# repository_dispatch on success → triggers downstream chart-bump
# workflow that stamps the image SHA into per-Sovereign overlay

View File

@ -15,6 +15,15 @@ on:
paths:
- 'core/controllers/environment/**'
- 'core/controllers/internal/**'
# core/controllers/pkg/** is the shared HTTP-client tree (gitea,
# keycloak, kc-mappers, …) consumed by every Group C controller's
# Containerfile via `COPY core/controllers/pkg`. Without this path
# entry a change to the shared pkg/ tree rebuilds the image only
# if the same PR also happens to touch files under environment/ —
# which silently held the t38 #1997 gitea-405 fix in main for
# ~12h. Uniform pattern across every build-*-controller.yaml
# (TBD-A69 #2006).
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- '.github/workflows/build-environment-controller.yaml'
@ -23,6 +32,7 @@ on:
paths:
- 'core/controllers/environment/**'
- 'core/controllers/internal/**'
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- '.github/workflows/build-environment-controller.yaml'
@ -36,10 +46,19 @@ jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
# contents: write — the deploy step below pushes a values.yaml SHA
# bump back to main so the bp-catalyst-platform chart picks up the
# newly-built image without an operator manually editing the file
# (per `feedback_no_mvp_no_workarounds.md` rule 1: target-state,
# never "manual follow-up bump"). Pre-#2006 this workflow shipped
# without auto-bump — same deploy-gap class as #1997.
contents: write
packages: write
# id-token write is required by cosign keyless signing (Sigstore).
id-token: write
# actions: write — required for `gh workflow run` to dispatch the
# downstream blueprint-release chart re-publish workflow.
actions: write
outputs:
sha_short: ${{ steps.vars.outputs.sha_short }}
digest: ${{ steps.build.outputs.digest }}
@ -127,3 +146,67 @@ jobs:
--predicate <(echo '{"sbom":"in-toto-spdx attached at build time"}') \
--type spdx \
"${IMAGE}@${DIGEST}"
# Auto-bump the chart values.yaml tag so the next Sovereign chart
# rollout picks up this image without a manual edit. Per
# `feedback_no_mvp_no_workarounds.md` rule 1 (target-state, no
# operator-action gates) and `feedback_inviolable_principles.md`
# (event-driven, never cron). Mirrors the pattern in
# build-application-controller.yaml + build-organization-controller.yaml.
# Added as part of TBD-A69 (#2006) — pre-#2006 this workflow shipped
# without auto-bump, so the same deploy-gap class as #1997 was live
# for every environment-controller code fix.
- name: Bump controllers.environment.image.tag in values.yaml
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
env:
SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
run: |
VALUES="products/catalyst/chart/values.yaml"
# awk: find ` environment:` under `controllers:`, then update
# the next `tag: "..."` line. Stops at the next top-level key
# so we don't accidentally bump a sibling controller's tag.
awk -v sha="${SHA_SHORT}" '
/^controllers:/ { in_ctrls=1 }
in_ctrls && /^ environment:/ { print; in_env=1; next }
in_ctrls && /^ [a-z]/ && !/^ environment:/ { in_env=0 }
in_env && /^ tag:/ { sub(/"[^"]*"/, "\"" sha "\""); in_env=0 }
{ print }
' "${VALUES}" > "${VALUES}.tmp" && mv "${VALUES}.tmp" "${VALUES}"
echo "values.yaml after bump:"
grep -A4 "^ environment:" "${VALUES}" | head -10
- name: Commit and push values.yaml bump
id: deploy_commit
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
env:
SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet products/catalyst/chart/values.yaml; then
echo "no values.yaml change — already pinned to ${SHA_SHORT}"
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git add products/catalyst/chart/values.yaml
git commit -m "deploy: bump environment-controller image to ${SHA_SHORT}"
# Pull-rebase to avoid races with parallel build commits.
git pull --rebase --autostash origin main || true
git push origin HEAD:main
echo "pushed=true" >> "$GITHUB_OUTPUT"
# GitHub Actions does NOT trigger workflows from bot pushes by
# default (anti-recursion safeguard). Without this dispatch the
# rebuilt image is NEVER baked into a new chart version, so
# Sovereigns keep installing the previous chart with the previous
# image tag (`feedback_no_mvp_no_workarounds.md` rule 1 violation).
- name: Dispatch blueprint-release for chart re-publish
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && steps.deploy_commit.outputs.pushed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run blueprint-release.yaml \
--repo "${GITHUB_REPOSITORY}" \
--ref main \
-f blueprint=catalyst \
-f tree=products

View File

@ -0,0 +1,49 @@
name: Controller-workflow uniformity guardrail
# Regression test for TBD-A69 (#2006). Asserts every
# build-*-controller.yaml + *-controller-build.yaml workflow contains
# the canonical CI shape:
#
# 1. `core/controllers/pkg/**` in BOTH push.paths and pull_request.paths.
# 2. `contents: write` + auto-bump step that stamps short SHA into
# the chart values.yaml.
# 3. blueprint-release.yaml dispatch after the bot push (catalyst
# bundle workflows only; sandbox is exempt — its own chart).
#
# Pre-#2006: only build-organization-controller.yaml carried the full
# shape (added in PR #2005); the other six controllers had partial /
# missing pieces and shipped the #1997 18h deploy gap.
#
# Per CLAUDE.md "every workflow MUST be event-driven, NEVER scheduled":
# this workflow is push-on-merge + pull-request-on-touch. No cron.
on:
push:
branches: [main]
paths:
- '.github/workflows/build-*-controller.yaml'
- '.github/workflows/*-controller-build.yaml'
- '.github/workflows/check-controller-workflow-uniformity.yaml'
- 'scripts/check-controller-workflow-uniformity.sh'
pull_request:
paths:
- '.github/workflows/build-*-controller.yaml'
- '.github/workflows/*-controller-build.yaml'
- '.github/workflows/check-controller-workflow-uniformity.yaml'
- 'scripts/check-controller-workflow-uniformity.sh'
workflow_dispatch:
permissions:
contents: read
jobs:
check:
name: Controller-workflow uniformity
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run controller-workflow uniformity check
run: bash scripts/check-controller-workflow-uniformity.sh

View File

@ -17,6 +17,15 @@ on:
paths:
- 'core/controllers/useraccess/**'
- 'core/controllers/internal/**'
# core/controllers/pkg/** is the shared HTTP-client tree (gitea,
# keycloak, kc-mappers, …) consumed by every Group C controller's
# Containerfile via `COPY core/controllers/pkg`. Without this path
# entry a change to the shared pkg/ tree rebuilds the image only
# if the same PR also happens to touch files under useraccess/ —
# which silently held the t38 #1997 gitea-405 fix in main for
# ~12h. Uniform pattern across every build-*-controller.yaml
# (TBD-A69 #2006).
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- '.github/workflows/useraccess-controller-build.yaml'
@ -26,6 +35,7 @@ on:
paths:
- 'core/controllers/useraccess/**'
- 'core/controllers/internal/**'
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- '.github/workflows/useraccess-controller-build.yaml'
@ -68,9 +78,18 @@ jobs:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
# contents: write — the deploy step below pushes a values.yaml SHA
# bump back to main so the bp-catalyst-platform chart picks up the
# newly-built image without an operator manually editing the file
# (per `feedback_no_mvp_no_workarounds.md` rule 1: target-state,
# never "manual follow-up bump"). Pre-#2006 this workflow shipped
# without auto-bump — same deploy-gap class as #1997.
contents: write
packages: write
id-token: write
# actions: write — required for `gh workflow run` to dispatch the
# downstream blueprint-release chart re-publish workflow.
actions: write
outputs:
sha_short: ${{ steps.vars.outputs.sha_short }}
digest: ${{ steps.build.outputs.digest }}
@ -114,3 +133,67 @@ jobs:
# Keep the image small and reproducible: no labels added by
# build-push-action's defaults; the Containerfile is the
# single source of truth.
# Auto-bump the chart values.yaml tag so the next Sovereign chart
# rollout picks up this image without a manual edit. Per
# `feedback_no_mvp_no_workarounds.md` rule 1 (target-state, no
# operator-action gates) and `feedback_inviolable_principles.md`
# (event-driven, never cron). Mirrors the pattern in
# build-application-controller.yaml + build-organization-controller.yaml.
# Added as part of TBD-A69 (#2006) — pre-#2006 this workflow shipped
# without auto-bump, so the same deploy-gap class as #1997 was live
# for every useraccess-controller code fix.
- name: Bump controllers.useraccess.image.tag in values.yaml
if: github.ref == 'refs/heads/main'
env:
SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
run: |
VALUES="products/catalyst/chart/values.yaml"
# awk: find ` useraccess:` under `controllers:`, then update
# the next `tag: "..."` line. Stops at the next top-level key
# so we don't accidentally bump a sibling controller's tag.
awk -v sha="${SHA_SHORT}" '
/^controllers:/ { in_ctrls=1 }
in_ctrls && /^ useraccess:/ { print; in_ua=1; next }
in_ctrls && /^ [a-z]/ && !/^ useraccess:/ { in_ua=0 }
in_ua && /^ tag:/ { sub(/"[^"]*"/, "\"" sha "\""); in_ua=0 }
{ print }
' "${VALUES}" > "${VALUES}.tmp" && mv "${VALUES}.tmp" "${VALUES}"
echo "values.yaml after bump:"
grep -A4 "^ useraccess:" "${VALUES}" | head -10
- name: Commit and push values.yaml bump
id: deploy_commit
if: github.ref == 'refs/heads/main'
env:
SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet products/catalyst/chart/values.yaml; then
echo "no values.yaml change — already pinned to ${SHA_SHORT}"
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git add products/catalyst/chart/values.yaml
git commit -m "deploy: bump useraccess-controller image to ${SHA_SHORT}"
# Pull-rebase to avoid races with parallel build commits.
git pull --rebase --autostash origin main || true
git push origin HEAD:main
echo "pushed=true" >> "$GITHUB_OUTPUT"
# GitHub Actions does NOT trigger workflows from bot pushes by
# default (anti-recursion safeguard). Without this dispatch the
# rebuilt image is NEVER baked into a new chart version, so
# Sovereigns keep installing the previous chart with the previous
# image tag (`feedback_no_mvp_no_workarounds.md` rule 1 violation).
- name: Dispatch blueprint-release for chart re-publish
if: github.ref == 'refs/heads/main' && steps.deploy_commit.outputs.pushed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run blueprint-release.yaml \
--repo "${GITHUB_REPOSITORY}" \
--ref main \
-f blueprint=catalyst \
-f tree=products

View File

@ -0,0 +1,116 @@
#!/usr/bin/env bash
# check-controller-workflow-uniformity.sh
#
# Regression test for TBD-A69 (#2006). Asserts that every
# build-*-controller.yaml + *-controller-build.yaml workflow contains the
# canonical CI shape that auto-deploys controller code fixes:
#
# 1. `core/controllers/pkg/**` is in BOTH the push.paths and
# pull_request.paths filters. Without this, a fix that only touches
# the shared client tree (gitea/keycloak/kc-mappers) silently fails
# to rebuild the image — root cause of the 18h #1997 deploy gap.
#
# 2. `permissions.contents: write` and an auto-bump step that stamps
# the freshly-built short SHA into the chart values.yaml. Without
# this, the chart's image-tag pin lags main HEAD across multiple
# chart releases (same #1997 class).
#
# 3. A `gh workflow run blueprint-release.yaml` dispatch after the
# auto-bump commits. Without this, GitHub Actions' anti-recursion
# safeguard silently drops the bot-pushed values.yaml change and
# the chart never re-publishes.
#
# Failure here is a hard CI-fail: future controller workflows must
# inherit the canonical shape or the same deploy-gap class re-opens.
#
# Mirrors scripts/check-vendor-coupling.sh's shape (single script,
# fail-loudly, no external deps beyond grep/awk).
set -euo pipefail
WORKFLOWS_DIR=".github/workflows"
# Canonical list. Every controller image whose source lives under
# core/controllers/ MUST have a workflow in this list. New controllers
# get appended here at the same time as their workflow file lands.
CONTROLLERS=(
"build-application-controller.yaml"
"build-blueprint-controller.yaml"
"build-continuum-controller.yaml"
"build-environment-controller.yaml"
"build-organization-controller.yaml"
"build-sandbox-controller.yaml"
"useraccess-controller-build.yaml"
)
fail=0
check_pkg_path_filter() {
local file="$1"
# Both push.paths and pull_request.paths must contain
# core/controllers/pkg/**. We count occurrences (expected ≥ 2 — one
# under push.paths, one under pull_request.paths).
local count
count=$(grep -cE "^[[:space:]]+- 'core/controllers/pkg/\*\*'" "${file}" || true)
if [ "${count}" -lt 2 ]; then
echo "::error file=${file}::missing 'core/controllers/pkg/**' in push.paths AND pull_request.paths (found ${count} occurrences, expected ≥ 2)"
fail=1
fi
}
check_auto_bump() {
local file="$1"
# 1. contents: write must be present (so the auto-bump commit can
# push back to main).
if ! grep -qE "^[[:space:]]+contents:[[:space:]]+write" "${file}"; then
echo "::error file=${file}::missing 'contents: write' permission — auto-bump cannot push values.yaml"
fail=1
fi
# 2. A step that bumps the image tag into a values.yaml file.
if ! grep -qE "Bump .*image\.tag|Bump .*\.image\.tag in values\.yaml" "${file}"; then
echo "::error file=${file}::missing auto-bump step (no 'Bump …image.tag…' step name) — controller image pin will lag main"
fail=1
fi
# 3. A commit/push step.
if ! grep -qE "Commit and push values\.yaml bump" "${file}"; then
echo "::error file=${file}::missing 'Commit and push values.yaml bump' step — auto-bump never lands in repo"
fail=1
fi
}
check_blueprint_release_dispatch() {
local file="$1"
# sandbox-controller has its own product chart and does NOT need the
# catalyst blueprint-release dispatch — it auto-bumps to
# platform/sandbox/chart/values.yaml. Exempt by filename.
case "$(basename "${file}")" in
build-sandbox-controller.yaml) return 0 ;;
esac
if ! grep -qE "gh workflow run blueprint-release\.yaml" "${file}"; then
echo "::error file=${file}::missing 'gh workflow run blueprint-release.yaml' dispatch — bot push won't fire downstream chart re-publish"
fail=1
fi
}
echo "Checking ${#CONTROLLERS[@]} controller workflows for canonical auto-bump shape (TBD-A69)…"
for wf in "${CONTROLLERS[@]}"; do
file="${WORKFLOWS_DIR}/${wf}"
if [ ! -f "${file}" ]; then
echo "::error::expected workflow file ${file} not found — CONTROLLERS list out of sync with .github/workflows/"
fail=1
continue
fi
check_pkg_path_filter "${file}"
check_auto_bump "${file}"
check_blueprint_release_dispatch "${file}"
done
if [ "${fail}" -ne 0 ]; then
echo
echo "FAIL: one or more controller workflows are missing the canonical auto-bump shape."
echo "See scripts/check-controller-workflow-uniformity.sh + PR #2005 for the canonical pattern."
exit 1
fi
echo "OK: all ${#CONTROLLERS[@]} controller workflows carry the canonical pkg/** filter + auto-bump pipeline."