Skip to content

fix(baseline): one acknowledgement can name every module that emits the defect - #971

Merged
hyperpolymath merged 1 commit into
mainfrom
fix/baseline-list-valued-rule-module
Sep 22, 2026
Merged

hyperpolymath merged 1 commit into
mainfrom
fix/baseline-list-valued-rule-module

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Closes #966.

The defect

scripts/apply-baseline.sh matched rule_module by exact string equality,
so one acknowledgement could name exactly one emitting module.

One defect is not always one module. Hypatia raises invalid_actions_lock
from both workflow_audit and workflow_hardening for a single desynced
lockfile. Acknowledging it suppressed one finding and left the other blocking
main.

What made this expensive to diagnose is that the entry looks correct in every
visible respect
— right file, right severity, right type, glob verified — and
the finding is still kept. (The **actions.lock pattern was suspected and
cleared: it tokenises to \A.*actions\.lock\z and matched correctly all along.)

The only available cure was a second near-identical entry: two expires_at
dates and two tracking_issue links for one decision, which then drift.

The change

rule_module accepts a string or a list of strings; a list matches when
any member equals the finding's module.

{
  "severity": "high",
  "rule_module": ["workflow_audit", "workflow_hardening"],
  "type": "invalid_actions_lock",
  "file_pattern": "**actions.lock"
}

The bare string form is unchanged — which is what all 212 existing entries
use, so this is purely additive.

Three sites, not one. rule_module is validated in three places, and a key
merely dropped from a check is unchecked, not permissive:

Site Change
the jq matcher normalise to a list, any() membership
the inline validator in apply-baseline.sh rule_module split out of the all-strings required-key loop, with its own type check and per-member pattern check
.machine_readable/hypatia-baseline.schema.json oneOf string / non-empty unique array

An empty list is rejected: an entry naming no module matches nothing, and a
rule that can never apply reads as coverage while providing none.

Tests

scripts/tests/apply-baseline-test.sh — 26 assertions, up from 15, all pass.
Full suite: All 51 test file(s) passed, no regressions.

Beyond the happy path:

  • non-first member — a .[0]-only implementation passes every assertion whose fixtures happen to order the matching module first
  • over-match control — a list must not become a wildcard
  • single-element list == bare string, both directions
  • bare string still matches / still rejects — the compatibility control for the 212 existing entries
  • validator negatives — malformed member rejected, empty list rejected

The mutant replaces any() with .[0] and reproduces the #966 symptom on
demand
: the two-module case regresses to 1,1 — half-suppressed, entry still
looking correct. Without it, every assertion above would pass identically
against an implementation that ignored the list and matched on
severity+type+file alone.

Deliberately not in this PR

The two live invalid_actions_lock entries in .hypatia-baseline.json are
not collapsed here. They are the workaround this feature removes, so
collapsing them is the obvious next step — but .hypatia-baseline.json is
LEDGERS[0] and the failure mode of getting it wrong is red main plus a
lost acknowledgement
. It belongs in its own small, independently revertible
commit once this is proven green. (The collapse also shrinks the ledger
212 → 211, so it needs no Ratchet-exception trailer.)

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR

`apply-baseline.sh` matched `rule_module` by exact string equality, so an
acknowledgement could name exactly one emitting module.

One defect is not always one module. Hypatia raises `invalid_actions_lock`
from BOTH `workflow_audit` and `workflow_hardening` for a single desynced
lockfile. Acknowledging it suppressed one finding and left the other
blocking `main` — and the entry looked correct in every visible respect:
right file, right severity, right type. The only cure available was a
second near-identical entry, which is two expiry dates and two tracking
issues for one decision.

`rule_module` now accepts a string OR a list of strings; a list matches if
any member equals the finding's module. The bare string form is unchanged,
which is what all 212 existing entries use.

Three sites, because the key is validated in three places and a key that
is merely absent from a check is unchecked, not permissive:
  - the jq matcher
  - the inline validator in apply-baseline.sh (rule_module split out of
    the all-strings required-key loop, with per-member pattern checks)
  - .machine_readable/hypatia-baseline.schema.json

An empty list is rejected: an entry naming no module matches nothing, and
a rule that can never apply reads as coverage while providing none.

Tests: 26 assertions, up from 15. Includes the non-first-member case (a
first-element-only implementation passes every naive ordering), an
over-match control, and both validator negatives. The mutant replaces
any() with .[0] and reproduces the #966 symptom on demand — half
suppressed, entry still looking correct.

The two live invalid_actions_lock entries are deliberately NOT collapsed
here. That is a ledger edit whose failure mode is red main and a lost
acknowledgement; it belongs in its own revertible commit once this is
proven green.

