|
| 1 | +// SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) |
| 3 | + |
| 4 | += Reusable-Workflow Orphan-SHA Pin + BP Name-Prefix Mismatch — 2026-05-27 |
| 5 | +:toc: |
| 6 | +:toclevels: 2 |
| 7 | +:source-highlighter: rouge |
| 8 | +:icons: font |
| 9 | + |
| 10 | +Companion machine-readable manifest: `audit-hypatia-pin-orphan-2026-05-27.a2ml`. |
| 11 | +Sidecar to: `audit-admin-merge-wrapper-sweep-2026-05-26.adoc` (the parent |
| 12 | +reusables-campaign closure). |
| 13 | + |
| 14 | +== Summary |
| 15 | + |
| 16 | +The hypatia-scan wrapper sweep (`audit-admin-merge-wrapper-sweep-2026-05-26.adoc`) |
| 17 | +filed 278 thin wrappers across the estate, each pinning to the standards-side |
| 18 | +reusable's HEAD SHA at sweep time: |
| 19 | + |
| 20 | +```yaml |
| 21 | +uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@97df762107501909f50bb770e9bc200b6c415600 |
| 22 | +``` |
| 23 | + |
| 24 | +After standards#193 was squash-merged into `standards/main` on 2026-05-26T19:37, |
| 25 | +the `97df762...` SHA was **orphaned** — it lives only on the feature branch and |
| 26 | +is no longer reachable from `main`: |
| 27 | + |
| 28 | +``` |
| 29 | +$ gh api repos/hyperpolymath/standards/compare/main...97df762107501909f50bb770e9bc200b6c415600 |
| 30 | +{ "status": "diverged", "ahead_by": 1, "behind_by": 24 } |
| 31 | +``` |
| 32 | + |
| 33 | +The merge-commit SHA on `main` is `915139d73560e65a8240b8fc7768698658502c89`. |
| 34 | +File content at both SHAs is byte-identical (`diff -q` returns empty); only the |
| 35 | +reachability differs. |
| 36 | + |
| 37 | +**Symptom.** GitHub Actions cannot resolve reusable-workflow references to |
| 38 | +orphaned commits. Every hypatia-scan run on any repo with the orphan pin fails |
| 39 | +at parse-stage with banner "This run likely failed because of a workflow file |
| 40 | +issue" and `jobs: []` in the run JSON — no job is ever instantiated. |
| 41 | + |
| 42 | +**Estate scope.** `gh search code "@97df762" --owner hyperpolymath` returns |
| 43 | +**100 repos** with the orphan pin in `.github/workflows/hypatia-scan.yml`. |
| 44 | + |
| 45 | +**Fix sweep** (2026-05-27, this session): 99 PRs filed (one per repo bar |
| 46 | +typed-wasm, which received the fix via PR #75 directly), each replacing the SHA |
| 47 | +in the wrapper. Filing via Contents API + auto-merge SQUASH armed. |
| 48 | + |
| 49 | +== Two distinct failure classes — diagnose both |
| 50 | + |
| 51 | +The pre-existing-baseline-rot diagnosis from the parent campaign (per `audit- |
| 52 | +admin-merge-wrapper-sweep-2026-05-26.adoc`) is **not** the same failure class. |
| 53 | +Two distinct phenomena are at play in the post-sweep CI red on hypatia-scan: |
| 54 | + |
| 55 | +. **Orphan-SHA class** (this audit). Reusable reference does not resolve; the |
| 56 | + job never starts; no check is published. Banner: "workflow file issue". |
| 57 | +. **Wrapper-prefix BP mismatch class** (sidebar §3 below). The reusable does |
| 58 | + resolve and the job runs successfully, but the published check name is |
| 59 | + `hypatia / Hypatia Neurosymbolic Analysis` (caller-job-name `/` reusable- |
| 60 | + job-display-name) — whereas branch protection still requires the bare |
| 61 | + pre-wrapper name `Hypatia Neurosymbolic Analysis`. Required check is never |
| 62 | + marked done. |
| 63 | + |
| 64 | +The fix for class (1) is the SHA repin, mechanical, covered by the sweep below. |
| 65 | +The fix for class (2) requires either a per-repo branch-protection patch |
| 66 | +(changing the required context string) or an upstream redesign of the reusable |
| 67 | +to suppress the prefix (not currently supported by GitHub Actions for the |
| 68 | +`<caller-job> / <reusable-job-display-name>` convention). |
| 69 | + |
| 70 | +== Why orphan SHAs slip through CI |
| 71 | + |
| 72 | +The original sweep PR (filed via the parent campaign) was generated *before* |
| 73 | +standards#193 was squash-merged. At generation time, the only SHA pointing |
| 74 | +to the reusable file was the feature-branch commit `97df762...`. The wrappers |
| 75 | +pinned to that SHA, opened, and auto-merged — but the receiving repos' CI did |
| 76 | +not yet exercise the wrapper because each receiving repo's `pull_request`-triggered |
| 77 | +hypatia-scan runs the *previous* `hypatia-scan.yml` file off the receiving |
| 78 | +repo's main (not the PR's wrapper version). So the wrapper landed on main |
| 79 | +with the orphaned SHA, and the orphan was only exercised on the *next* PR |
| 80 | +opened against that repo — at which point standards#193 had been squash-merged |
| 81 | +and the original feature-branch commit was unreachable. |
| 82 | + |
| 83 | +In other words: the orphan was created by the time-ordering of the merge. |
| 84 | +The wrappers were *technically correct* at filing time and only became broken |
| 85 | +once `97df762` was orphaned. |
| 86 | + |
| 87 | +== Sidebar: wrapper-prefix BP mismatch (class 2) |
| 88 | + |
| 89 | +A reusable workflow called by: |
| 90 | + |
| 91 | +```yaml |
| 92 | +jobs: |
| 93 | + hypatia: # caller job |
| 94 | + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@<sha> |
| 95 | +``` |
| 96 | + |
| 97 | +invoking a reusable with: |
| 98 | + |
| 99 | +```yaml |
| 100 | +jobs: |
| 101 | + scan: |
| 102 | + name: Hypatia Neurosymbolic Analysis # reusable display name |
| 103 | +``` |
| 104 | + |
| 105 | +publishes the status check as **`hypatia / Hypatia Neurosymbolic Analysis`** — |
| 106 | +the caller-job-name, a literal ` / `, then the reusable's display name. |
| 107 | + |
| 108 | +The pre-wrapper monolithic `hypatia-scan.yml` published the same check as the |
| 109 | +**bare** `Hypatia Neurosymbolic Analysis` (no prefix), because the scan job |
| 110 | +was at the caller's top level. |
| 111 | + |
| 112 | +Branch protection's required-status-checks list still references the bare |
| 113 | +name on every estate repo. The two strings never match → required check is |
| 114 | +never satisfied → no PR can auto-merge. |
| 115 | + |
| 116 | +Evidence (`Exnovation.jl#12`, a known-merged sample): |
| 117 | + |
| 118 | +``` |
| 119 | +SUCCESS hypatia / Hypatia Neurosymbolic Analysis (wrapper-emitted) |
| 120 | +SUCCESS Hypatia (caller-job aggregate) |
| 121 | +required: Hypatia Neurosymbolic Analysis (BP requirement, neither matches) |
| 122 | +``` |
| 123 | + |
| 124 | +That PR landed via owner manual-merge through the `--admin` path — the same |
| 125 | +admin-merge pattern the parent campaign documents, but applied to the post- |
| 126 | +sweep regression class rather than the pre-existing baseline rot. |
| 127 | + |
| 128 | +== Fix recipe (per repo) |
| 129 | + |
| 130 | +. **SHA repin.** Replace `97df762107501909f50bb770e9bc200b6c415600` with |
| 131 | + `915139d73560e65a8240b8fc7768698658502c89` in |
| 132 | + `.github/workflows/hypatia-scan.yml`. |
| 133 | +. **BP context update.** PATCH branch protection's required-status-checks |
| 134 | + contexts: `Hypatia Neurosymbolic Analysis` → `hypatia / Hypatia Neurosymbolic Analysis`. |
| 135 | + |
| 136 | +Both fixes are mechanical and idempotent. |
| 137 | + |
| 138 | +== Lessons for future reusable rollouts |
| 139 | + |
| 140 | +. **Pin to merge-commit SHA, not PR-branch SHA.** When generating sweep PRs |
| 141 | + against a not-yet-merged reusable, gate the sweep on the parent reusable PR |
| 142 | + being merged first. Or accept that the sweep PRs land first with the |
| 143 | + feature-branch SHA, then file a follow-up sweep to repin (this audit is |
| 144 | + effectively that follow-up). |
| 145 | +. **Test the wrapper-emitted check name before sweeping.** A single test |
| 146 | + wrapper run on one repo would have surfaced the `hypatia / Hypatia |
| 147 | + Neurosymbolic Analysis` prefix, allowing the BP patch to be bundled with |
| 148 | + the sweep instead of split out as a second audit. |
| 149 | +. **Idempotent re-pin sweeps are cheap.** The Contents API + auto-merge |
| 150 | + SQUASH path applies cleanly across 99 repos in ~4 minutes (paced at |
| 151 | + 5-per-12s per `audit-admin-merge-wrapper-sweep-2026-05-26.adoc`'s |
| 152 | + rate-limit recovery pattern). Future audits can adopt the same shape. |
| 153 | + |
| 154 | +== What this campaign does NOT discharge |
| 155 | + |
| 156 | +* The wrapper-prefix BP-mismatch class (sidebar §3) on the 99 estate repos |
| 157 | + outside typed-wasm is **not** patched by the SHA-repin sweep. It requires |
| 158 | + a separate per-repo `gh api -X PATCH .../branches/main/protection/required_status_checks` |
| 159 | + pass. Out of scope for this audit; tracked as follow-up. |
| 160 | +* The same orphan-SHA pattern probably exists for **the other four reusables** |
| 161 | + introduced in the parent campaign (#187 mirror, #190 secret-scanner, #192 |
| 162 | + codeql, #205 scorecard). Search the estate for each reusable's feature-branch |
| 163 | + SHA before assuming this audit covers them. |
| 164 | +* The structural cause — workflow-sweep PRs pinning to non-merged SHAs — is |
| 165 | + policy work for the standards repo's PR review checklist, not a code change |
| 166 | + in any single repo. |
0 commit comments