docs(backlog): record the leak-gate six-digit review, verdict leave it (BACKLOG #1436) #6
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: pre-commit replay | |
| # Re-runs the commit-time gates in CI, because git does not run them for a replayed commit | |
| # (BACKLOG #1395). | |
| # | |
| # THE DEFECT THIS ANSWERS, measured with a positive control. `git` never invokes the `pre-commit` | |
| # hook for a commit created by the sequencer. An ordinary `git commit` printed 11 hook result lines; | |
| # a `git cherry-pick` and a `git rebase --continue` each printed 0 and still created a commit. So a | |
| # rebase -- which every branch behind `main` needs, and which is an entirely innocent daily operation | |
| # -- lands a commit with NONE of the eleven gates having run, and nothing anywhere reports it. The | |
| # commit is indistinguishable from a gated one afterwards: `git log` cannot show which commits were | |
| # gated, and the only evidence is the terminal output of the run that created it. | |
| # | |
| # WHY A CI RE-RUN RATHER THAN A `post-rewrite` / `pre-merge-commit` HOOK PAIR. #1395 names both and | |
| # prefers this one, for a reason that is structural rather than a preference: a local hook is | |
| # advisory by construction, and this row is the proof. A second local hook can be skipped by the same | |
| # class of workflow that skips the first. | |
| # | |
| # WHAT THIS LEG ACTUALLY ADDS, STATED HONESTLY, BECAUSE OVERSTATING IT WOULD MAKE IT THE | |
| # "COMPENSATING CONTROL RESTING ON A FALSE PREMISE" THAT CLAUDE.md SECTION 11 FORBIDS. | |
| # | |
| # * IT IS NOT THE FIRST LINE, AND IT IS NOT EVEN EARLY. Ten of the eleven hooks already have a | |
| # hand-written CI mirror that runs on the tip tree, so on a replayed commit the leak guard and | |
| # the secret detector are LATE, not absent. This leg is late in the same way -- a push to this | |
| # public repository publishes before any runner starts. It buys defence in depth, not earliness. | |
| # * IT DOES NOT INSPECT INDIVIDUAL COMMITS. `--from-ref/--to-ref` resolves a CHANGED-FILE set | |
| # between two refs and runs the hooks against the checked-out tree. Content that exists only in | |
| # an intermediate commit and never reaches the tip is out of its reach, exactly as it is out of | |
| # the tip-tree mirrors' reach. | |
| # * WHAT IT DOES BUY IS THAT IT CANNOT DRIFT. Every other CI mirror is a separate hand-maintained | |
| # re-implementation of a hook, and that drift has already happened once in this repository (see | |
| # the "again" in security.yml's bandit comment). This leg runs THE HOOKS THEMSELVES, with their | |
| # own args, from their own pinned environments -- so for the nine it runs, hook-versus-CI | |
| # equivalence is true by construction rather than by a test. It also covers a future hook that is | |
| # added with no mirror at all. | |
| # | |
| # TWO HOOKS ARE SKIPPED, AND THE RULE IS NARROW: skip only where running it here would produce a | |
| # WRONG answer -- a green that means nothing, or a red that is not the pull request's fault. | |
| # Everything else runs. | |
| # | |
| # ledger-gate Its hook entry carries no `--ci`, so the OWNERSHIP arm would run. That arm | |
| # reads an allocation registry living in `.git/mefor-coord/`, which never | |
| # reaches a runner, so it would red every pull request on a check no author can | |
| # satisfy. ci.yml already runs `ledger_check.py --ci`, which is the | |
| # duplicate-number half -- the half CI can enforce. #1395 records that the | |
| # ownership arm is unavoidably CI-skipped rather than an oversight, and this | |
| # leg must not "fix" that by teaching CI to read allocations. | |
| # forbidden-content The hook fails closed via `--require-tokens`, and the real token list is a | |
| # git-ignored file that no runner has. Reproducing security.yml's fork/secret | |
| # branching in a NON-REQUIRED leg would either red every fork pull request, or | |
| # hand back a structural-only green that reads as a leak-gate pass. Both are | |
| # worse than not running it. security.yml's `forbidden-content` job is REQUIRED | |
| # and scans the whole tree with the token secret and a per-section detector | |
| # floor; branch-leak-scan.yml runs the same scanner on every push. Nothing is | |
| # lost by skipping it here. | |
| # | |
| # tests/test_gate_ci_mirror_parity.py pins that skip list to exactly those two ids. Without that, the | |
| # list grows one convenient entry at a time until the leg runs nothing and still reports green. | |
| # | |
| # NOT A REQUIRED CHECK, and it must not become one without the owner's decision. Branch protection's | |
| # required set is read from the server, and `.github/required-contexts.txt` plus the count pinned in | |
| # tests/test_required_contexts.py have to move in the same pull request as any change to it. This job | |
| # is deliberately absent from that file. It is non-required for a second reason too: it builds every | |
| # pinned hook environment from scratch on each run, so it is the slowest way to learn any of these | |
| # facts and belongs off the critical path. | |
| # | |
| # NO `paths:` FILTER, DELIBERATELY. The subject is "which gates ran on the commits in this pull | |
| # request", and that question is live for any diff. A paths filter would answer it only for pull | |
| # requests that happen to touch the files it names. | |
| # | |
| # NO `merge_group:` TRIGGER, DELIBERATELY. A non-required context cannot gate a queue entry, so | |
| # running it there would spend queue time and change no outcome. (For a REQUIRED check the omission | |
| # would be the total failure codeql.yml's header records under BACKLOG #340 -- that hazard is the | |
| # reason this line says why it is absent rather than leaving it to be re-derived.) | |
| # | |
| # NO HOOK-ENVIRONMENT CACHE. `actions/cache` is used by no workflow in this repository, so adding the | |
| # first one means choosing and pinning a new third-party action -- its own decision, not a side | |
| # effect of this leg. The cost is a few minutes on a job that blocks nothing. | |
| on: | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: precommit-replay-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| replay: | |
| name: pre-commit re-run over the diff | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| # fetch-depth: 0 is required, not defensive. `--from-ref/--to-ref` resolves the changed-file | |
| # set between two refs, which needs a merge base; the default depth-1 checkout gives the head | |
| # no history to find one in, so a shallow fetch of the base alone would not help. | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false # read-only lint job; do not persist the token (zizmor: artipacked) | |
| # setup-python is LOAD-BEARING HERE, not boilerplate. Four of the hooks are `language: system` | |
| # with `entry: python ...`, and .pre-commit-config.yaml records the measurement that `python` | |
| # (as distinct from `python3`) is not on a stock Ubuntu PATH. setup-python puts both on PATH, | |
| # which is what lets those four resolve at all on this runner. | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3.14' | |
| # Pinned to the version this repository has actually measured against: the comments in | |
| # tests/test_lint_scope_parity.py and .pre-commit-config.yaml both record behaviour verified | |
| # against pre-commit 4.6.1. It is version-pinned but NOT hash-pinned, which is a real residual: | |
| # pre-commit is in no dependency group, and adding one means a `uv lock`/`uv export` pass. | |
| # Routing it through ci/locks/ the way the scanners are is the correct end state. | |
| - name: Install pre-commit | |
| run: python -m pip install --disable-pip-version-check "pre-commit==4.6.1" | |
| # The base branch, resolved by NAME rather than by SHA so a re-targeted pull request still | |
| # compares against the branch it will actually merge into. On workflow_dispatch there is no | |
| # base_ref, so it falls back to the repository default branch. | |
| - name: Fetch the base branch | |
| env: | |
| BASE_REF: ${{ github.base_ref || github.event.repository.default_branch }} | |
| run: git fetch --no-tags origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}" | |
| # SKIP is pre-commit's own comma-separated hook-id list. The two ids and the reasoning are in | |
| # the header; tests/test_gate_ci_mirror_parity.py asserts this value stays exactly those two. | |
| # | |
| # This step may go red because a hook REWROTE a file rather than because it found something -- | |
| # `ruff-check` carries `args: [--fix]`. That is the same signal a developer gets locally, and | |
| # --show-diff-on-failure prints the rewrite so the report says what to apply. | |
| - name: Re-run every commit-time gate over the pull request diff | |
| env: | |
| BASE_REF: ${{ github.base_ref || github.event.repository.default_branch }} | |
| SKIP: ledger-gate,forbidden-content | |
| run: | | |
| pre-commit run \ | |
| --show-diff-on-failure \ | |
| --from-ref "refs/remotes/origin/${BASE_REF}" \ | |
| --to-ref HEAD |