Skip to content

actions.lock: list the lock-sync gate, and make the checker require coverage - #137

Merged
hyperpolymath merged 1 commit into
mainfrom
fix/lock-coverage
Sep 22, 2026
Merged

hyperpolymath merged 1 commit into
mainfrom
fix/lock-coverage

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

What this fixes

The lock-sync gate merged into this repository cannot start, and the
checker it ships cannot detect why. Both are fixed here.

A workflow file with no key in .github/workflows/actions.lock is refused
by GitHub at startup — jobs=0, startup_failure — even when it contains
zero uses: refs and so has nothing to pin.

Evidence — single-variable flip, two independent repositories

repo before after adding the one-line key
hyperpolymath/verisimdb 7 consecutive startup_failure success
hyperpolymath/blocky-writer 2 of 2 startup_failure success

Nothing else changed in either case. A control commit that touched the tree but
not the lock still failed; the commit adding the key passed. This is a state
effect, not a re-indexing side effect of "any lock change".

The spelling is not invented — gh actions-lock already emits the empty-list
form for other zero-uses: workflows in this same lockfile (labels.yml). The
generator simply omitted this file, which is itself an upstream defect.

The gate could not defend its own fix

This is the guard-asks-a-different-question-than-its-consumer trap:

  • clauses 1–3 ask: is every uses: locked under its own workflow path?
  • GitHub asks: is every workflow file represented in the lock?

A workflow with no uses: satisfies clauses 1–3 vacuously and is still
refused. Thirteen repositories passed the gate with exactly this gap present,
so a green gate today is not evidence a lock is complete.

Clause 4 (COVERAGE) diffs the set of files under .github/workflows/
against the set of lockfile keys, fails on any file with no key, names it, and
quotes the empty-list form to add. Remediation step 4 warns that re-running
gh actions-lock may not add it, because the omission is the tool's own defect.

Mutation-tested, both directions

mutant expected result
delete the lock-sync-gate.yml key gate fails ✅ fails, names the file
delete the unrelated labels.yml key gate fails ✅ fails, names the file
unmutated tree gate passes ✅ passes

A passing gate proves nothing until it kills a mutant, so both are recorded here.

Also included

workflow_dispatch: on the gate. A startup-failed run cannot be re-run
(gh run rerun refuses it), which is what made this defect expensive to
diagnose; a dispatch handle makes it reproducible on demand.

Scope

The checker is patched in place — each repository's copy has diverged
slightly in comments, and only the clause-4 block, its remediation step and one
success line are added. No existing clause is altered.

Verification

  • bash -n scripts/check-lock-sync.sh clean
  • checker passes on this tree after the lock key is added
  • both mutants killed
  • the gate run on this PR reports jobs > 0 (it could not start before)

🤖 Generated with Claude Code

https://claude.ai/code/session_01X3hgXxWm6umMgZkjYyHnnm

…overage

Two defects, one cause: a workflow file that has NO KEY in actions.lock is
refused by GitHub at startup (jobs=0, startup_failure) even when it contains
zero `uses:` refs and therefore has nothing to pin.

1. Add the missing key for the gate this repo already ships:

       '.github/workflows/lock-sync-gate.yml': []

   MEASURED, single-variable flip on two independent repositories:
     * hyperpolymath/verisimdb  - 7 consecutive startup_failure -> success
     * hyperpolymath/blocky-writer - 2 of 2 startup_failure -> success
   Nothing else changed in either case. An empty commit with the lock
   untouched still failed; the commit adding this line passed. `gh actions-lock`
   already emits this empty-list form for other zero-`uses:` workflows in this
   very lockfile (labels.yml), so the spelling is the generator's own
   convention - the generator simply omitted this file.

