From f539070807b63b262b2132f18274b3117a4c20cd Mon Sep 17 00:00:00 2001 From: antoinetoussaint-byte <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Sat, 26 Sep 2026 19:36:38 -0400 Subject: [PATCH] ci: fail when src/saas_sdk/_gen drifts from the ref in SOURCE.txt The generated bindings were once stale for months because nothing checked them. A gen-drift job now checks out module-saas-starter at the commit SOURCE.txt records (sparse, proto path only), reruns scripts/generate.sh with buf 1.73.0, protobuf 5.29.3 for strip_options.py, and the remote plugins buf.gen.yaml already pins (python/pyi v29.3), and fails with the diff if the committed tree differs. Mirrors the gen-drift job in saas-sdk-go. Also sets the workflow token to contents: read, as that repo does. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/ci.yml | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e507c64..6ce276c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,9 @@ on: branches: [main] pull_request: +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest @@ -21,3 +24,72 @@ jobs: # Runs the Work Context wire-golden parity test, which pins the token # byte-for-byte to sdk-go's TestWorkContextWireGolden. - run: python -m pytest -q + + # src/saas_sdk/_gen must be exactly what scripts/generate.sh produces from the + # proto ref recorded in SOURCE.txt: buf builds the image, strip_options.py + # strips the custom options and shared descriptors, and buf.gen.yaml's remote + # plugins (versions pinned there) write the bindings. A stale or edited + # binding shows up as a diff. + gen-drift: + runs-on: ubuntu-latest + env: + # buf compiles the proto image the plugins consume. Pinned so a buf + # release cannot move the tree; bump deliberately, as its own change. + BUF_VERSION: 1.73.0 + # strip_options.py rewrites the image with the protobuf runtime. Pinned + # to the gencode line the bindings are built with (pyproject floor). + PROTOBUF_VERSION: 5.29.3 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: sdk + - name: read the recorded source ref + id: source + run: | + set -euo pipefail + line=$(grep -m1 -oE 'codefly-dev/module-saas-starter@[0-9a-f]{40}' sdk/SOURCE.txt || true) + if [ -z "$line" ]; then + echo "::error file=SOURCE.txt::no 'codefly-dev/module-saas-starter@<40-hex sha>' ref found" + exit 1 + fi + proto=$(grep -m1 -oE 'accounts public proto: [^)]+' sdk/SOURCE.txt | sed 's/^accounts public proto: //') + if [ -z "$proto" ]; then + echo "::error file=SOURCE.txt::no '(accounts public proto: )' line found" + exit 1 + fi + echo "sha=${line#*@}" >> "$GITHUB_OUTPUT" + echo "proto=$proto" >> "$GITHUB_OUTPUT" + echo "source: codefly-dev/module-saas-starter@${line#*@} ($proto)" + # The source repo is public, so the default token is enough. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: codefly-dev/module-saas-starter + ref: ${{ steps.source.outputs.sha }} + path: source + sparse-checkout: ${{ steps.source.outputs.proto }} + sparse-checkout-cone-mode: false + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.12" + - run: python -m pip install "protobuf==${PROTOBUF_VERSION}" + - uses: bufbuild/buf-action@85aebf73123b5c15fd5528aaecbf9129cddf7fa7 # v1.6.0 + with: + version: ${{ env.BUF_VERSION }} + setup_only: true + - name: regenerate src/saas_sdk/_gen + run: sdk/scripts/generate.sh "source/${{ steps.source.outputs.proto }}" + - name: require no drift + working-directory: sdk + run: | + set -euo pipefail + if [ -z "$(git status --porcelain)" ]; then + echo "src/saas_sdk/_gen reproduces byte-for-byte from $(grep -m1 -oE 'module-saas-starter@[0-9a-f]{40}' SOURCE.txt)" + exit 0 + fi + echo "::error::src/saas_sdk/_gen does not match a regeneration from the ref in SOURCE.txt. Never hand-edit _gen/: regenerate it with scripts/generate.sh (README, Regenerating), or correct SOURCE.txt if it records the wrong ref." + git status --short + git add -A -N + git --no-pager diff --stat + git --no-pager diff | head -n 400 + exit 1