Skip to content

Commit e6b2884

Browse files
feat(governance): add mirror-reusable.yml — consolidate 289-repo mirror.yml drift (#187)
## Summary Extends the reusable-workflow pattern from #168 (governance-reusable + deno-ci-reusable) and #174 (rust-ci-reusable + elixir-ci-reusable) to the **mirror.yml** template. Estate audit picked this as the highest-leverage next foundational reusable across 5 candidates (codeql, secret-scanner, hypatia-scan, mirror, scorecard). ### Drift survey `gh api /search/code` paginated against `org:hyperpolymath`, then blob-SHA grouped: | Template | Deployments | Sampled | Unique SHAs | Top-SHA share | |---|---|---|---|---| | **mirror.yml** | **289** | **100** | **76 (76%)** | **16% — long tail** | | codeql.yml | 263 | 100 | 70 (70%) | 32% | | secret-scanner.yml | 281 | 100 | 55 (55%) | 47% | | scorecard.yml | 258 | 258 (full) | 46 (18%) | 39% | | hypatia-scan.yml | 255 | 200 | 31 (15.5%) | 50% | (scorecard + hypatia-scan are already mostly converged → low leverage now.) mirror.yml ranks first on **drift × deployments** (76% × 289 ≈ 220) and was verified to have **low feature variance**: all 4 top-SHA variants sampled (covering 29/100 sampled repos: bgp-backbone-lab, ipfs-overlay, kaldor-iiot, vcs-ircd) carried the **same 7 forge jobs** (gitlab, bitbucket, codeberg, sourcehut, disroot, gitea, radicle). Drift is action-SHA / whitespace churn — not feature variance — exactly the shape that consolidates cleanly behind one workflow_call reusable. ### Design - **No per-call inputs other than runs-on** — per-repo forge selection already externalised to Actions vars vars.<FORGE>_MIRROR_ENABLED == 'true', so the reusable mirrors the gating pattern verbatim. - **secrets: inherit required at the call site** — the per-forge SSH keys (GITLAB_SSH_KEY, BITBUCKET_SSH_KEY, CODEBERG_SSH_KEY, SOURCEHUT_SSH_KEY, DISROOT_SSH_KEY, GITEA_SSH_KEY) and RADICLE_KEY flow through implicitly. Without secrets: inherit the inner secrets.X references evaluate to empty (silent push failure on each enabled forge). - **vars.GITEA_HOST** consumed verbatim from the caller repo's Actions vars — same as the canonical mirror.yml. - All actions SHA-pinned; SPDX header present; top-level permissions: contents: read; passes the workflow-lint job in governance-reusable.yml. No filtering logic, so no regression-test file (cf. scripts/tests/apply-baseline-test.sh for the governance/baseline path that needs one). ### Caller wrapper shape (post-merge) \`\`\`yaml # SPDX-License-Identifier: PMPL-1.0-or-later name: Mirror to Git Forges on: push: branches: [main] workflow_dispatch: permissions: contents: read jobs: mirror: uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@<sha> secrets: inherit \`\`\` ~10 lines per repo, replacing ~145 lines. ### Rollout plan (downstream wrapper sweep) **NOT started in this PR — owner-gated, same as #174's rust-ci sweep (which capped at 82 PRs).** Numbers (from the 100-repo SHA-sample, extrapolated to 289): - **289 repos** total deployments to convert - **~85% trivially convertible** (forge set matches canonical 7-forge list; SHA-pinned actions only differ in pin SHA / whitespace). One mechanical wrapper PR per repo, same shape as the #168 wrappers (absolute-zero#41, tma-mark2#41). - **~10-15% need careful review** — long-tail SHAs may include legitimate custom forges or local additions. Surface a per-repo diff during sweep; defer non-canonical variants to a follow-up. - Sweep order: pin wrappers to **this PR's HEAD SHA** while owner-gated; rebase to merged-main SHA in the wave's final batch (same protocol as the rust-ci sweep). ### Pattern hardening (no per-PR action required) - Same workflow_call shape as #168 / #174 — no new infrastructure. - Independent of #174 (rust-ci-reusable.yml) and #180 (apply-baseline.sh glob fix) — no conflicts; can land in any order. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent fd11d1f commit e6b2884

1 file changed

Lines changed: 165 additions & 0 deletions

File tree

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# mirror-reusable.yml — Reusable git-forge mirror bundle.
3+
#
4+
# Consolidates the per-repo `mirror.yml` workflow (estate-wide: 289
5+
# deployments with 76% SHA drift across a 100-repo sample, top SHA
6+
# covering only 16% of sampled repos). All sampled variants carried the
7+
# same 7 forge jobs; drift was almost entirely SHA-pin / whitespace
8+
# churn, not feature variance — exactly the shape that consolidates
9+
# cleanly behind a `workflow_call` reusable.
10+
#
11+
# Each mirror job is gated on the per-repo variable
12+
# `vars.<FORGE>_MIRROR_ENABLED == 'true'`, so forge selection is
13+
# configured per-repo via Actions vars — no per-call inputs required.
14+
#
15+
# Caller (one-line wrapper) MUST use `secrets: inherit` so the reusable
16+
# can read the per-forge SSH keys (GITLAB_SSH_KEY, BITBUCKET_SSH_KEY,
17+
# CODEBERG_SSH_KEY, SOURCEHUT_SSH_KEY, DISROOT_SSH_KEY, GITEA_SSH_KEY)
18+
# and RADICLE_KEY from the caller repo. Without `secrets: inherit`,
19+
# `${{ secrets.X }}` inside the reusable evaluates to empty.
20+
#
21+
# Caller example (wrapper):
22+
# # SPDX-License-Identifier: PMPL-1.0-or-later
23+
# name: Mirror to Git Forges
24+
# on:
25+
# push:
26+
# branches: [main]
27+
# workflow_dispatch:
28+
# permissions:
29+
# contents: read
30+
# jobs:
31+
# mirror:
32+
# uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@<sha>
33+
# secrets: inherit
34+
35+
name: Mirror to Git Forges (reusable)
36+
37+
on:
38+
workflow_call:
39+
inputs:
40+
runs-on:
41+
description: Runner label for all mirror jobs
42+
type: string
43+
required: false
44+
default: ubuntu-latest
45+
46+
permissions:
47+
contents: read
48+
49+
jobs:
50+
mirror-gitlab:
51+
runs-on: ${{ inputs.runs-on }}
52+
if: vars.GITLAB_MIRROR_ENABLED == 'true'
53+
steps:
54+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
55+
with:
56+
fetch-depth: 0
57+
- uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
58+
with:
59+
ssh-private-key: ${{ secrets.GITLAB_SSH_KEY }}
60+
- name: Mirror to GitLab
61+
run: |
62+
ssh-keyscan -t ed25519 gitlab.com >> ~/.ssh/known_hosts
63+
git remote add gitlab git@gitlab.com:hyperpolymath/${{ github.event.repository.name }}.git || true
64+
git push --force gitlab main
65+
66+
mirror-bitbucket:
67+
runs-on: ${{ inputs.runs-on }}
68+
if: vars.BITBUCKET_MIRROR_ENABLED == 'true'
69+
steps:
70+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
71+
with:
72+
fetch-depth: 0
73+
- uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
74+
with:
75+
ssh-private-key: ${{ secrets.BITBUCKET_SSH_KEY }}
76+
- name: Mirror to Bitbucket
77+
run: |
78+
ssh-keyscan -t ed25519 bitbucket.org >> ~/.ssh/known_hosts
79+
git remote add bitbucket git@bitbucket.org:hyperpolymath/${{ github.event.repository.name }}.git || true
80+
git push --force bitbucket main
81+
82+
mirror-codeberg:
83+
runs-on: ${{ inputs.runs-on }}
84+
if: vars.CODEBERG_MIRROR_ENABLED == 'true'
85+
steps:
86+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
87+
with:
88+
fetch-depth: 0
89+
- uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
90+
with:
91+
ssh-private-key: ${{ secrets.CODEBERG_SSH_KEY }}
92+
- name: Mirror to Codeberg
93+
run: |
94+
ssh-keyscan -t ed25519 codeberg.org >> ~/.ssh/known_hosts
95+
git remote add codeberg git@codeberg.org:hyperpolymath/${{ github.event.repository.name }}.git || true
96+
git push --force codeberg main
97+
98+
mirror-sourcehut:
99+
runs-on: ${{ inputs.runs-on }}
100+
if: vars.SOURCEHUT_MIRROR_ENABLED == 'true'
101+
steps:
102+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
103+
with:
104+
fetch-depth: 0
105+
- uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
106+
with:
107+
ssh-private-key: ${{ secrets.SOURCEHUT_SSH_KEY }}
108+
- name: Mirror to SourceHut
109+
run: |
110+
ssh-keyscan -t ed25519 git.sr.ht >> ~/.ssh/known_hosts
111+
git remote add sourcehut git@git.sr.ht:~hyperpolymath/${{ github.event.repository.name }} || true
112+
git push --force sourcehut main
113+
114+
mirror-disroot:
115+
runs-on: ${{ inputs.runs-on }}
116+
if: vars.DISROOT_MIRROR_ENABLED == 'true'
117+
steps:
118+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
119+
with:
120+
fetch-depth: 0
121+
- uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
122+
with:
123+
ssh-private-key: ${{ secrets.DISROOT_SSH_KEY }}
124+
- name: Mirror to Disroot
125+
run: |
126+
ssh-keyscan -t ed25519 git.disroot.org >> ~/.ssh/known_hosts
127+
git remote add disroot git@git.disroot.org:hyperpolymath/${{ github.event.repository.name }}.git || true
128+
git push --force disroot main
129+
130+
mirror-gitea:
131+
runs-on: ${{ inputs.runs-on }}
132+
if: vars.GITEA_MIRROR_ENABLED == 'true'
133+
steps:
134+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
135+
with:
136+
fetch-depth: 0
137+
- uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
138+
with:
139+
ssh-private-key: ${{ secrets.GITEA_SSH_KEY }}
140+
- name: Mirror to Gitea
141+
run: |
142+
ssh-keyscan -t ed25519 ${{ vars.GITEA_HOST }} >> ~/.ssh/known_hosts
143+
git remote add gitea git@${{ vars.GITEA_HOST }}:hyperpolymath/${{ github.event.repository.name }}.git || true
144+
git push --force gitea main
145+
146+
mirror-radicle:
147+
runs-on: ${{ inputs.runs-on }}
148+
if: vars.RADICLE_MIRROR_ENABLED == 'true'
149+
steps:
150+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
151+
with:
152+
fetch-depth: 0
153+
- name: Setup Rust
154+
uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
155+
with:
156+
toolchain: stable
157+
- name: Install Radicle
158+
run: |
159+
cargo install radicle-cli --locked
160+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
161+
- name: Mirror to Radicle
162+
run: |
163+
echo "${{ secrets.RADICLE_KEY }}" > ~/.radicle/keys/radicle
164+
chmod 600 ~/.radicle/keys/radicle
165+
rad sync --announce || echo "Radicle sync attempted"

0 commit comments

Comments
 (0)