Skip to content

fix(governance): green the Validate Hypatia Baseline gate on main - #856

Merged
hyperpolymath merged 1 commit into
mainfrom
fix/855-governance-baseline-red
Sep 22, 2026
Merged

hyperpolymath merged 1 commit into
mainfrom
fix/855-governance-baseline-red

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

What this fixes

governance / Validate Hypatia Baseline has been the only real red on
main
— run 35783374301, job 106934294699, 38s. A 38-second job is a real
verdict, not a startup death:

##[error]Gate failed: 5 unfiltered finding(s) at or above 'info'

A standing red on main is itself a detector-blinding condition: while it sits
there, nobody can tell a new gate failure from the old one. That is the
reason to clear it, and the reason not to clear it by widening a suppression.

BLOCKING_THRESHOLD is info and the scanner only emits >= medium, so
"kept" must reach zero. There is no headroom to trade.

The five findings, two clusters, two different causes

1 — three security_errors / secret_detected (critical) at lib/hypatia/scanner_suppression.ex:252

The comment documenting the three @secret_patterns that match on form
alone
spells those three shapes out — and is therefore matched by all three
of the patterns it documents. Same family as the RE005 defect in #834:
documenting a false-positive class creates instances of it.

Cured with a line-scoped inline directive. Deliberately not the
alternatives:

  • Not a baseline entry. The baseline match key is severity AND
    rule_module AND type AND (file exact OR file_pattern glob) — there is
    no line field. An entry would suppress every present and future
    secret finding in this file.
  • Not @training_corpus_paths / @default_exemptions. Those are compiled
    into the scanner, and the gate builds the scanner from a pin (below), so they
    cannot affect this gate at all — and they would blind the scanner core to a
    real leak.

2 — two code_safety (high) on ffi/zig/src/main.zig

zig_ptr_cast and zig_align_cast on the mandatory opaque-handle idiom.
main.zig declares pub const Handle = opaque {}, so the concrete
HandleState is never named in the C header and recovering it requires exactly
@ptrCast(@alignCast(handle)). Already filed as #834.

Cured with two .hypatia-baseline.json acknowledgements carrying
tracking_issue. The baseline is the only mechanism that can reach these:
the rule reports the finding at main.zig:1 rather than the cast site, so no
per-line directive applies, and the only other source-level option is
file-wide.

These two entries are to be DELETED when #834 lands. #834's own
acceptance criteria require main.zig clean without changing the cast.
They are acknowledged rule defects, not accepted debt.

Root cause of cluster 1 — stated, but deliberately not fixed here

The gate builds the scanner from a pinned commit and scans HEAD. They are
different commits.

  • Scanner built from 0e913426 (2026-09-06)
  • Tree scanned: main @ 8e8943f (2026-09-22)

bc8812e — label-aware secret suppression, #782, 2026-09-14 — is our own fix
for this exact false-positive class. Measured both directions:

git merge-base --is-ancestor bc8812e 8e8943f   -> IS an ancestor of main
git merge-base --is-ancestor bc8812e 0e913426  -> is NOT an ancestor of the pin

Corroborated by a grep count of context_safe_line?|@form_ambiguous|label in
lib/hypatia/scanner_suppression.ex: pinned = 4, HEAD = 12.

So the cure is in the repo and absent from the scanner enforcing the gate.
Bumping HYPATIA_PIN in standards' governance-reusable.yml is a required
status check on ~120 caller repos
, so it is filed separately rather than
folded in here. Tracked in #855.

Verification — local reproduction of CI's three steps

