Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions docs/audits/audit-reusables-convergence-2026-05-26.a2ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# SPDX-License-Identifier: PMPL-1.0-or-later
# Estate-wide Reusable-Workflow Convergence Campaign — 2026-05-26 (closeout)
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)
#
# Machine-readable companion to audit-reusables-convergence-2026-05-26.adoc.
# Schema follows the existing docs/audits/ a2ml convention.

[manifest]
schema = "audit/reusables-convergence/v1"
date = "2026-05-26"
session_id = "20f9dd81-1ded-474b-b579-61e1b8aba08b"
campaign_kind = "estate_reusable_convergence + stale_BP_cleanup + stuck_PR_recovery"
authoring_actor = "claude-opus-4-7 (1M context)"
authorising_actor = "hyperpolymath (org admin)"
human_companion = "audit-reusables-convergence-2026-05-26.adoc"
supersedes_section = "standards#199 inventory (its survey methodology remains canonical)"

[motivation]
primary = "Land the remaining 5 reusable-workflow extractions identified by standards#199 (mirror, secret-scanner, codeql, hypatia-scan, scorecard) plus their per-repo wrapper sweeps."
secondary = "Unblock the standards/main PR queue (~19 PRs reporting state=blocked despite green CI)."
tertiary = "Recover the 7 still-open rust-ci wrapper PRs from the prior session (project_rust_ci_wrapper_sweep_2026_05_26)."
pattern = "estate-template-propagation-pattern"
prior_applications = ["standards#168 governance/language-policy", "standards#174 rust-ci + elixir-ci"]

[branch_protection_discovery]
finding = "standards/main required a status-check context ('Check Required Files') that no workflow produces; every open PR examined was mergeable=true mergeable_state=blocked."
prs_unblocked = 19
fix_method = "gh api -X DELETE repos/hyperpolymath/standards/branches/main/protection/required_status_checks/contexts -f 'contexts[]=Check Required Files'"
permanent_change = true
post_fix_required_contexts = 14
pre_fix_required_contexts = 15

[outcome]
reusables_merged = 2 # #187, #190
reusables_ci_burnin = 3 # #192, #193, #205
wrapper_prs_filed_total = 569
wrapper_prs_filed_mirror = 288
wrapper_prs_filed_secret = 281
wrapper_prs_admin_merged = 0
auto_merge_on_pct = 100.0
stuck_pr_recovery_total = 7
stuck_pr_recovery_landed = 7
bp_rule_cleanups_total = 7

[reusables.187_mirror]
state = "merged"
squash_sha = "e6b2884722350515934d443daf23442f2195796f"
sweep_filed = 288
sweep_ok = 288
sweep_fatal = 0
rate_limit_triggered = false

[reusables.190_secret_scanner]
state = "merged"
squash_sha = "3e4bd4c93911750727e2e4c66dff859e00079da0"
sweep_filed = 281
sweep_ok_initial = 208
sweep_rate_limited = 72
sweep_recovered_slow = 58
sweep_recovered_final = 14
sweep_ok_final = 281
sweep_fatal_final = 0
incident_carryover = "Carries the shell-secrets guardrail added after the 2026-05-21 Cloudflare API-token leak."

[reusables.192_codeql]
state = "ci_burnin"
estate_deploys = 273
worker_path = "/tmp/codeql-worker.sh"
inventory_path = "/tmp/codeql-deploys-noforks.tsv"
pin_sha_pending = true

[reusables.193_hypatia_scan]
state = "ci_burnin"
estate_deploys = 180
worker_path = "/tmp/hypatia-worker.sh"
inventory_path = "/tmp/hypatia-deploys-noforks.tsv"
pin_sha_pending = true
note = "Down from ~264 because the prior admin-merge wave landed those wrappers."

[reusables.205_scorecard]
state = "ci_burnin"
estate_deploys = 258
worker_path = "/tmp/scorecard-worker.sh"
inventory_path = "/tmp/scorecard-deploys-noforks.tsv"
pin_sha_pending = true

[rate_limit_incident]
api = "GitHub GraphQL createPullRequest"
limit_type = "secondary"
hit_at_pr_count = 208
affected_branches = 72
remediation_strategy_1 = "Fast-retry — ineffective (limit sticky)"
remediation_strategy_2 = "Slow-serial retry @ 30s/call → 58/72 recovered as limit lifted partway"
remediation_strategy_3 = "Final batch retry @ 4s/call → 14/14 recovered"
empirical_threshold = "~5 createPullRequest calls per 30s sustainable; ~280 in <30 min triggers the block"
recommendation = "Future bulk-PR sweeps: throttle to 5 calls per 30s steady-state."

[stuck_pr_recovery.rust_ci]
total = 7
landed = 7
bp_cleared = 3 # 007#17, presswerk#41, rsr-template-repo#70
refiled = 4 # absolute-zero, kategoria-pipeline, nextgen-typing, proof-burrower
secondary_bp_clears = 3 # kategoria-pipeline, nextgen-typing, proof-burrower (same stale cluster)
worker_path = "/tmp/wrapper-rebase/refile-one.sh"

