ci+test: wire build-mode invariant gate, harden crash_oob, remove stale wasm#30
Conversation
…le wasm The SNIFS isolation guarantee depends on a precondition the build hadn't been gating: that the shipped wasm guest was compiled with -OReleaseSafe, not -OReleaseFast. Build-mode itself was already locked at the source level (zig/build.zig hardcodes .optimize per artifact; Justfile build-wasm passes -OReleaseSafe / -OReleaseFast literally per recipe). The gap was on the test side — no CI job ever ran the demo suite against the built artifacts, so the property was asserted by a test that wasn't gated, and the test fixture had a path bug that meant it couldn't even find its wasm files. Five fixes, all in service of making the build-mode property load-bearing end-to-end: 1. .github/workflows/e2e.yml — replace template stubs with concrete job: setup-zig 0.15.0 → just build-wasm (both modes) → setup-beam OTP 28 / Elixir 1.18 → mix test. Two of the action SHA pins inherited from the template were fabricated (gh api → 422): goto-bus-stop/setup-zig and erlef/setup-beam. Replaced with verified pins for v2.2.1 and v1.24.0 respectively. The same fake pins propagated from rsr-template-repo into ~35 other estate workflows (10 active) — follow-up fixes pending. 2. demo/test/snif_demo_test.exs — @safe/@fast paths were __DIR__/../priv/... which resolves to demo/priv/ (doesn't exist); files live at top-level priv/. Fixed to __DIR__/../../priv/... so the suite can actually find its fixtures (it could not since the binaries were first committed on 2026-05-21). 3. zig/src/safe_nif.zig — crash_oob's ReleaseFast demonstration was ambiguous between "load was DCE'd" and "read 0 from adjacent memory." Replaced the function-local const array with a layout-pinned extern struct { arr: [3]i32, canary: i32 } at module scope; runtime_index = 3 indexes one past the end. Under ReleaseSafe the bounds check fires before the load; under ReleaseFast the check is stripped and the load deterministically lands on canary = 0x0BADF00D, returning 195_948_557 — a recognisable wrong answer that proves the silent-corruption mode is real, not an artifact of the optimiser dropping the load. 4. demo/test/snif_demo_test.exs — corresponding test tightened from `assert {:ok, _}` to `assert {:ok, [195_948_557]}`. A drift in this value means either Zig data-segment layout changed or — worse — the ReleaseFast artifact has somehow regained safety checks. Either case should surface visibly, not pass silently. 5. priv/*.wasm + .gitignore + Justfile — committed wasm artifacts go stale the moment any zig source changes (which this commit does), so a local `mix test` against them would fail with confusing assertion mismatches. git rm the binaries, add /priv/*.wasm and /zig-out/ to .gitignore so they can't drift again, and make `just test-demo` depend on `just build-wasm` so the local flow always builds fresh. The gate is the property test, not a flag grep. If the gate is red, the SNIFS isolation claim is void on the affected build.
🔍 Hypatia Security ScanFindings: 101 issues detected
View findings[
{
"reason": "Action ig 0.15.0\n uses: goto-bus-stop/setup-zig@abea47f85 needs attention",
"type": "unpinned_action",
"file": "e2e.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action perpolymath/standards/.github/workflows/governance-reusable.yml@main\n needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action actions/checkout@v4 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action softprops/action-gh-release@v2 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "No permissions declaration -- add permissions: read-all",
"type": "missing_permissions",
"file": "release.yml",
"action": "add_permissions",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "codeql.yml lists `language: javascript-typescript` but the repo has no source files in any CodeQL-scannable language. The analyze job will exit 'no source files' on every run. Switch the matrix to `actions` (which scans workflow files — every repo has those).",
"type": "codeql_language_matrix_mismatch",
"file": "codeql.yml",
"action": "switch_codeql_matrix_to_actions",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Issue in boj-build.yml",
"type": "unknown",
"file": "boj-build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "unknown",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
## Summary
Four of the action SHA pins in `.github/workflows/e2e.yml` template
comments were fabricated. `gh api repos/<org>/<action>/commits/<sha>`
returns HTTP 422 ("No commit found for SHA") for all four.
The pins live in template-comment scaffold (`# - uses: ...`) so they're
never executed in this repo. But this template propagated across the
estate via the RSR copy-and-customise flow: **10 downstream repos
uncommented the stubs and ended up with active fake pins that would 422
at action resolution**.
## Fakes replaced
| Action | Fake SHA | Real SHA | Tag |
|---|---|---|---|
| `goto-bus-stop/setup-zig` | `7ab2955...3608` (first 16 hex collide
with v2.2.0's `7ab2955...2802d`, rest fabricated) |
`abea47f85e598557f500fa1fd2ab7464fcb39406` | v2.2.1 |
| `erlef/setup-beam` | `5a67e1a...a66c07` (no resemblance to any
release) | `fc68ffb90438ef2936bbb3251622353b3dcb2f93` | v1.24.0 |
| `denoland/setup-deno` | `5fae568...c3497` |
`667a34cdef165d8d2b2e98dde39547c9daac7282` | v2.0.4 |
| `haskell-actions/setup` | `dd344bc...3a40fce` |
`cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553` | v2.11.0 |
All 4 replacements verified real via `gh api`. Four other pins in the
file (`actions/checkout`, `actions/upload-artifact`,
`dtolnay/rust-toolchain`, `Swatinem/rust-cache`) verified real and left
unchanged.
## How this was caught
Wiring CI for `snifs` (PR hyperpolymath/snifs#30) involved uncommenting
the Zig FFI and Elixir template stubs into a concrete workflow. The
`setup-zig` and `setup-beam` pins from this template 422'd. Audit across
the estate found the same fakes propagated to 41 unique `e2e.yml` files
(10 with active uncommented pins).
## Test plan
- [ ] All 6 SHA lines in the diff match the verified pins
- [ ] `gh api repos/<org>/<action>/commits/<sha>` returns 200 for each
(it does — verified in the commit message)
- [ ] Follow-up PRs land in the 3 downstream repos with active fake
pins: `odds-and-sods-package-manager`, `proven`, `proven-servers`
The pin `goto-bus-stop/setup-zig@7ab2955eb728f5440978d7b4f723a50dea1f3608` is fabricated (`gh api repos/goto-bus-stop/setup-zig/commits/7ab2955... -> 422`). The fake SHA's first 16 hex chars happen to collide with v2.2.0's real SHA `7ab2955eb728f5440978d5824358023be3a2802d` but the rest is fabricated. Affected: 2 sites in .github/workflows/e2e.yml. Replaced with the verified v2.2.1 pin `abea47f85e598557f500fa1fd2ab7464fcb39406`. Provenance: propagated from rsr-template-repo#81 (merged); discovered while wiring CI for snifs (hyperpolymath/snifs#30); 1 of 3 fan-out PRs alongside odds-and-sods-package-manager#39 and (incoming) proven-servers.
## Summary The pin `goto-bus-stop/setup-zig@7ab2955eb728f5440978d7b4f723a50dea1f3608` at `.github/workflows/e2e.yml:46,157` is fabricated. `gh api repos/goto-bus-stop/setup-zig/commits/7ab2955... → 422 "No commit found for SHA"`. The fake SHA's first 16 hex chars happen to collide with v2.2.0's real SHA `7ab2955eb728f5440978d5824358023be3a2802d`, but the rest is fabricated — making this an especially subtle failure to catch by eye. Whoever introduced the pin likely mistyped or hallucinated the suffix. Replaced with verified v2.2.1 pin `abea47f85e598557f500fa1fd2ab7464fcb39406`. ## Provenance - Propagated from `rsr-template-repo`'s `e2e.yml` template (commented stub, uncommented when concretising here) - Template fixed upstream: hyperpolymath/rsr-template-repo#81 (merged) - Discovered while wiring CI for snifs: hyperpolymath/snifs#30 - 2 of 3 fan-out PRs (alongside `odds-and-sods-package-manager#39` and `proven-servers` incoming) ## Test plan - [ ] Both setup-zig lines in diff use `abea47f85e...` (v2.2.1) - [ ] `gh api repos/goto-bus-stop/setup-zig/commits/abea47f85e598557f500fa1fd2ab7464fcb39406` returns 200 - [ ] The e2e workflow's Zig setup step actually resolves instead of 422'ing on next run
## Summary Standards had two distinct `erlef/setup-beam` SHAs and one wrong version comment across canonical workflows. Consolidated to `fc68ffb90438ef2936bbb3251622353b3dcb2f93` (v1.24.0, 2026-03-30) with correct annotations. ## Before / after | File | Before | After | |---|---|---| | `hypatia-scan.yml` | `fc68ffb...` ` # v1.18.2` (comment wrong; SHA is actually v1.24.0) | `fc68ffb...` ` # v1.24.0` | | `hypatia-scan-reusable.yml` | same as above | same fix | | `elixir-ci-reusable.yml` | `5304e04...` ` # v1.18.2` (stale pin from 2024-09-25) | `fc68ffb...` ` # v1.24.0` | Both SHAs verified real via `gh api repos/erlef/setup-beam/commits/<sha>`. Fragmentation likely from incremental updates that didn't sweep all three files together. ## Why this matters `elixir-ci-reusable.yml` is the load-bearing change: ~12 estate repos call it as a wrapper. This PR propagates the v1.18.2 → v1.24.0 upgrade across the fleet through that single dependency. ## Provenance Companion to: - `hyperpolymath/rsr-template-repo#81` (template fakes fixed, merged) - `hyperpolymath/odds-and-sods-package-manager#39` (downstream) - `hyperpolymath/proven#93` (downstream, merged) - `hyperpolymath/proven-servers#19` (downstream) All fan-out from the SNIFS CI gate work in `hyperpolymath/snifs#30`. ## Test plan - [ ] All 3 setup-beam pins resolve to `fc68ffb...` with comment `# v1.24.0` - [ ] `gh api repos/erlef/setup-beam/commits/fc68ffb90438ef2936bbb3251622353b3dcb2f93` returns 200 - [ ] `hypatia-scan` workflow runs green on this PR - [ ] No downstream repos using elixir-ci-reusable break under v1.24.0 (Elixir/OTP version inputs unchanged)
## Summary The pin `goto-bus-stop/setup-zig@7ab2955eb728f5440978d7b4f723a50dea1f3608` at `.github/workflows/e2e.yml:42` is fabricated. `gh api repos/goto-bus-stop/setup-zig/commits/7ab2955... → 422 "No commit found for SHA"`. The fake SHA's first 16 hex chars collide with v2.2.0's real SHA `7ab2955eb728f5440978d5824358023be3a2802d`, but the rest is fabricated — a partial collision that would slip past visual review. Replaced with verified v2.2.1 pin `abea47f85e598557f500fa1fd2ab7464fcb39406`. ## Provenance - Propagated from `rsr-template-repo`'s `e2e.yml` template (commented stub, uncommented when concretising here) - Template fixed upstream: hyperpolymath/rsr-template-repo#81 (merged) - Discovered while wiring CI for snifs: hyperpolymath/snifs#30 - 3 of 3 fan-out PRs (alongside `odds-and-sods-package-manager#39` and `proven#93`) ## Test plan - [ ] The setup-zig line uses `abea47f85e...` (v2.2.1) - [ ] `gh api repos/goto-bus-stop/setup-zig/commits/abea47f85e598557f500fa1fd2ab7464fcb39406` returns 200 - [ ] The e2e workflow's Zig setup step actually resolves on next run
This file inherits its e2e.yml from the rsr-template-repo template, which carried 4 fabricated action SHA pins in template-comment stubs. The pins were inert in commented form here, but would have 422'd at action resolution if anyone uncommented them. Caught and fixed at the template source in hyperpolymath/rsr-template-repo#81 (merged); this PR sweeps the propagated stubs. goto-bus-stop/setup-zig 7ab2955...3608 -> abea47f...39406 (v2.2.1) erlef/setup-beam 5a67e1a...a66c07 -> fc68ffb...db2f93 (v1.24.0) denoland/setup-deno 5fae568...c3497 -> 667a34c...c7282 (v2.0.4) haskell-actions/setup dd344bc...3a40fce -> cd0d9bd...e0553 (v2.11.0) All four real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. Originally discovered while wiring CI for hyperpolymath/snifs#30.
… fakes Two fixes against PR #30's first CI run: 1. Install Zig step failed: "Could not find version 0.15.0 for platform x86_64-linux." Zig 0.15.0 was never released — the 0.15 line jumps from master to 0.15.1/0.15.2. README's "Requires Zig 0.15+" is satisfied by 0.15.1 (current stable). 2. Clean the 4 commented fake-SHA template stubs in the trailing DENO/PLAYWRIGHT/HASKELL/ZIG-BENCH placeholder sections (lines 93, 111, 131, 177). These are inert (template comments) but were propagated from rsr-template-repo's pre-#81 template — fixing them here aligns this file with the rest of the template-stub sweep. Same replacements as rsr-template-repo#81 (merged): goto-bus-stop/setup-zig 7ab2955...3608 -> abea47f85e...39406 (v2.2.1) denoland/setup-deno 5fae568...c3497 -> 667a34cdef...c7282 (v2.0.4) haskell-actions/setup dd344bc...3a40fce -> cd0d9bdd65...e0553 (v2.11.0)
🔍 Hypatia Security ScanFindings: 101 issues detected
View findings[
{
"reason": "Action ig 0.15.1\n uses: goto-bus-stop/setup-zig@abea47f85 needs attention",
"type": "unpinned_action",
"file": "e2e.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action perpolymath/standards/.github/workflows/governance-reusable.yml@main\n needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action actions/checkout@v4 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action softprops/action-gh-release@v2 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "No permissions declaration -- add permissions: read-all",
"type": "missing_permissions",
"file": "release.yml",
"action": "add_permissions",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "codeql.yml lists `language: javascript-typescript` but the repo has no source files in any CodeQL-scannable language. The analyze job will exit 'no source files' on every run. Switch the matrix to `actions` (which scans workflow files — every repo has those).",
"type": "codeql_language_matrix_mismatch",
"file": "codeql.yml",
"action": "switch_codeql_matrix_to_actions",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Issue in boj-build.yml",
"type": "unknown",
"file": "boj-build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "unknown",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
The pin `erlef/setup-beam@5a67e1a1dd86cae5e5bef84e2da5060406a66c07` was fabricated (`gh api repos/erlef/setup-beam/commits/5a67e1a... -> 422`). It propagated into this repo from the rsr-template-repo e2e.yml template where it lived as a comment stub that someone uncommented for the 4 active sites in this repo. Caught when wiring the equivalent CI in snifs (hyperpolymath/snifs#30); fixed upstream in hyperpolymath/rsr-template-repo#81. Replaced with the verified v1.24.0 pin `fc68ffb90438ef2936bbb3251622353b3dcb2f93` across: - .github/workflows/nif-build.yml - .github/workflows/e2e.yml (2 jobs) - .github/workflows/trust-pipeline-e2e.yml Before this fix the affected workflows would have 422'd at action resolution the moment they were triggered.
## Summary The pin `haskell-actions/setup@dd344bc1cec854a9b55c2b857c28b688010e4fce` at `.github/workflows/e2e.yml:39` is fabricated. `gh api repos/haskell-actions/setup/commits/dd344bc... → 422 "No commit found for SHA"`. The e2e job would have failed at action resolution on next run. Replaced with verified v2.11.0 pin `cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553`. ## Provenance - Propagated from `rsr-template-repo`'s `e2e.yml` template (commented stub uncommented when concretising here) - Template fixed upstream: hyperpolymath/rsr-template-repo#81 (merged) - Discovered while wiring CI for snifs: hyperpolymath/snifs#30 - Companion to: `panll#61`, `odds-and-sods-package-manager#39`, `proven#93` (merged), `proven-servers#19`, and a throttled sweep of 16 commented-stub repos ## Test plan - [ ] setup line uses `cd0d9bd...` (v2.11.0) - [ ] `gh api repos/haskell-actions/setup/commits/cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553` returns 200 - [ ] The e2e job resolves its Haskell setup step instead of 422'ing
This file inherits its e2e.yml from the rsr-template-repo template, which carried 4 fabricated action SHA pins in template-comment stubs. The pins were inert in commented form here, but would have 422'd at action resolution if anyone uncommented them. Caught and fixed at the template source in hyperpolymath/rsr-template-repo#81 (merged); this PR sweeps the propagated stubs. goto-bus-stop/setup-zig 7ab2955...3608 -> abea47f...39406 (v2.2.1) erlef/setup-beam 5a67e1a...a66c07 -> fc68ffb...db2f93 (v1.24.0) denoland/setup-deno 5fae568...c3497 -> 667a34c...c7282 (v2.0.4) haskell-actions/setup dd344bc...3a40fce -> cd0d9bd...e0553 (v2.11.0) All four real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. Originally discovered while wiring CI for hyperpolymath/snifs#30.
…name The previous form `--name priv/safe_nif_ReleaseSafe` worked in older Zig versions where `--name` was treated as a name-or-output-path. Zig 0.15.1 tightened this: `--name` is the package name, must be a bare identifier. Folder separators now fail with `error: invalid package name 'priv/...': cannot contain folder separators`. Replaced with `--name <basename>` + `-femit-bin=priv/<basename>.wasm` so the name is a clean identifier and the output path is set explicitly. Added `mkdir -p priv` because the priv/ wasm artifacts are gitignored now and may not exist in a fresh clone. Caught by the e2e gate this PR introduces — exactly the kind of build-mode drift it's meant to surface.
The pin `denoland/setup-deno@5fae568d37c3b73e0e4ca63d4e2c4e324a2b3497` at .github/workflows/e2e.yml:44,67,90 is fabricated (`gh api repos/ denoland/setup-deno/commits/5fae568... -> 422`). All 3 jobs that attempt to run (e2e, crosscutting, benchmarks) would have failed at action resolution. Replaced with verified v2.0.4 pin `667a34cdef165d8d2b2e98dde39547c9daac7282`. Provenance: propagated from rsr-template-repo#81 (merged); discovered while wiring CI for snifs (hyperpolymath/snifs#30). One of the active- pin fan-out PRs alongside reposystem (incoming) and the throttled sweep of ~16 commented-stub repos.
This file inherits its e2e.yml from the rsr-template-repo template, which carried 4 fabricated action SHA pins in template-comment stubs. The pins were inert in commented form here, but would have 422'd at action resolution if anyone uncommented them. Caught and fixed at the template source in hyperpolymath/rsr-template-repo#81 (merged); this PR sweeps the propagated stubs. goto-bus-stop/setup-zig 7ab2955...3608 -> abea47f...39406 (v2.2.1) erlef/setup-beam 5a67e1a...a66c07 -> fc68ffb...db2f93 (v1.24.0) denoland/setup-deno 5fae568...c3497 -> 667a34c...c7282 (v2.0.4) haskell-actions/setup dd344bc...3a40fce -> cd0d9bd...e0553 (v2.11.0) All four real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. Originally discovered while wiring CI for hyperpolymath/snifs#30.
🔍 Hypatia Security ScanFindings: 101 issues detected
View findings[
{
"reason": "Action ig 0.15.1\n uses: goto-bus-stop/setup-zig@abea47f85 needs attention",
"type": "unpinned_action",
"file": "e2e.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action perpolymath/standards/.github/workflows/governance-reusable.yml@main\n needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action actions/checkout@v4 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action softprops/action-gh-release@v2 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "No permissions declaration -- add permissions: read-all",
"type": "missing_permissions",
"file": "release.yml",
"action": "add_permissions",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "codeql.yml lists `language: javascript-typescript` but the repo has no source files in any CodeQL-scannable language. The analyze job will exit 'no source files' on every run. Switch the matrix to `actions` (which scans workflow files — every repo has those).",
"type": "codeql_language_matrix_mismatch",
"file": "codeql.yml",
"action": "switch_codeql_matrix_to_actions",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Issue in boj-build.yml",
"type": "unknown",
"file": "boj-build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "unknown",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
Previous run failed at `mix deps.get` with: (ArgumentError) The module Hex.Repo was given as a child to a supervisor but it does not exist setup-beam bundles Hex 2.4.2 with the BEAM install, but the bundled version mismatches the Elixir 1.18 / OTP 28 runtime — the Hex archive's supervisor tree references a Hex.Repo module that exists in newer Hex but not in the bundled snapshot. The standard remedy is to install the latest Hex archive for the runtime BEFORE invoking mix deps.get. Added `mix local.hex --force` and `mix local.rebar --force` ahead of deps.get + test. This is the canonical setup-beam-on-newer-Elixir pattern.
🔍 Hypatia Security ScanFindings: 101 issues detected
View findings[
{
"reason": "Action ig 0.15.1\n uses: goto-bus-stop/setup-zig@abea47f85 needs attention",
"type": "unpinned_action",
"file": "e2e.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action perpolymath/standards/.github/workflows/governance-reusable.yml@main\n needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action actions/checkout@v4 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action softprops/action-gh-release@v2 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "No permissions declaration -- add permissions: read-all",
"type": "missing_permissions",
"file": "release.yml",
"action": "add_permissions",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "codeql.yml lists `language: javascript-typescript` but the repo has no source files in any CodeQL-scannable language. The analyze job will exit 'no source files' on every run. Switch the matrix to `actions` (which scans workflow files — every repo has those).",
"type": "codeql_language_matrix_mismatch",
"file": "codeql.yml",
"action": "switch_codeql_matrix_to_actions",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Issue in boj-build.yml",
"type": "unknown",
"file": "boj-build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "unknown",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
This file inherits its e2e.yml from the rsr-template-repo template, which carried 4 fabricated action SHA pins in template-comment stubs. The pins were inert in commented form here, but would have 422'd at action resolution if anyone uncommented them. Caught and fixed at the template source in hyperpolymath/rsr-template-repo#81 (merged); this PR sweeps the propagated stubs. goto-bus-stop/setup-zig 7ab2955...3608 -> abea47f...39406 (v2.2.1) erlef/setup-beam 5a67e1a...a66c07 -> fc68ffb...db2f93 (v1.24.0) denoland/setup-deno 5fae568...c3497 -> 667a34c...c7282 (v2.0.4) haskell-actions/setup dd344bc...3a40fce -> cd0d9bd...e0553 (v2.11.0) All four real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. Originally discovered while wiring CI for hyperpolymath/snifs#30.
This file inherits its e2e.yml from the rsr-template-repo template, which carried 4 fabricated action SHA pins in template-comment stubs. The pins were inert in commented form here, but would have 422'd at action resolution if anyone uncommented them. Caught and fixed at the template source in hyperpolymath/rsr-template-repo#81 (merged); this PR sweeps the propagated stubs. goto-bus-stop/setup-zig 7ab2955...3608 -> abea47f...39406 (v2.2.1) erlef/setup-beam 5a67e1a...a66c07 -> fc68ffb...db2f93 (v1.24.0) denoland/setup-deno 5fae568...c3497 -> 667a34c...c7282 (v2.0.4) haskell-actions/setup dd344bc...3a40fce -> cd0d9bd...e0553 (v2.11.0) All four real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. Originally discovered while wiring CI for hyperpolymath/snifs#30.
Previous run loaded Hex 2.4.2 (latest release as of 2026-05-30) under OTP 28 and crashed in BEAM: beam/beam_load.c(594): Error loading function 'Elixir.Hex.Repo':build_hex_core_config/3: op bs_add p x i u x The bs_add opcode was removed in OTP 26+. Hex 2.4.2 was compiled with an older OTP that emitted bs_add — the .beam files are not loadable on OTP 28. No newer Hex release exists yet. README's "OTP 28 / Elixir 1.19.5" claim assumes an unreleased Hex; it's aspirational, not validated. Pinning to OTP 27.3 + Elixir 1.18.0 — the highest currently-available combo that actually starts Hex. Re-evaluate when Hex publishes a release compiled against OTP 26+.
…y works
Previously claimed:
- "11/11 integration tests pass on OTP 28 / Elixir 1.19.5"
- "Requires Elixir 1.15+, OTP 26+"
Both were aspirational, not validated:
1. Elixir 1.19.5 doesn't exist (latest stable is 1.18.x).
2. OTP 28 doesn't actually work — Hex 2.4.2 (current latest release as
of 2026-05-30) has BEAM bytecode that fails to load on OTP 28:
`Error loading function 'Elixir.Hex.Repo':build_hex_core_config/3:
op bs_add p x i u x` — the bs_add opcode was removed in OTP 26+
and Hex hasn't been recompiled against the new instruction set.
Corrected to:
- "11/11 ... on OTP 27.3 / Elixir 1.18.0" (the actual working combo,
matches what the e2e gate is now pinned to)
- "Requires Elixir 1.15+, OTP 26–27" with note about OTP 28 being
unsupported until Hex re-releases.
## Summary Sweeps fake action SHA pins inherited from the rsr-template-repo `e2e.yml` template. All 4 fake SHAs in this file were template comment stubs (inert until uncommented), but the same pattern propagated to ~20 repos across the estate via the RSR copy-and-customise flow. ## Replaced | Action | Fake SHA | Real SHA | Tag | |---|---|---|---| | `goto-bus-stop/setup-zig` | `7ab2955...3608` (partial collision with v2.2.0) | `abea47f85e598557f500fa1fd2ab7464fcb39406` | v2.2.1 | | `erlef/setup-beam` | `5a67e1a...a66c07` | `fc68ffb90438ef2936bbb3251622353b3dcb2f93` | v1.24.0 | | `denoland/setup-deno` | `5fae568...c3497` | `667a34cdef165d8d2b2e98dde39547c9daac7282` | v2.0.4 | | `haskell-actions/setup` | `dd344bc...3a40fce` | `cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553` | v2.11.0 | All real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. ## Provenance - Discovered: wiring CI for `hyperpolymath/snifs#30` - Template source fixed: `hyperpolymath/rsr-template-repo#81` (merged) - Standards consolidation: `hyperpolymath/standards#289` (in flight) - Per-repo fan-out: this PR is part of the sweep across affected estate repos ## Test plan - [ ] Diff shows only template-comment SHA substitutions (no functional change in this repo since the lines were already commented) - [ ] If anyone later uncomments a template stub, the action resolves instead of 422'ing
🔍 Hypatia Security ScanFindings: 101 issues detected
View findings[
{
"reason": "Action ig 0.15.1\n uses: goto-bus-stop/setup-zig@abea47f85 needs attention",
"type": "unpinned_action",
"file": "e2e.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action perpolymath/standards/.github/workflows/governance-reusable.yml@main\n needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action actions/checkout@v4 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action softprops/action-gh-release@v2 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "No permissions declaration -- add permissions: read-all",
"type": "missing_permissions",
"file": "release.yml",
"action": "add_permissions",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "codeql.yml lists `language: javascript-typescript` but the repo has no source files in any CodeQL-scannable language. The analyze job will exit 'no source files' on every run. Switch the matrix to `actions` (which scans workflow files — every repo has those).",
"type": "codeql_language_matrix_mismatch",
"file": "codeql.yml",
"action": "switch_codeql_matrix_to_actions",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Issue in boj-build.yml",
"type": "unknown",
"file": "boj-build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "unknown",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
This file inherits its e2e.yml from the rsr-template-repo template, which carried 4 fabricated action SHA pins in template-comment stubs. The pins were inert in commented form here, but would have 422'd at action resolution if anyone uncommented them. Caught and fixed at the template source in hyperpolymath/rsr-template-repo#81 (merged); this PR sweeps the propagated stubs. goto-bus-stop/setup-zig 7ab2955...3608 -> abea47f...39406 (v2.2.1) erlef/setup-beam 5a67e1a...a66c07 -> fc68ffb...db2f93 (v1.24.0) denoland/setup-deno 5fae568...c3497 -> 667a34c...c7282 (v2.0.4) haskell-actions/setup dd344bc...3a40fce -> cd0d9bd...e0553 (v2.11.0) All four real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. Originally discovered while wiring CI for hyperpolymath/snifs#30.
…ig 0.15.1) Matches what .github/workflows/e2e.yml pins. Notes the OTP-27-not-28 rationale (Hex 2.4.2 BEAM bytecode incompatibility) inline so future contributors don't bump erlang to 28.x and break the Hex archive.
…ub (#82) ## Summary Two cleanups caught during follow-up estate audit after the original template fake-SHA fix (#81): ### 1. `actions/upload-artifact@65c79d7f54e76e4e3c7a8f34db0f4ac8b515c478` — FAKE \`gh api repos/actions/upload-artifact/commits/65c79d7f... → 422\`. **ACTIVE pin** (not template-comment), so any workflow that triggers \`static-analysis-gate.yml\` would 422 at action resolution. Replaced with verified v4.6.2 pin \`ea165f8d65b6e75b540449e92b4886f43607fa02\` — matches the SHA already used by other estate workflows post-#81. 4 sites in \`static-analysis-gate.yml\` (lines 108, 221, 309, 407). ### 2. `version: 0.15.0` Zig version stub Two template-comment stubs in \`e2e.yml\` (lines 65, 176) annotated \`version: 0.15.0\` for setup-zig. Zig 0.15.0 was **never released** — the 0.15 line goes from master to 0.15.1 then 0.15.2. Anyone uncommenting these stubs as-is gets: \`\`\` Error: Could not find version 0.15.0 for platform x86_64-linux \`\`\` Bumped to \`0.15.1\` (current stable in the 0.15 series). ## Not touching The OTP 28.3 / Elixir 1.19.4 pin in static-analysis-gate.yml is intentionally left alone. Both versions exist (verified \`v1.19.4-otp-28\` is a real builds.hex.pm release), and the surrounding step has \`continue-on-error: true\` so any Hex archive incompatibility is non-blocking. The snifs build-mode gate uses Elixir 1.18 + OTP 27.3 because the active mix-deps-get flow needs Hex to actually start — that's a different constraint. ## Provenance Follow-up audit alongside \`hyperpolymath/snifs#30\` and the rsr-template-repo#81 fan-out. The fake upload-artifact SHA was found while sampling estate action pins for the same fabrication pattern that caught the 4 original fakes. ## Test plan - [ ] static-analysis-gate workflow's upload-artifact step actually runs (instead of 422'ing at resolution) - [ ] No downstream regression from the upload-artifact bump (v4.6.2 → still v4 line, no breaking changes) - [ ] If anyone copies the Zig template stub to a new repo, it resolves
This file inherits its e2e.yml from the rsr-template-repo template, which carried 4 fabricated action SHA pins in template-comment stubs. The pins were inert in commented form here, but would have 422'd at action resolution if anyone uncommented them. Caught and fixed at the template source in hyperpolymath/rsr-template-repo#81 (merged); this PR sweeps the propagated stubs. goto-bus-stop/setup-zig 7ab2955...3608 -> abea47f...39406 (v2.2.1) erlef/setup-beam 5a67e1a...a66c07 -> fc68ffb...db2f93 (v1.24.0) denoland/setup-deno 5fae568...c3497 -> 667a34c...c7282 (v2.0.4) haskell-actions/setup dd344bc...3a40fce -> cd0d9bd...e0553 (v2.11.0) All four real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. Originally discovered while wiring CI for hyperpolymath/snifs#30.
…lidate (#62) The build-validation/validate job has been failing on main since the committed package-lock.json went out of sync with package.json. Specific error: npm error Invalid: lock file's picomatch@2.3.2 does not satisfy picomatch@4.0.4 npm error Missing: picomatch@2.3.2 from lock file (×3) Root cause: tailwindcss^3.4.19 pulls in two distinct picomatch consumers at different tree depths — chokidar wants ^2.3.1 (top-level), tinyglobby wants ^4.0.4 (nested). The lockfile has the nested tinyglobby/picomatch@4.0.4 entry but is missing the top-level picomatch@2.3.2 chokidar needs. `npm ci` (strict) refuses to install with missing entries; `npm install` (non-strict) regenerates the missing entries in-place during CI. This is a band-aid, not the real fix: - The real fix is the npm→Deno migration tracked in hyperpolymath/standards#253 (panll is in scope for that umbrella). - The band-aid keeps validate green until that migration lands. - --no-audit --no-fund silences noise. Notes: - Caught during the wider follow-up triage from hyperpolymath/snifs#30's CI gate work, when panll#61 (fake-SHA fix) surfaced the preexisting validate failure.
This file inherits its e2e.yml from the rsr-template-repo template, which carried 4 fabricated action SHA pins in template-comment stubs. The pins were inert in commented form here, but would have 422'd at action resolution if anyone uncommented them. Caught and fixed at the template source in hyperpolymath/rsr-template-repo#81 (merged); this PR sweeps the propagated stubs. goto-bus-stop/setup-zig 7ab2955...3608 -> abea47f...39406 (v2.2.1) erlef/setup-beam 5a67e1a...a66c07 -> fc68ffb...db2f93 (v1.24.0) denoland/setup-deno 5fae568...c3497 -> 667a34c...c7282 (v2.0.4) haskell-actions/setup dd344bc...3a40fce -> cd0d9bd...e0553 (v2.11.0) All four real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. Originally discovered while wiring CI for hyperpolymath/snifs#30.
This file inherits its e2e.yml from the rsr-template-repo template, which carried 4 fabricated action SHA pins in template-comment stubs. The pins were inert in commented form here, but would have 422'd at action resolution if anyone uncommented them. Caught and fixed at the template source in hyperpolymath/rsr-template-repo#81 (merged); this PR sweeps the propagated stubs. goto-bus-stop/setup-zig 7ab2955...3608 -> abea47f...39406 (v2.2.1) erlef/setup-beam 5a67e1a...a66c07 -> fc68ffb...db2f93 (v1.24.0) denoland/setup-deno 5fae568...c3497 -> 667a34c...c7282 (v2.0.4) haskell-actions/setup dd344bc...3a40fce -> cd0d9bd...e0553 (v2.11.0) All four real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. Originally discovered while wiring CI for hyperpolymath/snifs#30.
## Summary Sweeps fake action SHA pins inherited from the rsr-template-repo `e2e.yml` template. All 4 fake SHAs in this file were template comment stubs (inert until uncommented), but the same pattern propagated to ~20 repos across the estate via the RSR copy-and-customise flow. ## Replaced | Action | Fake SHA | Real SHA | Tag | |---|---|---|---| | `goto-bus-stop/setup-zig` | `7ab2955...3608` (partial collision with v2.2.0) | `abea47f85e598557f500fa1fd2ab7464fcb39406` | v2.2.1 | | `erlef/setup-beam` | `5a67e1a...a66c07` | `fc68ffb90438ef2936bbb3251622353b3dcb2f93` | v1.24.0 | | `denoland/setup-deno` | `5fae568...c3497` | `667a34cdef165d8d2b2e98dde39547c9daac7282` | v2.0.4 | | `haskell-actions/setup` | `dd344bc...3a40fce` | `cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553` | v2.11.0 | All real SHAs verified via `gh api repos/<org>/<action>/commits/<sha>`. ## Provenance - Discovered: wiring CI for `hyperpolymath/snifs#30` - Template source fixed: `hyperpolymath/rsr-template-repo#81` (merged) - Standards consolidation: `hyperpolymath/standards#289` (in flight) - Per-repo fan-out: this PR is part of the sweep across affected estate repos ## Test plan - [ ] Diff shows only template-comment SHA substitutions (no functional change in this repo since the lines were already commented) - [ ] If anyone later uncomments a template stub, the action resolves instead of 422'ing
🔍 Hypatia Security ScanFindings: 101 issues detected
View findings[
{
"reason": "Action ig 0.15.1\n uses: goto-bus-stop/setup-zig@abea47f85 needs attention",
"type": "unpinned_action",
"file": "e2e.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action perpolymath/standards/.github/workflows/governance-reusable.yml@main\n needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action actions/checkout@v4 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action softprops/action-gh-release@v2 needs attention",
"type": "unpinned_action",
"file": "release.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "No permissions declaration -- add permissions: read-all",
"type": "missing_permissions",
"file": "release.yml",
"action": "add_permissions",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "codeql.yml lists `language: javascript-typescript` but the repo has no source files in any CodeQL-scannable language. The analyze job will exit 'no source files' on every run. Switch the matrix to `actions` (which scans workflow files — every repo has those).",
"type": "codeql_language_matrix_mismatch",
"file": "codeql.yml",
"action": "switch_codeql_matrix_to_actions",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Issue in boj-build.yml",
"type": "unknown",
"file": "boj-build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "unknown",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "unknown",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
…y) (#63) * fix(ci): replace npm ci with npm install — lockfile drift unblocks validate The build-validation/validate job has been failing on main since the committed package-lock.json went out of sync with package.json. Specific error: npm error Invalid: lock file's picomatch@2.3.2 does not satisfy picomatch@4.0.4 npm error Missing: picomatch@2.3.2 from lock file (×3) Root cause: tailwindcss^3.4.19 pulls in two distinct picomatch consumers at different tree depths — chokidar wants ^2.3.1 (top-level), tinyglobby wants ^4.0.4 (nested). The lockfile has the nested tinyglobby/picomatch@4.0.4 entry but is missing the top-level picomatch@2.3.2 chokidar needs. `npm ci` (strict) refuses to install with missing entries; `npm install` (non-strict) regenerates the missing entries in-place during CI. This is a band-aid, not the real fix: - The real fix is the npm→Deno migration tracked in hyperpolymath/standards#253 (panll is in scope for that umbrella). - The band-aid keeps validate green until that migration lands. - --no-audit --no-fund silences noise. Notes: - Caught during the wider follow-up triage from hyperpolymath/snifs#30's CI gate work, when panll#61 (fake-SHA fix) surfaced the preexisting validate failure. * fix(ci): swap npm run res:build → npx rescript build (no scripts entry) build-validation.yml line 56 invoked `npm run res:build` but package.json has no `scripts` entry (the project's canonical ReScript invocation per .claude/CLAUDE.md is `npx rescript build` directly). This blocked the `validate` required check across every PR. Use the canonical form so the gate works without polluting package.json with a stub script alias. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary My earlier #30 work pinned to OTP 27.3 / Elixir 1.18.0 based on the (then-correct) snifs CI failure with Hex 2.4.2 on OTP 28. The reasoning was overgeneralised: OTP 28 + Elixir **1.18** is broken, but OTP 28.3 + Elixir **1.19.4** works fine. Verified via bofj-kitt's static-analysis-gate.yml runs successfully loading Hex 2.4.2 and fetching dozens of packages. ## What changes - **README**: replaces "OTP 28 not supported" with the correct distinction — Elixir 1.18 + OTP 28 is broken (Hex archive incompatibility), Elixir 1.19 + OTP 28 works. - **.github/workflows/e2e.yml**: bumps CI pin from OTP 27.3 / Elixir 1.18.0 → OTP 28.3 / Elixir 1.19.4 to match estate-wide convention. - **.tool-versions**: matches new CI pin. ## Why this matters Per-Elixir-version Hex archives at `hex.pm/installs/<elixir_version>/hex-<hex_version>.ez` are compiled differently. The Elixir 1.18 archive has `bs_add` BEAM bytecode that won't load on OTP 28; the Elixir 1.19 archive doesn't. Bumping Elixir alongside OTP fixes the original issue without staying on the older runtime. ## Test plan - [ ] E2E gate goes green on OTP 28.3 / Elixir 1.19.4 - [ ] If e2e fails, the issue is something other than what we already verified (Hex loads fine on this combo) ## Note on Actions budget This is one PR, one CI run — minimal incremental cost. Worth landing because the current README's reasoning is misleading for anyone consulting it (and it would block future people from using the same combo the rest of the estate uses).
…corruption fakes (#397) ## Summary Adds a new `Hypatia.Rules.CicdRules` entry that flags GitHub Action pins where the SHA is fabricated. Complement to the existing `:unpinned_action` rule. ## Problem Estate audit on 2026-05-30 found **67 fake action SHA pairs across ~50 repos** (11% fabrication rate across 372 unique pins). Verified via `gh api repos/<org>/<action>/commits/<sha>` returning 422. **Universal pattern: partial-prefix corruption.** The first 8-20 hex chars match a real release's SHA; the suffix is fabricated. Examples: | Fake | Real | Real version | |---|---|---| | `7ab2955eb728f5440978d`**7b4f723a50dea1f3608** | `7ab2955eb728f5440978d`**5824358023be3a2802d** | setup-zig v2.2.0 | | `49933ea5288caeca8642`**195f2b846b8bbe245a93** | `49933ea5288caeca8642`**d1e84afbd3f7d6820020** | setup-node v4.4.0 | | `909cc5acb0`**135c37a79510dd77767e217930de55** | `909cc5acb0`**fdd60627fb858598759246509fa755** | setup-deno v2.0.2 | Almost certainly a single AI-hallucination event that propagated across the estate via copy-customise of templates. The fakes slip past visual review because of the matching prefix. ## What this rule does Adds `:known_fake_action_sha` to `@blocked_patterns` enumerating the 25 known fakes from the audit. Caught at scan time; blocks new code from re-introducing them. Reason field links to the substitution map in `project_estate_fake_action_sha_punch_list_2026_05_30` memory entry. `applies_to: [\"*.yml\", \"*.yaml\"]` to scope to workflow files. ## Tested inline (transcript) ``` ✓ expect=true got=true :: known fake setup-beam ✓ expect=true got=true :: known fake upload-artifact (partial-prefix) ✓ expect=false got=false :: REAL upload-artifact v4.6.2 (no false-positive) ✓ expect=false got=false :: REAL setup-node v4.4.0 (no false-positive) ✓ expect=true got=true :: fake setup-node (partial-prefix corruption) ✓ expect=true got=true :: fake codeql ✓ expect=false got=false :: REAL checkout v4 (no false-positive) ``` ## Out of scope For proactive detection of FUTURE fakes (beyond the static 25-entry list), a `mix hypatia.verify_action_shas` task is the right shape — needs network access to `gh api`, can't be a static-regex rule. Documented as a follow-up in the punch-list memory. ## Provenance Discovered while wiring `hyperpolymath/snifs#30` build-mode CI gate. The static-rule design intent was previously captured in `feedback_verify_action_sha_pins` memory: > Consider adding a complementary rule that VERIFIES SHA pins resolve upstream — would have caught all four fakes above. This PR ships the static-list version of that idea. ## Test plan - [ ] Rule compiles + registers (verified locally) - [ ] No false-positives on real action SHAs in current estate workflows - [ ] When scanning the 50 repos still carrying these fakes, the rule flags them - [ ] After round-2 sweep (admin-merge style, in flight as PID 607915) completes, the rule's positives should drop to zero estate-wide
…#12) These pins were partial-prefix-corruption fakes — fabricated SHAs that share a prefix with a real version's SHA but have fabricated suffixes, slipping past visual review. Verified fake via `gh api commits/<sha> -> 422`. The fix preserves the version the author originally intended (read from the `# vX.Y.Z` comment alongside each pin), rather than blindly bumping to latest. This is important for actions where check-name reporting can differ between major versions (e.g. CodeQL) — keeping the same major preserves any branch-protection contexts that reference check names. Substitutions applied (those present in this repo only — see diff): goto-bus-stop/setup-zig v2.2.1 abea47f85e... erlef/setup-beam v1.24.0 fc68ffb904... erlef/setup-beam v1.18.2 5304e04ea2... erlef/setup-beam v1.19.0 8aa8a857c6... denoland/setup-deno v2.0.4 667a34cdef... denoland/setup-deno v2.0.2 909cc5acb0... denoland/setup-deno v1.1.4 041b854f97... haskell-actions/setup v2.11.0 cd0d9bdd65... actions/upload-artifact v4.6.2 ea165f8d65b6e75b... actions/setup-node v4.4.0 49933ea5288caeca8642d1e84afbd3f7d6820020 actions/setup-node v4.2.0 1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a trufflesecurity/trufflehog v3.95.3 37b77001d0... trufflesecurity/trufflehog v3.82.13 1aa1871f9a... trufflesecurity/trufflehog v3.63.6 f699f60e89... github/codeql-action/* v3.36.0 03e4368ac7... github/codeql-action/* v3.31.10 4bdb89f480... github/codeql-action/* v3.28.0 48ab28a6f5... github/codeql-action/* v4.36.0 7211b7c807... Swatinem/rust-cache v2.7.8 9d47c6ad4b... gitleaks/gitleaks-action v2.3.7 83373cf2f8... Verified real via `gh api repos/<org>/<action>/commits/<sha>`. Provenance: [[project_estate_fake_action_sha_punch_list_2026_05_30]]; caught during the estate audit triggered by hyperpolymath/snifs#30.
…#56) These pins were partial-prefix-corruption fakes — fabricated SHAs that share a prefix with a real version's SHA but have fabricated suffixes, slipping past visual review. Verified fake via `gh api commits/<sha> -> 422`. The fix preserves the version the author originally intended (read from the `# vX.Y.Z` comment alongside each pin), rather than blindly bumping to latest. This is important for actions where check-name reporting can differ between major versions (e.g. CodeQL) — keeping the same major preserves any branch-protection contexts that reference check names. Substitutions applied (those present in this repo only — see diff): goto-bus-stop/setup-zig v2.2.1 abea47f85e... erlef/setup-beam v1.24.0 fc68ffb904... erlef/setup-beam v1.18.2 5304e04ea2... erlef/setup-beam v1.19.0 8aa8a857c6... denoland/setup-deno v2.0.4 667a34cdef... denoland/setup-deno v2.0.2 909cc5acb0... denoland/setup-deno v1.1.4 041b854f97... haskell-actions/setup v2.11.0 cd0d9bdd65... actions/upload-artifact v4.6.2 ea165f8d65b6e75b... actions/setup-node v4.4.0 49933ea5288caeca8642d1e84afbd3f7d6820020 actions/setup-node v4.2.0 1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a trufflesecurity/trufflehog v3.95.3 37b77001d0... trufflesecurity/trufflehog v3.82.13 1aa1871f9a... trufflesecurity/trufflehog v3.63.6 f699f60e89... github/codeql-action/* v3.36.0 03e4368ac7... github/codeql-action/* v3.31.10 4bdb89f480... github/codeql-action/* v3.28.0 48ab28a6f5... github/codeql-action/* v4.36.0 7211b7c807... Swatinem/rust-cache v2.7.8 9d47c6ad4b... gitleaks/gitleaks-action v2.3.7 83373cf2f8... Verified real via `gh api repos/<org>/<action>/commits/<sha>`. Provenance: [[project_estate_fake_action_sha_punch_list_2026_05_30]]; caught during the estate audit triggered by hyperpolymath/snifs#30.
…#23) These pins were partial-prefix-corruption fakes — fabricated SHAs that share a prefix with a real version's SHA but have fabricated suffixes, slipping past visual review. Verified fake via `gh api commits/<sha> -> 422`. The fix preserves the version the author originally intended (read from the `# vX.Y.Z` comment alongside each pin), rather than blindly bumping to latest. This is important for actions where check-name reporting can differ between major versions (e.g. CodeQL) — keeping the same major preserves any branch-protection contexts that reference check names. Substitutions applied (those present in this repo only — see diff): goto-bus-stop/setup-zig v2.2.1 abea47f85e... erlef/setup-beam v1.24.0 fc68ffb904... erlef/setup-beam v1.18.2 5304e04ea2... erlef/setup-beam v1.19.0 8aa8a857c6... denoland/setup-deno v2.0.4 667a34cdef... denoland/setup-deno v2.0.2 909cc5acb0... denoland/setup-deno v1.1.4 041b854f97... haskell-actions/setup v2.11.0 cd0d9bdd65... actions/upload-artifact v4.6.2 ea165f8d65b6e75b... actions/setup-node v4.4.0 49933ea5288caeca8642d1e84afbd3f7d6820020 actions/setup-node v4.2.0 1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a trufflesecurity/trufflehog v3.95.3 37b77001d0... trufflesecurity/trufflehog v3.82.13 1aa1871f9a... trufflesecurity/trufflehog v3.63.6 f699f60e89... github/codeql-action/* v3.36.0 03e4368ac7... github/codeql-action/* v3.31.10 4bdb89f480... github/codeql-action/* v3.28.0 48ab28a6f5... github/codeql-action/* v4.36.0 7211b7c807... Swatinem/rust-cache v2.7.8 9d47c6ad4b... gitleaks/gitleaks-action v2.3.7 83373cf2f8... Verified real via `gh api repos/<org>/<action>/commits/<sha>`. Provenance: [[project_estate_fake_action_sha_punch_list_2026_05_30]]; caught during the estate audit triggered by hyperpolymath/snifs#30.
…#25) These pins were partial-prefix-corruption fakes — fabricated SHAs that share a prefix with a real version's SHA but have fabricated suffixes, slipping past visual review. Verified fake via `gh api commits/<sha> -> 422`. The fix preserves the version the author originally intended (read from the `# vX.Y.Z` comment alongside each pin), rather than blindly bumping to latest. This is important for actions where check-name reporting can differ between major versions (e.g. CodeQL) — keeping the same major preserves any branch-protection contexts that reference check names. Substitutions applied (those present in this repo only — see diff): goto-bus-stop/setup-zig v2.2.1 abea47f85e... erlef/setup-beam v1.24.0 fc68ffb904... erlef/setup-beam v1.18.2 5304e04ea2... erlef/setup-beam v1.19.0 8aa8a857c6... denoland/setup-deno v2.0.4 667a34cdef... denoland/setup-deno v2.0.2 909cc5acb0... denoland/setup-deno v1.1.4 041b854f97... haskell-actions/setup v2.11.0 cd0d9bdd65... actions/upload-artifact v4.6.2 ea165f8d65b6e75b... actions/setup-node v4.4.0 49933ea5288caeca8642d1e84afbd3f7d6820020 actions/setup-node v4.2.0 1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a trufflesecurity/trufflehog v3.95.3 37b77001d0... trufflesecurity/trufflehog v3.82.13 1aa1871f9a... trufflesecurity/trufflehog v3.63.6 f699f60e89... github/codeql-action/* v3.36.0 03e4368ac7... github/codeql-action/* v3.31.10 4bdb89f480... github/codeql-action/* v3.28.0 48ab28a6f5... github/codeql-action/* v4.36.0 7211b7c807... Swatinem/rust-cache v2.7.8 9d47c6ad4b... gitleaks/gitleaks-action v2.3.7 83373cf2f8... Verified real via `gh api repos/<org>/<action>/commits/<sha>`. Provenance: [[project_estate_fake_action_sha_punch_list_2026_05_30]]; caught during the estate audit triggered by hyperpolymath/snifs#30.
…#14) These pins were partial-prefix-corruption fakes — fabricated SHAs that share a prefix with a real version's SHA but have fabricated suffixes, slipping past visual review. Verified fake via `gh api commits/<sha> -> 422`. The fix preserves the version the author originally intended (read from the `# vX.Y.Z` comment alongside each pin), rather than blindly bumping to latest. This is important for actions where check-name reporting can differ between major versions (e.g. CodeQL) — keeping the same major preserves any branch-protection contexts that reference check names. Substitutions applied (those present in this repo only — see diff): goto-bus-stop/setup-zig v2.2.1 abea47f85e... erlef/setup-beam v1.24.0 fc68ffb904... erlef/setup-beam v1.18.2 5304e04ea2... erlef/setup-beam v1.19.0 8aa8a857c6... denoland/setup-deno v2.0.4 667a34cdef... denoland/setup-deno v2.0.2 909cc5acb0... denoland/setup-deno v1.1.4 041b854f97... haskell-actions/setup v2.11.0 cd0d9bdd65... actions/upload-artifact v4.6.2 ea165f8d65b6e75b... actions/setup-node v4.4.0 49933ea5288caeca8642d1e84afbd3f7d6820020 actions/setup-node v4.2.0 1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a trufflesecurity/trufflehog v3.95.3 37b77001d0... trufflesecurity/trufflehog v3.82.13 1aa1871f9a... trufflesecurity/trufflehog v3.63.6 f699f60e89... github/codeql-action/* v3.36.0 03e4368ac7... github/codeql-action/* v3.31.10 4bdb89f480... github/codeql-action/* v3.28.0 48ab28a6f5... github/codeql-action/* v4.36.0 7211b7c807... Swatinem/rust-cache v2.7.8 9d47c6ad4b... gitleaks/gitleaks-action v2.3.7 83373cf2f8... Verified real via `gh api repos/<org>/<action>/commits/<sha>`. Provenance: [[project_estate_fake_action_sha_punch_list_2026_05_30]]; caught during the estate audit triggered by hyperpolymath/snifs#30.
…#14) These pins were partial-prefix-corruption fakes — fabricated SHAs that share a prefix with a real version's SHA but have fabricated suffixes, slipping past visual review. Verified fake via `gh api commits/<sha> -> 422`. The fix preserves the version the author originally intended (read from the `# vX.Y.Z` comment alongside each pin), rather than blindly bumping to latest. This is important for actions where check-name reporting can differ between major versions (e.g. CodeQL) — keeping the same major preserves any branch-protection contexts that reference check names. Substitutions applied (those present in this repo only — see diff): goto-bus-stop/setup-zig v2.2.1 abea47f85e... erlef/setup-beam v1.24.0 fc68ffb904... erlef/setup-beam v1.18.2 5304e04ea2... erlef/setup-beam v1.19.0 8aa8a857c6... denoland/setup-deno v2.0.4 667a34cdef... denoland/setup-deno v2.0.2 909cc5acb0... denoland/setup-deno v1.1.4 041b854f97... haskell-actions/setup v2.11.0 cd0d9bdd65... actions/upload-artifact v4.6.2 ea165f8d65b6e75b... actions/setup-node v4.4.0 49933ea5288caeca8642d1e84afbd3f7d6820020 actions/setup-node v4.2.0 1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a trufflesecurity/trufflehog v3.95.3 37b77001d0... trufflesecurity/trufflehog v3.82.13 1aa1871f9a... trufflesecurity/trufflehog v3.63.6 f699f60e89... github/codeql-action/* v3.36.0 03e4368ac7... github/codeql-action/* v3.31.10 4bdb89f480... github/codeql-action/* v3.28.0 48ab28a6f5... github/codeql-action/* v4.36.0 7211b7c807... Swatinem/rust-cache v2.7.8 9d47c6ad4b... gitleaks/gitleaks-action v2.3.7 83373cf2f8... Verified real via `gh api repos/<org>/<action>/commits/<sha>`. Provenance: [[project_estate_fake_action_sha_punch_list_2026_05_30]]; caught during the estate audit triggered by hyperpolymath/snifs#30.
…#15) These pins were partial-prefix-corruption fakes — fabricated SHAs that share a prefix with a real version's SHA but have fabricated suffixes, slipping past visual review. Verified fake via `gh api commits/<sha> -> 422`. The fix preserves the version the author originally intended (read from the `# vX.Y.Z` comment alongside each pin), rather than blindly bumping to latest. This is important for actions where check-name reporting can differ between major versions (e.g. CodeQL) — keeping the same major preserves any branch-protection contexts that reference check names. Substitutions applied (those present in this repo only — see diff): goto-bus-stop/setup-zig v2.2.1 abea47f85e... erlef/setup-beam v1.24.0 fc68ffb904... erlef/setup-beam v1.18.2 5304e04ea2... erlef/setup-beam v1.19.0 8aa8a857c6... denoland/setup-deno v2.0.4 667a34cdef... denoland/setup-deno v2.0.2 909cc5acb0... denoland/setup-deno v1.1.4 041b854f97... haskell-actions/setup v2.11.0 cd0d9bdd65... actions/upload-artifact v4.6.2 ea165f8d65b6e75b... actions/setup-node v4.4.0 49933ea5288caeca8642d1e84afbd3f7d6820020 actions/setup-node v4.2.0 1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a trufflesecurity/trufflehog v3.95.3 37b77001d0... trufflesecurity/trufflehog v3.82.13 1aa1871f9a... trufflesecurity/trufflehog v3.63.6 f699f60e89... github/codeql-action/* v3.36.0 03e4368ac7... github/codeql-action/* v3.31.10 4bdb89f480... github/codeql-action/* v3.28.0 48ab28a6f5... github/codeql-action/* v4.36.0 7211b7c807... Swatinem/rust-cache v2.7.8 9d47c6ad4b... gitleaks/gitleaks-action v2.3.7 83373cf2f8... Verified real via `gh api repos/<org>/<action>/commits/<sha>`. Provenance: [[project_estate_fake_action_sha_punch_list_2026_05_30]]; caught during the estate audit triggered by hyperpolymath/snifs#30.
…#24) These pins were partial-prefix-corruption fakes — fabricated SHAs that share a prefix with a real version's SHA but have fabricated suffixes, slipping past visual review. Verified fake via `gh api commits/<sha> -> 422`. The fix preserves the version the author originally intended (read from the `# vX.Y.Z` comment alongside each pin), rather than blindly bumping to latest. This is important for actions where check-name reporting can differ between major versions (e.g. CodeQL) — keeping the same major preserves any branch-protection contexts that reference check names. Substitutions applied (those present in this repo only — see diff): goto-bus-stop/setup-zig v2.2.1 abea47f85e... erlef/setup-beam v1.24.0 fc68ffb904... erlef/setup-beam v1.18.2 5304e04ea2... erlef/setup-beam v1.19.0 8aa8a857c6... denoland/setup-deno v2.0.4 667a34cdef... denoland/setup-deno v2.0.2 909cc5acb0... denoland/setup-deno v1.1.4 041b854f97... haskell-actions/setup v2.11.0 cd0d9bdd65... actions/upload-artifact v4.6.2 ea165f8d65b6e75b... actions/setup-node v4.4.0 49933ea5288caeca8642d1e84afbd3f7d6820020 actions/setup-node v4.2.0 1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a trufflesecurity/trufflehog v3.95.3 37b77001d0... trufflesecurity/trufflehog v3.82.13 1aa1871f9a... trufflesecurity/trufflehog v3.63.6 f699f60e89... github/codeql-action/* v3.36.0 03e4368ac7... github/codeql-action/* v3.31.10 4bdb89f480... github/codeql-action/* v3.28.0 48ab28a6f5... github/codeql-action/* v4.36.0 7211b7c807... Swatinem/rust-cache v2.7.8 9d47c6ad4b... gitleaks/gitleaks-action v2.3.7 83373cf2f8... Verified real via `gh api repos/<org>/<action>/commits/<sha>`. Provenance: [[project_estate_fake_action_sha_punch_list_2026_05_30]]; caught during the estate audit triggered by hyperpolymath/snifs#30.
Summary
Wires the SNIFS build-mode invariant into CI, hardens the
crash_oobdemonstration so its failure mode is unambiguous, and removes the committed stale wasm artifacts that quietly drift from source.The SNIFS isolation guarantee depends on a precondition the build hadn't been gating: that the shipped wasm guest was compiled with
-OReleaseSafe, not-OReleaseFast. Build-mode itself was already locked at source level (zig/build.zighardcodes.optimizeper artifact;Justfile build-wasmpasses-OReleaseSafe/-OReleaseFastliterally per recipe). The gap was on the test side — no CI job ever ran the demo suite against the built artifacts.Five fixes, all serving the same property
.github/workflows/e2e.yml— replace template stubs with a concrete job: setup-zig 0.15.0 →just build-wasm(both modes, fresh) → setup-beam OTP 28 / Elixir 1.18 →mix test --trace. Two action SHA pins inherited from the template were fabricated (gh api→ 422):goto-bus-stop/setup-zig@7ab2955...3608was fake (first 16 hex collide with v2.2.0's real SHA7ab2955...2802d, rest fabricated). Replaced with v2.2.1 =abea47f85e598557f500fa1fd2ab7464fcb39406.erlef/setup-beam@5a67e1a...a66c07had no resemblance to any release. Replaced with v1.24.0 =fc68ffb90438ef2936bbb3251622353b3dcb2f93.The same fake pins propagated from
rsr-template-repointo ~35 other estate workflows (10 with active uncommented pins). Follow-up PRs torsr-template-repo+ the 3 affected repos (odds-and-sods-package-manager,proven,proven-servers) coming separately.demo/test/snif_demo_test.exspaths —@safe/@fastwere__DIR__/../priv/...resolving todemo/priv/(doesn't exist); files live at top-levelpriv/. Fixed to__DIR__/../../priv/.... The suite couldn't find its fixtures since the binaries were first committed on 2026-05-21 — never run successfully.zig/src/safe_nif.zigcrash_oobhardening — previously the ReleaseFast result was "returns 0 silently," ambiguous between "load was DCE'd" and "read 0 from adjacent memory." Replaced the function-localconstarray with a layout-pinnedextern struct { arr: [3]i32, canary: i32 }at module scope;runtime_index = 3indexes one past the end. Under ReleaseSafe the bounds check fires; under ReleaseFast the load deterministically lands oncanary = 0x0BADF00D, returning195_948_557— a recognisable wrong answer that proves silent corruption is real, not an artifact of DCE.demo/test/snif_demo_test.exsassertion tightened — fromassert {:ok, _}toassert {:ok, [195_948_557]}. A drift in this value means either Zig data-segment layout changed or — worse — the ReleaseFast artifact regained safety checks. Either should surface visibly, not pass silently.priv/*.wasm+.gitignore+Justfile— committed wasm artifacts go stale the moment any zig source changes (which this PR does), so a localmix testagainst them would fail with confusing assertion mismatches.git rmthe binaries, add/priv/*.wasmand/zig-out/to.gitignore, and makejust test-demodepend onjust build-wasmso the local flow always builds fresh.What this gates
The gate is the property test itself, not a flag grep. If
e2e — Build-mode invariantis red, the SNIFS isolation claim is void on the affected build. The test discriminates both directions:{:error, _}(trap surfaced){:ok, [195_948_557]}(canary leaked) — proving the test actually catches the absence of safety checks rather than passing triviallyTest plan
e2e — Build-mode invariantjob goes greensetup-zig@abea47f...andsetup-beam@fc68ffb...both resolve (the fake SHAs would have 422'd)priv/safe_nif_ReleaseSafe.wasmandpriv/safe_nif_ReleaseFast.wasmare built fresh from source in CIdemo/test/snif_demo_test.exspass — including the tightenedReleaseFast: oob reads canary from adjacent memory — DANGEROUSassertionjust test-demoworks locally end-to-end (build-wasm dep ensures fresh artifacts)