Closes #966

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR
@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 3 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: f4e9256b-dacd-4cc2-b769-43924264c717

📥 Commits

Reviewing files that changed from the base of the PR and between 12d76e8 and 7ff3ae0.

📒 Files selected for processing (4)
  • .machine_readable/hypatia-baseline.schema.json
  • docs/HYPATIA-BASELINE-FORMAT.adoc
  • scripts/apply-baseline.sh
  • scripts/tests/apply-baseline-test.sh

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.

@sonarqubecloud

Copy link
Copy Markdown

@hyperpolymath
hyperpolymath merged commit 8aea869 into main Sep 22, 2026
48 checks passed
@hyperpolymath
hyperpolymath deleted the fix/baseline-list-valued-rule-module branch September 22, 2026 11:27
hyperpolymath added a commit that referenced this pull request Sep 22, 2026
)

Spends the capability #971 added. Closes the loop on #966.

## What

`.hypatia-baseline.json`: **212 → 211** entries. The two
`invalid_actions_lock`
acknowledgements become one entry whose `rule_module` is
`["workflow_audit", "workflow_hardening"]`.

Hypatia emits this **one defect from two rule modules** —
`workflow_audit` /
`parse_actions_lock` (file `actions.lock`) and WH004 standalone
(`workflow_hardening`, full path). Acking it twice meant **two expiry
dates for
one decision**: whichever half expires first silently reopens the gate
while
the ledger still looks correct in every visible respect. That is the
shape
#966 was filed about.

The ratchet is satisfied with **no `Ratchet-exception` trailer** — the
ledger
shrank:

```
SHRANK         .hypatia-baseline.json: 212 -> 211  <- debt paid down
Exemption ratchet: OK.
```

## ⚠ This is a widening, not a refactor — stated, not buried

One entry carries one matcher, and the two emissions carry **different
`file`
values**, so the surviving entry must match by `file_pattern`. The
`workflow_hardening` half therefore moves from the exact path
`.github/workflows/actions.lock` to `**actions.lock` — the breadth the
`workflow_audit` half **already had** at the same severity and type.

Measured, not assumed:

| finding | before | after |
|---|---|---|
| `workflow_hardening` / `invalid_actions_lock` @ `vendor/actions.lock`
| **KEPT** | **SUPPRESSED** |

Accepted because both halves are the same defect with the same exit
criteria.
The entry's `note` records the widening and says to **narrow this
entry** if a
second `actions.lock` is ever vendored, rather than trusting it.

(`**` in a pattern is allowed here: the ratchet's no-wildcard rule 3
binds the
banned-language migration ledger, and explicitly carves out
`.hypatia-baseline.json` "where a note explains them". This entry
carries both
a `note` and a `tracking_issue`, so it is also not anonymous debt under
rule 2.)

## Tests — 26 → 29, reading the shipped ledger

The three new assertions run against the **real**
`.hypatia-baseline.json`, not
a fixture, so the collapse itself is pinned rather than merely
demonstrated.

Two mutants, each killed by the **right** assertion:

| mutant | assertion that reds | result |
|---|---|---|
| split the entry back into two | "one entry naming two modules" | `2,1`
≠ `1,2` |
| swap `file_pattern` for an exact `file` | "suppresses BOTH emission
paths" | **`1,1`** |

The second mutant is the valuable one: `1,1` is exactly the
half-suppression
that reddens main — the #966 symptom reproduced on demand.

An over-match control asserts the ack is **not** a blanket amnesty for
`actions.lock`: a different `type` and a different `rule_module` are
both still
kept (`0,2`).

## Verification

- `52/52` local suites green (`scripts/tests/*.sh`, `tests/*.sh`); no
regressions.
- The collapsed ledger validates against `hypatia-baseline.schema.json`
and
suppresses both real emissions through the real
`scripts/apply-baseline.sh`
  (`suppressed=2 kept=0`).
- `governance-reusable.yml:282` prefers the **caller's own**
`scripts/apply-baseline.sh`, so standards self-lints with the tree under
test — this PR's ledger is filtered by this PR's script, not by main's.
- The ledger edit is `+5/−11` and local: `jq -a --indent 1` was
confirmed to
round-trip the file **byte-identically** before editing, so the diff
shows
  only the two acks (the file stores em-dashes as `—`; without `-a` the
  whole ledger reflows).

Refs #966, #951, #971.

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

https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR

---------

Co-authored-by: Claude Opus 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.

Hypatia baseline: one defect needs one entry per rule_module, silently

1 participant