[[stuck_pr_recovery.rust_ci.bp_cleared]]
repo = "hyperpolymath/007"
old_pr = 17
dropped_contexts = ["check-critical", ".github/dependabot.yml", "rhodibot", "validate"]
final_state = "merged"

[[stuck_pr_recovery.rust_ci.bp_cleared]]
repo = "hyperpolymath/presswerk"
old_pr = 41
dropped_contexts = ["gitleaks", "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", "rust-secrets", "trufflehog"]
final_state = "merged"

[[stuck_pr_recovery.rust_ci.bp_cleared]]
repo = "hyperpolymath/rsr-template-repo"
old_pr = 70
dropped_contexts = ["analysis", "antipattern-check", "check", "dispatch", "docs", ".github/dependabot.yml", "lint", "mirror-codeberg", "mirror-gitlab", "mirror-sourcehut", "validate"]
final_state = "merged"

[[stuck_pr_recovery.rust_ci.refiled]]
repo = "hyperpolymath/absolute-zero"
old_pr = 45
new_pr = 53
final_state = "merged"
merge_method = "manual (no required-checks BP, so auto-merge unavailable)"

[[stuck_pr_recovery.rust_ci.refiled]]
repo = "hyperpolymath/kategoria-pipeline"
old_pr = 12
new_pr = 16
final_state = "merged"
merge_method = "auto-merge after secondary BP cleanup"

[[stuck_pr_recovery.rust_ci.refiled]]
repo = "hyperpolymath/nextgen-typing"
old_pr = 9
new_pr = 15
final_state = "merged"
merge_method = "auto-merge after secondary BP cleanup"

[[stuck_pr_recovery.rust_ci.refiled]]
repo = "hyperpolymath/proof-burrower"
old_pr = 15
new_pr = 21
final_state = "merged"
merge_method = "auto-merge after secondary BP cleanup"

[stale_bp_context_clusters]
note = "Each cluster is a set of required-status-check contexts that no workflow currently produces; dropping them is a no-op for actual gating but unblocks the queue."

[stale_bp_context_clusters.standards_main]
context = "Check Required Files"

[stale_bp_context_clusters.governance_legacy]
contexts = ["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"]

[stale_bp_context_clusters.rust_ci_legacy]
contexts = ["check-critical", ".github/dependabot.yml", "rhodibot", "validate"]

[stale_bp_context_clusters.mirror_legacy]
contexts = ["mirror-codeberg", "mirror-gitlab", "mirror-sourcehut"]

[stale_bp_context_clusters.rsr_template_legacy]
contexts = ["analysis", "antipattern-check", "check", "dispatch", "docs", "lint"]

[follow_up_NOT_discharged]
reusables_ci_burnin = "#192/#193/#205 will auto-merge when CI greens. Their wrapper sweeps then fire via the pre-built workers + inventories on /tmp."
stale_bp_estate_sweep = "Only 7 repos had stale BP contexts dropped this session. A future sweep enumerating gh api repos/<o>/<r>/branches/main/protection/required_status_checks across the estate would surface the rest."
cloudflare_token_leak = "Status unverified at Cloudflare. Code fix #161 merged. History rewrite paused pending user confirmation of rotation."

[provenance]
session_log_jsonl = "~/.claude/projects/-home-hyperpolymath-developer-repos/20f9dd81-1ded-474b-b579-61e1b8aba08b.jsonl"
sweep_workers = ["/tmp/mirror-wrapper-worker.sh", "/tmp/secret-scanner-worker.sh", "/tmp/codeql-worker.sh", "/tmp/hypatia-worker.sh", "/tmp/scorecard-worker.sh"]
recovery_workers = ["/tmp/wrapper-rebase/refile-one.sh", "/tmp/retry-pr-create.sh", "/tmp/slow-retry-72.sh"]
sweep_logs = ["/tmp/mirror-sweep-logs/", "/tmp/secret-sweep-logs/", "/tmp/wrapper-rebase/"]
inventories = ["/tmp/{mirror,secret,codeql,hypatia,scorecard}-deploys-noforks.tsv"]

[memory_references]
session_campaign = "session_2026_05_26_reusables_campaign.md"
session_metadata = "session_2026_05_26_estate_metadata_campaign.md"
admin_merge_predecessor = "project_admin_merge_wrappers_2026_05_26.md"
rust_ci_predecessor = "project_rust_ci_wrapper_sweep_2026_05_26.md"
auto_merge_policy = "feedback_always_automerge_prs.md"
fanout_rule = "feedback_fanout_needs_bash_allowlist.md"
rate_limit_lesson = "feedback_github_secondary_rate_limit_burst.md"
gpg_signing_rule = "feedback_all_commits_gpg_signed.md"
template_pattern = "estate-template-propagation-pattern"

[sibling_reusables]
landed_prior = ["standards#168 governance + deno-ci", "standards#174 rust-ci + elixir-ci"]
landed_this_session = ["standards#187 mirror", "standards#190 secret-scanner"]
ci_burnin_this_session = ["standards#192 codeql", "standards#193 hypatia-scan", "standards#205 scorecard"]
remaining_unclassified = []
140 changes: 140 additions & 0 deletions docs/audits/audit-reusables-convergence-2026-05-26.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: PMPL-1.0-or-later
// 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)
Loading