Skip to content

Commit d9ecb5c

Browse files
docs(audits): orphan-SHA pin + BP wrapper-prefix mismatch — 2026-05-27 sidecar to #215 (#220)
## Summary Sidecar audit to `audit-admin-merge-wrapper-sweep-2026-05-26.{adoc,a2ml}` (PR #215 closure track) documenting a regression class introduced by the parent reusables-campaign sweep: - The 278-wrapper hypatia-scan sweep pinned each wrapper to `97df762...`, the **PR-branch HEAD** of #193 — NOT the merge-commit. After #193 squash-merged on 2026-05-26T19:37, the SHA was orphaned (`ahead_by=1, behind_by=24` against main); merge-commit is `915139d7...` with byte-identical content. - GitHub Actions can't resolve reusable-workflow references to orphaned commits. Every estate repo's hypatia-scan fails at parse stage (banner: "workflow file issue"; `jobs: []`). - Estate scope: 100 repos. Sweep filed 2026-05-27 by typed-wasm session: 99 PRs via Contents API + auto-merge SQUASH armed. ## Sidebar finding The audit also documents a sibling failure class — **wrapper-prefix BP mismatch**. The wrapper-emitted check name `hypatia / Hypatia Neurosymbolic Analysis` (`<caller-job> / <reusable-display-name>`) does not match branch protection's required bare `Hypatia Neurosymbolic Analysis` (the pre-wrapper monolithic name). Required check never satisfied even when the workflow succeeds. Fixed on typed-wasm with explicit sign-off; out of scope for this audit on the 99 others. ## Test plan - [ ] Review the .adoc narrative for accuracy against the actual sweep - [ ] Review the .a2ml schema for machine-readability + parent-campaign linkage - [ ] Note the three not-discharged items for the next reusables session 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ad366b6 commit d9ecb5c

2 files changed

