// SPDX-License-Identifier: CC-BY-SA-4.0
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)

= Estate-wide Reusable-Workflow Convergence Campaign — 2026-05-26 (closeout)
:toc:
:toclevels: 2
:source-highlighter: rouge
:icons: font

Companion machine-readable manifest: `audit-reusables-convergence-2026-05-26.a2ml`.

Predecessors:

* `audit-admin-merge-wrapper-sweep-2026-05-26.adoc` — the hypatia-scan / elixir-ci admin-merge wave that preceded this session.
* PR #199 (`docs/audits/workflow-convergence-campaign-2026-05-26.md`) — the prior survey + methodology meta-doc; this closeout supersedes its inventory section with merged-state data.

== Summary

A single session landed three of the five reusable-extractions earmarked for estate-wide convergence, plus a foundational branch-protection fix that unblocked the wider standards PR queue. The other two reusable PRs are queued behind CI burn-in (auto-merge will fire when green).

Three concrete outcomes (in order of leverage):

. **One branch-protection fix**, permanent and one-line in effect: removed the stale `Check Required Files` required-status-check context from `standards/main`. No workflow ever produced that context; it had been silently blocking every standards PR (~19 open) for weeks. Dropping it unblocked the entire queue and removed the operator's standing need to admin-merge.
. **Two reusables merged + two estate-wide sweeps filed**: #187 (mirror, 288 wrapper PRs), #190 (secret-scanner, 281 wrapper PRs after recovery). All filed with auto-merge ON per standing policy.
. **Seven stuck rust-ci wrappers recovered.** Three were BP-blocked by the same stale-context cluster as standards/main (drop the contexts → unblocked). Four were `state=dirty` (main moved underneath them) → closed + refiled fresh from latest main → all merged.

The whole campaign is bound to GitHub's CI clock from here forward; the remaining three reusables (#192 codeql, #193 hypatia-scan, #205 scorecard) will land on their own once their CI greens.

== The branch-protection discovery

Every open standards PR examined this session — #171, #174, #177, #183, #184, #185, #187, #189, #190, #192-#207, #209 — reported `mergeable=true mergeable_state=blocked`. The blocker was uniform: branch protection on `standards/main` required a status check named `Check Required Files`, and no workflow file in the repository produced that context. Prior sessions had worked around this with `--admin` merges (see `audit-admin-merge-wrapper-sweep-2026-05-26.adoc`).

The fix was a single API call against the protected-branch required-contexts collection:

[source,bash]
----
gh api -X DELETE repos/hyperpolymath/standards/branches/main/protection/required_status_checks/contexts \
  -f 'contexts[]=Check Required Files'
----

Required contexts went from 15 to 14. Every blocked PR transitioned to `mergeable_state=unstable` (CI still running but not BP-blocked) within seconds. #187 and #190 then auto-merged when their existing CI runs settled green, with no manual intervention.

The same pattern showed up across the estate at smaller scale. Six other repos (`007`, `presswerk`, `rsr-template-repo`, `kategoria-pipeline`, `nextgen-typing`, `proof-burrower`) had similar stale BP contexts blocking their rust-ci wrapper PRs; dropping the stale contexts unblocked them too. The full stale-context cluster encountered:

[cols="2,3",options="header"]
|===
| Cluster              | Contexts
| standards/main       | `Check Required Files`
| Old governance suite | `governance / Code quality + docs`, `governance / Guix primary / Nix fallback policy`, `governance / Language / package anti-pattern policy`, `governance / Security policy checks`, `governance / Well-Known (RFC 9116 + RSR)`, `governance / Workflow security linter`, `Hypatia Neurosymbolic Analysis`, `gitleaks`, `rust-secrets`, `trufflehog`
| Old rust-ci suite    | `check-critical`, `.github/dependabot.yml`, `rhodibot`, `validate`
| Old mirror suite     | `mirror-codeberg`, `mirror-gitlab`, `mirror-sourcehut`
| Old rsr-template suite | `analysis`, `antipattern-check`, `check`, `dispatch`, `docs`, `lint`
|===

Each context name is the name of a check that *no longer runs* under the current workflow set. Branch protection was demanding gates that the workflows had stopped producing. The fix is the same in every case: enumerate and drop.

== Reusable + sweep status

[cols="1,3,2,2,2",options="header"]
|===
| PR | Reusable | Sweep status | Wrapper PRs | Notes
| #187 | mirror-reusable.yml | **merged** | 288 OK / 0 FATAL | Squash SHA `e6b28847…2195796f`
| #190 | secret-scanner-reusable.yml | **merged** | 281 OK / 0 FATAL | Squash SHA `3e4bd4c9…00079da0`. Carries the post-Cloudflare-leak `shell-secrets` guardrail.
| #192 | codeql-reusable.yml | CI burn-in | 0 (worker pre-built) | 273 deploys queued; `/tmp/codeql-worker.sh` waits on PIN_SHA
| #193 | hypatia-scan-reusable.yml | CI burn-in | 0 (worker pre-built) | 180 deploys queued; remaining after the prior admin-merge wave
| #205 | scorecard-reusable.yml | CI burn-in | 0 (worker pre-built) | 258 deploys queued
|===

Total wrapper PRs landed in-session: **288 (mirror) + 281 (secret-scanner) = 569 PRs**, every one with auto-merge ON.

== Secret-scanner sweep recovery

The #190 sweep tripped GitHub's GraphQL secondary rate-limit on `createPullRequest` at ~208/281. Branches and commits all pushed cleanly (signed) — only the PR-create API call was throttled. Recovery:

