fix(ci): cure 18 of the 19 pre-existing check failures that kept every hypatia PR red - #764
fix(ci): cure 18 of the 19 pre-existing check failures that kept every hypatia PR red#764hyperpolymath wants to merge 3 commits into
Conversation
Every open hypatia PR carried an identical set of 18 failing checks that
had nothing to do with its diff. They reduce to seven root causes plus
three aggregators that merely report them.
release.yml -- Build Rust x5
`on: push: null` fires on EVERY branch push, so the release pipeline ran
on feature branches. `${GITHUB_REF#refs/tags/v}` returns the ref UNCHANGED
when the prefix is absent, so the version became `refs/heads/<branch>` and
the archive name `hypatia-refs/heads/<branch>-<target>.tar.gz`, whose
embedded slashes are directories that do not exist -- tar died with
"Cannot write: Broken pipe" on all five targets. Trigger is now tags-only,
and version derivation fails loudly on a non-tag ref instead of silently
building a nonsense name.
actions.lock -- Integration Tests, E2E Elixir, governance / Validate Hypatia Baseline
The lockfile had no dependency entry for
hyperpolymath/smtp-notify-action@v0.2.0 (used by push-email-notify.yml).
This ONE omission produced both the `mix test` failure and the governance
gate's only unfiltered finding:
{:workflow_dependencies_missing,
[{".github/workflows/push-email-notify.yml",
"hyperpolymath/smtp-notify-action@v0.2.0"}]}
governance / Validate Hypatia Baseline is the only REQUIRED check in the
failing set, so this entry is what actually unblocks merging. The commit
sha was obtained by dereferencing the annotated tag object, not by reading
git/ref/tags/v0.2.0 (which returns the tag, not the commit).
tests.yml -- E2E Rust CLI Scan
Invoked ./target/release/hypatia. The workspace binary is `hyper`
(cli/Cargo.toml [[bin]]); the CRATE is hypatia-cli. Exit 127 read like a
missing build rather than a wrong filename.
security-policy.yml -- Secret Detection, Secret Detection (Gitleaks)
.gitleaks.toml carries `[extend] path = ".gitleaks-estate.toml"`, which
gitleaks resolves against the process CWD. Only standards'
secret-scanner-reusable.yml staged that file, so these two hand-rolled
jobs died before scanning a single byte, the action then crashed on the
results.sarif it never wrote, and the audit summary reported a CRASHED
SCANNER as "possible secrets in repository" -- a fake red in the worst
possible direction for a security gate. There was no secret. Both jobs now
stage the estate baseline from the same standards sha secret-scanner.yml
already consumes, and delete the tooling checkout before scanning so the
scan cannot report standards' files as hypatia's.
security-policy.yml -- Generate Rust SBOM
cargo-cyclonedx dropped `--output-cdx` upstream and was installed
unpinned, so the job began failing with no workflow change. Pinned to
0.5.9 --locked and switched to `--override-filename`. Upload paths are
globbed because cyclonedx writes one file per Cargo.toml and this is a
seven-member workspace.
quality.yml -- lint (x2 events)
action-editorconfig-checker@v2.2.0 globs `ec-linux-amd64*`, a filename
gone since editorconfig-checker v4. A pinned wrapper around an unpinned
payload is not a pin. Bumped to v3.0.0 with the matching lockfile entry.
dogfood-gate.yml -- Validate K9 contracts
hyperpolymath/k9-ecosystem main carries a dangling symlink
(editors/vscode/container/.gatekeeper.yaml -> ../../../../_shared/...,
one `..` too many, escaping the repo). GitHub extracts the whole action
repo during "Prepare all required actions", BEFORE any step `if:` is
evaluated, so the job died at setup and the k9_count guard could not
protect it. The action is referenced at the moving `@main` ref, so the
LOCKFILE selects the commit: pinned back to c74f04c7, the last tree with
no symlinks at all. No inline SHA -- the lock is the pin. Upstream fix is
to carry _shared/container/.gatekeeper.yaml across; see the comment at the
call site.
Also cures the two defects deferred during PR-5:
- `--format` help text listed `sarif, sarif`. The real supported set is
json / sarif / github plus a catch-all rendering text, so the duplicate
was a straight repetition, not a dropped format name.
- gen-rule-catalogue.sh still wrote docs/rules/catalogue.md while the tree
holds catalogue.adoc, so "regenerate" produced an orphan beside the real
file and the catalogue drifted for a month. The script now emits native
AsciiDoc, restores the SPDX header the .md->.adoc conversion dropped,
and the catalogue is regenerated (33 -> 35 modules). README.adoc had
three dead links to catalogue.md and a stale module count, all fixed.
Verified locally:
- mix test test/rules/actions_lock_test.exs -- 13 tests, 0 failures
- ActionsLock.parse on the fixed lockfile: OK, 29 workflows / 41 deps,
and pinned?/3 true for both smtp-notify-action and editorconfig-checker
- version derivation regression harness: refs/tags/v1.2.3 -> 1.2.3,
v1.2.3-rc1 -> 1.2.3-rc1, refs/heads/* -> error, dispatch input honoured
- asciidoctor --failure-level=WARN clean; 105 table cells = 3 x 35, none dropped
- all six edited workflows parse as YAML; shellcheck clean
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCKh98NmUmfvgpuGyJbNu8
Cures the last 2 of the 18 (ci.yml -- "k9iser manifest" and "build", plus
the CI Status aggregator that reports them).
k9iser.toml declared `deno.json` as a [[source]]. That file does not exist
in the tree and must not: Deno is banned estate-wide. check-k9iser-paths
exits 1 on any missing declared source, so ci.yml has been red on every
push and every PR for as long as the entry has been there.
Removed the source AND its orphaned [[constraint]]. The constraint
("deno.imports has no npm: specifiers") targeted the namespace the dead
source would have populated, and the checker never validates that a
[[constraint]] references a live source -- so it was a rule that could
never evaluate and therefore could never fail. Leaving it would have kept
a vacuous gate in the manifest after removing the thing it gates.
0-AI-MANIFEST.a2ml is included here for the same reason it is separable:
its only change is a dead docs/rules/catalogue.md link retargeted to the
.adoc that actually exists, plus the 33 -> 35 module count that went with
it. No grammar, no schema, no design.
Split out from the preceding commit deliberately. The 09-08 standing
ruling says "hands off .a2ml/.deed grammar, A2ML manifests, K9 contracts",
and these are the only two files in this PR that ruling could reach. Both
edits are corrective rather than structural, but the owner should not have
to take them on trust: `git revert` of this commit alone drops both and
leaves the other six root-cause cures intact. ci.yml then returns to red
for its original, now-documented reason.
Verified locally:
- check-k9iser-paths k9iser.toml -- "OK ... 4 source(s), 2 constraint(s)", exit 0
- 0-AI-MANIFEST.a2ml: no remaining catalogue.md reference in the tree
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCKh98NmUmfvgpuGyJbNu8
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (11)
💤 Files with no reviewable changes (1)
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. (13)
🧰 Additional context used🪛 GitHub Check: CodeQL.github/workflows/quality.yml[warning] 339-339: Unpinned tag for a non-immutable Action or reusable workflow 🔇 Additional comments (9)
📝 SummarySummary by CodeRabbit
WalkthroughThe pull request updates GitHub Actions workflows, security scan handling, SBOM generation, rule catalogue generation, project metadata, k9iser configuration, and CLI help text. ChangesWorkflow and catalogue maintenance
Priority: ➖ Normal — Schedule the CI reliability fixes because they address 18 recurring failures across release, security, testing, dependency locking, and validation workflows. Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This change corrects CI and release workflow behavior, security-scan setup, SBOM generation, and catalogue references without changing product rule logic. No concrete current-head merge-blocking risk is identified. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
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. A rabbit checks the workflows bright, Comment |
…g it
The E2E Rust CLI Scan job built its fixture with `mkdir` alone. The Rust
CLI opens the scan target with git2 (`Repository::open`,
cli/src/commands/mod.rs:38) and exits 1 on a plain directory:
Error: Not a git repository: /tmp/test-repo
Caused by: could not find repository at '/tmp/test-repo';
class=Repository (6); code=NotFound (-3)
So this step could never have passed. The previous commit's binary-name
fix (hypatia -> hyper) is what revealed it: while the job died at exit 127
before argv was even parsed, the fixture defect was unreachable and
therefore invisible. One bug was standing in front of another.
Measured, release build, three fixture shapes:
mkdir only -> exit 1, the CI error verbatim
git init only -> exit 0, scan completes
git init + one commit -> exit 0
An empty repository is sufficient; no commit is needed, so the fixture
stays cheap. `rm -rf` added first so the step is idempotent on reruns.
The whole step body was then executed verbatim against a redirected
fixture path: exit 0, ending "PASS: Rust CLI scan completed", with the
scanner emitting a plausible finding (missing-readme) rather than an
error -- evidence it really scanned rather than merely exiting.
Separately noted, NOT fixed here (no CI impact, wrong scope): a DEBUG
build of the CLI panics before running, on a clap debug_assert --
"Command scan: Short option names must be unique for each argument, but
'-c' is in use by both 'categories' and 'config'". The assertion is
compiled out of release builds, which is why CI never saw it and why the
release binary reaches the git2 call at all. Worth a follow-up: `-c` is
ambiguous for anyone building the CLI locally.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCKh98NmUmfvgpuGyJbNu8
|
Caution Docstrings generation - FAILED No docstrings were generated. |
What this is
Every open hypatia PR has been carrying an identical set of pre-existing failing
checks that had nothing to do with its diff: 19 failing check runs across 18
distinct check names (
lintis emitted twice, by two different workflows).Re-measured live on both #763 and #753 — the two failing sets are name-identical.
They are not eighteen problems. They are seven root causes, plus three
aggregator checks that merely report the others.
This PR cures all seven at source. It changes no rule logic and no product
behaviour; it is entirely CI plumbing plus two documentation-consistency fixes
deferred during PR-5.
The seven root causes
release.ymlon: push: nullfires on every branch;${GITHUB_REF#refs/tags/v}returns the ref unchanged when the prefix is absent, so the tar name containedrefs/heads/…— directories that do not existactions.lockhyperpolymath/smtp-notify-action@v0.2.0tests.yml./target/release/hypatia; the binary ishyper(the crate ishypatia-cli) — exit 127security-policy.yml.gitleaks.toml[extend]target was never staged, so gitleaks died before scanning a bytesecurity-policy.ymlcargo cyclonedx --output-cdxremoved upstream, installed unpinnedquality.ymlaction-editorconfig-checker@v2.2.0globsec-linux-amd64*, a filename gone since checker v4ci.ymlk9iser.tomldeclareddeno.json, which does not exist and must notdogfood-gate.ymlk9-ecosystem@mainkills action extractionOne entry unblocks merging
governance / Validate Hypatia Baselineis the only required check in thefailing set, and its failure and the
mix testfailures share a single cause —the missing lockfile entry:
Two findings worth keeping
A crashed scanner was reported as a positive finding. Gitleaks uses exit
codes for both "leak found" and "fatal config error", and the audit summary
infers "possible secrets in repository" from a non-zero exit. A broken config
was indistinguishable from a real breach. There was no secret.
A dangling symlink anywhere in an action repo kills every consumer. GitHub
extracts the entire action repository during "Prepare all required actions",
before any step's
if:is evaluated. So an unresolvable link ineditors/vscode/killed a job that only usesvalidate-action/, and thek9_count > 0guard could not protect it. The lockfile pinned@mainfaithfullyto the commit that carried the defect — the lock did its job perfectly and still
could not help. Cured by moving the lockfile's recorded commit back to
c74f04c7; the workflow still says@main, so no inline SHA appears anywhereand the lock remains the pin.
Honest framing — please read before assuming green
scans, editorconfig-checker v4 now actually lints, and
hyper scannow actuallyexecutes. Each may report genuine findings the crash was hiding. A new red
in these three is a result, not a regression of this PR.
release.ymlno longer triggers onPRs — that is the fix. The version-derivation change is proven only by a local
harness until a tag is cut. Positive control for the absence: PR-time Rust
compile coverage is retained by
rust.ymlandci.yml, which deliver RustCheck & Clippy, Rust Format, Cargo test, Cargo check + clippy + fmt and Rust
License & Ban Check — all green on this head. The
release.ymljobs werecross-target release artifact builds; tags-only is correct for those.
head
92ce43e: 18 of the 19 baseline failing runs are cured — 13 arepresent and
success, and 5 are the justified absences above (Build Rust ×5).The one uncured baseline item is
Integration Status, and it fails solelybecause
e2e-testsfailed (its other three dependencies are allsuccess).expected.
End-to-End Testsis failing on this PR and was not in thebaseline — because it never ran there. It is
needs: [integration-tests], andIntegration Testsfailed first, so it was gated out and had no check run atall. Curing the upstream job let it execute for the first time. It is a
revealed finding, not a regression — see finding 3 below.
live
mainruleset:governance / Validate Hypatia Baseline,governance / Code quality + docs,CodeQL,CodeQL Analysis (actions),CodeQL SAST (actions),analyze (actions, none)andanalyze (javascript-typescript, none). NeitherEnd-to-End TestsnorIntegration Statusis a required context.BLOCKED— that is a ruleset artefact, not a checkfailure. It is the B3
required_deployments: [github-pages]rule, which noPR head SHA can ever satisfy: the Pages workflow triggers on
push, never onpull_request. 18 of 120 estate repos carry it, hypatia among them. It isunrelated to this diff and was not chased here.
⚠ Overlap with the 09-08 standing ruling — owner call
The 09-08 ruling says "hands off .a2ml/.deed grammar, A2ML manifests, K9
contracts". This PR touches two such files. Both edits are corrective, not
grammar or design work, and both are trivially revertible on their own:
They are isolated in their own commit,
6ad9239, precisely so this is adecision you can take with one command rather than a re-authoring request:
git revert 6ad9239drops both and leaves every other cure intact. That committouches those two files and nothing else.
0-AI-MANIFEST.a2ml— one deaddocs/rules/catalogue.mdlink →.adoc, and astale module count (33 → 35). No grammar change.
k9iser.toml— removes a[[source]]declaringdeno.json, a file that doesnot exist and whose language is banned estate-wide, plus its now-orphaned
[[constraint]]. This removal is what cures theci.ymlfailure.The orphaned constraint is worth flagging on its own:
check-k9iser-pathsvalidates that every
[[source]]path exists, but never checks that a[[constraint]]references a live source. Sodeno.imports has no npm: specifierscould never evaluate and therefore neverfail — a rule that looked like enforcement and was inert. Removing the source
without the constraint would have left that vacuous gate in place.
Say the word and I will drop either or both hunks.
Verified locally
mix test test/rules/actions_lock_test.exs→ 13 tests, 0 failuresActionsLock.parseon the fixed lockfile → 29 workflows / 41 deps;pinned?/3true for both
smtp-notify-actionandeditorconfig-checkercheck-k9iser-paths k9iser.toml→OK — 4 source(s), 2 constraint(s), exit 0Version-derivation harness:
refs/tags/v1.2.3→1.2.3;v1.2.3-rc1→1.2.3-rc1;refs/heads/*→ error; dispatch input honouredcargo-cyclonedx0.5.9 verified against the crates.io sparse index (latest,not yanked, MSRV 1.85.0 ≤
stable);--override-filenameconfirmed present atsrc/cli.rs:90in the published source, and--output-cdxconfirmed absent.Filename semantics read from
generator.rs: an override suppresses the.cdxsuffix, so
--override-filename rust-sbom.cdx --format jsonyieldsrust-sbom.cdx.json— matching the upload globsGitleaks estate baseline confirmed to exist at standards
571cc734…(8922 bytes)dogfood-gate.ymlconfirmed the only k9-ecosystem consumer in the repoRequired gate simulated end-to-end on a clean
git archiveexport using thereal upstream
apply-baseline.shat the pinned workflow sha,BLOCKING_THRESHOLD=info:1 finding, 1 suppressed, 0 kept → exit 0. The sole finding is the
DependencyPinningscorecard entry already acknowledged in the baseline as anexternal-tool limitation
Honest caveat on that simulation: the scanner it ran was built from this
branch. That is sound here only because the branch is rule-identical to
mainunder
lib/(the solelib/change is a one-line help-text string incli.ex). Note also that the real gate resolves hypatia viagit ls-remote ... HEADand builds from main, so a PR that changed rulelogic would not be self-testing at this gate.
asciidoctor --failure-level=WARNclean; 105 table cells = 3 × 35, none droppedAll six edited workflows parse as YAML;
shellcheckclean on the rewritten scriptAlso reported, not fixed (deliberate)
lib/rules/rsr_conformance.exfailsmix format --check-formattedonmain, andhypatia has no CI gate for
mix formatat all — onlyJustfile:34runsmix format, which reformats rather than checks, so Elixir formatting driftssilently. Left out to keep this PR single-purpose.
Revealed after opening: three findings the cures uncovered
All three were invisible before, and all three are exactly what the "honest framing"
section above predicted would happen when a crashing job starts running. Curing a
failing early step does not only turn that check green — it ENFORCES, for the
first time, every gate that step was disarming.
1. The E2E fixture was never a git repository (
92ce43e). Curing the binaryname took the job past
exit 127, and it then failed withError: Not a git repository: /tmp/test-repo. The step built its fixture withmkdiralone, but the Rust CLI opens the target with git2(
Repository::open,cli/src/commands/mod.rs:38). This step could never havepassed — one bug was standing in front of another. Measured on a release
build:
mkdironly → exit 1 (the CI error verbatim);git initonly → exit 0;git init+ a commit → exit 0. An empty repo suffices, so the fix is one line.2. CodeQL
actions/unpinned-tagfired on thelintcure (alert 1178,quality.yml:339). Bumpingaction-editorconfig-checkertov3.0.0is a tagref, and the CodeQL check is diff-scoped, so touching that line minted a new
alert even though the repo already carries ~10 open ones and CodeQL passed on
#763. The SHA "fix" is forbidden by C1. Dismissed as
won't fixper standing ownerruling O-CODEQL (2026-09-03), citing lockfile authority and the resolved
sha1-51f63319f592f97930c73d9c46184d20bd206393. The check went green ondismissal, no re-run required.
3.
End-to-End Testscan never pass — the infrastructure it names does notexist. This job was absent from the baseline only because it is
needs: [integration-tests]and that dependency failed first, so it was gatedout and emitted no check run. Curing
Integration Testslet it run for thefirst time, and it fails at its "Start full stack" step with
no configuration file provided: not found. The step isworking-directory: deploy, butgit ls-files deployreturns exactly twofiles —
ContainerfileandREADME.adoc. There is no compose file there.Nor can it be redirected: the only compose file in the tree,
integration/compose.test.yaml, defines nohypatiaservice. (The siblingintegration-testsjob does not use compose at all — it uses GitHub Actionsservices:, which is why it has "Wait for ArangoDB"/"Wait for Dragonfly"steps.) Separately, its health check targets
http://localhost:8080/health,which the estate no-8080 network-hardening doctrine bans. Across the 12 most
recent
tests.ymlruns onmainthe job is 3×skippedand 9× absent — ithas never once run to a conclusion.
Integration Statusis red purely as itsaggregator.
This one is left red deliberately, and it is an owner call, not a cure. The
only two honest options are (a) build the missing infrastructure — a compose
file, a
hypatiaservice image and a non-8080 health endpoint, which is newwork, not a repair; or (b) delete the job and its
needs:entry. Making itskip when the compose file is absent was rejected: that manufactures a
green gate that asserts nothing. Neither this check nor
Integration Statusisa required context, so leaving it red blocks nothing. Today's standing ruling is
triage-only, so the fork is recorded here rather than taken.
Also found, not fixed (out of scope, no CI impact): a debug build of the
CLI panics before doing anything, on a clap
debug_assert—Command scan: Short option names must be unique for each argument, but '-c' is in use by both 'categories' and 'config'. It is compiled out of release builds, which is why CInever saw it. It does mean
cargo run -- scanis broken for anyone developingthe CLI locally.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TCKh98NmUmfvgpuGyJbNu8