Skip to content

Commit 0dbd720

Browse files
committed
ci(lint): report how many gates never ran when the job aborts
`Lint & Repo Gates` runs ~161 gates as separate Actions steps and stops at the first non-zero exit. That abort is deliberate and adjudicated (#13690); what was missing is the number it leaves behind. One red is a LOWER BOUND on the problems in the tree, and a gate that never executed produces the same "no output" a passing gate does. Measured on run 34468505490 before writing this: the jobs API and the web UI do mark the un-run steps `skipped` (112 of them), but never count them, and use that same word for a step its own `if:` turned off. The check-run `output` was null on every field and the job log said nothing at all. Adds `scripts/report-unmeasured-gate-tail.mjs` plus one `if: failure()` step that prints the count to the job log, the job summary and one `notice` annotation. Fail-fast is untouched, no gate moved, no `continue-on-error`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
1 parent d46deba commit 0dbd720

2 files changed

Lines changed: 651 additions & 4 deletions

File tree

‎.github/workflows/lint.yml‎

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ jobs:
177177
runs-on: ubuntu-latest
178178
permissions:
179179
contents: read
180+
# ⭐ Read-only, and it buys exactly one thing: the LAST step of this job
181+
# asks the jobs API how many of the steps below it never ran, because the
182+
# size of that tail is the one fact no step can work out locally (the
183+
# runner alone knows which step index it stopped at). See
184+
# `scripts/report-unmeasured-gate-tail.mjs`. Nothing else in this job
185+
# reads it, and no step writes anything anywhere.
186+
actions: read
180187

181188
steps:
182189
- name: Checkout repository
@@ -4468,14 +4475,56 @@ jobs:
44684475
# author copying the outer number into the inner block got a 3.6-second
44694476
# cache and no error anywhere. No baseline, by ruling: the gate is green
44704477
# only at zero offenders, and a red is a rename under an ADR-0087
4471-
# conversion (or a describe to fix), never a ledger row. Placed LAST in
4472-
# this job on purpose: it is a new tree-wide ratchet, and the job aborts
4473-
# at the first non-zero exit — nothing behind it would be masked by its
4474-
# red. Reads src/ through tsx (no build); self-tests first, wired into
4478+
# conversion (or a describe to fix), never a ledger row. Placed LAST
4479+
# AMONG THE GATES on purpose: it is a new tree-wide ratchet, and the job
4480+
# aborts at the first non-zero exit — nothing behind it would be masked
4481+
# by its red. (The two steps after it are not gates and cannot be masked:
4482+
# one is an offline self-test, the other runs only ON failure and exits 0.
4483+
# The qualifier is this PR's, forced by appending them; the ruling it
4484+
# describes is untouched, and no gate's relative order moved.) Reads src/ through tsx (no build); self-tests first, wired into
44754485
# the package script as `check:self-test-wired` requires.
44764486
- name: Duration-shaped spec keys carry their unit in the key name
44774487
run: pnpm --filter @objectstack/spec check:duration-unit-keys
44784488

4489+
# ── NOT GATES. The two steps below judge nothing about the tree. ──────
4490+
#
4491+
# ⭐ One red in this job is a LOWER BOUND on the number of problems in the
4492+
# tree, not a count — every gate behind the failing one never executed,
4493+
# and a gate that never executed produced no output, which is the same
4494+
# shape a passing gate has. The GATE ORDER note above `jobs:` already
4495+
# names this quantity ("the unmeasured tail") and is tuned to shrink it;
4496+
# until now nothing printed it. These steps print it.
4497+
#
4498+
# ⛔ The fix is NOT to make the skipped gates run: that would undo the
4499+
# adjudicated fail-fast order. ⛔ Nor is it `continue-on-error`, which
4500+
# trades a red for silence. The job's conclusion is untouched.
4501+
#
4502+
# Measured before this was written, on run 34468505490 of this very
4503+
# workflow (job 102842713265, failed at step #55): the jobs API and the web
4504+
# UI DO mark the un-run steps `skipped` — 112 of them there — but they
4505+
# never count them, and they use that same word for a step its own `if:`
4506+
# turned off, so the one surface that says it cannot tell the two apart.
4507+
# The check-run `output` was null on title, summary and text alike, and the
4508+
# job log — what an API or agent reader gets — said nothing at all. So what
4509+
# is added here is a machine-readable COUNT, on all three surfaces.
4510+
4511+
# Offline, no token, no network: drives the reporter's judge() over
4512+
# recorded jobs-API shapes. Wired as a step because an unrun self-test is
4513+
# a phantom check. Sub-second.
4514+
- name: Unmeasured-gate-tail reporter self-test
4515+
run: node scripts/report-unmeasured-gate-tail.mjs --self-test
4516+
4517+
# The only step in this job that runs BECAUSE something failed. On a green
4518+
# head `failure()` is false, so it is skipped and contributes zero bytes to
4519+
# the log, the summary and the annotations. It always exits 0: the job is
4520+
# already failed when it runs, and a non-zero exit here would only add a
4521+
# second annotation pointing at the reporter instead of at the gate.
4522+
- name: Report how many gates never ran
4523+
if: failure()
4524+
env:
4525+
GITHUB_TOKEN: ${{ github.token }}
4526+
run: node scripts/report-unmeasured-gate-tail.mjs
4527+
44794528
# Lane 1 of 4 behind the required `TypeScript Type Check` context. The
44804529
# aggregator job at the bottom of this file explains the split, holds the
44814530
# contract, and is the thing the merge queue actually requires — read it

0 commit comments

Comments
 (0)