From 5bc6f6e93fb8c3527e8cdd8ef24df15c9bea27d8 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 2 May 2026 23:52:49 +0100 Subject: [PATCH] feat(ci): add AffineScript Canary workflow (advisory) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compiles every .affine file in the repo using the canonical affinescript CLI on every PR and main push that touches .affine sources. Failures are surfaced via GitHub annotations and step summaries but do NOT block the merge — this is the canary stage. Promote to required-for-merge once parity with the .res fallback is proven (Burble grade B target per ROADMAP.adoc). Includes a paired-fallback audit that warns on any .affine file lacking a .res sibling. Drop the audit job once the canary is required. Caches the affinescript binary by AFFINESCRIPT_REF to avoid rebuilding the OCaml/dune toolchain on every CI run. To bump affinescript, change AFFINESCRIPT_REF in env. Closes the canary CI gap noted in burble READINESS.adoc → "Validation gaps (NOT ready for production)" → AffineScript runtime compilation under full CI matrix. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/affinescript-canary.yml | 156 ++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 .github/workflows/affinescript-canary.yml diff --git a/.github/workflows/affinescript-canary.yml b/.github/workflows/affinescript-canary.yml new file mode 100644 index 0000000..f96f707 --- /dev/null +++ b/.github/workflows/affinescript-canary.yml @@ -0,0 +1,156 @@ +# SPDX-License-Identifier: PMPL-1.0-or-later +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# affinescript-canary.yml — AffineScript Compilation Canary +# +# Compiles every .affine file in the repo using the canonical affinescript +# CLI. ADVISORY (canary) — failures are surfaced via GitHub annotations and +# job summary, but do NOT block merges. Promote to required-for-merge once +# parity with the .res fallback is proven (Burble grade B target). +# +# Companion: every .affine should have a .res sibling for fallback until the +# canary is required. The pairing-audit job warns on unpaired files. +# +# To bump the affinescript version, change AFFINESCRIPT_REF below. + +name: AffineScript Canary + +on: + pull_request: + branches: ['**'] + paths: + - '**.affine' + - '.github/workflows/affinescript-canary.yml' + push: + branches: [main, master] + paths: + - '**.affine' + - '.github/workflows/affinescript-canary.yml' + workflow_dispatch: + +permissions: + contents: read + +env: + AFFINESCRIPT_REPO: hyperpolymath/affinescript + AFFINESCRIPT_REF: fc37bb5896de24cefe65dbfc5cd657a2d0087df7 # main as of 2026-05-02; bump as needed + OCAML_COMPILER: '5.1' + +jobs: + # --------------------------------------------------------------------------- + # Job 1: Compile every .affine file (canary — advisory) + # --------------------------------------------------------------------------- + compile-affinescript: + name: Compile .affine files (canary) + runs-on: ubuntu-latest + continue-on-error: true # canary: surface failures, do not block merges + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Detect .affine files + id: detect + run: | + COUNT=$(find . -name '*.affine' -not -path './node_modules/*' -not -path './_build/*' -not -path './.git/*' | wc -l) + echo "count=$COUNT" >> "$GITHUB_OUTPUT" + echo "Found $COUNT .affine files" + if [ "$COUNT" -eq 0 ]; then + echo "::notice::No .affine files in repo — canary has nothing to do" + fi + + - name: Cache affinescript build + if: steps.detect.outputs.count > 0 + id: cache-affinescript + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + with: + path: /usr/local/bin/affinescript + key: affinescript-${{ env.AFFINESCRIPT_REF }}-${{ runner.os }} + + - name: Setup OCaml (cache miss only) + if: steps.detect.outputs.count > 0 && steps.cache-affinescript.outputs.cache-hit != 'true' + uses: ocaml/setup-ocaml@v3 + with: + ocaml-compiler: ${{ env.OCAML_COMPILER }} + + - name: Build affinescript from source (cache miss only) + if: steps.detect.outputs.count > 0 && steps.cache-affinescript.outputs.cache-hit != 'true' + run: | + set -euo pipefail + git clone --depth 1 "https://github.com/${AFFINESCRIPT_REPO}.git" /tmp/affinescript-src + cd /tmp/affinescript-src + git fetch --depth 1 origin "${AFFINESCRIPT_REF}" + git checkout "${AFFINESCRIPT_REF}" + opam install --yes . --deps-only + opam exec -- dune build bin/main.exe + sudo cp _build/default/bin/main.exe /usr/local/bin/affinescript + affinescript --help | head -5 || echo "(--help unavailable, binary installed)" + + - name: Compile every .affine file + if: steps.detect.outputs.count > 0 + id: compile + run: | + set +e + failed=0 + total=0 + tmp=$(mktemp) + while IFS= read -r f; do + total=$((total+1)) + if ! affinescript check "$f" >"$tmp" 2>&1; then + echo "::warning file=$f::affinescript check failed" + echo "--- $f ---" + cat "$tmp" + failed=$((failed+1)) + fi + done < <(find . -name '*.affine' -not -path './node_modules/*' -not -path './_build/*' -not -path './.git/*') + rm -f "$tmp" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "## AffineScript Canary Result" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Metric | Value |" >> "$GITHUB_STEP_SUMMARY" + echo "|---|---|" >> "$GITHUB_STEP_SUMMARY" + echo "| Total .affine files | $total |" >> "$GITHUB_STEP_SUMMARY" + echo "| Failed compilation | $failed |" >> "$GITHUB_STEP_SUMMARY" + echo "| AffineScript ref | \`${AFFINESCRIPT_REF}\` |" >> "$GITHUB_STEP_SUMMARY" + if [ "$failed" -gt 0 ]; then + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Canary advisory: $failed file(s) failed compilation. This does not block the merge — see annotations for details." >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "All .affine files compiled cleanly." >> "$GITHUB_STEP_SUMMARY" + + # --------------------------------------------------------------------------- + # Job 2: Pairing audit (.affine ↔ .res fallback) + # --------------------------------------------------------------------------- + # While the canary is advisory, every .affine must have a .res sibling so + # the build can fall back if affinescript miscompiles. Drop this job once + # the canary is promoted to required-for-merge (Burble grade B). + pairing-audit: + name: Pairing audit (.affine ↔ .res) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Audit .affine ↔ .res pairing + run: | + unpaired=0 + while IFS= read -r f; do + res="${f%.affine}.res" + if [ ! -f "$res" ]; then + echo "::warning file=$f::No .res sibling (fallback missing). Drop this rule once the AffineScript canary is required-for-merge." + unpaired=$((unpaired+1)) + fi + done < <(find . -name '*.affine' -not -path './node_modules/*' -not -path './_build/*' -not -path './.git/*') + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "## .affine ↔ .res Pairing Audit" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + if [ "$unpaired" -eq 0 ]; then + echo "All .affine files have .res fallbacks." >> "$GITHUB_STEP_SUMMARY" + else + echo "$unpaired .affine file(s) have no .res sibling. See annotations." >> "$GITHUB_STEP_SUMMARY" + fi + # Advisory only at canary stage — exit clean + exit 0