From 461353f9aa8807f330d47a37392ebf752fc94ba0 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sun, 31 May 2026 18:38:37 -0400 Subject: [PATCH 1/3] ci(iac/admin): fail on stale vendored infra.proto descriptor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds scripts/check-vendored-proto.sh and .github/workflows/proto-vendor-staleness.yml to catch upstream drift in iac/admin/testdata/infra.proto. How the script works: 1. Reads '// Source version: ' from the vendored file header 2. Fetches the upstream proto from GoCodeAlone/workflow-plugin-infra@ via the GitHub raw API (no local checkout required) 3. Extracts the *Config message name sets from both files 4. Diffs them; exit 1 + operator-readable error if any message was added or removed (with 'make vendor-infra-proto' refresh instructions) Verified locally: bash scripts/check-vendored-proto.sh → exit 0 (in-sync) (mutate proto) && bash ... → exit 1 (drift detected) actionlint proto-vendor-staleness.yml → exit 0 (no YAML issues) The CI job runs on push/PR to main when the proto or script changes, plus a weekly cron to catch upstream drift even when the local file is not touched. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/proto-vendor-staleness.yml | 43 +++++++ scripts/check-vendored-proto.sh | 112 +++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 .github/workflows/proto-vendor-staleness.yml create mode 100755 scripts/check-vendored-proto.sh diff --git a/.github/workflows/proto-vendor-staleness.yml b/.github/workflows/proto-vendor-staleness.yml new file mode 100644 index 00000000..50059f01 --- /dev/null +++ b/.github/workflows/proto-vendor-staleness.yml @@ -0,0 +1,43 @@ +name: Proto Vendor Staleness + +# Fails when iac/admin/testdata/infra.proto drifts from the upstream +# GoCodeAlone/workflow-plugin-infra proto at the pinned source tag. +# Refresh procedure: `make vendor-infra-proto` + update Source version header. +# +# Why this matters: catalog_proto_parity_test.go (iac/admin/catalog/) asserts +# every *Config message in the vendored proto has a catalog entry. If the +# vendored proto silently lags the upstream, new resource types added by +# workflow-plugin-infra land in production without field-spec catalog coverage. + +on: + push: + branches: [ main ] + paths: + - 'iac/admin/testdata/infra.proto' + - 'scripts/check-vendored-proto.sh' + - '.github/workflows/proto-vendor-staleness.yml' + pull_request: + branches: [ main ] + paths: + - 'iac/admin/testdata/infra.proto' + - 'scripts/check-vendored-proto.sh' + - '.github/workflows/proto-vendor-staleness.yml' + # Also run on a weekly schedule to catch upstream drift even when the + # vendored file isn't touched locally. + schedule: + - cron: '0 6 * * 1' # Mondays 06:00 UTC + workflow_dispatch: + +permissions: + contents: read + +jobs: + check-vendored-proto: + name: Check vendored infra.proto against upstream + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check vendored proto staleness + run: bash scripts/check-vendored-proto.sh diff --git a/scripts/check-vendored-proto.sh b/scripts/check-vendored-proto.sh new file mode 100755 index 00000000..c01c4d6c --- /dev/null +++ b/scripts/check-vendored-proto.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# check-vendored-proto.sh — assert iac/admin/testdata/infra.proto is in sync +# with the upstream GoCodeAlone/workflow-plugin-infra proto descriptor. +# +# Exit 0: vendored copy matches upstream message set. +# Exit 1: drift detected or environment error. +# +# How it works: +# 1. Reads the "Source version: " comment from the vendored file header +# to know which upstream tag to fetch. +# 2. Fetches the upstream proto from GitHub at that tag via the raw API +# (no local checkout required — CI-safe). +# 3. Extracts the set of `message *Config { ... }` names from both files. +# 4. Diffs the two sets. Any addition or removal fails the check. +# +# Refresh procedure: `make vendor-infra-proto` (see Makefile target). +# The vendored file header must then be updated: update "Source version:" to +# the new upstream tag. +# +# Usage: bash scripts/check-vendored-proto.sh [--vendored PATH] [--tag TAG] +# --vendored PATH Override the vendored proto path +# (default: iac/admin/testdata/infra.proto) +# --tag TAG Override the upstream tag to fetch +# (default: read from vendored file header) +set -euo pipefail + +VENDORED_PROTO="${VENDORED_PROTO:-iac/admin/testdata/infra.proto}" +UPSTREAM_REPO="GoCodeAlone/workflow-plugin-infra" +UPSTREAM_PATH="internal/contracts/infra.proto" + +# Parse flags. +while [[ $# -gt 0 ]]; do + case "$1" in + --vendored) VENDORED_PROTO="$2"; shift 2 ;; + --tag) OVERRIDE_TAG="$2"; shift 2 ;; + *) echo "Unknown flag: $1" >&2; exit 1 ;; + esac +done + +# Resolve script root (works whether called from repo root or scripts/). +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +VENDORED_PROTO="$REPO_ROOT/$VENDORED_PROTO" + +if [[ ! -f "$VENDORED_PROTO" ]]; then + echo "ERROR: vendored proto not found: $VENDORED_PROTO" >&2 + exit 1 +fi + +# Extract the source tag from the vendored file header. +# Expected line: "// Source version: v1.0.0 (sourced 2026-05-27)" +if [[ -n "${OVERRIDE_TAG:-}" ]]; then + UPSTREAM_TAG="$OVERRIDE_TAG" +else + UPSTREAM_TAG=$(grep -m1 '// Source version:' "$VENDORED_PROTO" | \ + sed 's|// Source version: \([^ ]*\).*|\1|') + if [[ -z "$UPSTREAM_TAG" ]]; then + echo "ERROR: cannot read '// Source version: ' from $VENDORED_PROTO" >&2 + echo " Update the header or pass --tag " >&2 + exit 1 + fi +fi + +echo "Checking vendored proto against upstream $UPSTREAM_REPO @ $UPSTREAM_TAG ..." + +# Fetch upstream proto. +UPSTREAM_URL="https://raw.githubusercontent.com/$UPSTREAM_REPO/$UPSTREAM_TAG/$UPSTREAM_PATH" +UPSTREAM_TMP=$(mktemp /tmp/infra-proto-upstream.XXXXXX.proto) +trap 'rm -f "$UPSTREAM_TMP"' EXIT + +if ! curl -fsSL "$UPSTREAM_URL" -o "$UPSTREAM_TMP"; then + echo "ERROR: failed to fetch $UPSTREAM_URL" >&2 + echo " Check connectivity and that tag '$UPSTREAM_TAG' exists in $UPSTREAM_REPO" >&2 + exit 1 +fi + +# Extract *Config message names from a proto file (line-by-line regex). +extract_config_messages() { + grep -oE '^[[:space:]]*message[[:space:]]+([A-Za-z0-9_]+Config)[[:space:]]*\{' "$1" \ + | sed 's|.*message[[:space:]]\+\([A-Za-z0-9_]*Config\)[[:space:]]*{.*|\1|' \ + | sort +} + +VENDORED_MSGS=$(extract_config_messages "$VENDORED_PROTO") +UPSTREAM_MSGS=$(extract_config_messages "$UPSTREAM_TMP") + +if [[ "$VENDORED_MSGS" == "$UPSTREAM_MSGS" ]]; then + echo "OK: vendored infra.proto message set is in sync with upstream $UPSTREAM_TAG." + exit 0 +fi + +# Compute diff for the error message. +DIFF=$(diff <(echo "$VENDORED_MSGS") <(echo "$UPSTREAM_MSGS") || true) +ADDED=$(echo "$DIFF" | grep '^>' | sed 's/^> / + /' || true) +REMOVED=$(echo "$DIFF" | grep '^<' | sed 's/^< / - /' || true) + +echo "ERROR: vendored infra.proto is stale!" >&2 +echo "" >&2 +if [[ -n "$REMOVED" ]]; then + echo "Messages in vendored but NOT in upstream (upstream removed them):" >&2 + echo "$REMOVED" >&2 +fi +if [[ -n "$ADDED" ]]; then + echo "Messages in upstream but NOT in vendored (upstream added them):" >&2 + echo "$ADDED" >&2 +fi +echo "" >&2 +echo "To refresh, run:" >&2 +echo " make vendor-infra-proto" >&2 +echo "" >&2 +echo "Then update '// Source version:' in $VENDORED_PROTO to match the new upstream tag." >&2 +exit 1 From 5c3bfde3501b8dea22809cb5f4eee50b823c4649 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sun, 31 May 2026 18:41:21 -0400 Subject: [PATCH 2/3] docs(scripts): annotate extract_config_messages scope matches catalog parity test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per spec-reviewer cosmetic suggestion: explain that *Config message scope is intentional — matches catalog_proto_parity_test.go surface. Co-Authored-By: Claude Sonnet 4.6 --- scripts/check-vendored-proto.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/check-vendored-proto.sh b/scripts/check-vendored-proto.sh index c01c4d6c..31a40166 100755 --- a/scripts/check-vendored-proto.sh +++ b/scripts/check-vendored-proto.sh @@ -75,6 +75,10 @@ if ! curl -fsSL "$UPSTREAM_URL" -o "$UPSTREAM_TMP"; then fi # Extract *Config message names from a proto file (line-by-line regex). +# Scope is intentionally limited to `*Config` messages — these are the +# typed resource configs that catalog_proto_parity_test.go asserts have +# catalog entries. Non-Config messages (service RPCs, generic types) are +# out of scope for the parity test and are therefore excluded here too. extract_config_messages() { grep -oE '^[[:space:]]*message[[:space:]]+([A-Za-z0-9_]+Config)[[:space:]]*\{' "$1" \ | sed 's|.*message[[:space:]]\+\([A-Za-z0-9_]*Config\)[[:space:]]*{.*|\1|' \ From 8d1ed1f25b3cb42ff0bf832f25d89dfd950e77c0 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sun, 31 May 2026 18:43:53 -0400 Subject: [PATCH 3/3] fix(scripts): use sed -E (ERE) for BSD/macOS portability in extract_config_messages \+ is a GNU BRE extension unsupported by BSD sed; -E with + works on both macOS and Linux. CI (ubuntu-latest) was correct either way; this fixes local dev experience on macOS. Co-Authored-By: Claude Sonnet 4.6 --- scripts/check-vendored-proto.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check-vendored-proto.sh b/scripts/check-vendored-proto.sh index 31a40166..a2f4c91e 100755 --- a/scripts/check-vendored-proto.sh +++ b/scripts/check-vendored-proto.sh @@ -81,7 +81,7 @@ fi # out of scope for the parity test and are therefore excluded here too. extract_config_messages() { grep -oE '^[[:space:]]*message[[:space:]]+([A-Za-z0-9_]+Config)[[:space:]]*\{' "$1" \ - | sed 's|.*message[[:space:]]\+\([A-Za-z0-9_]*Config\)[[:space:]]*{.*|\1|' \ + | sed -E 's|.*message[[:space:]]+([A-Za-z0-9_]+Config)[[:space:]]*\{.*|\1|' \ | sort }