name: repo hygiene # The one workflow with NO path filter. # # Every other workflow in this directory gates a specific crate and is # scoped with `paths:`. That is correct for build and test jobs -- there # is no reason to compile the CAD module because a payment file moved. # But it leaves a structural hole: a commit that touches only unfiltered # paths runs no checks whatsoever. # # That hole is not hypothetical. Commit 8c9ccb9 pushed 30 unresolved # conflict-marker lines across five files: # # .forgejo/workflows/nigig-build.yml <- invalid YAML: the CAD gates # could not even be parsed, # so they silently stopped running # .forgejo/workflows/pdf.yml # crates/apps/.../cad/workspace.rs <- ~3,000 lines also lost # crates/apps/.../spreadsheet-engine/src/formula2.rs # crates/apps/.../cad/ARCHITECTURE.md # # `git diff --check` already existed as a step inside nigig-build.yml and # would have caught this -- but it was in the file the same commit broke, # and it only ran for paths under that workflow's filter. # # So the checks here must satisfy three properties: # 1. no path filter, # 2. no dependency on the Rust toolchain or the network, so they cannot # be knocked out by an unrelated build break, and # 3. they must validate the CI configuration itself. # NOTE the list form. This was previously written as # # on: # push: # pull_request: # # which is valid YAML -- both keys parse as null -- and is how GitHub # spells "every branch". This instance does not schedule it. Across the # first 47 task records after a runner was registered, every other # workflow ran and this one never did, not once, despite having no path # filter and despite pushes touching every filtered path in the repo. # # That is the worst possible failure for this particular file, because # its entire reason to exist is to be the workflow that always runs. on: [push, pull_request] jobs: hygiene: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 # An unresolved merge is the highest-severity thing that can reach # main: the file does not parse, and if it is a workflow the checks # that would have caught it stop running. - name: No conflict markers anywhere in the tree run: | set -euo pipefail # Anchored to line start, and the closing marker requires the # trailing space git writes, so ordinary text (a Markdown rule, # a Rust shift operator) does not trip it. if git grep -nE '^(<{7} |={7}$|>{7} )' -- \ ':(exclude).forgejo/workflows/repo-hygiene.yml'; then echo echo "ERROR: unresolved conflict markers are committed above." echo "Resolve the merge before pushing; these files do not parse." exit 1 fi echo "OK" # A workflow that is not valid YAML does not fail -- it does not run. # That is strictly worse than a red build, because it is invisible. - name: Every workflow file must be valid YAML run: | set -euo pipefail python3 - <<'PY' import sys, pathlib, yaml bad = [] for f in sorted(pathlib.Path('.forgejo/workflows').glob('*.yml')): try: doc = yaml.safe_load(f.read_text()) except Exception as exc: bad.append((f, exc)) continue if not isinstance(doc, dict) or 'jobs' not in doc: bad.append((f, 'no top-level `jobs:` mapping')) for f, why in bad: print(f"{f}: {why}") if bad: print() print("ERROR: the workflow file(s) above do not parse, so their") print("checks are silently not running.") sys.exit(1) print("OK") PY # A script a workflow invokes as `./tools/x.sh` must be executable # in the index, not just in whoever's working copy. The mode is # what gets committed; a local `chmod +x` that is never staged is # invisible until CI runs. # # This was live on main. The first real CI run after a runner was # registered failed two pay-domain jobs at their first real step: # # ./tools/makepad-native-libs.sh: Permission denied # ./tools/test-mpesa-store-clean.sh: Permission denied # # Five of the six scripts under tools/ were mode 100644. Every one # of them is invoked with a leading `./` from pay-domain.yml or # pdf.yml, so those jobs could never have passed. Nobody noticed # because nothing had ever executed them. - name: Scripts invoked by a workflow must be executable run: | set -euo pipefail fail=0 while IFS= read -r script; do mode=$(git ls-files -s -- "$script" | cut -d' ' -f1) if [ "$mode" != "100755" ]; then echo "$script is mode $mode, expected 100755" fail=1 fi done < <(git ls-files 'tools/*.sh') if [ "$fail" -ne 0 ]; then echo echo "ERROR: the script(s) above are committed non-executable." echo "A workflow that runs them as ./tools/.sh dies with" echo "'Permission denied' before doing any work." echo "Fix with: git update-index --chmod=+x