Skip to content

test(actions-lock): closure contract corpus shared with standards - #941

Merged
hyperpolymath merged 2 commits into
mainfrom
claude/actions-lock-closure-corpus
Oct 20, 2026
Merged

hyperpolymath merged 2 commits into
mainfrom
claude/actions-lock-closure-corpus

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Summary

This implements the owner's decision on hyperpolymath/standards#963, criterion 5:

  • Hypatia stays the authority for actions.lock closure.
  • standards adds a deterministic local guard, close-lock.sh --check, so the same defect is caught before push.
  • A shared corpus of test lockfiles binds the two implementations. This PR is Hypatia's half of that contract; the standards half follows in a separate PR.

A lock is closed when every ref under workflows: and in a record's nested uses: has a top-level record of its own, comparing owner/repo case-insensitively and refs exactly. This is the workflow_dependencies_missing / transitive_dependencies_missing check in ActionsLock.

Changes

Path What
test/fixtures/actions_lock/closure/ Byte-identical copy of standards' scripts/tests/fixtures/actions-lock-closure/ (checked with sha256 and diff -r): five locks plus expected.tsv. Each file carries its own SPDX header, so the existing .reuse/dep5 entry for verbatim gh actions-lock output is left alone.
test/rules/actions_lock_closure_corpus_test.exs Reads expected.tsv at compile time (@external_resource). Checks there is exactly one row per lock, closed locks parse {:ok, _}, and each dangling lock fails with the expected kind and exactly the listed refs, both compared through normalise_ref/1.
Lock Expected ActionsLock.parse/1 at aa34a4cc
closed-v0.0.2.lock closed {:ok, _}
closed-v0.0.3.lock (with hostname: 'github.com') closed {:ok, _}
closed-case-variant.lock (Example/Rust-Cache@… vs record example/rust-cache@…) closed {:ok, _}
dangling-nested.lock (the standards#963 shape) dangling example/setup-python@v2 transitive_dependencies_missing
dangling-workflow-ref.lock dangling example/generator@4444… workflow_dependencies_missing

lib/ is unchanged. The parser already agrees on all five rows. The case-variant row agrees because normalise_ref/1 lowercases owner/repo as each ref is read, which is the same rule standards' check applies.

Testing

hex.pm is unreachable from this session, so mix test cannot fetch deps. I ran the parser and test files standalone with Elixir 1.14 / OTP 25 (compiled lib/rules/actions_lock.ex, then ran the test files under ExUnit):

Check Result
actions_lock_closure_corpus_test.exs 6 tests, 0 failures
actions_lock_test.exs (unchanged) 23 tests, 1 failure: the legacy Hypatia.Rules case, which needs the full engine. Same on main.
Mutant: validate_transitive_dependencies stubbed to :ok fails exactly dangling-nested.lock
Code.format_string!/1 on the new file no change

CI's full mix test is the authoritative run.

Keeping the copies in step

The moduledoc says to change both copies together. If either side edits its copy alone, its own suite still passes against its own table, so drift becomes visible only when the other side is re-synced or re-run against it. A cross-repo checksum gate would need network access in one of the two CIs, so I left it out. It can be added if you want the stronger guarantee.


Generated by Claude Code

Owner decision on standards#963, criterion 5: Hypatia is the authority
for actions.lock closure. standards adds `close-lock.sh --check` as a
local pre-push guard, and a shared corpus binds the two implementations.

test/fixtures/actions_lock/closure/ is a byte-identical copy of
standards' scripts/tests/fixtures/actions-lock-closure/. It holds five
locks and expected.tsv:
- closed v0.0.2;
- closed v0.0.3 with hostname;
- closed, with owner/repo spelled in a different case from its record;
- dangling, with a record's nested uses: naming a ref that has no record
  (the standards#963 shape);
- dangling, with a workflows: ref that has no record.

The new test reads expected.tsv at compile time (@external_resource).
It checks that there is one row per lock, that ActionsLock.parse/1
returns {:ok, _} for each closed lock, and that a dangling lock fails as
workflow_dependencies_missing or transitive_dependencies_missing with
exactly the listed refs, both sides compared through normalise_ref/1.

The parser is unchanged. At aa34a4c it already agrees on all five rows.

Verification: Elixir 1.14 / OTP 25, running the parser and the test
files standalone because hex.pm is unreachable from this session.
- This file passes 6/6.
- actions_lock_test.exs is unchanged, at 23 tests with 1 failure. The
  failure is the legacy Hypatia.Rules case, which needs the full engine,
  exactly as on main.
- With validate_transitive_dependencies stubbed to :ok, the new test
  fails exactly the dangling-nested row.
- Code.format_string!/1 leaves the file unchanged.

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

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next included review available in 53 minutes.

Check out review usage here.

View limit details

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 177c7c8f-2ccc-4698-a1b5-722b2f359266
📥 Commits

Reviewing files that changed from the base of the PR and between 61023d1 and fa6cf92.

⛔ Files ignored due to path filters: 6
  • test/fixtures/actions_lock/closure/closed-case-variant.lock is excluded by !**/*.lock
  • test/fixtures/actions_lock/closure/closed-v0.0.2.lock is excluded by !**/*.lock
  • test/fixtures/actions_lock/closure/closed-v0.0.3.lock is excluded by !**/*.lock
  • test/fixtures/actions_lock/closure/dangling-nested.lock is excluded by !**/*.lock
  • test/fixtures/actions_lock/closure/dangling-workflow-ref.lock is excluded by !**/*.lock
  • test/fixtures/actions_lock/closure/expected.tsv is excluded by !**/*.tsv
📒 Files selected for processing: 1
  • test/rules/actions_lock_closure_corpus_test.exs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T12:39:53.107728Z fa6cf92 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@hyperpolymath
hyperpolymath enabled auto-merge (squash) October 20, 2026 12:38

Copy link
Copy Markdown
Owner Author

governance / Validate Hypatia Baseline failed on 4d62172 (job 118630784045). The failure does not come from this PR.

I'll watch the run on fa6cf92. If it reports any finding that does not come from the files listed above, I'll treat it as this PR's.


Generated by Claude Code

hyperpolymath added a commit to hyperpolymath/standards that referenced this pull request Oct 20, 2026
Implements the recommendation for #963 criterion 5, as the owner
decided:

- **Hypatia stays the authority** for `actions.lock` closure.
- A deterministic local check guards the same property before push.
- A shared corpus of test lockfiles binds the two implementations. The
Hypatia half is hyperpolymath/hypatia#941.

## What changes

**`scripts/close-lock.sh --check [DIR | LOCKFILE]`**
- Reports the dangling edges the repair passes would close, and nothing
else: no `gh` call, no write.
- An edge dangles when a ref under `workflows:` or in a record's nested
`uses:` has no top-level record of its own.
- owner/repo compare case-insensitively and refs compare exactly. That
is the same normalisation as GitHub's parser, #1294/#1314, and Hypatia's
`ActionsLock.normalise_ref/1`.
- Exit codes: 0 closed, 1 dangling, 2 no lock.

**`scripts/lock-selfcheck.sh`**
- A dangling edge becomes a third POISON verdict, after unkeyed refs and
self-SHA refs.
- It runs inside the existing "Lockfile self-consistency" job, so no
required-check name changes.

**`.githooks/pre-commit`**
- When `actions.lock` is staged, the hook checks the staged content.
- Like the canonical-names guard, it runs only where the repository
carries `scripts/close-lock.sh`.

**`scripts/tests/fixtures/actions-lock-closure/`**: five locks plus
`expected.tsv`:
- closed v0.0.2
- closed v0.0.3 with `hostname`
- closed case-variant
- the #963 nested shape
- a dangling `workflows:` ref

Hypatia at `aa34a4cc` agrees on all five. hyperpolymath/hypatia#941
vendors a byte-identical copy and tests `ActionsLock.parse/1` against
the same table. If either side drifts, one of the two suites goes red.

## Tests

- **`close-lock-test`: 34/34, 8 of them new.**
- Each corpus row is checked by exit code and by the exact dangling
list.
- The lock bytes must be unchanged, `gh` must not be called, and a
missing lock exits 2.
- Two mutants each fail exactly one row: no case folding, and ignoring
`workflows:` refs.
- **`lock-selfcheck-test`: 9/9, 2 of them new.**
- The base fixture lock had no `dependencies:` section, so it was not
closed (Hypatia rejects that shape). It now carries the record.
  - A mutant without the closure verdict fails both new cases.
- **Unchanged and passing:** `apply-baseline-test` 52/52,
`exemption-ratchet-test` 14/14, `hypatia-blocking-gate-test` 46/46.
- **This branch's own state:** its lock is closed, and
`lock-selfcheck.sh HEAD` reports SELF-CONSISTENT.

No licence headers are touched. The new fixture files carry MPL-2.0 SPDX
from birth.

Refs #963 · companion: hyperpolymath/hypatia#941

---
_Generated by [Claude
Code](https://claude.ai/code/session_01TbbnKqskQA5mj5gfMniPzi)_

Co-authored-by: Claude <noreply@anthropic.com>
@hyperpolymath
hyperpolymath merged commit ce9fcf9 into main Oct 20, 2026
80 of 81 checks passed
@hyperpolymath
hyperpolymath deleted the claude/actions-lock-closure-corpus branch October 20, 2026 12:54
hyperpolymath added a commit that referenced this pull request Oct 20, 2026
## Summary

This is the last step of the baseline-gate stack. `governance.yml` and
`hypatia-scan.yml` pass `hypatia-ref: aa34a4c` (#940), which predates
the rule fixes that clear this repo's own false positives. This PR moves
both to **hypatia `main` at `23dc74d`**, which contains:

- #938: ActionsLock accepts v0.0.3 locks (already in `aa34a4c`);
- #942: WH013 judges writes per job and skips PAT/app-token writes. This
clears `estate-rescan.yml`; `inbox-steward-intake.yml` was also repaired
in #943;
- #947: RE005 skips `trap` handlers, and job extraction survives
comments between jobs.

`uses:` refs are unchanged, so `actions.lock` is untouched.

## What this PR's governance run will show

This PR's own `governance / Validate Hypatia Baseline` run is the
measurement: it builds the scanner at `23dc74d`. The jump from `aa34a4c`
also brings #931's new rules (WF026, WF027, PIN-003), which haven't been
measured on this repo yet. hex.pm is blocked in my session, so I can't
build the escript to pre-run them.

**Expected remaining findings, by class:**
- **RE001 (harden-runner):** most of the original 18 jobs, including the
2 dispatched label workflows, which must stay action-free.
- These need an owner decision; the harden-runner steps were removed
again in #948 because the lock has no record.
- **WH013, RE004, RE005, `invalid_actions_lock`:** expected 0.

I'll post the measured list here once the run completes.

## Type of change

- [x] 🔧 Build / CI / tooling

## How has this been verified?

- `actionlint` 1.7.7 is clean on both files.
- `23dc74d` is `origin/main` at the time of writing. `git log
aa34a4c..23dc74d` contains #942, #947, #945, #931, #941 and #936.

## Checklist

- [ ] My commits are **signed**. The session's signing shim is
platform-mediated.
- [x] I ran the project's own checks/tests locally and they pass.
- [x] New files carry the correct SPDX (none added).
- [x] Docs are updated (the pin comment names what it carries).
- [x] I have not introduced a soundness hole.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01CYR5bf5FBLS7cXQK72aVNM)_

---------

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