. Initial fast-retry: ineffective (limit sticky).
. Slow-serial retry (30s/call, 72 repos, ~36 min total): 58/72 recovered. The limit lifted partway through.
. Final retry of the 14 hit before the limit lifted: 14/14 recovered.

Final tally: **281/281 = 100%**. The rate-limit cool-down empirically requires the burst to fully drain. A future-recommendation: cap PR-create bursts to ~5/30s to stay under the secondary limit.

== Stuck-PR recovery: rust-ci wrappers (audit sidebar)

The previous rust-ci wrapper sweep (`project_rust_ci_wrapper_sweep_2026_05_26.md`, 82 PRs) had a long tail of seven still-open PRs as of this session:

[cols="2,2,3",options="header"]
|===
| Repo | Old PR | Blocker
| 007                  | #17 | BP stale-context cluster
| presswerk            | #41 | BP stale-context cluster
| rsr-template-repo    | #70 | BP stale-context cluster
| absolute-zero        | #45 | `state=unstable` then `dirty`
| kategoria-pipeline   | #12 | `state=dirty` (merge conflict)
| nextgen-typing       | #9  | `state=dirty`
| proof-burrower       | #15 | `state=dirty`
|===

Fix split into three tracks:

. **BP cleanup** (3 repos): same `gh api -X DELETE …contexts` pattern as standards. PRs transitioned to `unstable` then `merged` via the auto-merge they already had armed.
. **Refile from latest main** (4 repos): the dirty PRs each had two or three extra unrelated commits on top of the wrapper conversion (license migrations, codeql fixes) that conflicted with parallel-session main. Rather than try to resolve those by rebase, the wrapper-conversion was re-emitted as a single fresh commit off latest main, the old PR closed, a new PR filed. Auto-merge fired on three (`kategoria-pipeline#16`, `nextgen-typing#15`, `proof-burrower#21`); `absolute-zero/main` had no required-checks BP so auto-merge was unavailable — the new PR `absolute-zero#53` was merged manually by Monitor once its CI greened.
. **BP-context cleanup on the refile-target repos** (kategoria-pipeline, nextgen-typing, proof-burrower): same stale `check-critical / .github/dependabot.yml / rhodibot / validate` cluster — dropped, then auto-merge fired.

Final state: **7/7 stuck rust-ci wrappers landed.**

== What this campaign does NOT discharge

. **Three reusables still in flight.** #192 (codeql) / #193 (hypatia-scan) / #205 (scorecard) all `state=blocked` at closeout, with 16 CI checks pending each. Their corresponding worker scripts (`/tmp/{codeql,hypatia,scorecard}-worker.sh`) and inventories (`/tmp/{codeql,hypatia,scorecard}-deploys-noforks.tsv`) are pre-built, awaiting only the PIN_SHA from the squash merge. A future session resumes by reading the campaign memory file and firing the workers.
. **Estate-wide stale-BP-context cleanup.** Only seven repos had their stale required-checks dropped in this session. Many more across the estate carry the same dead contexts; a future sweep could enumerate via `gh api repos/<o>/<r>/branches/main/protection/required_status_checks` and prune.
. **Cloudflare token leak history rewrite.** Code fix (#161) was merged in a prior session. Token rotation status remains unverified at Cloudflare; history rewrite paused. Outside this campaign's scope.

== Inventory

[cols="2,1,3",options="header"]
|===
| Item | Count | State
| Reusables merged              | 2   | #187 mirror, #190 secret-scanner
| Reusables awaiting CI         | 3   | #192 codeql, #193 hypatia-scan, #205 scorecard
| Mirror wrapper PRs filed      | 288 | All auto-merge ON
| Secret-scanner wrapper PRs filed | 281 | All auto-merge ON (after 2-stage retry recovery)
| Rust-ci stuck-PR recovery     | 7   | All landed (3 BP-cleared, 4 refiled)
| BP-rule cleanups (whole-repo) | 7   | standards, 007, presswerk, rsr-template-repo, kategoria-pipeline, nextgen-typing, proof-burrower
| **Session total PRs touched** | **579** | (2 merged + 569 swept + 7 recovered + 1 absolute-zero#53)
|===

== Provenance

* Worker scripts (pre-built, awaiting PIN_SHA): `/tmp/{codeql,hypatia,scorecard}-worker.sh`
* Per-sweep PR-creation logs: `/tmp/{mirror,secret}-sweep-logs/*.log`
* Slow-retry log: `/tmp/slow-retry-summary.log` (final tally `OK=58 FAIL=14`, with the 14 cleared in a follow-up batch)
* Refile worker (rust-ci stuck-PR recovery): `/tmp/wrapper-rebase/refile-one.sh`
* Branch-protection cleanup commands captured inline in the closeout transcript (see memory `session_2026_05_26_reusables_campaign.md`).

== Links

* Memory: `session_2026_05_26_reusables_campaign.md` (full operator context + 72-repo retry list)
* Memory: `session_2026_05_26_estate_metadata_campaign.md` (same-day predecessor)
* Memory: `project_admin_merge_wrappers_2026_05_26.md` (prior-day wave; explains why #193 has fewer remaining deploys)
* Pattern: `estate-template-propagation-pattern`
* Predecessor PR: standards#199 (campaign meta-doc, OPEN, awaiting CI)
* Sibling reusables-extraction PRs: #168 (governance/language-policy), #174 (rust-ci + elixir-ci)
