build: extend the main-sync gate to the image build and the k8s deploy
Some checks failed
CI/CD / lint (pull_request) Successful in 1m15s
CI/CD / test (pull_request) Successful in 1m23s
CI/CD / typecheck (pull_request) Successful in 2m59s
CI/CD / smoke (pull_request) Failing after 1m57s
CI/CD / build (pull_request) Successful in 4m58s
CI/CD / publish (pull_request) Has been skipped

Same hazard as the package build, with the cluster on the receiving end: a
branch behind main builds images missing whatever landed there, and deploy-k8s.sh
pins that sha in Pulumi — making the stale build the cluster's source of truth.

build-mcpd.sh gets its own call because it is run standalone as well as from
deploy-k8s.sh, so neither can rely on the other having checked.

`--dry-run` is exempt. It builds and cuts over nothing, and blocking a read-only
inspection is exactly what teaches people to export MCPCTL_ALLOW_BEHIND_MAIN=1
permanently — which would disable the gate for the real deploys too.

The failure text is now artifact-agnostic ("produce an artifact" / "shipping
it"), since one helper now speaks for packages, images and deploys.

Verified against a synthetic ref one commit ahead: build-mcpd.sh exits 1 before
any docker work, and deploy-k8s.sh exits 1 before the test gate, the pg_dump,
the image build and pulumi. Neither the working tree nor HEAD was moved to test
this — the ref was built with git commit-tree and deleted afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wUmrfkVQR6CKcYKxENq7k
This commit is contained in:
Michal
2026-08-10 16:41:26 +01:00
parent dd29f98f82
commit 96e27c8716
4 changed files with 31 additions and 4 deletions

View File

@@ -890,8 +890,14 @@ ships a binary missing whatever landed on main meanwhile, and `rpm -U --force`
overwrites the good one with it. That happened on 2026-08-10: a build from a overwrites the good one with it. That happened on 2026-08-10: a build from a
stale checkout replaced `/usr/bin/mcpctl` with one that had no `statusline` stale checkout replaced `/usr/bin/mcpctl` with one that had no `statusline`
command, months after the status line landed. `scripts/check-main-sync.sh` command, months after the status line landed. `scripts/check-main-sync.sh`
(sourced by `build-rpm.sh` and `build-deb.sh`) fetches `main`, compares, and fetches `main`, compares, and fails before any work happens, listing the commits
fails before any work happens, listing the commits you are missing. you are missing. It gates every path that produces something others consume:
`build-rpm.sh`, `build-deb.sh`, `build-mcpd.sh` (each is also run standalone, so
none can rely on another having checked) and `deploy-k8s.sh` — where a stale
branch would pin its sha in Pulumi and make it the cluster's source of truth.
`deploy-k8s.sh --dry-run` skips the check: it builds and cuts over nothing, and
blocking a read-only inspection only teaches people to export the escape hatch
permanently, disabling the gate for real deploys too.
```bash ```bash
git merge main # the fix git merge main # the fix

View File

@@ -16,6 +16,11 @@ if [ -f .env ]; then
set -a; source .env; set +a set -a; source .env; set +a
fi fi
# This pushes an image to the registry, so the same staleness gate as the package
# builds applies. Run standalone as well as from deploy-k8s.sh, hence its own copy.
source "$SCRIPT_DIR/check-main-sync.sh"
check_main_sync
# Push directly to internal address (external proxy has body size limit) # Push directly to internal address (external proxy has body size limit)
REGISTRY="10.0.0.194:3012" REGISTRY="10.0.0.194:3012"
IMAGE="mcpd" IMAGE="mcpd"

View File

@@ -84,8 +84,10 @@ check_main_sync() {
echo "" >&2 echo "" >&2
echo "ERROR: '$branch' is $behind commit(s) behind $base — refusing to build." >&2 echo "ERROR: '$branch' is $behind commit(s) behind $base — refusing to build." >&2
echo "" >&2 echo "" >&2
echo " Building now would package a binary without these, and installing it" >&2 # Deliberately artifact-agnostic: the same helper gates RPM/DEB packages, the
echo " would overwrite a good one with a version missing them:" >&2 # mcpd image, and the k8s deploy.
echo " Building now would produce an artifact without these, and shipping it" >&2
echo " would replace a good one with a version missing them:" >&2
echo "" >&2 echo "" >&2
git log --oneline --no-decorate "HEAD..$ref" | head -15 | sed 's/^/ /' >&2 git log --oneline --no-decorate "HEAD..$ref" | head -15 | sed 's/^/ /' >&2
if [ "$behind" -gt 15 ]; then if [ "$behind" -gt 15 ]; then

View File

@@ -80,6 +80,20 @@ cat <<EOF
EOF EOF
[ -f "$PULUMI_YAML" ] || die "Pulumi config not found: $PULUMI_YAML" [ -f "$PULUMI_YAML" ] || die "Pulumi config not found: $PULUMI_YAML"
# ── 0. Staleness gate ──
# Same hazard as the RPM build, with the cluster on the receiving end: a branch
# behind main deploys images missing whatever landed there, and the sha pinned in
# Pulumi makes that the new source of truth. Skipped for --dry-run, which builds
# and cuts over nothing — blocking a read-only inspection only teaches people to
# export MCPCTL_ALLOW_BEHIND_MAIN=1 permanently, which would disable the gate for
# the real deploys too.
if [ "$DRY_RUN" = true ]; then
warn "dry-run: skip the main-sync check"
else
source "$SCRIPT_DIR/check-main-sync.sh"
check_main_sync || die "branch is behind main — merge it before deploying"
fi
# ── 1. Test gate ── # ── 1. Test gate ──
if [ "$SKIP_TESTS" = true ]; then warn "skipping unit tests (--skip-tests)"; else if [ "$SKIP_TESTS" = true ]; then warn "skipping unit tests (--skip-tests)"; else
say "1/7 Unit tests (pnpm test:run)" say "1/7 Unit tests (pnpm test:run)"