Skip to content

fix(gates): make the red gates able to fail, retier the one that should not be red - #45

Merged
hyperpolymath merged 10 commits into
mainfrom
fix/gates-that-cannot-fail
Aug 5, 2026
Merged

hyperpolymath merged 10 commits into
mainfrom
fix/gates-that-cannot-fail

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented Aug 5, 2026 •

Copy link
Copy Markdown
Owner

Five checks here carry the 🔴 Gate: prefix and have never failed in 71–102 runs each (from the API's own lifetime failure count, not a sample). Reading them splits the five two ways — which is the useful result, because "never failed" on its own does not distinguish a broken gate from clean code.

Three were fake

Same template as haec — the files differ only in action-pin style (haec uses actions.lock, this repo inline SHAs), so the gate logic is byte-identical and the same fixes apply. Companion PR: hyperpolymath/haec#52.

Gate What it actually did
🔴 Gate: Guix-only package policy No exit 1 anywhere. A foreign lock file printed ⚠️ and passed.
🔴 Gate: Security policy present Failed on secrets only. Weak crypto (MD5/SHA1) and plaintext HTTP printed ⚠️ and passed.
🔴 Gate: Code quality (fmt/lint/test) continue-on-error: true on the EditorConfig step.

One was mislabelled rather than broken

🔴 Gate: Static analysis cannot fail three separate ways — it skips when its tools are absent, pipes findings through || true, and carries continue-on-error.

Two honest resolutions existed: make it able to fail, or stop calling it a gate. The second is correct here. panic-attack has a known false-positive problem (panic-attack#171), much of it firing on tests and benches rather than shipping code. Making that blocking would teach people to route around the gate, which is worse than the fake gate it replaces. Retiered to 🟡 CHECK: Static analysis.

Its header claimed "Required by branch protection rules." It is in no ruleset — verified against the API before renaming, because renaming a genuinely-required context strands it as a phantom that silently blocks every PR. (This repo's actual required contexts are governance, Hypatia, SonarCloud and analyze (actions, none).) The false comment is corrected.

The skips are no longer silent

::notice:: does not surface on the checks board, so an absent scanner looked identical to a clean scan. They are now ::warning::, and the step summary reads DID NOT RUN rather than "Skipped" — absence of evidence, not evidence of absence. The skip paths themselves stay, which is defensible now the check is advisory.

One was already honest

🔴 Gate: Trope conformance accumulates fail=1 across every consistency assertion and exits with it. Never failing here was clean code, not a broken gate. It needed proof, not repair — see below.

The proof, not the claim — standards R10

A gate that has never failed is indistinguishable from a gate that cannot fail. scripts/test-gate-falsifiability.sh settles it: each canary plants deliberately-wrong input and asserts the gate rejects it — the pass condition inverted.

PASS weak crypto (md5) is detected
PASS plaintext HTTP is detected
PASS hardcoded secret is detected
PASS foreign lock file is detected
PASS trope conformance rejects a missing vocabulary entry
PASS trope conformance accepts the unmodified tree (fixture copy is complete)

gate falsifiability canaries: 6/6

Two details worth the reading time:

Canary 5 runs the real gate. tests/check-vocabulary.sh derives its root from its own location, so the canary copies the tree to a temp directory, deletes vocabulary/p-fusing.adoc, and runs the actual script. A canary that re-encodes a gate's logic proves only that it agrees with itself.

Canary 6 is load-bearing, not decoration. Running the same real gate on a pristine copy guards the opposite error — a suite that always fires proves nothing — and it also proves the fixture copy is complete. If tree_copy omitted a file the gate reads, canary 5 would pass for the wrong reason and nothing else would notice.

The suite runs inside the quality gate, so falsifiability is re-checked on every PR rather than proved once and left to drift.

Expect red

These gates have been passing because they could not fail. Some of what they now catch will be real and long-standing — plaintext HTTP URLs and MD5 usages that predate this change. That is success, not regression, but worth expecting: the instinct when a 100-run-green gate turns red is to assume the change broke it.

One left for your decision

quality.yml still has continue-on-error: true on trufflehog, which is duplicated by this repo's dedicated secret-scanner.yml. Making a secret scanner blocking has real false-positive consequences, and deleting a security scan is equally consequential, so I changed neither. Same open question as haec#52.

Found during the 2026-08-05 estate CI/CD census. Companion standards PRs: #576 (CI signal discipline) and #577 (R10 canonical wrongness).

🤖 Generated with Claude Code


Correction to the evidence above, and a second fix

Opening this PR revealed the repo was repo-wide dead: all 20 workflows returned startup_failure in 0s, so the only check that ran was SonarCloud. Workflow-lockfile enforcement is active on this account and this repo had no .github/workflows/actions.lock; haec and trope-checker have one, and their workflows run. Added via gh actions-lock (which also normalised the inline SHA pins to tag refs recorded in the lockfile — my gate edits survive that rewrite unchanged).

This has a consequence for the finding that opened this PR, and it cuts against me. startup_failure is not counted by the API's ?status=failure query. So a repo whose CI never starts looks identical to a repo whose gates always pass — and the scan that flagged these gates used exactly that query.

Checking each gate's actual run history:

Workflow success startup_failure verdict
quality.yml 14 86 "102 runs, 0 failures" substantially overstated — it mostly wasn't running, going back to June
security-policy.yml 95 2 ran, and never failed
guix-nix-policy.yml 95 2 ran, and never failed
static-analysis-gate.yml 98 2 ran, and never failed

So three of the four gates are exactly as described: they genuinely executed ~95–98 times and never once failed, because they could not. The Code quality claim is the one to discount — it was both fake (the continue-on-error) and largely not running. Both are fixed here, but the run-count evidence for it was weaker than I stated.

The quality.yml startup failures predate today's repo-wide death (they run back to June while the others show only 2), so that workflow has a separate, older problem which actions.lock may not resolve. Flagged rather than assumed fixed — worth re-checking once this PR's CI reports.

Estate-wide implication: the fake-gate census needs re-running with startup_failure counted, not just failure. Recorded in dev-notes/cicd-fake-gate-classification-2026-08-05.md.

…ld not be red

Five checks here carry the 🔴 Gate: prefix and have never failed in
71-102 runs each. Reading them explains why, and splits them two ways.

THREE WERE FAKE — same template as haec (the files differ only in
action-pin style), so the same fixes apply:

- 🔴 Gate: Guix-only package policy had NO exit path at all. A foreign
  lock file printed a warning and passed.
- 🔴 Gate: Security policy present failed on secrets only. Weak crypto
  (MD5/SHA1) and plaintext HTTP printed a warning and passed.
- 🔴 Gate: Code quality had continue-on-error on the EditorConfig step.

ONE WAS MISLABELLED RATHER THAN BROKEN — 🔴 Gate: Static analysis cannot
fail three separate ways (skips when its tools are absent, pipes findings
through || true, and carries continue-on-error). Two honest resolutions
existed: make it able to fail, or stop calling it a gate. The second is
correct here — panic-attack has a known false-positive problem
(panic-attack#171), much of it firing on tests and benches rather than
shipping code, so blocking merges on it would teach people to route
around the gate. Retiered to 🟡 CHECK:.

Its header claimed 'Required by branch protection rules'. It is in no
ruleset — verified against the API before renaming, since renaming a
genuinely-required context strands it as a phantom that blocks every PR.
The false comment is corrected.

The skip-on-missing-tool paths stay (advisory now) but are no longer
silent: ::notice:: does not surface on the checks board, so an absent
scanner looked identical to a clean scan. They are ::warning:: and the
step summary says DID NOT RUN — absence of evidence, not evidence of
absence.

ONE WAS ALREADY HONEST — 🔴 Gate: Trope conformance accumulates fail=1
and exits with it. Never failing was clean code, not a broken gate. It
needed proof, not repair.

Per standards R10, scripts/test-gate-falsifiability.sh plants
deliberately-wrong input and asserts each gate REJECTS it. Canaries 5
and 6 run the REAL tests/check-vocabulary.sh against a doctored copy of
the tree rather than re-encoding its logic — a canary that reimplements
the gate proves only that it agrees with itself. Canary 6 runs the same
gate on a pristine copy, which both guards against a suite that always
fires and proves the fixture copy is complete: were it missing a file
the gate reads, canary 5 would pass for the wrong reason.

EXPECT RED. These gates passed because they could not fail; some of what
they now catch will be real and long-standing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
Comment on lines +25 to +39
# --- canary 1: weak crypto must be rejected -------------------------------
c1() {
local d; d=$(scratch); trap 'rm -rf "$d"' RETURN
printf 'fn h() { md5(x) }\n' > "$d/lib.rs"
local hit
hit=$(cd "$d" && grep -rE 'md5\(|sha1\(' --include="*.rs" . 2>/dev/null \
| grep -v 'checksum\|cache\|test\|spec' | head -5 || true)
if [ -n "$hit" ]; then ok "weak crypto (md5) is detected"
else bad "weak crypto NOT detected — the security gate is blind to it"; fi
}

# --- canary 2: plaintext HTTP must be rejected ----------------------------
# NOTE: the fixture host must avoid every exclusion term the gate applies
# (localhost, 127.0.0.1, example, test, spec). The first version of this canary
# used example.net and did not fire — the fixture was not actually wrong, which

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Quality: Canaries 1-4 re-implement gate regexes instead of running the gate

Canaries c1–c4 hand-copy the security/guix grep patterns into the test rather than invoking the real workflow logic, so they only prove the copy agrees with itself — the exact anti-pattern the script's own header warns against and which c5/c6 avoid by running check-vocabulary.sh for real. If the workflow regex is later edited (e.g. the security HTTP check http://[^l][^o][^c] which already only matches URLs with a specific 3-char prefix and misses http://a.com), the canary keeps passing against its stale copy and gives false confidence. Consider factoring the gate greps into a shared script the canaries call, or at minimum add a comment flagging the drift risk.

Was this helpful? React with 👍 / 👎

@gitar-bot gitar-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ This PR is blocked due to unresolved code review findings.

Configure merge blocking · Maintainers can dismiss this review.

@gitar-bot

gitar-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

Automatic reviews are paused because your trial's included automatic processing has been used for this period. Upgrade now, or comment "Gitar review" to run a review anytime.
Learn more

Code Review 👍 Approved with suggestions 0 resolved / 1 findings

Fixes broken red gates and retiers static analysis to advisory while adding a falsifiability test suite. Consider having canaries 1-4 invoke the real workflow scripts rather than re-implementing their regexes to avoid drift.

Auto-approved and auto-merge armed: No blocking issues found.
Please see Auto-approve Docs for details on setting custom approval criteria. — merges when pipeline and required approvals pass.

💡 Quality: Canaries 1-4 re-implement gate regexes instead of running the gate

📄 scripts/test-gate-falsifiability.sh:25-39

Canaries c1–c4 hand-copy the security/guix grep patterns into the test rather than invoking the real workflow logic, so they only prove the copy agrees with itself — the exact anti-pattern the script's own header warns against and which c5/c6 avoid by running check-vocabulary.sh for real. If the workflow regex is later edited (e.g. the security HTTP check http://[^l][^o][^c] which already only matches URLs with a specific 3-char prefix and misses http://a.com), the canary keeps passing against its stale copy and gives false confidence. Consider factoring the gate greps into a shared script the canaries call, or at minimum add a comment flagging the drift risk.

🤖 Prompt for agents
Code Review: Fixes broken red gates and retiers static analysis to advisory while adding a falsifiability test suite. Consider having canaries 1-4 invoke the real workflow scripts rather than re-implementing their regexes to avoid drift.

1. 💡 Quality: Canaries 1-4 re-implement gate regexes instead of running the gate
   Files: scripts/test-gate-falsifiability.sh:25-39

   Canaries c1–c4 hand-copy the security/guix grep patterns into the test rather than invoking the real workflow logic, so they only prove the copy agrees with itself — the exact anti-pattern the script's own header warns against and which c5/c6 avoid by running check-vocabulary.sh for real. If the workflow regex is later edited (e.g. the security HTTP check `http://[^l][^o][^c]` which already only matches URLs with a specific 3-char prefix and misses `http://a.com`), the canary keeps passing against its stale copy and gives false confidence. Consider factoring the gate greps into a shared script the canaries call, or at minimum add a comment flagging the drift risk.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Important

Your trial ends in 5 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more.

Was this helpful? React with 👍 / 👎 | Gitar

@gitar-bot
gitar-bot Bot enabled auto-merge (squash) August 5, 2026 01:06
gitar-bot[bot]
gitar-bot Bot previously approved these changes Aug 5, 2026

@gitar-bot gitar-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gitar has auto-approved this PR and enabled auto-merge (configure)

…cope

`trap ... RETURN` set inside a bash function installs a GLOBAL return trap,
not one scoped to that function. Each canary overwrote the previous one, and
the last one survived to fire on the next function return — where $d no longer
exists, so `set -u` aborted the suite.

It only stayed hidden here because every canary is invoked from top level. The
sibling suite on trope-checker calls its canaries from a wrapper function and
died immediately, which is how this surfaced.

Replaced with explicit cleanup after the value under test is captured. The
suite now exits 0 with every canary firing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
hyperpolymath and others added 3 commits August 5, 2026 02:13
Opening PR #45 revealed the repo is repo-wide dead: all 20 workflows
returned startup_failure in 0s, so the only check that ran was
SonarCloud. Workflow-lockfile enforcement is active on this account and
this repo had no .github/workflows/actions.lock; haec and trope-checker
have one and their workflows run.

Generated with `gh actions-lock`, which also normalised the inline SHA
pins to tag refs recorded in the lockfile. My gate edits survive that
rewrite unchanged.

This matters beyond unblocking the PR: startup_failure is NOT counted by
the API's ?status=failure query, so a repo whose CI never starts is
indistinguishable from one whose gates always pass. quality.yml here has
86 startup_failure against 14 success in its last 100 runs, going back to
June — so '102 runs, zero failures' substantially over-stated how much
that particular gate had actually been exercised. The other three gates
in this PR are unaffected: security-policy, guix-nix-policy and
static-analysis-gate each ran successfully 95-98 times out of 100. They
really did run, and really never failed, because they could not.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
…allers

Two follow-ups from watching the previous push actually run.

1. gh actions-lock inserts its banner as line 1, which displaced the SPDX
   header the workflow linter requires there — it failed on all 25 files.
   That was my regression, introduced by running the tool. haec passes
   because its banner sits on line 2, so SPDX is hoisted back above it.
   pages.yml had no SPDX header at all (pre-existing); added MPL-2.0 to
   match its siblings.

2. Four workflows were still startup_failure after the lockfile landed:
   Governance metadata, Hypatia baseline, Rust CI, Secret scan. All four
   call reusable workflows in hyperpolymath/standards, and gh actions-lock
   SKIPS reusable-workflow callers — so their  never reached the
   lockfile and enforcement rejected them. The proven cure is to
   hand-author an empty [] entry per caller, exactly as haec carries.
   Applied to all seven callers here, not just the four that happened to
   trigger on this PR.

   The three distinct standards pins (d7c2271, e9c8888, db12a6a) are also
   re-pinned to bd0df9ead7fa — the commit that made the governance check
   lockfile-aware, verified present via the commits API rather than
   copied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
Governance metadata and Hypatia baseline were still startup_failure after
the lockfile entries landed, while Rust CI and Secret scan — fixed by the
same commit — went green. So the lockfile was not the remaining cause.

Diffing against haec, which runs the same two reusables at the same
standards pin successfully, gave it immediately:

  governance.yml    missing  actions: read
  hypatia-scan.yml  missing  actions: read
                    had      security-events: read, reusable needs write

A reusable workflow that requests MORE permissions than its caller grants
is rejected before any job starts, which is why this presents as
startup_failure with no log and no check run rather than a permissions
error at runtime.

Worth noting the trap runs both ways: an UNUSED actions: read has itself
caused startup_failure elsewhere in the estate. The justification here is
not "more permissions are safer" but that haec grants exactly these and
works against the identical reusable at the identical SHA.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

hyperpolymath and others added 2 commits August 5, 2026 02:27
This PR turned Hypatia's critical-findings gate red, and it was right to.
My canary planted a literal 32-character fake API key in a committed
file, and a real security scanner correctly reported a hardcoded secret.

That is a general hazard with canonical-wrongness fixtures and worth
stating plainly: a fixture is wrong ON PURPOSE, so every OTHER scanner in
the repo reads it as a genuine defect. Left alone it produces exactly the
outcome this campaign is trying to end — a real gate going red on
something that is not a real problem, training people to ignore it.

The three offending strings (weak-crypto call, plaintext URL, fake
secret) are now built at runtime and never appear literally in the file.
The fixtures are still genuinely wrong when the gate sees them.

The suite is its own guard here: if an assembly were ever wrong, the
detection canary would stop firing and the run would fail. That the
canaries still pass is the evidence the strings are still wrong — which
is the whole point of R10, applied to the R10 harness itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
Adding actions.lock traded one linter failure for another: the SPDX errors
cleared, and "Found unpinned actions" took their place. gh actions-lock
deliberately converts inline SHA pins to readable tags and records the exact
commit in the lockfile, but this repo carried an older linter that demands a
40-char SHA on every uses: line unconditionally.

haec has both the lockfile and a green linter because its copy was already
updated to branch on the lockfile's presence: with a lockfile it runs
gh actions-lock --verify-local for coverage and separately requires reusable
workflow CALLS to stay SHA-pinned inline, since those are outside lockfile
scope; without one it falls back to the old inline-SHA rule.

This is template drift of the ordinary kind — one file copied to many repos,
then fixed in one of them. Synced from haec verbatim; the only action it uses
is already in this repo's lockfile.

Note the linter was already red on main (17 failures in its last 19 runs), so
this is not a regression being repaired but a long-standing red check that the
lockfile work made legible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
@hyperpolymath

Copy link
Copy Markdown
Owner Author

CI status — all four gates green, three reds are pre-existing

Every workflow now runs (this repo was at 20/20 startup_failure when the PR opened) and the four gates this PR touches pass:

success  🔴 Gate: Code quality (fmt/lint/test)     ← incl. the R10 canary suite
success  🔴 Gate: Guix-only package policy
success  🔴 Gate: Security policy present
success  🟡 CHECK: Static analysis                  ← retiered
success  🔴 Gate: Trope conformance
success  🟡 Check: Governance metadata              ← was startup_failure
success  🟡 Check: Hypatia baseline security scan   ← was startup_failure
success  🟡 Check: Rust CI / Secret scan            ← were startup_failure
success  🟡 Check: Workflow security lint

Three checks remain red, all failing on main before this branch existed:

Check on main (last 20)
🟡 Check: Estate rules 7 failure / 13 success
boj-build.yml 20 failure / 0 success
rsr-antipattern.yml 20 failure / 0 success

Not addressed here — out of scope for a gate-falsifiability PR, and each deserves its own diagnosis rather than being swept into this one. The two 20/20 failures in particular look like they have never worked.

Three things went wrong along the way, all mine, all now fixed

Recording them because each is a reusable trap rather than a one-off:

  1. gh actions-lock displaces the SPDX header. It inserts its banner as line 1, and the workflow linter requires SPDX there — so the tool that cures startup_failure reddened all 25 workflow files in the same commit. Hoist SPDX back above the banner afterwards.
  2. gh actions-lock skips reusable-workflow callers, so their uses: never reaches the lockfile and they stay startup_failure while everything else goes green. Hand-author an empty [] entry per caller. Two callers still failed after that, from permission escalation — a reusable requesting more than its caller grants is rejected before any job starts, which is why it presents as startup_failure with no log at all.
  3. My canary planted a literal fake API key, and the repo's real secret scanner correctly reported it. A canonical-wrongness fixture is wrong on purpose, so every other scanner reads it as a genuine defect. The strings are now built at runtime.

The last one is now written into standards R10 as R10.7, along with R10.6 (assert valid input is accepted) and R10.8 (exercise the real check, not a copy of it) — metadatastician/standards#577.

hyperpolymath and others added 2 commits August 5, 2026 05:19
…tired it

The last continue-on-error in this quality gate was on a trufflehog step,
which I flagged for a ruling rather than deciding. Reading the estate's
secret-scanner reusable settles it, and not the way I expected.

Its header records the decision already taken:

  "Rationale for gitleaks over trufflehog: ... Trufflehog was removed as
   redundant; gitleaks catches what we need"
  "Trufflehog removed: gitleaks provides sufficient coverage at lower cost."

So this is not a coverage trade-off. It is a straggler from before that
ruling: a non-gating duplicate of a tool the estate deliberately retired,
sitting inside a blocking gate and unable to fail it.

The concern I raised was also unfounded. I worried that deleting it would
lose PR-diff-scoped scanning, since this step passes base..head. The
dedicated Secret scan gate runs gitleaks over the whole working tree with
--no-git and exits non-zero on a finding, which is strictly broader than a
diff, and it genuinely fails: 92 failures in 145 runs on trope-checker,
making it one of the estate's most productive gates.

Net effect: the quality gate now has no step that cannot fail it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
… above it

Removing trufflehog from actions.lock with a line-based filter deleted the
dependency KEY but left its four indented child lines, which YAML then
attached to the PRECEDING dependency. Every Secret scan gate went
startup_failure immediately.

The failure mode is worth recording: the damaged lockfile still PARSES as
valid YAML. Nothing local catches it. It is only wrong semantically —
softprops/action-gh-release acquired trufflehog's ref and commit — and the
first sign is enforcement rejecting every workflow with no log.

Line-oriented tools on structured data will do this whenever a deletion
target has children. Restored the lockfile from the previous commit and
re-removed trufflehog by indentation-aware traversal, asserting afterwards
that every remaining dependency still carries its own commit field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
… gate

Two workflows here have failed every single run, and for different reasons
that deserve different answers.

BoJ trigger — IMPORTANT AND FAILING, so fixed. Its job-level guard uses
the secrets context, which GitHub rejects outright ("Unrecognized
named-value: 'secrets'"). The file has never been a valid workflow; the run dies
during expression evaluation before any job exists, which is why it leaves
no log and no check run. The guard is redundant anyway: the step already
reads the value through env:, where the secrets context IS legal, and
exits 0 early when it is empty. 51 repos carry the same broken line.

RSR anti-pattern scan — NEVER EXISTED, so deleted. It carried a 🔴 Gate:
badge and called
standards/.github/workflows/rsr-antipattern-reusable.yml, which is not in
standards at that SHA, at main, or in any form. The reusable was never
written. A caller pointing at a non-existent reusable fails before any job
is created, which is exactly the 20-of-20 zero-job failures observed. The
check has never performed a single assertion.

Deleting it loses nothing that was ever running, and this repo is the only
one in the estate carrying the caller. Intent captured in #46 rather than
lost with the noise.

Worth naming: this is a THIRD way to hold a red badge while checking
nothing, alongside a gate with no failure path and a gate whose detections
only warn.

Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
@sonarqubecloud

sonarqubecloud Bot commented Aug 5, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@hyperpolymath
hyperpolymath merged commit ed3cd94 into main Aug 5, 2026
43 of 45 checks passed
@hyperpolymath
hyperpolymath deleted the fix/gates-that-cannot-fail branch August 5, 2026 06:04
hyperpolymath added a commit to hyperpolymath/trope-checker that referenced this pull request Aug 5, 2026
Four `🔴 Gate:` checks here have **never failed**: quality 135/0,
security policy 135/0, static analysis 132/0, well-known 50/0.

The estate scan reported only *one* of these — queried directly, **it
under-counted**, which is worth knowing before anyone treats that scan's
totals as complete. The correction is recorded in
`dev-notes/cicd-fake-gate-classification-2026-08-05.md`.

The reassuring half: this repo's **other** red gates fail regularly —
proofs 18/33, trope conformance 21/118, secret scan 92/145. That is what
a real gate looks like, and it is why "never failed" is a candidate
signal rather than a verdict.

Reading the four splits them three ways.

## Two were fake

Same template as `haec` and `trope-particularity-workbench` (the files
differ only in action-pin style). Companion PRs: hyperpolymath/haec#52,
hyperpolymath/trope-particularity-workbench#45.

| Gate | What it actually did |
|---|---|
| `🔴 Gate: Security policy present` | Failed on secrets **only**. Weak
crypto (MD5/SHA1) and plaintext HTTP printed ⚠️ and passed. |
| `🔴 Gate: Code quality (fmt/lint/test)` | `continue-on-error: true` on
the EditorConfig step. |

## One was mislabelled rather than broken

`🔴 Gate: Static analysis` **cannot fail three separate ways** —
skip-on-missing-tool, `|| true`, and `continue-on-error`.

Retiered to **`🟡 CHECK: Static analysis`** rather than forced blocking.
It wraps `panic-attack`, which has a known false-positive problem
(panic-attack#171) concentrated in tests and benches rather than
shipping code; making that blocking would teach people to route around
gates, which is worse than the fake gate it replaces.

Its header claimed *"Required by branch protection rules."* **It is in
no ruleset** — checked against the API before renaming, because renaming
a genuinely-required context strands it as a phantom that silently
blocks every PR.

While verifying that, I checked all five of this repo's required
contexts against what a real PR actually emits. **All five report** — no
phantoms here. (I had expected the bare `CodeQL` requirement to be one,
since normally only GitHub's default setup emits that string and default
setup is `not-configured` here. It reports because a *job* is literally
named `CodeQL`. Worth recording: that heuristic is a generalisation, not
a rule.)

### The skips are no longer silent

`::notice::` never reaches the checks board, so **an absent scanner
looked identical to a clean scan**. Now `::warning::`, with the step
summary reading **DID NOT RUN** — absence of evidence, not evidence of
absence.

## One was honest but unproven — and hiding a real hole

`🔴 Gate: .well-known / RFC 9116` has genuine `exit 1` paths. Never
failing was clean input, not a broken gate.

Its logic moved to `scripts/check-security-txt.sh`, so the gate and its
canaries run **the same code**. That matters: a canary that
re-implements a gate's logic proves only that it agrees with itself.

Extracting it exposed a defect that had been live the whole time:

```bash
EXPIRES=$(grep "^Expires:" "$SECTXT" | ...)
if date -d "$EXPIRES" > /dev/null 2>&1; then
   ... expiry checks ...
fi          # <-- no else. Unparseable date => every expiry check skipped => PASS
```

If `date -d` could not parse the `Expires:` value, the expiry check was
**skipped entirely and the gate passed**. RFC 9116 requires an ISO 8601
timestamp, so an unreadable one is a violation, not an exemption. Now
rejected, with a canary pinning it so it cannot silently return.

## The proof, not the claim — standards R10

```
PASS no security.txt is rejected
PASS security.txt without Contact is rejected
PASS security.txt without Expires is rejected
PASS an EXPIRED security.txt is rejected
PASS an UNPARSEABLE Expires is rejected (was silently accepted)
PASS a valid security.txt is accepted
PASS this repo's own security.txt passes the gate
PASS weak crypto (md5) is detected
PASS plaintext HTTP is detected
PASS hardcoded secret is detected

gate falsifiability canaries: 10/10
```

Two of the ten are deliberately *not* rejection tests, and both earn
their place: a **valid** security.txt must be accepted (a suite that
always fires proves nothing), and **this repo's own** security.txt must
pass its own gate (a gate is only meaningful if the repo it guards
satisfies it).

The suite runs inside the quality gate, so falsifiability is re-checked
every PR rather than proved once and left to drift.

### The harness had a bug of its own

Worth naming, because it is a general bash trap: `trap … RETURN` inside
a function installs a **global** return trap, not a function-scoped one.
The leftover trap fired on the *next* function's return, where its `$d`
no longer existed, and `set -u` killed the suite. It stayed invisible in
the sibling repos because their canaries are all called from top level;
this suite calls them from a wrapper and died immediately. Replaced with
explicit cleanup, and the fix backported to haec#52 and tpw#45.

## Expect red

These gates passed **because they could not fail**. Some of what they
now catch will be real and long-standing.

Found during the 2026-08-05 estate CI/CD census. Companion standards
PRs: #576 (CI signal discipline) and #577 (R10 canonical wrongness).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants