Skip to content

fix(actions-lock-gate): read uses: with yq so KYAML is not RED - #1217

Merged
hyperpolymath merged 1 commit into
mainfrom
fix/lock-gate-yq-kyaml
Oct 9, 2026
Merged

hyperpolymath merged 1 commit into
mainfrom
fix/lock-gate-yq-kyaml

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Summary

scripts/check-actions-lock-gate.sh (the actions-lock-verify gate) now reads uses: refs with yq instead of an anchored line grep, in its lockless branch only. YAML-POLICY Y-1 calls for this.

Why. A KYAML step value is quoted and ends in a comma: uses: "a/b@<sha>",. That value failed the grep's @<sha>([[:space:]]|$) test, and a quoted "./local" missed its exemption. So a fully pinned KYAML workflow went RED, as on jaffascript#72 (2026-10-09). This is the third §2a precondition: the "Produce today" column says a workflow stays in block YAML until this gate reads KYAML. The grep also treated a run: body line starting with uses: as a step ref.

What did not change.

  • The lock-present branch: the authoritative gh actions-lock --verify-local verifier.
  • The grace window and the exit contract (0 / 1 / 2 / 3).
  • The four exemptions (./, docker://, actions/github-script, hyperpolymath/standards/*).
  • The 40-hex pin test.

Behaviour changes, all in the fail-closed direction:

Case Before After
Fully pinned KYAML workflow 1 (false RED) 0 / 3, same as block YAML
Workflow yq cannot parse 3 (ledger-excusable): the grep found no unpinned line 1 UNEXAMINED, never counted as pinned
Workflow that parses but has no jobs: map (unclosed-quote swallow, all-commented template, non-workflow YAML) whatever the grep saw 1 UNEXAMINED
uses: text inside a run: body read as a ref not a ref
yq missing, or a yq that cannot read a known block + KYAML probe n/a 2 (infrastructure). mikefarah yq v4 is already required by R5
Nested .github/workflows/sub/*.yml read (grep -r) not read; GitHub does not run them, and the verifier snapshots the same top-level set

Closes # (none: this implements YAML-POLICY Y-1 for this script)

Type of change

  • 🐛 Bug fix (non-breaking change that fixes an issue): fully pinned KYAML workflows no longer fail.
  • ✨ New feature: no new capability.
  • 💥 Breaking change (would change existing behaviour): an unparseable or job-less workflow is now exit 1, where it was exit 3. 4 ledgered repos turn red (see Notes).
  • 🕳️ Soundness fix (fixes a checker/proof false-negative): an unparseable workflow is no longer excusable as missing-lock debt.
  • 📖 Documentation: only the script's own header changed.
  • 🧹 Refactor / tech debt: behaviour changes, see above.
  • ⚡ Performance: not a goal.
  • 🔧 Build / CI / tooling

📌 New pins

  • Head SHA: 0d50cbe52d01a00eea404ea07dff31e6e8e789c6
  • No new or changed pins. No uses: change, no actions.lock change, no lockfile or container digest.

How has this been verified?

All of the following were run on head 0d50cbe5, 2026-10-09:

  • bash scripts/tests/check-actions-lock-gate-test.sh → passed=23 failed=0. There are 12 new cases under "no lockfile, KYAML and parser cases":

    • The KYAML control is generated by yq -p yaml -o kyaml from the block fixture.
    • A JSON-identity assertion checks the control carries the same data as the block fixture.
    • A second assertion checks the control really is flow style ("./local" present).
  • Mutant kill. Running the same suite against origin/main's gate gives 15 pass, 8 fail:

    • KYAML pinned, before and after the cutoff;
    • the KYAML unpinned mutant;
    • the run: heredoc decoy;
    • the unparseable file (old gate: exit 3);
    • the quote-swallow file;
    • yq missing;
    • wrong yq.

    The other 4 new cases are regression guards that pass on both, not kills: control identity, the flow-style assertion, the job-level reusable call by tag, and the short SHA.

  • Full self-test suite at this head: All 80 test file(s) passed. CI's runner yq writes KYAML: kyaml-format-test.sh reported 17/17 in run 37913997559 on main.

  • shellcheck on both files: clean.

  • .githooks/docstring-scan.sh --range origin/main..HEAD: 2/2 functions documented, 100%.

  • Blast-radius census of the ledger.

    • Scope: the 163 lock-allow.txt entries. 162 resolved by GraphQL. hyperpolymath/avow-protocol did not resolve (renamed or deleted).
    • Method: each repo's default-branch HEAD .github/workflows/, run through both gates with LOCK_TODAY=2026-10-09.
    Group Repos Old → new
    actions.lock present (verifier branch, unchanged) 62 —
    Lockless 89 3 → 3
    Lockless 7 1 → 1
    Lockless 4 3 → 1
    Lockless 0 1 → 3

Checklist

  • My commits are signed: git log --format=%G? → G.
  • I ran the project's own checks/tests locally and they pass: the 23-case suite, the full 80-file self-test, and the pre-commit hooks.
  • New files carry the correct SPDX-License-Identifier: no new files. Both modified scripts keep their existing MPL-2.0 header.
  • Docs are updated, and no public claim now overstates what the code does: the script header states the yq requirement, UNEXAMINED, and the new exit 1 and exit 2 causes. governance-reusable.yml L1478-1482 still describes the old grep (follow-up below).
  • I have not introduced a soundness hole (or I have flagged where I might have):
    • A yq/GitHub parser differential is the residual risk. It is why an unparseable file is RED rather than skipped: a file yq rejects but GitHub accepts would otherwise run with unexamined refs.

Notes for reviewers

The 4 repos that turn red. Each holds workflow files that GitHub itself cannot parse:

Repo Unreadable files
cloudguard-server dogfood-gate.yml
rrecord-verity dogfood-gate.yml
email-octad-experiment dogfood-gate.yml, workflow-linter.yml, and e2e.yml (all jobs commented out)
hyperpolymath-sovereign-registry 5 files: boj-build.yml, guix-nix-policy.yml, quality.yml, rsr-antipattern.yml, security-policy.yml

GitHub's view of the same files:

  • On each repo, the workflow API reports the workflow name equal to its file path. That is GitHub's sign that it could not parse the file.
  • Each last run, from 2026-10-01, ended in failure.
  • hyperpolymath-sovereign-registry's last run has total_count: 0 jobs.

So none of those refs has ever run. The old gate excused these repos at exit 3 only because the grep saw nothing unpinned. The fix belongs in those 4 repos' workflow files, not in the ledger, which may excuse exit 3 only.

Follow-ups, not in this PR:

  • governance-reusable.yml L1478-1482: the comment still describes the grep regex and "11/11" tests. Left alone, because §2a puts workflow edits behind their own preconditions.

  • update-actions-lock.sh snapshots and restores workflow bytes around --verify-local (L172-177), but its update pass (L169, gh actions-lock default mode) runs before that snapshot, so the update pass's rewrites persist.

  • P0-006 is an owner question: should the estate recipe use gh actions-lock default mode at all? Those rewrites are:

    • de-pinning: measured, default mode turns any new or changed SHA pin into a tag, while an unchanged locked SHA survives;
    • the ./ → $/ local-path rewrite.

    Separate script, separate PR.

Merge. Squash. This is held for the owner's decision on the 4-repo flip and is not armed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Wo32J8Ym7XpPr9EYdBCgVB

The lockless branch of check-actions-lock-gate.sh found `uses:` with an
anchored line grep. A KYAML step value is quoted and ends in a comma
(`uses: "a/b@<sha>",`), so it failed the `@<sha>([[:space:]]|$)` test and a
quoted `"./local"` missed its exemption: a fully pinned KYAML workflow went
RED (jaffascript#72, 2026-10-09). The grep also read a `run:` body line that
starts with `uses:` as a step ref.

The gate now reads each top-level *.yml/*.yaml with yq (YAML-POLICY Y-1):
every string `uses:` value, step and job level, block and flow style alike.
The four exemptions and the 40-hex pin test are unchanged. Fail-closed cases:

- a file yq cannot parse, or one with no `jobs:` map (an unclosed quote can
  swallow a file and still parse), is UNEXAMINED: exit 1, never 0 or 3.
  The old gate returned 3 (ledgerable debt) for an unparseable file.
- yq missing, or a yq that cannot read the ref from a known block and KYAML
  input, is exit 2. mikefarah yq v4 is already required by R5.

Tests: 12 new cases. Run against origin/main's gate, 8 of them fail
(KYAML pinned x2, KYAML mutant, run: decoy, unparseable, quote-swallow,
yq missing, wrong yq); against this gate all 23 pass.

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

coderabbitai Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

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: b3e5ae76-a9a0-4d92-8abc-10234e1eb4bf

📥 Commits

Reviewing files that changed from the base of the PR and between 8496854 and 0d50cbe.


📒 Files selected for processing (2)
  • scripts/check-actions-lock-gate.sh
  • scripts/tests/check-actions-lock-gate-test.sh

 ____________________________________
< Zero-day? Zero chance on my watch. >
 ------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR


  • 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.

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

K9 contract conformance

run https://github.com/hyperpolymath/standards/actions/runs/37928116805

K9 normative contract typecheck

k9_contract.ncl typechecks

K9 contract self-test

== the bash mirrors cannot drift from the normative contract ==
ok   leash_levels mirrors k9_contract.ncl
ok   core_capabilities mirrors k9_contract.ncl
ok   contract_version mirrors k9_contract.ncl
ok   schema_major mirrors k9_contract.ncl
== capability arithmetic (§8) ==
ok   capability_ok fs.read accepted
ok   capability_ok rollback.apply accepted
ok   capability_ok x-acme.gpu.alloc accepted
ok   capability_ok x-acme rejected
ok   capability_ok x-.gpu rejected
ok   capability_ok fs.delete rejected
ok   capability_ok  rejected
== the extractor ==
ok   extracts pedigree.security.leash
ok   extracts pedigree.component_type
ok   extracts pedigree.metadata.name
ok   pedigree leash is not reported as top-level leash
ok   required_capabilities for a quiet component
ok   required_capabilities follows allow_network
== the envelope strip keeps line numbers (§3.6) ==
ok   line 1 becomes a comment
ok   line count is preserved
ok   schema_version stays on line 5
== L3: signature presence is not verification (§10) ==
ok   no verifier -> K9-C001 is SKIPPED, never a pass
ok   the skip states presence does not authorise 'Hunt
ok   verifier accepts -> verdict 'Verified, no K9-C001 finding
ok   verifier refuses -> K9-C001 error, verdict 'Rejected
== the fixture runner's attribution cannot be fooled by a filename ==
ok   every extracted finding is well-formed rule+layer
ok   the rule that really fired is attributed
ok   a rule named only in the filename is NOT attributed
ok   K9-C001 is present as a skipped finding
ok   and that same finding is NOT extractable as a rejection
== no Nickel reserved word is used as an identifier ==
ok   the contract and all 27 fixtures avoid Nickel's reserved words

self-test: all assertions passed

K9 conformance fixtures

== positive controls (must pass) ==
ok   extension-capability.k9.ncl
ok   extension-fields.k9.ncl
ok   hunt-fully-granted.k9.ncl
ok   kennel-data.k9.ncl
ok   library-base.ncl
ok   yard-typed-config.k9.ncl

== negative controls (must fail, by the named rule) ==
ok   L0-K9-E001-bad-magic.k9.ncl (rejected by K9-E001 at L0)
ok   L0-K9-E002-nul-byte.k9.ncl (rejected by K9-E002 at L0)
ok   L0-K9-E003-crlf.k9.ncl (rejected by K9-E003 at L0)
ok   L0-K9-E004-no-spdx.k9.ncl (rejected by K9-E004 at L0)
ok   L0-K9-E005-unclaimed-body.k9.ncl (rejected by K9-E005 at L0)
ok   L0-K9-S012-library-with-pedigree.ncl (rejected by K9-S012 at L0)
ok   L0-K9-S014-stray-leash.ncl (rejected by K9-S014 at L0)
ok   L1-K9-S001-no-pedigree.k9.ncl (rejected by K9-S001 at L1)
ok   L1-K9-S002-wrong-major.k9.ncl (rejected by K9-S002 at L1)
ok   L1-K9-S003-todo-component-type.k9.ncl (rejected by K9-S003 at L1)
ok   L1-K9-S004-unknown-leash.k9.ncl (rejected by K9-S004 at L1)
ok   L1-K9-S005-missing-name.k9.ncl (rejected by K9-S005 at L1)
ok   L1-K9-S006-unknown-capability.k9.ncl (rejected by K9-S006 at L1)
ok   L1-K9-S007-ungranted-flag.k9.ncl (rejected by K9-S007 at L1)
ok   L1-K9-S008-hunt-signature-not-required.k9.ncl (rejected by K9-S008 at L1)
ok   L1-K9-S009-hunt-no-signature-block.k9.ncl (rejected by K9-S009 at L1)
ok   L1-K9-S010-hunt-empty-side-effects.k9.ncl (rejected by K9-S010 at L1)
ok   L1-K9-S011-recipes-at-yard.k9.ncl (rejected by K9-S011 at L1)
ok   L1-K9-S013-dangling-import.k9.ncl (rejected by K9-S013 at L1)
ok   L2-K9-N001-two-segment-version.k9.ncl (rejected by K9-N001 at L2)
ok   L2-K9-N001-wrong-field-type.k9.ncl (rejected by K9-N001 at L2)

fixtures: 6 positive, 21 negative (0 needing nickel), 0 failure(s)

K9 corpus conformance (L2)

[validate-k9] debt rhodium-standard-repositories/rsr-compliance-checklist.k9.ncl (fail) — K9-N001 K9-S004 K9-S005 K9-S014 (grandfathered; touching it makes it blocking)
[validate-k9] 14 conforming, 1 grandfathered (layer all, contract v1.0.0)

@sonarqubecloud

sonarqubecloud Bot commented Oct 9, 2026

Copy link
Copy Markdown

@hyperpolymath
hyperpolymath merged commit 7712ae1 into main Oct 9, 2026
63 of 64 checks passed
@hyperpolymath
hyperpolymath deleted the fix/lock-gate-yq-kyaml branch October 9, 2026 12:09
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