Skip to content

fix(rules): latent logic bugs #862 shipped behind a compile error (incl. poisoned pin classified arm_auto) - #877

Merged
hyperpolymath merged 2 commits into
mainfrom
fix/pin-integrity-latent-logic
Sep 30, 2026
Merged

hyperpolymath merged 2 commits into
mainfrom
fix/pin-integrity-latent-logic

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Stacked on #876 (the 3-character compile fix, included here as its first commit; it drops out of the diff once #876 lands).

Why these were never caught

PinIntegrity and PrAutomerge never compiled on main (#862), so their 17 tests had never executed. Once #876 lands they run and fail. Nothing outside test/ calls either module, so no live behaviour was wrong; these are fixed before anything wires them in.

Fixes

PinIntegrity

  • claimed_version/1 — Regex.run drops trailing unmatched groups; a v-led match returned one element that neither clause matched → nil. This cascaded into PI002 (mislabel) and PI005 (stale) never firing.
  • relabel_line/2 produced ## (prefixed # onto relabel/2's already-#-led result). Now mirrors estate-pin-integrity.sh exactly: head without #, comment handed over with it.
  • relabel/2 restores # for the #-stripped shape pin_sites/1 emits (v3). #v3 and "" keep the shell mirror's byte-identical output.

PrAutomerge

  • pin_deltas/3 returned a bare map from a flat_map callback → every delta flattened into {key, value} tuples.
  • verdict/3 discarded the scan → decisions lacked deltas / licence_touch / pin_only.
  • ⚠ Safety: close_poison_only went through accept/4, so a pin-only PR onto a denylisted pin was classified safety: "arm_auto". Now reject/4. Mutant control: reverting this line alone turns exactly a pin-only change onto the denylist is closed, not merged red.
  • decision_manifest/2 vetoes now use string keys, like the rest of the schema-shaped manifest.

One test fixture changed — please review this one. a patch that also edits a permissions block is not pin-only carried its permissions lines as diff context (leading space) under a mismatched @@ -1,4 +1,4 @@, so it asserted an edit the diff did not contain. The code was correct; the fixture now makes the edit real (-/+ lines, @@ -1,2 +1,3 @@). The test still asserts refute pin_lines_only?.

Verified

🤖 Generated with Claude Code

https://claude.ai/code/session_0136eszqrQ53Kj7aBH1D4rXK

hyperpolymath and others added 2 commits September 30, 2026 10:46
#862 put an unescaped `/` inside a character class in three `~r/.../`
sigils (pin_integrity.ex @uses_regex and locked_refs/1, pr_automerge.ex
@pin_re). In a `~r/` sigil that `/` terminates the sigil, so
`mix compile` fails with MismatchedDelimiterError. Every estate Hypatia
scan (hypatia-scan-reusable.yml resolves hypatia@main) has failed since.

`\/` inside a character class matches the same byte set as `/`; this
change is semantics-preserving and only restores compilation.

The two modules' own tests (17) now run for the first time and fail on
latent logic bugs in #862; nothing outside test/ calls either module.
Those are fixed in a follow-up PR, kept separate so this outage fix
stays three characters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136eszqrQ53Kj7aBH1D4rXK
PinIntegrity and PrAutomerge never compiled on main, so their 17 tests
had never run. With #876 they run; this fixes what they found.

PinIntegrity
- claimed_version/1: Regex.run drops trailing unmatched groups, so a
  `v`-led match returned a 1-element list neither clause matched -> nil.
  That cascaded into pi002 and pi005 never firing.
- relabel_line/2 prepended `#` to relabel/2's already-`#`-led result
  (`##`). Now mirrors estate-pin-integrity.sh: head without `#`, comment
  handed over with it.
- relabel/2 restores `# ` for the `#`-stripped comment pin_sites/1
  emits; `#v3` and "" keep the shell mirror's byte-identical output.

PrAutomerge
- pin_deltas/3 returned a bare map from a flat_map callback, so every
  delta was flattened into {key, value} tuples.
- verdict/3 dropped the scan, so decisions lacked deltas/licence_touch.
- SAFETY: close_poison_only went through accept/4, i.e. a pin-only PR
  onto a DENYLISTED pin was classified safety=arm_auto. Now reject/4.
  Mutant control: reverting this alone turns exactly that test red.
- decision_manifest/2 vetoes use string keys like the rest of the
  manifest (the schema-shaped output).

Test fixture: "a patch that also edits a permissions block" carried
the permissions lines as diff CONTEXT (leading space) under a mismatched
hunk header, so it asserted an edit the diff did not contain. The code
was right; the fixture now makes the edit real (-/+ lines).

mix test: 1673 tests, 0 failures. mix compile --warnings-as-errors: clean.

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

coderabbitai Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 47 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: 0eeb3bee-a1f1-4eff-b97d-42e38e4af1ca

📥 Commits

Reviewing files that changed from the base of the PR and between cc75fa5 and e6419d4.

📒 Files selected for processing (3)
  • lib/rules/pin_integrity.ex
  • lib/rules/pr_automerge.ex
  • test/rules/pr_automerge_test.exs

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.

@hyperpolymath
hyperpolymath enabled auto-merge (squash) September 30, 2026 09:50
@hyperpolymath
hyperpolymath merged commit d369a80 into main Sep 30, 2026
37 of 45 checks passed
@hyperpolymath
hyperpolymath deleted the fix/pin-integrity-latent-logic branch September 30, 2026 10:00
hyperpolymath added a commit that referenced this pull request Sep 30, 2026
… red since #820) (#880)

## What

The required check `abi-codegen-drift` has failed on every uncached run
since #820. Main was red on 09-27, 09-28 and 09-29, and #876/#877 are
both stuck on it. The cause:

```
/home/runner/work/_temp/….sh: line 10: idris2: command not found
##[error]Process completed with exit code 127.
```

#820 moved `make install` to `PREFIX="$HOME/.idris2"`, but the same step
still ends with a bare `idris2 --version`. `$HOME/.idris2/bin` only
joins `$GITHUB_PATH` in the **next** step. The fix calls the binary by
its full path. `verify-proofs.yml` has the identical line and gets the
same one-line fix.

## Why it matters

`abi-codegen-drift` is one of main's two required contexts, so while it
is red no hypatia PR can merge. That includes #876, which restores
compilation after #862. Every Hypatia scan in the estate is failing
until #876 lands.

## Verification

This PR's own `abi-codegen-drift` run is the control. It either gets
past `Build Idris 2 from source` or it doesn't.

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

https://claude.ai/code/session_0136eszqrQ53Kj7aBH1D4rXK

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Sep 30, 2026
…us + inline directives honoured (#881)

> **Stacked on #877** (itself on #876). The base retargets to `main`
once #877 lands. Squash automerge will be armed then, not before: armed
now, it would merge into #877's branch.

## Why

A Hypatia `hardcoded_tmp` alert on launch-scaffolder#46 exposed a real
CWE-377 defect in the launcher generator. That was fixed upstream in
launch-scaffolder #54/#58/#62. The rule itself was part of the problem:

| Input | Before | After |
|---|---|---|
| `PID_FILE="/tmp/x.pid"` | fires | **fires** (positive control) |
| `d=$(mktemp -d /tmp/foo.XXXXXX)`, the rule's own advice | **fires** |
clean |
| `# comment mentioning "/tmp/"` | **fires** | clean |
| `/tmp` line under `tests/fixtures/`, `test/`, `lib/rules/`,
`scripts/fix-scripts/` | **fires** | suppressed |
| `# hypatia: allow content_patterns/hardcoded_tmp -- …` | **silently
ignored** | honoured |

## Changes

- **Remediation text.** It now separates pid/state files, which the next
run must re-find (per-user
`${XDG_RUNTIME_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}}/<app>/`),
from scratch files (`mktemp -d` with no `/tmp` template, plus `trap …
EXIT`). The old text said "use mktemp", which is the wrong cure for a
pid file.
- **Own-cure skip.** `skip_comment_lines: true`, plus a new generic rule
key `skip_if_line_matches` (here `~r/\bmktemp\b/`).
- **Training-corpus exemption.** Added under `"content_patterns"`, the
key the findings are **emitted** under in `cli.ex`. A `"cicd_rules"` key
would be vacuous.
- **Inline directives.** `hypatia: allow <module>/<rule>` is now
honoured by the content engine under either module spelling. Rule ids
are atoms, so they are stringified before `inline_allowed?/4`.
- **Line-1 fix.** Line 1 no longer treats the file's **last** line as
its "previous line" (`Enum.at(lines, -1)` wraps around).
- **Docs.** The `.hypatia-ignore` header and `.claude/CLAUDE.md` now
document the two-spelling trap and the directive behaviour.

## Not changed, deliberately

- `applies_to` stays `["*.sh"]`. Widening it to `*.rs` would flag
`#[cfg(test)]` literals under `src/`, and cicd_rules has no Rust comment
stripping.
- The file-level `hypatia: allow` form is still not wired for content
patterns. CLAUDE.md now says so.

## Trade-off, stated plainly

After this PR, fixtures under `tests/` are no longer scanned for this
rule. On the launch-scaffolder side, the generator's own literal-pinned
tests (`DEFAULT_PID_LINE`) are now the detector for a `/tmp` regression.

## Verification

- `test/hardcoded_tmp_pipeline_test.exs` has 16 tests running the
**real** pipeline (`collect_findings` → normalise → suppress), never a
hand-built finding.
- Four mutants were each killed:

  | Mutant | Red tests |
  |---|---|
  | Exemption key renamed | 4 |
  | mktemp skip removed | 1 |
  | `n > 1` guard reverted | 1 |
  | `inline_allowed?` clause removed | 3 |

- Full suite: 1689 tests, 0 failures. `mix compile --warnings-as-errors
--force` is clean.

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

https://claude.ai/code/session_0136eszqrQ53Kj7aBH1D4rXK

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Sep 30, 2026
…in (#882)

`main` fails `mix compile --force --warnings-as-errors` (the mode used
by `tests.yml:109` and `escript-soundness.yml:141`):

```
warning: variable "new" is unused
 248 │   defp delta(old, new, from, to, status, source) do
```

**Why it is on main:** #877 was squash-merged at 2026-09-30T10:00Z with
commits `09c6b16` + `e6419d4` only. This fix (`76b8c89`) was pushed to
the same branch but did not make the squash, so `main` (d369a80) carries
the warning.

**Change:** `new` → `_new`. One token; no behaviour change.

**Verified:** forced strict compile on this head → rc=0; on
`origin/main` → rc=1.

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

https://claude.ai/code/session_0136eszqrQ53Kj7aBH1D4rXK

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

1 participant