Scanner built from the pin 0e913426; apply-baseline.sh at its own
sparse-checkout pin 874ffe58 (not the reusable workflow's da2c748a);
BLOCKING_THRESHOLD=info.

Run rc raw kept suppressed
control (unmodified main) 1 6 5 1
cured (this branch) 0 3 0 3

The control reproduces CI field-for-field: 5 kept at or above info.

Mutants — six, all killed

# Mutant Result
1 ghp_ + 36 chars in lib/hypatia/cli.ex 🔴 kept 1 — detection still live in other files
2 ghp_ + 36 chars elsewhere in this file 🔴 kept 1 — the allow is line-scoped, not file-scoped
3 delete the two zig baseline entries 🔴 kept 2 — those entries do the work
4 delete the inline directive line 🔴 kept 3 — regenerates the original failure exactly
5 revert all 🟢 kept 0
6 drop the Ratchet-exception trailer (same tree) 🔴 exemption ratchet rc=1

Mutant 4 is the strongest: removing one comment line regenerates the original
raw=6 / three-secret-finding state, so the cure and the disease are provably
the same object.

The fixture token was used only inside an isolated tree under
$CLAUDE_JOB_DIR/tmp. No fixture token ever touched this worktree.

Accepted trade-off — stated, not left to be found

A real secret placed on the one directive-covered line would be
suppressed.
This is measured (mutant 2b), not assumed.

That line is a fixed documentation literal listing three regex shapes. Any edit
to it is visible in review, and every other line of this file, and every
other file, still fails the gate
— proven by mutants 1 and 2. The inline
directive is strictly narrower than either alternative: a baseline entry would
cover the whole file forever, and a file_allowed? header directive would
cover the whole file too.

The exemption ratchet

Both ratchet jobs carry if: github.event_name == 'pull_request', so they were
skipped on every push to main and fire here for the first time.
.hypatia-baseline.json is LEDGERS[0], and this PR grows it 45 → 47, so the
commit carries a declared Ratchet-exception: naming that ledger.

Ratchet rules 2 and 3 pass on their own merits rather than by exception: both
new entries carry a note and a tracking_issue (neither is anonymous
debt), and both use an exact file rather than a file_pattern wildcard.
Verified by running standards' actual check-exemption-ratchet.sh:

OK (declared)  .hypatia-baseline.json: 45 -> 47  [Ratchet-exception present]
unchanged      .hypatia-ignore: 1
unchanged      .gitleaks.toml: 4

The debt ratchet is a no-op — no .machine_readable/Debtfile.a2ml at base or
HEAD.

Diff integrity

16	0	.hypatia-baseline.json
10	1	lib/hypatia/scanner_suppression.ex

The baseline change is purely additive and the 45 pre-existing entries are
byte-identical (jq -S both sides + cmp -s → IDENTICAL). The entries were
spliced textually on purpose: rewriting the file through jq silently
re-encodes — escape sequences as literal em dashes in entries it was
never asked to touch, which would have shown up as a spurious deletion.

ffi/zig/src/main.zig is not modified — #834 requires the cast stay as it
is.

Caveat

Local toolchain is Elixir 1.18.3 / OTP 27; CI uses 1.19.4 / OTP 28.3. The
scanner source is the pinned commit in both cases — only the BEAM differs.
The PR run is the authoritative measurement.

Refs #855, #834, #782. Not Closes — #855's acceptance criterion is green on
main
, which only the post-merge run can prove.

🤖 Generated with Claude Code

https://claude.ai/code/session_0113HQM9LVGkNCzU1WwkJZSV

`governance / Validate Hypatia Baseline` has been the only real red on main
(run 35783374301, job 106934294699, 38s -- a real verdict, not a startup
death): "Gate failed: 5 unfiltered finding(s) at or above 'info'". A standing
red on main is itself a detector-blinding condition -- while it sits there
nobody can distinguish a new gate failure from the old one.

Five findings, two clusters, two different causes.

1. Three security_errors/secret_detected (critical) at
   lib/hypatia/scanner_suppression.ex:252. The comment documenting the three
   @secret_patterns that match on form alone spells out the three shapes --
   and is therefore matched by all three of the patterns it documents. Same
   family as the RE005 defect in #834: documenting a false-positive class
   creates instances of it.

   Cured with a line-scoped inline directive. Not a baseline entry: the
   baseline match key has no `line` field, so an entry would suppress every
   present and future secret finding in this file. Not @training_corpus_paths
   either -- that is compiled into the scanner, which the gate builds from a
   pin, so it cannot affect this gate at all, and it would blind the scanner
   core to a real leak.

2. Two code_safety findings (high) on ffi/zig/src/main.zig -- zig_ptr_cast and
   zig_align_cast on the mandatory opaque-handle idiom. Already filed as #834.

   Cured with two .hypatia-baseline.json acknowledgements carrying
   tracking_issue: #834. The baseline is the only
   mechanism that can reach these: the rule reports at main.zig:1 rather than
   the cast site, so no per-line directive applies, and the only other
   source-level option is file-wide. These entries are to be DELETED when #834
   lands -- #834's own acceptance criteria require main.zig clean without
   changing the cast.

Root cause of cluster 1, for the record: the gate builds the scanner from
0e91342 (2026-09-06) and scans HEAD. bc8812e -- label-aware secret
suppression, #782, 2026-09-14 -- is an ancestor of main and is NOT an ancestor
of the pin. Our own cure for this class is in the repo and absent from the
scanner enforcing the gate. Bumping the pin is a required check on ~120 caller
repos, so it is filed separately rather than folded in here.

Verified by local reproduction of CI's three steps (scanner built from the pin,
apply-baseline.sh at its own sparse-checkout pin 874ffe58, BLOCKING_THRESHOLD
info): control 6 raw / 5 kept / 1 suppressed, matching CI field-for-field;
cured 3 raw / 0 kept / 3 suppressed.

Five mutants, all killed:
  - ghp_ + 36 chars in lib/hypatia/cli.ex      -> red (kept 1)
  - ghp_ + 36 chars elsewhere in this file      -> red (kept 1); the allow is
    LINE-scoped, not file-scoped
  - delete the two zig baseline entries         -> red (kept 2)
  - delete the inline directive line            -> red (kept 3), regenerating
    the original failure exactly
  - revert all                                  -> green (kept 0)

Accepted trade-off, stated rather than left to be found: a real secret placed
on the one directive-covered line would be suppressed. That line is a fixed
documentation literal listing three regex shapes; any edit to it is visible in
review, and every other line and file still fails the gate.

The 45 pre-existing baseline entries are byte-identical (16 insertions, 0
deletions) -- the entries were spliced textually. Rewriting the file through jq
silently re-encodes \u2014 escape sequences as literal em dashes in entries it
was never asked to touch, which would have shown up as a spurious deletion.

Ratchet-exception: .hypatia-baseline.json — two acknowledgements for the
opaque-handle false positive tracked in #834. The ledger
grows 45 -> 47 and the exemption ratchet is right to ask. The baseline is the
only mechanism that can reach a finding the rule reports at main.zig:1, and
#834's acceptance criteria require deleting both entries when it lands. Both
carry a note and a tracking_issue, so neither is anonymous debt.

Refs #855, #834, #782

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0113HQM9LVGkNCzU1WwkJZSV
@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: fd11ed2c-2423-497f-b1ab-c38d26fa082f

📥 Commits

Reviewing files that changed from the base of the PR and between 8e8943f and d84e18a.

📒 Files selected for processing (2)
  • .hypatia-baseline.json
  • lib/hypatia/scanner_suppression.ex
 __________________________________________________________
< Solving problems you didn't know you were about to have. >
 ----------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • 🔴 Error committing to branch - (🔄 Check to retry)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

❌ Failed to create Coding Agent finishing-touch task. Please try again.

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.

1 participant