Skip to content

fix(main-red-round2): asana restore+ack, fail-closed validator, self-lint baseline, debt re-baseline - #954

Merged
hyperpolymath merged 2 commits into
mainfrom
fix/main-red-round2
Sep 22, 2026
Merged

hyperpolymath merged 2 commits into
mainfrom
fix/main-red-round2

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Round 2 of the post-#899 fix-forward. #947 merged (7b05a32) but CI on its merge ref exposed 7 failures; this PR fixes the six that are code (the seventh, Scorecard Token-Permissions on propagate-hooks, needs an owner dismiss — the contents: write is required for the self-push to hyperpolymath/standards).

What broke on #947 and why

  1. uses-lock: fix(main-red): repair the post-#899 red main (code + baseline triage + regen) #947 deleted asana/push-signed-commits from actions.lock to silence Hypatia's transitive finding — but .github/actions/signed-push/action.yml:42 really uses it (the ref entered via fix(actions-lock): stop advisory findings blocking the lock wrapper #946's line, invisible on the PR branch alone). Deleting true lock metadata to satisfy a scanner is gaming; restored verbatim, finding acked honestly instead.
  2. Hypatia gate (latent): restoring the metadata re-arms Hypatia's transitive_dependencies_missing (asana floats setup-python@v2 upstream — verified still floating on asana main 2026-09-22). Acked in the baseline (210 -> 211, hypatia invalid_actions_lock: asana/push-signed-commits floats setup-python@v2 transitive #951), proven to match with positive + negative controls.
  3. Repo self-tests: my validator change (empty scan -> valid) fixed the hypatia-gate suite but broke science-ci, which pins empty -> exit 2. The fix(ci): reject malformed Hypatia reports and support blocking high findings #741 control tested the pre-fix: CI/CD hooks follow-up fixes #771 slurp accident; fail-closed is the documented intent (comment predates fix: CI/CD hooks follow-up fixes #771, both suites now agree). Reverted validator, fixed the stale control.
  4. Validate Hypatia Baseline: governance-reusable.yml validated the new baseline with main's OLD apply-baseline.sh (no warn severity) — exit 2 on a valid file. Self-lint preference: caller's own script when present, main-pinned fallback for consumers.
  5. Debt ratchet: three breaches. deno-residue counted retirement-doc comments as residue — probe refined to non-comment matches (0, ceiling holds at 1). gate-scripts + todo-fixme counts had fossilized (runner only ratchets down; chore(debt): re-measure Debtfile probes #820's own tree already measured 38/79) — re-baselined to measured 40/40 + 80/80 with per-entry declarations (debt paydown: gate-scripts-without-tests re-baselined 30->40, todo-fixme 76->80 #953).
  6. Exemption ratchet: baseline 129 -> 210 grew without a trailer on fix(main-red): repair the post-#899 red main (code + baseline triage + regen) #947. This PR's 210 -> 211 carries Ratchet-exception: .hypatia-baseline.json.

Verified locally (tip worktree)

gh verify valid:true, uses-lock 0 exceptions, both security suites green (24 gate PASSes), debt structure + run + both ratchets green, registry in sync, baseline ack matches both file variants with a failing negative control.

Fixes the six code failures; closes #951 and #953 as implemented (paydown continues in #953).

…-lint baseline step

- actions.lock: restore asana/push-signed-commits (usage + metadata,
  verbatim) — #947 deleted it to silence Hypatia, but the composite
  action .github/actions/signed-push really uses it (uses-lock failed).
  The transitive setup-python@v2 finding is acked honestly in the
  baseline instead (next commit).
- hypatia-scan-reusable.yml: revert the validator to fail-closed
  (length > 0); an empty scan is a scanner error, never a clean scan.
  The #741 control tested the pre-#771 slurp accident; science-ci pins
  empty-exit-2 too.
- scripts/tests/hypatia-blocking-gate-test.sh: 'empty findings are
  valid' becomes 'empty findings refuse (fail-closed)', expect 2.
- governance-reusable.yml: baseline-validation step prefers the
  caller's own scripts/apply-baseline.sh (self-lint); main-pinned
  fallback for consumers. Fixes the stale-script trap where main's
  old script rejected new severities (warn).
- .githooks/validate-actions-lock.sh: remove the stale deno
  EXPECTED_ABSENT exception (last consumer gone); guard the loops for
  the empty array (bash 3.2 + set -u safe).
- .hypatia-baseline.json: 210 -> 211. Ack workflow_audit/
  invalid_actions_lock (**actions.lock): asana's setup-python@v2
  transitive floats upstream; gh verify says valid:true, Hypatia is
  stricter. Tracked in #951.
- Debtfile: deno-residue probe counts non-comment matches only (3
  retirement-doc comments are not residue); count 2 -> 0, ceiling
  holds at 1.
- Debtfile: re-baseline fossilized counts to measured (#953):
  gate-scripts-without-tests 31/30 -> 40/40, todo-fixme-markers
  77/76 -> 80/80. The runner only ratchets down; #820's own tree
  already measured 38/79.

Ratchet-exception: .hypatia-baseline.json — one honest ack for the asana float-transitive (see #951); the growth is the ack, not new debt.
Debt-exception: gate-scripts-without-tests — re-baselining fossilized 31/30 to measured 40/40 (#953); falls as tests land.
Debt-exception: todo-fixme-markers — re-baselining fossilized 77/76 to measured 80/80 (#953); advisory markers only.
@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: 3bf4ff7d-44a0-412b-b184-ae9d23bfe2bd

📥 Commits

Reviewing files that changed from the base of the PR and between be6c958 and e60e3ad.

⛔ Files ignored due to path filters (1)
  • .github/workflows/actions.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • .githooks/validate-actions-lock.sh
  • .github/workflows/governance-reusable.yml
  • .github/workflows/hypatia-scan-reusable.yml
  • .hypatia-baseline.json
  • .machine_readable/Debtfile.a2ml
  • scripts/tests/hypatia-blocking-gate-test.sh
 ______________________________________________________________
< How would you like your code review? Scrambled or over-easy? >
 --------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • 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.

@sonarqubecloud

Copy link
Copy Markdown

@hyperpolymath
hyperpolymath merged commit 9c256b6 into main Sep 22, 2026
44 of 48 checks passed
@hyperpolymath
hyperpolymath deleted the fix/main-red-round2 branch September 22, 2026 10:15
hyperpolymath added a commit that referenced this pull request Sep 22, 2026
Round 3. #954 merged as 9c256b6 (with its 3 known failures); this PR
carries the two fixes that landed after the merge, rebased onto
main+9c256b67 (+#948, +#952):

1. **Standards map integrity** (`www` unmapped since #851, `SECURITY.md`
unmapped since the #947 squash, `.well-known` entry stale since #851):
replace with `www/` + `SECURITY.md` entries, entry_count 122 -> 123. All
5 assertions pass locally.
2. **Both Hypatia gates**: the real scanner emits `invalid_actions_lock`
TWICE (verified by building Hypatia locally and scanning this tree):
`workflow_audit` (acked in round 2) and WH004 standalone (`rule_module
workflow_hardening`, full path — acked here, 211 -> 212, same #951 root
cause).

Also verified by local full-fidelity scan (194 findings, token-enabled):
the only unacknowledged findings are CI-invisible (git-state dirty-tree
+ code-scanning-alert echoes that need a live PAT — see #957 for the
PAT-refresh tripwire). #948/#952 introduce zero new findings.

Local battery: debt run 0 breached, map rc=0, uses-lock clean, registry
in sync, both ratchets OK, gate keeps nothing CI-visible.

Co-authored-by: hyperpolymath <hyperpolymath@users.noreply.github.com>
hyperpolymath added a commit that referenced this pull request Sep 22, 2026
…it past #946 (#962)

## What this fixes

The lock gate is staged from a **third pin**.

There are not two pins in this system, there are three:

1. the caller's `uses: hyperpolymath/standards/...@<sha>` ref,
2. `actions.lock`'s record of that ref, and
3. **a SHA hardcoded inside `governance-reusable.yml`** for its own
   `actions/checkout` of the lock-gate tooling.

Bumping a caller cannot reach the third one. A *called* reusable
workflow has no
reliable context exposing its own commit (`github.workflow_sha` resolves
to the
**caller's**), which is what forces the hardcode in the first place.

`standards#946` fixed `scripts/update-actions-lock.sh` so advisory
findings stop
counting toward the blocking tally — and did not bump pin 3. So every
caller
kept being judged by the **pre-#946** verifier, including

[`metadatastician/burble#226`](metadatastician/burble#226),
which had bumped its own pin *specifically to pick that fix up* and
still went
red on `governance / Actions lockfile verify`.

Measured, not inferred: the failing job logs `HEAD is now at 4f7f02c`,
and the
two wrappers disagree on the same tree —

| wrapper staged from | `--verify-local` rc | `grep -c
is_advisory_category` |
|---|---|---|
| `4f7f02ca` (what CI ran) | **1** | 0 |
| `e977cc67` (post-#946) | **0** | present |

## Why no existing control caught it

The pin's **shape** was already guarded, correctly:
`tests/test_governance_reusable_shape.sh:63-64` asserts `ref:
[0-9a-f]{40}` and
refuses `ref: main`, scoped to the `actions-lock-verify` job.

Its **currency** was guarded by nothing. A perfectly well-formed 40-hex
SHA can
point at stale tooling, and this one did for the whole life of #946.

That is the guard/consumer trap in its plainest form: **the guard asks
"is this
40 hex characters?", the consumer needs "does this contain today's
verifier?"**

The file already carried a `⚠ BUMP THIS whenever ... changes` comment. A
comment
is not a gate, and this PR is the difference.

Separately, `scripts/tests/governance-reusable-contract-test.sh` bound
its
checkout assertions only to the step named `Checkout the pinned
Standards policy
helpers` — the **dupkey** step. The lock gate is a *different* step,
`Checkout standards for the lock gate`, and the two share the nouns
"checkout",
"pinned" and "standards", so a name-match guard written for one proves
nothing
about the other. It now names the lock-gate step too.

## The predicate, and why it is not the obvious one

The obvious assertion — *the pin contains the working tree's helpers* —
**deadlocks**. A PR that edits a helper would have to pin to its own
merge
commit, which does not exist yet. Unsatisfiable-in-PR is the same
failure class
as a required check that can never report.

So the assertion is:

> the pinned commit must already contain everything on the **compare**
ref,
> path-scoped to the step's own `sparse-checkout:` list.

- **`pull_request`** → compare is the PR's base SHA. A PR that edits a
helper
**passes** (its edit is not on base yet). A PR opened while `main` is
*already*
stale is **forced to bump**, and can, because the needed commit exists.
- **`push` to `main`** → compare is `HEAD`. Red exactly when a helper
change has
just landed and the bump is owed; healed by the very next PR, which the
  `pull_request` run will not let through unbumped.

Under this predicate, **#954 would have been forced to bump after #946
landed**,
and burble#226 would have gone green on its first attempt.

The pin is therefore **one change behind by construction**. That is
inherent, it
is acceptable, and the comment at the pin now says so rather than asking
a human
to remember.

Comparison is **path-scoped**, so a rebase or any unrelated commit
cannot fail
it — only a real divergence in the staged tooling can.

**Scope is read out of the step's own `sparse-checkout:` list, never
hardcoded**,
so adding a file to what the gate stages automatically extends what the
guard
protects. A hardcoded list here would itself be a guard asking a
different
question than its consumer.

## Verification

`scripts/tests/check-lock-gate-pin-freshness-test.sh` — **10 controls**,
each
against a throwaway git repo with real commits, fully offline:

| control | asserts |
|---|---|
| stale pin is refused | rc=1, names `scripts/update-actions-lock.sh` |
| stale report is path-scoped | never names the unrelated file that also
changed |
| fresh pin is accepted | rc=0 |
| unrelated divergence does not fail it | a rebase must not redden the
gate |
| `ref: main` is refused | pinning is the point |
| abbreviated sha is refused | 40-hex only |
| **renamed step fails loudly** | rc=1 — the exact way the contract test
lost its subject |
| **unresolvable pin fails, not skips** | a skip is indistinguishable
from a pass |
| missing `ref:` is refused | would follow the default branch |
| empty staged scope is refused | nothing to compare is not a free pass
|

**Meta-mutant.** Removing the path scoping from the guard
(`git diff --name-only $pin $compare -- $paths` → without `-- $paths`)
kills
**exactly the two controls that assert it**, 8 passed / 2 failed.
Restored, 10/10.

**Contract-test mutants.** `ref: main` → `FAIL: the lock gate is not
staged from
an immutable 40-hex commit`. Renaming the step → `FAIL: governance
workflow has
no step named 'Checkout standards for the lock gate'`. Both rc=1.

Full suite: **all 51 test files pass** on this branch.

The guard **fails the job**. It is not `continue-on-error` and it is not
a
`::warning::`, which cannot fail a job.

## Notes

- `self-test.yml` gains `fetch-depth: 0`. The guard compares two commits
and
**fails rather than skips** on an unresolvable pin, so the history is a
requirement, not an optimisation. This is a `with:` change only — no
`uses:`
  ref moves, so `actions.lock` is untouched.
- The new Self Test step passes the base SHA through `env:`, not by
interpolating an expression into the `run:` body. The repo's own
injection
  scanner (`tests/test_tag_ruleset_canon.sh`) still passes.
- `grep -A N` cannot delimit the step block: it is 19 lines today, so
any fixed
`N` is either short of the `ref:` or long enough to capture the **next**
step's
`ref:` and assert against the wrong pin. Both the guard and the contract
test
  take the range from `- name:` to `- name:` with awk.

## ⚠ This does not turn burble#226 green on its own

`metadatastician/burble#226` pins standards at `e977cc67`, and **that**
copy of
`governance-reusable.yml` still carries `4f7f02ca` at the lock-gate
step. Pin 3
travels with the pinned YAML.

Sequence: **merge this → take the resulting SHA → re-bump burble#226 to
it**
(all 9 sites plus `actions.lock`, transitive `uses:` list re-extracted
against a
positive control) → then #226 can go green.

## Out of scope, filed separately

The **dupkey** pin `317101e0` is also stale — 45 files differ under
`scripts/`
versus `main`. Its `sparse-checkout` is the whole `scripts` directory,
so the
same predicate applied verbatim would be permanently red and useless; it
needs a
scope narrowed to what that step actually executes. Per the stopping
rule that is
an issue with acceptance criteria, not scope for this PR.

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

https://claude.ai/code/session_01X3hgXxWm6umMgZkjYyHnnm

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Sep 22, 2026
…blocks red main) (#965)

## `main` is red, and round 2 only half-landed

`main` has been red on **both** Hypatia checks since #954 (`9c256b67`) —
`scan / Hypatia Neurosymbolic Analysis` and `governance / Validate
Hypatia Baseline`. Every PR opened since inherits it, including #961.

The cause is not the lockfile. It is that **one defect is emitted by two
rule modules, and round 2 acknowledged only one of them.**

| | acknowledged in #954 | still blocking |
|---|---|---|
| `rule_module` | `workflow_audit` | **`workflow_hardening`** |
| `file` | `actions.lock` | `.github/workflows/actions.lock` |
| `type` | `invalid_actions_lock` | `invalid_actions_lock` |
| `severity` | `high` | `high` |
| `reason` | transitive_dependencies_missing,
asana/push-signed-commits@d615 | *identical* |

`scripts/apply-baseline.sh:188` matches by **exact string equality**:

```jq
.severity   == $finding.severity
and .rule_module == $finding.rule_module      # <- exact, so workflow_audit != workflow_hardening
and .type   == $finding.type
```

The `file_pattern` was never the problem. `**actions.lock` tokenises to
`\A.*actions\.lock\z`, which matches the full path correctly.

## Evidence

Reproduced locally against **main's own committed** `apply-baseline.sh`,
schema and baseline, using the finding copied verbatim from the failing
run
[35716729172](https://github.com/hyperpolymath/standards/actions/runs/35716729172):

```
before:  kept=1  suppressed=0   ::error::Gate failed: 1 unfiltered finding(s) at or above 'high'.   exit=1
after:   kept=0  suppressed=1                                                                       exit=0
```

The `before` line is byte-identical to what CI printed.

**Four negative controls** confirm the entry suppresses only this exact
finding — all four stay `KEPT`:

| control | result |
|---|---|
| same finding at `critical` | KEPT |
| same finding, `rule_module: some_other_module` | KEPT |
| `type: unpinned_action` on the same file | KEPT |
| same finding on `.github/workflows/release.yml` | KEPT |

**Suites, all green on this branch:** `apply-baseline-test.sh` 15/0 ·
`filter-sarif-by-baseline-test.sh` 13/0 ·
`hypatia-blocking-gate-test.sh` 4/4. Full pre-commit hookset passes.

**Exemption ratchet, both directions:**

```
with the trailer:     OK (declared)  .hypatia-baseline.json: 211 -> 212  [Ratchet-exception present]   exit=0
without the trailer:  Exemption ratchet: FAILED.                                                       exit=1
```

The mutant was a throwaway branch, asserted and deleted — the gate has
teeth here, it is not being taken on trust.

## What this does *not* do

It does **not** accept new exposure. The underlying risk is unchanged
and still owned by #951: `asana/push-signed-commits@d615` (immutable
pin, ref `v1.3`) declares a transitive `actions/setup-python@v2`, which
floats upstream and can never be a lock key. `gh-actions-lock` v0.1.6
`verify` reports `valid:true` on the same file — Hypatia is stricter
than the authoritative tool. The exits remain exactly #951's: Asana pins
`setup-python`, we replace the action, or Hypatia downgrades
float-transitives.

The diff is **+9 / −0**, a pure append. No existing entry is touched.

## Follow-up worth considering (not in this PR)

One triage decision now needs **two** baseline entries that differ in a
single field, and nothing warns you when you write only one — which is
precisely how `main` went red. Letting `rule_module` accept a list would
make one decision one entry. That is a schema + `apply-baseline.sh` +
test change, so it is deliberately out of scope here; filing separately.

---

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

https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR

Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
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 invalid_actions_lock: asana/push-signed-commits floats setup-python@v2 transitive

1 participant