Repository navigation
scratch: fix C and prove both freshness branches in CI #2
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: scratch — deploy alarm proof | |
| # THROWAWAY. Lives only on `scratch/deploy-alarm-proof`; it is not on the | |
| # branch proposed for merge. | |
| # | |
| # It exercises the real `scripts/check-prod-deploy.mjs` against the real | |
| # GitHub Actions `needs`/`if: always()` semantics, with the ref check | |
| # retargeted from `main` to this scratch branch and the Vercel steps replaced | |
| # by an echo. What it proves: | |
| # | |
| # A. A failing gate skips the deploy job, and the alarm job STILL RUNS | |
| # (`if: always()`), sees `skipped`, and fails red. | |
| # B. A passing gate runs the deploy job, and the alarm job passes green. | |
| # C. The `cancel-in-progress` expression from ci.yml parses and evaluates. | |
| # | |
| # DRY_RUN=1 everywhere: the alarm's decision and the exact `gh` commands are | |
| # printed, but no issue is created in this repo. | |
| on: | |
| push: | |
| branches: ["scratch/deploy-alarm-proof"] | |
| concurrency: | |
| # Copied verbatim from ci.yml to prove the expression is accepted. | |
| group: scratch-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| # ---------- Scenario A: a gate fails, exactly like 2026-08-14 ---------- | |
| a-gate: | |
| name: A · gate (deliberately failing test) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - run: | | |
| echo "simulating the flaky test that failed in run 31806388257" | |
| exit 1 | |
| a-deploy: | |
| name: A · Deploy → Vercel (production) | |
| needs: [a-gate] | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/scratch/deploy-alarm-proof' && github.event_name == 'push' | |
| steps: | |
| - run: echo "would deploy to Vercel here" | |
| a-alarm: | |
| name: A · Production deploy — did it actually happen? | |
| needs: [a-deploy] | |
| runs-on: ubuntu-latest | |
| if: >- | |
| always() && | |
| github.ref == 'refs/heads/scratch/deploy-alarm-proof' && | |
| github.event_name == 'push' | |
| permissions: | |
| contents: read | |
| issues: read | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| - run: echo "needs.a-deploy.result == '${{ needs.a-deploy.result }}'" | |
| - name: Check that production got this commit | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| DRY_RUN: "1" | |
| DEPLOY_RESULT: ${{ needs.a-deploy.result }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: node ./scripts/check-prod-deploy.mjs --mode=run | |
| # ---------- Scenario B: everything green ---------- | |
| b-gate: | |
| name: B · gate (passing test) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - run: echo "green" | |
| b-deploy: | |
| name: B · Deploy → Vercel (production) | |
| needs: [b-gate] | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/scratch/deploy-alarm-proof' && github.event_name == 'push' | |
| steps: | |
| - run: echo "would deploy to Vercel here" | |
| b-alarm: | |
| name: B · Production deploy — did it actually happen? | |
| needs: [b-deploy] | |
| runs-on: ubuntu-latest | |
| if: >- | |
| always() && | |
| github.ref == 'refs/heads/scratch/deploy-alarm-proof' && | |
| github.event_name == 'push' | |
| permissions: | |
| contents: read | |
| issues: read | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| - run: echo "needs.b-deploy.result == '${{ needs.b-deploy.result }}'" | |
| - name: Check that production got this commit | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| DRY_RUN: "1" | |
| DEPLOY_RESULT: ${{ needs.b-deploy.result }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: node ./scripts/check-prod-deploy.mjs --mode=run | |
| # ---------- Scenario C: the freshness monitor against the live site ---------- | |
| # Unlike the real workflow this checks out the scratch branch (main does not | |
| # have the script yet) and reads main's head over the wire. | |
| c-freshness: | |
| name: C · Production freshness vs the live site (dry run) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| - id: head | |
| run: | | |
| git fetch --depth=1 origin main | |
| echo "sha=$(git rev-parse FETCH_HEAD)" >> "$GITHUB_OUTPUT" | |
| echo "iso=$(git log -1 --format=%cI FETCH_HEAD)" >> "$GITHUB_OUTPUT" | |
| - name: Compare production against main | |
| # Expected to ALARM today: /version.json does not exist on production | |
| # until this change merges. That is the point — an unverifiable | |
| # production is reported, not assumed healthy. | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| DRY_RUN: "1" | |
| SITE_URL: https://pretable.ai | |
| EXPECTED_SHA: ${{ steps.head.outputs.sha }} | |
| HEAD_COMMIT_ISO: ${{ steps.head.outputs.iso }} | |
| GRACE_MINUTES: "25" | |
| run: node ./scripts/check-prod-deploy.mjs --mode=freshness | |
| # ---------- Scenario D: freshness against a stand-in "production" ---------- | |
| # Proves both branches of the monitor inside CI by serving a /version.json the | |
| # test controls, since the real site cannot yet serve one. | |
| d-freshness-both-branches: | |
| name: D · Production freshness — current vs stale | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| - name: Serve a stand-in production | |
| run: | | |
| mkdir -p /tmp/fake-prod | |
| echo '{"commit":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef","builtAt":"2026-08-14T00:00:00Z"}' \ | |
| > /tmp/fake-prod/version.json | |
| (cd /tmp/fake-prod && python3 -m http.server 8099 > /tmp/fake-prod.log 2>&1 &) | |
| for i in $(seq 1 20); do | |
| curl -sSf -o /dev/null http://127.0.0.1:8099/version.json && break | |
| sleep 1 | |
| done | |
| curl -sS http://127.0.0.1:8099/version.json | |
| - name: Production serving main's head — must NOT alarm | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| DRY_RUN: "1" | |
| SITE_URL: http://127.0.0.1:8099 | |
| EXPECTED_SHA: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef | |
| HEAD_COMMIT_ISO: "2026-08-14T00:00:00Z" | |
| GRACE_MINUTES: "25" | |
| run: node ./scripts/check-prod-deploy.mjs --mode=freshness | |
| - name: Production stuck on an older commit — must alarm | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| DRY_RUN: "1" | |
| SITE_URL: http://127.0.0.1:8099 | |
| EXPECTED_SHA: a9ee55f45313ea9e4561bef11d7b9ebe9d6e9ab9 | |
| HEAD_COMMIT_ISO: "2026-08-14T00:00:00Z" | |
| GRACE_MINUTES: "25" | |
| run: | | |
| if node ./scripts/check-prod-deploy.mjs --mode=freshness; then | |
| echo "FAILED: a stale production did not alarm" | |
| exit 1 | |
| fi | |
| echo "OK: stale production alarmed (non-zero exit)" |