2. Teach scripts/check-lock-sync.sh to catch it (clause 4, COVERAGE).

   The gate could not defend the very fix it ships. Clauses 1-3 ask "is every
   `uses:` locked under its own workflow path?" GitHub asks a DIFFERENT
   question: "is every workflow file represented in the lock?" A workflow with
   no `uses:` satisfies clauses 1-3 vacuously and is still refused. Thirteen
   repositories passed the gate with exactly this gap present.

   Clause 4 diffs the set of files under .github/workflows/ against the set of
   lockfile keys and fails on any file with no key, naming it and quoting the
   empty-list form. Mutation-tested both ways: deleting the lock-sync-gate key
   fails the gate, and deleting the unrelated labels.yml key fails it too;
   the unmutated tree passes.

3. Add `workflow_dispatch:` to the gate so it can be exercised on demand.
   A startup-failed run cannot be re-run (`gh run rerun` refuses it), which is
   what made this defect expensive to diagnose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3hgXxWm6umMgZkjYyHnnm
@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.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 3a06f6ec-332b-4938-8662-8ab8e8a9a5e5

📥 Commits

Reviewing files that changed from the base of the PR and between f39a4bb and ed1ff72.

⛔ Files ignored due to path filters (1)
  • .github/workflows/actions.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • .github/workflows/lock-sync-gate.yml
  • scripts/check-lock-sync.sh

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (24)
  • GitHub Check: rust-ci / Cargo check + clippy + fmt
  • GitHub Check: governance / Code quality + docs
  • GitHub Check: governance / Exemption ratchet
  • GitHub Check: governance / Workflow security linter
  • GitHub Check: governance / Licence consistency
  • GitHub Check: governance / Actions lockfile verify
  • GitHub Check: governance / Trusted-base reduction policy
  • GitHub Check: governance / Security policy checks
  • GitHub Check: governance / Check Workflow Staleness
  • GitHub Check: governance / Allowlist Preflight
  • GitHub Check: governance / Live Actions policy (credentialed advisory)
  • GitHub Check: governance / Language / package anti-pattern policy
  • GitHub Check: governance / Debt ratchet
  • GitHub Check: governance / Guix packaging policy (Nix retired)
  • GitHub Check: hypatia / Hypatia Neurosymbolic Analysis
  • GitHub Check: scan / shell-secrets
  • GitHub Check: scan / gitleaks
  • GitHub Check: actions.lock is in sync with the workflow YAML
  • GitHub Check: analyze (rust, none)
  • GitHub Check: analyze (actions, none)
  • GitHub Check: Validate DEED manifests
  • GitHub Check: Empty-linter (invisible characters)
  • GitHub Check: Validate K9 contracts
  • GitHub Check: Groove manifest check
⚠️ CI failures not shown inline (2)

GitHub Actions: Workflow Security Linter / 0_lint-workflows.txt: actions.lock: list the lock-sync gate, and make the checker require coverage

Conclusion: failure

View job details

