Nightly (heavy doc gates) #43
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: Nightly (heavy doc gates) | |
| # Runs the HEAVY doc-execution gates that per-PR run-ci skips for speed: | |
| # SNIPPET-COMPILE · SNIPPET-RUN · EXAMPLES-RUN (tier=nightly in run-ci.sh) | |
| # via `SW_CI_TIER=nightly bash scripts/run-ci.sh`, which runs the FULL gate set | |
| # (nightly is a superset of the per-PR tier — nothing is only-nightly-untested). | |
| # | |
| # Skip-if-unchanged: the heavy gates depend on three inputs — this port, the | |
| # shared porting-sdk (mocks + gate scripts), and signalwire-python (the oracle). | |
| # The guard fingerprints all three HEAD SHAs into a cache key. A GREEN heavy run | |
| # saves that key; the next nightly's guard does a cache-restore probe on the same | |
| # key — a HIT means the exact (port, porting-sdk, python) triple already passed, so | |
| # the heavy job is skipped. A no-op nightly then costs a cache probe, not ~45 min. | |
| # (Cache, not run-name/API parsing: a job name does not surface as the run's .name, | |
| # so name-based state is unreliable; actions/cache is the GitHub-native state store.) | |
| on: | |
| schedule: | |
| - cron: '17 7 * * *' # 07:17 UTC daily (off the hour to avoid the cron stampede) | |
| workflow_dispatch: | |
| inputs: | |
| force: | |
| description: 'Run even if nothing changed since the last successful nightly' | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: nightly-${{ github.ref }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| jobs: | |
| guard: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| changed: ${{ steps.check.outputs.changed }} | |
| cache_key: ${{ steps.fp.outputs.cache_key }} | |
| steps: | |
| - name: Fingerprint the three input HEAD SHAs (port + porting-sdk + python) | |
| id: fp | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PSDK_TOKEN: ${{ secrets.PORTING_SDK_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| port_sha='${{ github.sha }}' | |
| # porting-sdk is private → authenticated ref lookup; python is public. | |
| psdk_sha=$(GH_TOKEN="$PSDK_TOKEN" gh api repos/signalwire/porting-sdk/commits/main --jq .sha) | |
| py_sha=$(gh api repos/signalwire/signalwire-python/commits/main --jq .sha) | |
| echo "cache_key=nightly-green-v1-${port_sha}-${psdk_sha}-${py_sha}" >> "$GITHUB_OUTPUT" | |
| echo "inputs → port=$port_sha psdk=$psdk_sha python=$py_sha" | |
| # Probe (restore-only, no save) the green-marker cache for this exact input | |
| # triple. A HIT means these three SHAs already passed the heavy gates. | |
| - name: Probe last-green marker | |
| id: probe | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: .nightly-green-marker | |
| key: ${{ steps.fp.outputs.cache_key }} | |
| lookup-only: true | |
| - name: Decide | |
| id: check | |
| env: | |
| FORCE: ${{ github.event.inputs.force }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$FORCE" = "true" ]; then | |
| echo "force=true → running"; echo "changed=true" >> "$GITHUB_OUTPUT"; exit 0 | |
| fi | |
| if [ "${{ steps.probe.outputs.cache-hit }}" = "true" ]; then | |
| echo "this (port, porting-sdk, python) triple already passed a nightly → skipping heavy gates" | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "input triple has not passed a nightly yet → running heavy gates" | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| heavy: | |
| needs: guard | |
| if: needs.guard.outputs.changed == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout signalwire-typescript | |
| uses: actions/checkout@v7 | |
| with: | |
| path: signalwire-typescript | |
| - name: Checkout porting-sdk (mock servers + audit scripts + EMISSION corpus) | |
| uses: actions/checkout@v7 | |
| with: | |
| repository: signalwire/porting-sdk | |
| # Coordinated-pass pin: 'main' normally; set the PORTING_SDK_REF repo variable | |
| # to a wave branch to test a coordinated porting-sdk change, declared on the PR | |
| # (see porting-sdk/COORDINATED_PASS.md). No revert commit. | |
| ref: ${{ vars.PORTING_SDK_REF || 'main' }} | |
| path: porting-sdk | |
| token: ${{ secrets.PORTING_SDK_TOKEN }} | |
| - name: Checkout signalwire-python (EMISSION oracle) | |
| uses: actions/checkout@v7 | |
| with: | |
| repository: signalwire/signalwire-python | |
| path: signalwire-python | |
| ref: ${{ vars.PORTING_SDK_REF || 'main' }} | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '24' | |
| cache: 'npm' | |
| cache-dependency-path: signalwire-typescript/package-lock.json | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.14' | |
| - name: Install Python test harnesses (mock_relay + mock_signalwire) | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -e porting-sdk/test_harness/mock_relay \ | |
| -e porting-sdk/test_harness/mock_signalwire \ | |
| pytest pytest-timeout requests websockets | |
| # scripts/run-py-lint.sh and run-py-format.sh fail LOUD when ruff is absent | |
| # rather than skipping, so PY-LINT/PY-FMT cannot pass vacuously on a runner | |
| # that lacks it — which means EVERY workflow that runs run-ci.sh must install | |
| # it. test.yml has had this step; this file did not, so both gates red here | |
| # with "FATAL: ruff not found" (nightly run 30908541718). That is a missing CI | |
| # dependency, not a code finding. scripts/_env.sh:82 already documents the | |
| # contract as `{test,nightly}.yml` — nightly was simply never wired. | |
| # | |
| # PINNED EXACT, in lockstep with SW_RUFF_VERSION in scripts/_env.sh: an | |
| # unbounded `pip install ruff` resolves whatever is newest at CI time, so a | |
| # ruff release that adds a rule or changes a format heuristic turns these red | |
| # on a commit that was green locally — the VERSION changed, not the code. | |
| - name: Install ruff (PY-LINT / PY-FMT gates) | |
| run: | | |
| pip install "ruff==0.15.21" | |
| # Assert the pin actually took: pip can satisfy a spec from an | |
| # unexpected index or cache, so verify rather than assume. | |
| ruff --version | |
| ruff --version | grep -qw '0\.15\.21' || { echo "FATAL: ruff is not the pinned 0.15.21" >&2; exit 1; } | |
| - name: Install signalwire-python (EMISSION oracle) if not importable | |
| run: pip install -e signalwire-python || true | |
| # SWAIG-HTTP-INVOKE differ drives the python reference through | |
| # starlette.testclient.TestClient, which needs an httpx backend (newest | |
| # starlette: httpx2; older: httpx). Install both so the import resolves. | |
| - name: Install starlette TestClient HTTP backend (SWAIG-HTTP differ) | |
| run: pip install httpx httpx2 || pip install httpx | |
| # ACTIONLINT gate (porting-sdk actionlint_gate.py) needs the actionlint | |
| # binary and fails LOUD when it is absent, so install it before run-ci. | |
| # VERSION PINNED — see the same step in test.yml for why; keep the two and | |
| # scripts/_env.sh's SW_ACTIONLINT_VERSION in lockstep. | |
| - name: Install actionlint (ACTIONLINT gate) | |
| run: | | |
| bash <(curl -sSfL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.7.12 | |
| sudo mv ./actionlint /usr/local/bin/actionlint | |
| actionlint --version | |
| actionlint --version | grep -qw '1\.7\.12' || { echo "FATAL: actionlint is not the pinned 1.7.12" >&2; exit 1; } | |
| - name: npm ci | |
| working-directory: signalwire-typescript | |
| run: npm ci | |
| - name: Run FULL gate set incl. heavy doc gates (SW_CI_TIER=nightly) | |
| working-directory: signalwire-typescript | |
| env: | |
| PORTING_SDK: ${{ github.workspace }}/porting-sdk | |
| SW_CI_TIER: nightly | |
| # COORDINATED-PASS reads the live PR body/labels via 'gh api' to see a | |
| # 'Coordinated-With:' declaration edited after the triggering push. | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: bash scripts/run-ci.sh | |
| # Save the green marker ONLY on success (this step runs only if run-ci passed), | |
| # so the next nightly skips this exact input triple. A failed heavy run saves | |
| # nothing → it re-runs next nightly until it goes green. | |
| - name: Record green marker for this input triple | |
| run: 'echo "$GITHUB_SHA green $(date -u +%FT%TZ)" > .nightly-green-marker' | |
| working-directory: ${{ github.workspace }} | |
| - uses: actions/cache/save@v6 | |
| with: | |
| path: .nightly-green-marker | |
| key: ${{ needs.guard.outputs.cache_key }} |