Context
Found by @neo-gpt during exact-head review of PR #17161, where I had claimed a new CI workflow closed a git push --no-verify hole. It does not, and the reason is not specific to that guard.
The Problem
Nineteen *-lint.yml workflows run on dev PRs. Zero of them are required status contexts.
ls .github/workflows/*-lint.yml | wc -l
→ 19
gh api repos/neomjs/neo/rules/branches/dev \
--jq '[.[] | select(.type=="required_status_checks")] | .[].parameters.required_status_checks[]?.context'
→ integration-parity
integration-parity is the entire required set. The branch-protection endpoint 404s, so ruleset 19087298 is the whole story.
The consequence is mechanical: a failing lint moves a PR to mergeStateStatus: UNSTABLE, which means mergeable with failing non-required checks. It is a visible red mark, not a gate. Combined with git push --no-verify bypassing the local hooks, every lint in this repository is advisory at the moment of merge.
This is not a claim that lints are useless — they are loud, they are read, and authors fix them. It is a claim that the repo's own substrate describes them as gates when the ruleset does not back that. Sampled from the workflow headers currently on dev:
atomic-write-shape-lint.yml: "CI mirror of the .husky/pre-commit guard, so git commit --no-verify cannot bypass it."
- The same sentence pattern recurs across the family.
Those comments are the enforcement claim, and the ruleset does not support any of them. An agent or human reading the header reasonably concludes the hole is closed.
Why this surfaced now
PR #17161 adds check-spec-retirement.mjs, whose entire purpose is catching a silent failure — a deleted unit spec, which no red build can report because the assertions leave with the file. I wired a CI mirror and wrote that it closed the --no-verify path. @neo-gpt checked the live ruleset instead of the workflow file and found the mirror is reachability, not enforcement.
That guard is a sharp instance because its defect class is silence: shipping it as advisory means a --no-verify push still deletes coverage silently, and the guard that exists to notice sits green-but-unconsulted beside it. But the gap is the family's, not that guard's.
The Architectural Reality
- Ruleset
19087298 on dev; active rule types: code_scanning, copilot_code_review, deletion, non_fast_forward, required_status_checks. Only the last carries contexts, and it carries exactly one.
test/playwright/unit/ai/scripts/lint/lintWorkflowScanRootParity.spec.mjs already enforces scanned ⊆ watched — that a lint's workflow watches every path the lint scans. That is the reachability half, and it is well built. Nothing asserts the enforcement half.
ai/scripts/lint/guard-ci-parity-registry.json reasons about which lint-staged guards have CI mirrors — again mirror existence, never required-context membership.
- So the repo has two mechanisms that both stop one step short of the gate, and no mechanism that reads the ruleset.
The Fix (shape — the decision is the deliverable, not the code)
Three candidate shapes; this ticket exists to pick one, and picking is operator-owned because every option mutates repo settings.
- One aggregate required context. A single
lint-gate job that fans out to the lint family and is the required context. One ruleset entry, no per-lint churn. Cost: a red aggregate does not name which lint failed without opening it.
- Per-lint required contexts. Precise, and it makes the ruleset a readable inventory. Cost: 19 entries, and every new lint needs a ruleset edit — which is exactly the drift
lintWorkflowScanRootParity exists to prevent, now moved to a surface a spec cannot see.
- Fold the lint family into
integration-parity's job. No ruleset change at all. Cost: conflates two concerns in one context, and a lint failure reports as an integration-parity failure.
Whichever is chosen, the mechanical follow-on is the same: a spec that reads the live ruleset and asserts the family's membership, so this gap cannot silently reopen. That is the piece an agent can build and the piece that makes the decision durable.
Acceptance Criteria
Out of Scope
- Changing what any individual lint checks.
- The pre-commit / pre-push hook layer itself — hooks are bypassable by design; this is about the gate behind them.
integration-parity's own scope.
Avoided Traps
- Fixing it for one guard. Adding only
spec-retirement-lint as a required context would close my instance and leave eighteen open, while making the substrate more misleading — one honest header among nineteen false ones.
- Treating the workflow file as the source of truth. That is the exact error this ticket documents. Every AC above demands live ruleset output, because a workflow header is a claim and the ruleset is the fact.
- Assuming advisory means broken. Nineteen lints run and authors fix them; the loop works socially. The defect is the gap between that and what the substrate says, which is what a
--no-verify push exploits without anyone lying.
Evidence class
L2 — live API reads against the deployed ruleset, reproducible by any seat with repo read access. The enforcement change itself needs repo-admin and is therefore operator-gated.
Related
#17151 (the guard whose PR surfaced this; its enforcement AC lands here) · PR #17161 · lintWorkflowScanRootParity.spec.mjs (the reachability half, already built) · guard-ci-parity-registry.json
Live latest-open sweep: checked the latest 20 open issues at 2026-08-15T12:01Z; no equivalent found — nearest is #17151 itself, which owns the guard rather than the gate. A2A in-flight claim sweep over the last 12 messages: no competing [lane-claim] on ruleset or CI-enforcement scope.
Origin Session ID: 5cd926fa-77e1-4309-8bbf-ca563ab07403
Retrieval Hint: query_raw_memories("lint workflows advisory not required status context ruleset dev") · falsification anchor: gh api repos/neomjs/neo/rules/branches/dev returns integration-parity as the sole required context.
Context
Found by @neo-gpt during exact-head review of PR #17161, where I had claimed a new CI workflow closed a
git push --no-verifyhole. It does not, and the reason is not specific to that guard.The Problem
Nineteen
*-lint.ymlworkflows run ondevPRs. Zero of them are required status contexts.integration-parityis the entire required set. The branch-protection endpoint 404s, so ruleset19087298is the whole story.The consequence is mechanical: a failing lint moves a PR to
mergeStateStatus: UNSTABLE, which means mergeable with failing non-required checks. It is a visible red mark, not a gate. Combined withgit push --no-verifybypassing the local hooks, every lint in this repository is advisory at the moment of merge.This is not a claim that lints are useless — they are loud, they are read, and authors fix them. It is a claim that the repo's own substrate describes them as gates when the ruleset does not back that. Sampled from the workflow headers currently on
dev:atomic-write-shape-lint.yml: "CI mirror of the.husky/pre-commitguard, sogit commit --no-verifycannot bypass it."Those comments are the enforcement claim, and the ruleset does not support any of them. An agent or human reading the header reasonably concludes the hole is closed.
Why this surfaced now
PR #17161 adds
check-spec-retirement.mjs, whose entire purpose is catching a silent failure — a deleted unit spec, which no red build can report because the assertions leave with the file. I wired a CI mirror and wrote that it closed the--no-verifypath. @neo-gpt checked the live ruleset instead of the workflow file and found the mirror is reachability, not enforcement.That guard is a sharp instance because its defect class is silence: shipping it as advisory means a
--no-verifypush still deletes coverage silently, and the guard that exists to notice sits green-but-unconsulted beside it. But the gap is the family's, not that guard's.The Architectural Reality
19087298ondev; active rule types:code_scanning,copilot_code_review,deletion,non_fast_forward,required_status_checks. Only the last carries contexts, and it carries exactly one.test/playwright/unit/ai/scripts/lint/lintWorkflowScanRootParity.spec.mjsalready enforces scanned ⊆ watched — that a lint's workflow watches every path the lint scans. That is the reachability half, and it is well built. Nothing asserts the enforcement half.ai/scripts/lint/guard-ci-parity-registry.jsonreasons about which lint-staged guards have CI mirrors — again mirror existence, never required-context membership.The Fix (shape — the decision is the deliverable, not the code)
Three candidate shapes; this ticket exists to pick one, and picking is operator-owned because every option mutates repo settings.
lint-gatejob that fans out to the lint family and is the required context. One ruleset entry, no per-lint churn. Cost: a red aggregate does not name which lint failed without opening it.lintWorkflowScanRootParityexists to prevent, now moved to a surface a spec cannot see.integration-parity's job. No ruleset change at all. Cost: conflates two concerns in one context, and a lint failure reports as an integration-parity failure.Whichever is chosen, the mechanical follow-on is the same: a spec that reads the live ruleset and asserts the family's membership, so this gap cannot silently reopen. That is the piece an agent can build and the piece that makes the decision durable.
Acceptance Criteria
devruleset, verified bygh api repos/neomjs/neo/rules/branches/devoutput pasted into the PR — not by workflow-file inspection.UNSTABLE.lintWorkflowScanRootParity's reachability check.--no-verifyclosure either becomes true or is corrected.spec-retirement-lint.ymlis already corrected in PR #17161 and is the model.Out of Scope
integration-parity's own scope.Avoided Traps
spec-retirement-lintas a required context would close my instance and leave eighteen open, while making the substrate more misleading — one honest header among nineteen false ones.--no-verifypush exploits without anyone lying.Evidence class
L2 — live API reads against the deployed ruleset, reproducible by any seat with repo read access. The enforcement change itself needs repo-admin and is therefore operator-gated.
Related
#17151 (the guard whose PR surfaced this; its enforcement AC lands here) · PR #17161 ·
lintWorkflowScanRootParity.spec.mjs(the reachability half, already built) ·guard-ci-parity-registry.jsonLive latest-open sweep: checked the latest 20 open issues at 2026-08-15T12:01Z; no equivalent found — nearest is #17151 itself, which owns the guard rather than the gate. A2A in-flight claim sweep over the last 12 messages: no competing
[lane-claim]on ruleset or CI-enforcement scope.Origin Session ID: 5cd926fa-77e1-4309-8bbf-ca563ab07403
Retrieval Hint:
query_raw_memories("lint workflows advisory not required status context ruleset dev")· falsification anchor:gh api repos/neomjs/neo/rules/branches/devreturnsintegration-parityas the sole required context.