##[group]Run errors=0
 �[36;1merrors=0�[0m
 �[36;1mfor f in .github/workflows/*.yml .github/workflows/*.yaml; do�[0m
 �[36;1m  [ -f "$f" ] || continue�[0m
 �[36;1m  if ! head -1 "$f" | grep -q "SPDX-License-Identifier"; then�[0m
 �[36;1m    echo "ERROR: $f missing SPDX header"�[0m
 �[36;1m    errors=$((errors + 1))�[0m
 �[36;1m  fi�[0m
 �[36;1mdone�[0m
 �[36;1mexit $errors�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 ERROR: .github/workflows/boj-build.yml missing SPDX header
 ERROR: .github/workflows/cargo-audit.yml missing SPDX header
 ERROR: .github/workflows/casket-pages.yml missing SPDX header
 ERROR: .github/workflows/cflite_batch.yml missing SPDX header
 ERROR: .github/workflows/cflite_pr.yml missing SPDX header
 ERROR: .github/workflows/codeql.yml missing SPDX header
 ERROR: .github/workflows/dogfood-gate.yml missing SPDX header
 ERROR: .github/workflows/governance.yml missing SPDX header
 ERROR: .github/workflows/hypatia-scan.yml missing SPDX header
 ERROR: .github/workflows/instant-sync.yml missing SPDX header
 ERROR: .github/workflows/label-triage.yml missing SPDX header
 ERROR: .github/workflows/labels.yml missing SPDX header
 ERROR: .github/workflows/mirror.yml missing SPDX header
 ERROR: .github/workflows/push-email-notify.yml missing SPDX header
 ERROR: .github/workflows/rust-ci.yml missing SPDX header
 ERROR: .github/workflows/scorecard.yml missing SPDX header
 ERROR: .github/workflows/secret-scanner.yml missing SPDX header
 ERROR: .github/workflows/workflow-linter.yml missing SPDX header
 ##[error]Process completed with exit code 18.

GitHub Actions: Workflow Security Linter / lint-workflows: actions.lock: list the lock-sync gate, and make the checker require coverage

Conclusion: failure

View job details

##[group]Run errors=0
 �[36;1merrors=0�[0m
 �[36;1mfor f in .github/workflows/*.yml .github/workflows/*.yaml; do�[0m
 �[36;1m  [ -f "$f" ] || continue�[0m
 �[36;1m  if ! head -1 "$f" | grep -q "SPDX-License-Identifier"; then�[0m
 �[36;1m    echo "ERROR: $f missing SPDX header"�[0m
 �[36;1m    errors=$((errors + 1))�[0m
 �[36;1m  fi�[0m
 �[36;1mdone�[0m
 �[36;1mexit $errors�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 ERROR: .github/workflows/boj-build.yml missing SPDX header
 ERROR: .github/workflows/cargo-audit.yml missing SPDX header
 ERROR: .github/workflows/casket-pages.yml missing SPDX header
 ERROR: .github/workflows/cflite_batch.yml missing SPDX header
 ERROR: .github/workflows/cflite_pr.yml missing SPDX header
 ERROR: .github/workflows/codeql.yml missing SPDX header
 ERROR: .github/workflows/dogfood-gate.yml missing SPDX header
 ERROR: .github/workflows/governance.yml missing SPDX header
 ERROR: .github/workflows/hypatia-scan.yml missing SPDX header
 ERROR: .github/workflows/instant-sync.yml missing SPDX header
 ERROR: .github/workflows/label-triage.yml missing SPDX header
 ERROR: .github/workflows/labels.yml missing SPDX header
 ERROR: .github/workflows/mirror.yml missing SPDX header
 ERROR: .github/workflows/push-email-notify.yml missing SPDX header
 ERROR: .github/workflows/rust-ci.yml missing SPDX header
 ERROR: .github/workflows/scorecard.yml missing SPDX header
 ERROR: .github/workflows/secret-scanner.yml missing SPDX header
 ERROR: .github/workflows/workflow-linter.yml missing SPDX header
 ##[error]Process completed with exit code 18.
🔇 Additional comments (2)
.github/workflows/lock-sync-gate.yml (1)

23-23: LGTM!

scripts/check-lock-sync.sh (1)

212-237: LGTM!

Also applies to: 276-279, 286-286


📝 Summary

Summary by CodeRabbit

  • New Features

    • Added the ability to run lock-sync validation manually from the Actions interface.
  • Bug Fixes

    • Improved workflow validation to detect workflow files missing from the lockfile, including workflows without dependency references.
    • Validation now reports the affected workflow and fails clearly, helping prevent incomplete workflow configurations from being accepted.

Walkthrough

The lock-sync gate can run manually. The lock-sync check now fails when any workflow lacks a lockfile key, including workflows with no uses: references.

Changes

Lock-sync workflow validation

Layer / File(s) Summary
Manual workflow trigger
.github/workflows/lock-sync-gate.yml
Adds the workflow_dispatch trigger to the lock-sync gate.
Workflow coverage validation
scripts/check-lock-sync.sh
Adds coverage checks for unlisted workflows. The guidance and success output now include workflows with zero uses: references.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to ed1ff

The lock-sync gate remains manually runnable, and workflow coverage validation now includes workflows without action references. No merge-blocking risk is identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the main changes: adding the lock-sync gate to actions.lock and enforcing lockfile coverage.
Description check ✅ Passed The description directly explains the startup failure, the coverage check, workflow_dispatch support, mutation testing, and verification results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🛠️ Fix failing CI checks
  • Commit to this branch
  • Create a new PR
📝 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

A rabbit checks the workflow gate,
Empty lists now count their weight.
Missing keys raise a careful sign,
Manual runs start on command line,
Lock and workflow paths align.

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

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

ℹ️ Nothing to fix from this PR. All 1 failing check(s) are already failing on main, so they aren't caused by your changes.

⏭️ 1 check(s) skipped — already failing on `main` (not caused by this PR)
  • GitHub Actions: Workflow Security Linter / 0_lint-workflows.txt

These need to be addressed on main (or by whoever owns them), not in this PR.

@hyperpolymath
hyperpolymath merged commit 8400fe8 into main Sep 22, 2026
36 of 38 checks passed
@hyperpolymath
hyperpolymath deleted the fix/lock-coverage branch September 22, 2026 21:09
hyperpolymath added a commit that referenced this pull request Sep 30, 2026
…he freeze bypass — with the sources cured (#141)

## What this sorts out

Closes the acceptance criteria of #138 and goes after the three
*sources* behind today's reds, in one change set. Each cure carries its
inverse (januskey); nothing was muted, demoted, or removed from any
required set (standards#994 AC5).

### 1. `lint-workflows` ×2 (issue #138) — determination: **FIXED**

Two governors claimed the same byte: `gh actions-lock` inserts `# This
workflow is managed by gh actions-lock.` at **line 1** whenever it mints
or refreshes a lockfile, while the local `Check SPDX headers` step
demanded the SPDX identifier on **line 1**. Every lock refresh re-failed
the check. Measured same-commit (`a045f44`):

| check | predicate | result on identical files |
|---|---|---|
| `lint-workflows` (local `workflow-linter.yml`) | SPDX on `head -1` | 🔴
18 of 19 files "missing" headers they all have (including its own file)
|
| `governance / Workflow security linter` (canonical,
`standards@fad242d`) | SPDX in the leading comment block | 🟢 green |

The canonical predicate has existed in `hyperpolymath/standards`
`governance-reusable.yml` **since 2026-08-07**, with the exact warning
recorded: a line-1 test "fights the estate's own tool and re-fails every
time a lockfile is refreshed" — it falsely reported 27 hypatia + 13
other workflows and "fixing" it mis-licensed 3 files. The local copy was
a stale divergent mutant of the estate's own check.

The fix converges the local copy to the canonical form
(`scripts/check-workflow-headers.sh`, byte-faithful): SPDX anywhere in
the leading comment block (banner- and doc-marker-tolerant) + top-level
`permissions:`. Same requirements, correct frame. Job name
`lint-workflows` kept; the check stays blocking.

**Expected-rejection controls** (`--self-test`, from occupancy-types'
gate contract and pons-asinorum's falsifier rule): 3 fixtures that must
pass and 2 that must be killed, run *first* in the job. A predicate that
cannot kill its own mutant is a Certified Null Operation (absolute-zero)
and is not allowed to judge the tree. This promotes PR #137's manual
mutation notes into a permanent control.

### 2. The freeze bypass — determination: **FIXED** (gate-enforced)

Dependabot #140 moved `github/codeql-action` `b96794f0` (v4.38.0) →
`2892aa5e` (a 2026-09-24 `releases/v4` merge, `update-v4.38.2`) **in SHA
form while copying the `# v4.38.0 (4.38.1 blocked estate-wide;
nexia-list#100)` comment verbatim** — the witness of intent survived the
rewrite with its meaning inverted. That pin is under an estate freeze
(nexia-list#100: v4.38.1+ refused at startup; `standards` main still
pins `b96794f0` today). This is the **second** documented bypass of the
hold (nexia-list#101: versions-scope; nexia-list#104: unconditional;
then this).

An ignore rule is a request to a robot; this PR reverts the bump and
adds `scripts/check-frozen-pins.sh` — the freeze as a check *this repo
owns*, with its own mutants (SHA-form and tag-form bump both killed in
self-test). Lifting the freeze becomes a deliberate, reviewable one-line
table edit.

### 3. The lock re-desync (standards#968 class) — determination:
**FIXED**, source cured

#140 bumped `haskell-actions/setup` v2.12.1 and did not regenerate
`actions.lock` ("60 of 60 repos have Dependabot AND a lockfile AND no
regeneration step — repos appear to go bad again; nobody broke them, the
clock did"). Measured on main today: Lock Sync Gate 🔴, `governance /
Actions lockfile verify` 🔴, CodeQL + GitHub Pages `startup_failure`
(jobs=0), cflite poised to die. The lock is re-synced
(`haskell-actions/setup@v2.12.1` peeled via API; all four
`check-lock-sync.sh` clauses verified), and `lock-sync-heal.yml` is the
source cure: `gh actions-lock` (the sanctioned writer) on every workflow
change + weekly backstop, opening one standing refresh PR whose body
carries the checker's output as a **receipt** — obtained on the
regenerated tree before the PR exists (epistemic-types: transported
proof arrives with a sound check of the receiver's claim). Inverse:
close the PR.

## Residue list (honest boundary, echo-types/OND)

Not claimed, not erased — listed with dates:

* `hypatia / Hypatia Neurosymbolic Analysis` — red on `main` since
2026-09-29 (`Build Hypatia scanner`). Cause not established from here
(job logs unreachable from the triage sandbox); candidate classes:
`hyperpolymath/hypatia` HEAD build-rot or transient hex.pm failure. It
is standards#994's class 3; the reusable builds the scanner from a
*moving HEAD*, which is the fragile contract. **It is a required context
and may block this PR's merge mechanically** — per the stopping rule,
nothing is required of a branch that its base does not satisfy.
* `mirror / mirror-gitea`, `mirror / mirror-disroot` — red on `main`
(infrastructure/credentials). Not measured further.

## Verification

- [x] `bash -n` clean on every script and every `run:` block (extracted
and parsed)
- [x] YAML parse clean on every touched workflow + lockfile
- [x] header predicate self-test: 3 accepted, 2 rejected
- [x] freeze gate self-test: 1 accepted, 2 rejected (SHA-form + tag-form
mutants killed)
- [x] both predicates green on the full tree (all 20 workflow files)
- [x] `check-lock-sync.sh` clauses 1–4 pass on the repaired lock
(verified via a clause-faithful port; gawk is not in the triage sandbox
— the gate itself runs the real script on ubuntu-latest)
- [ ] `lint-workflows` green on this PR head (both legs)
- [ ] Lock Sync Gate green on this PR head

## Theory basis (full map in `docs/ci-guard-cures-2026-09-29.adoc`)

pons-asinorum (contradiction taxonomy; negative corpus) · absolute-zero
(CNO vacuous gates; OND residue lists) · januskey (inversion metadata;
shared core over divergent copies) · echo-types (structured loss;
comments that outlive their meaning) · epistemic-types (warrant ≠
knowledge; receipts) · choreographic-types (YAML+lock is one cut) ·
occupancy-types (expected-rejection controls; rung contracts) ·
panic-attack (signatures: `TWO-GOVERNORS-ONE-INVARIANT`,
`ROBOT-PROMISE-WITHOUT-GATE`, `STATE-CURE-WITHOUT-SOURCE-CURE`).

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

---------

Co-authored-by: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.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