Lines changed: 254 additions & 0 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# Reusable-Workflow Orphan-SHA Pin + BP Name-Prefix Mismatch — 2026-05-27
3+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)
4+
#
5+
# Machine-readable companion to audit-hypatia-pin-orphan-2026-05-27.adoc.
6+
# Sidecar to audit-admin-merge-wrapper-sweep-2026-05-26.a2ml (parent campaign).
7+
8+
[manifest]
9+
schema = "audit/orphan-sha-pin/v1"
10+
date = "2026-05-27"
11+
campaign_kind = "reusable_workflow_repin + estate_sweep"
12+
authoring_actor = "claude-opus-4-7 (1M context)"
13+
authorising_actor = "hyperpolymath (org admin)"
14+
human_companion = "audit-hypatia-pin-orphan-2026-05-27.adoc"
15+
parent_audit = "audit-admin-merge-wrapper-sweep-2026-05-26.a2ml"
16+
17+
[diagnosis]
18+
failure_class = "reusable_workflow_unresolved_orphan_sha"
19+
failure_banner = "This run likely failed because of a workflow file issue"
20+
failure_signal = "run.jobs == [] and run.conclusion == 'failure'"
21+
root_cause = "Sweep PRs pinned to PR-branch HEAD SHA of standards#193 before squash-merge. After merge, that SHA was orphaned (not reachable from standards/main) so GitHub Actions cannot resolve the reusable."
22+
orphan_sha = "97df762107501909f50bb770e9bc200b6c415600"
23+
orphan_status_vs_main = { ahead_by = 1, behind_by = 24, status = "diverged" }
24+
merge_commit_sha = "915139d73560e65a8240b8fc7768698658502c89"
25+
merge_commit_status_vs_main = { ahead_by = 0, behind_by = 1, status = "behind" }
26+
content_diff = "byte-identical (diff -q empty)"
27+
28+
[sibling_class]
29+
name = "wrapper_prefix_bp_mismatch"
30+
description = "Reusable resolves and the scan job runs successfully, but the published check name is 'hypatia / Hypatia Neurosymbolic Analysis' (caller-job-name `/` reusable-job-display-name), whereas branch protection still requires the bare pre-wrapper name 'Hypatia Neurosymbolic Analysis'."
31+
required_context_old = "Hypatia Neurosymbolic Analysis"
32+
required_context_new = "hypatia / Hypatia Neurosymbolic Analysis"
33+
fix_method = "gh api -X PATCH repos/<repo>/branches/main/protection/required_status_checks"
34+
fix_status_typed_wasm = "applied 2026-05-26"
35+
fix_status_estate = "out_of_scope_for_this_audit"
36+
37+
[estate_scope]
38+
search_query = "@97df762107501909f50bb770e9bc200b6c415600 user:hyperpolymath path:.github/workflows/hypatia-scan.yml"
39+
total_repos = 100
40+
sweep_session_repos = 99 # excluding typed-wasm which received the fix via PR #75 directly
41+
42+
[sweep]
43+
session_id = "session-typed-wasm-2026-05-26-27"
44+
filing_pace = "5_parallel_per_12s"
45+
filing_method = "github_contents_api + gh_pr_create + auto_merge_squash"
46+
commit_signing = "github_web_flow (Verified, satisfies required_signatures BP)"
47+
log_files = [
48+
"/tmp/hypatia-sweep.log",
49+
"/tmp/hypatia-sweep-done.log",
50+
"/tmp/hypatia-sweep-skip.log",
51+
"/tmp/hypatia-sweep-fail.log",
52+
]
53+
54+
[typed_wasm_local_track]
55+
description = "typed-wasm received the fix via direct branch edit rather than the sweep, plus two active proof-debt PRs received cherry-picks."
56+
prs = [
57+
{ number = 75, branch = "ci/hypatia-scan-pin-fix", off = "main", purpose = "primary SHA-pin fix off main" },
58+
{ number = 72, branch = "claude/proof-debt-A-B-2026-05-26", purpose = "proof-debt items 1-8 (statement); SHA fix cherry-picked at commit 5e53490" },
59+
{ number = 74, branch = "claude/items-7-8-witnesses", purpose = "items 7+8 deepening (4 commits); SHA fix cherry-picked at commit e5c0e87" },
60+
]
61+
62+
[lessons]
63+
[lessons.pin_to_merge_commit]
64+
finding = "Sweep PRs against not-yet-merged reusables must pin to the merge-commit SHA, not the PR-branch HEAD SHA."
65+
mitigation_options = [
66+
"Gate the sweep on the parent reusable PR being merged first.",
67+
"File the sweep PRs with the feature-branch SHA, then a second-pass sweep to repin once the merge lands (this audit's pattern).",
68+
"Use a `@v1` or branch ref instead of SHA pinning (loses immutability guarantees but is reachable).",
69+
]
70+
71+
[lessons.check_published_check_name]
72+
finding = "A single test wrapper run on one repo before mass-sweeping would surface the `<caller-job> / <reusable-display-name>` check-name format, allowing the BP context update to be bundled with the sweep."
73+
74+
[lessons.idempotent_repin_sweeps]
75+
finding = "Contents API + auto-merge SQUASH path applies cleanly across ~100 repos in ~4 minutes at 5-per-12s pacing. Future audits can adopt the same shape."
76+
77+
[not_discharged]
78+
[not_discharged.estate_bp_patch]
79+
description = "The 99 estate repos still have branch protection requiring the bare `Hypatia Neurosymbolic Analysis` rather than the wrapper-prefixed form. SHA-pin sweep alone does not unblock their PRs from auto-merging through their BP."
80+
recommended_followup = "Per-repo `gh api -X PATCH` of required_status_checks, gated on owner approval."
81+
82+
[not_discharged.sibling_reusables]
83+
description = "The same orphan-SHA pattern probably exists for the other four reusables in the parent campaign: #187 mirror, #190 secret-scanner, #192 codeql, #205 scorecard."
84+
recommended_followup = "Search the estate for each reusable's feature-branch SHA via `gh search code @<sha>` and apply this audit's fix recipe."
85+
86+
[not_discharged.policy_change]
87+
description = "Structural prevention requires a standards-repo PR-review checklist item: 'Reusable-workflow sweep PRs MUST pin to merge-commit SHAs.'"
88+
recommended_followup = "Open standards#PR-review-checklist-orphan-sha-gate."
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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

Comments
 (0)