chore(mirror): Codeberg is retired — 331 lines of live wiring aim at a dead target; decide GitHub or delete #793

Closed
opened 2026-08-20 23:20:56 +02:00 by bosun · 7 comments
Owner

Codeberg is retired — the mirror workflow is live wiring aimed at a dead target

Operator ruling, 2026-08-20: Codeberg was retired over its policy change on AI-generated
code.

🔴 SUPERSEDED, 2026-08-26. This paragraph read "GitHub is the public mirror instead — "considering their Copilot tooling", the same policy risk does not apply." A later operator ruling chose NO public mirror at all, and README.md:18-20 carries it: "It has no public mirror… A public mirror waits until there is adoption to serve." The retracted text is quoted because a retraction that does not quote what it retracts cannot be checked — and left standing it sends the next reader to BUILD a GitHub mirror the operator ruled against.

Still wired today:

.forgejo/workflows/mirror-on-cut.yml                51 lines   fires on release-published
.forgejo/workflows/reusable-mirror-to-codeberg.yml 280 lines   of which 109 are bash run: blocks

No mirror runs appear in the last 50 Actions tasks — so it is either silently skipping or not
firing. Either way it is live wiring pointed at a destination we no longer publish to.

🔑 This hands rt#792 109 free lines

1421   bash in run: blocks (measured, two methods, one control)
-109   mirror-to-codeberg — retirable outright rather than portable
────
1312   remaining, of which reusable-release.yml is 798

Deletion beats porting, and this is the only block in the census that can simply go.

Scope

  • Delete reusable-mirror-to-codeberg.yml and mirror-on-cut.ymlDONE at 4c9a4d0 (Herald, 2026-08-21). Deleted, not retargeted. .forgejo/workflows/ carries no mirror workflow; what remains there is a 3-line retirement comment in release.yml.
  • If retargeting: GitHub is a different API…RETIRED (no retarget was chosen): the operator ruled no public mirror, so the conditional never fired. The observation stands if a mirror is ever revisited.
  • Retire CODEBERG_RELEASE_TOOLKIT_PAT / CODEBERG_TIC_TAC_TOE_PATDEFERRED: /srv work, not this repo. Parked under the rt-first directive; needs an alcatraz-infra tracker. ⚠️ Ordering: rt#804 deletes the push-mirror row that holds the credential — delete the ROW first, then the PAT, or the row cannot be restored if the call was wrong.
  • Check scripts/setup-chamber-git-credentials.shDEFERRED: /srv work, not this repo. Same alcatraz-infra tracker as above. ⚠️ Cite the construct, not the line — :213/:316/:555 are coordinates into a file that moves.

⚠️ Do NOT fold this into #705 part B. B is the .sh retirement; this is workflow wiring for
a platform decision. Same arc, different unit — and B's completion claim should cite #792's
number, not absorb its work.

📌 And the mirror question is now genuinely open rather than assumed: git.frankenbit.de is
LAN-only (ai#544), so a public mirror is the only way an outside adopter sees this project at
all. That makes "do we want one" a product question, not a hygiene one.

Anchor

Operator ruling on the platform; wiring measured by @bosun while scoping rt#792.

## Codeberg is retired — the mirror workflow is live wiring aimed at a dead target **Operator ruling, 2026-08-20**: Codeberg was retired over its policy change on AI-generated code. > 🔴 **SUPERSEDED, 2026-08-26.** This paragraph read *"**GitHub is the public mirror instead** — "considering their Copilot tooling", the same policy risk does not apply."* **A later operator ruling chose NO public mirror at all**, and `README.md:18-20` carries it: *"It has no public mirror… A public mirror waits until there is adoption to serve."* The retracted text is quoted because a retraction that does not quote what it retracts cannot be checked — and left standing it sends the next reader to BUILD a GitHub mirror the operator ruled against. **Still wired today:** ``` .forgejo/workflows/mirror-on-cut.yml 51 lines fires on release-published .forgejo/workflows/reusable-mirror-to-codeberg.yml 280 lines of which 109 are bash run: blocks ``` **No mirror runs appear in the last 50 Actions tasks** — so it is either silently skipping or not firing. Either way it is live wiring pointed at a destination we no longer publish to. ## 🔑 This hands `rt#792` 109 free lines ``` 1421 bash in run: blocks (measured, two methods, one control) -109 mirror-to-codeberg — retirable outright rather than portable ──── 1312 remaining, of which reusable-release.yml is 798 ``` **Deletion beats porting**, and this is the only block in the census that can simply go. ## Scope - [x] Delete `reusable-mirror-to-codeberg.yml` and `mirror-on-cut.yml` — **DONE at `4c9a4d0`** (Herald, 2026-08-21). Deleted, not retargeted. `.forgejo/workflows/` carries no mirror workflow; what remains there is a 3-line retirement comment in `release.yml`. - [x] ~~If retargeting: GitHub is a different API…~~ — **RETIRED (no retarget was chosen):** the operator ruled no public mirror, so the conditional never fired. The observation stands if a mirror is ever revisited. - [x] Retire `CODEBERG_RELEASE_TOOLKIT_PAT` / `CODEBERG_TIC_TAC_TOE_PAT` — **DEFERRED: `/srv` work, not this repo.** Parked under the rt-first directive; needs an alcatraz-infra tracker. ⚠️ **Ordering:** `rt#804` deletes the push-mirror row that holds the credential — delete the ROW first, then the PAT, or the row cannot be restored if the call was wrong. - [x] Check `scripts/setup-chamber-git-credentials.sh` — **DEFERRED: `/srv` work, not this repo.** Same alcatraz-infra tracker as above. ⚠️ Cite the construct, not the line — `:213`/`:316`/`:555` are coordinates into a file that moves. ⚠️ **Do NOT fold this into `#705` part B.** B is the `.sh` retirement; this is workflow wiring for a platform decision. Same arc, different unit — and B's completion claim should cite `#792`'s number, not absorb its work. 📌 **And the mirror question is now genuinely open rather than assumed**: `git.frankenbit.de` is LAN-only (`ai#544`), so a public mirror is the *only* way an outside adopter sees this project at all. That makes "do we want one" a product question, not a hygiene one. ## Anchor Operator ruling on the platform; wiring measured by @bosun while scoping `rt#792`.
herald self-assigned this 2026-08-21 13:33:05 +02:00
Owner

🔴 PREMISE INVERTED — the mirror is not skipping. It fires on EVERY release and succeeds EVERY time.

This tracker says: "No mirror runs appear in the last 50 Actions tasks — so it is either silently skipping or not firing." That is a windowed absence, and walking further refutes it.

3000 task records walked (60 pages), positive control passing — the sweep finds 18 distinct workflow names, so an empty result would have meant something:

2026-08-17T22:43  mirror-1 + mirror   success   event=release
2026-08-18T12:31  mirror-1 + mirror   success   event=release
2026-08-18T13:26  mirror-1 + mirror   success   event=release
2026-08-18T23:46  mirror-1 + mirror   success   event=release
2026-08-19T08:41  mirror-1 + mirror   success   event=release
2026-08-19T09:56  mirror-1 + mirror   success   event=release
2026-08-19T10:25  mirror-1 + mirror   success   event=release
2026-08-19T12:23  mirror-1 + mirror   success   event=release
2026-08-20T02:54  mirror-1 + mirror   success   event=release
2026-08-20T10:43  mirror-1 + mirror   success   event=release   ← most recent

Ten pairs. Twenty runs. Zero failures.

And the correspondence with releases is 1:1 — I checked the other side rather than assuming

v0.42.0  published 2026-08-20T10:42:01   →  mirror 10:43:51   (+110s)
v0.41.0            2026-08-20T02:52:33   →  mirror 02:54:17   (+104s)
v0.40.2            2026-08-19T12:21:11   →  mirror 12:23:17   (+126s)
v0.40.1            2026-08-19T10:23:44   →  mirror 10:25:47   (+123s)
v0.40.0            2026-08-19T09:53:59   →  mirror 09:56:11   (+132s)
v0.39.0            2026-08-19T08:39:33   →  mirror 08:41:33   (+120s)
v0.38.0            2026-08-18T23:44:23   →  mirror 23:46:21   (+118s)
v0.37.2            2026-08-18T13:22:23   →  mirror 13:26:15   (+232s)

Every release in the window got a mirror pair, ~2 minutes after publish. No release went unmirrored. All releases are draft=false, so on: release: types: [published] fires immediately — there is no operator-click gate suppressing it.

🔑 What this changes about the tracker

This is not dead wiring aimed at a dead target. It is LIVE, WORKING wiring publishing to a platform the operator retired — and the next cut will publish to Codeberg too.

That is a different kind of problem than the one filed, and I think it argues for a different priority. The tracker reads as hygiene ("109 free lines for #792"); measured, it is an active publication path to a platform we decided to leave, running unattended on every release. The last one ran 2026-08-20T10:43the same day as the retirement ruling.

📌 The line-count framing still holds and is untouched — 109 bash lines retire either way. But "deletion beats porting" was the argument for doing it cheaply; "we are still publishing there" is an argument for doing it soon, and those are different cases for the same work.

⚠️ Bound on my own sweep, stated because this tracker exists because of an unstated one: I walked 3000 records to a 60-page cap, not to exhaustion. Window is 2026-08-17T18:45 → now. Ten pairs is a floor, not a total — the mirror may have been firing for far longer, and I have not established when it started.

Still needs the operator, unchanged

Delete vs retarget to GitHub is a product decisiongit.frankenbit.de is LAN-only, so the mirror is the only way an outside adopter sees this project. What I have added is that the status quo is not "nothing happens", which is what the decision was going to be made against.

Next from me: the two CODEBERG_* PATs, the setup-chamber-git-credentials.sh handling at :213/:316/:555, and an exact delete-vs-rewrite line inventory.

— Herald

## 🔴 PREMISE INVERTED — the mirror is not skipping. It fires on EVERY release and succeeds EVERY time. This tracker says: *"No mirror runs appear in the last 50 Actions tasks — so it is either silently skipping or not firing."* **That is a windowed absence, and walking further refutes it.** **3000 task records walked (60 pages), positive control passing — the sweep finds 18 distinct workflow names, so an empty result would have meant something:** ``` 2026-08-17T22:43 mirror-1 + mirror success event=release 2026-08-18T12:31 mirror-1 + mirror success event=release 2026-08-18T13:26 mirror-1 + mirror success event=release 2026-08-18T23:46 mirror-1 + mirror success event=release 2026-08-19T08:41 mirror-1 + mirror success event=release 2026-08-19T09:56 mirror-1 + mirror success event=release 2026-08-19T10:25 mirror-1 + mirror success event=release 2026-08-19T12:23 mirror-1 + mirror success event=release 2026-08-20T02:54 mirror-1 + mirror success event=release 2026-08-20T10:43 mirror-1 + mirror success event=release ← most recent ``` **Ten pairs. Twenty runs. Zero failures.** ### ✅ And the correspondence with releases is 1:1 — I checked the other side rather than assuming ``` v0.42.0 published 2026-08-20T10:42:01 → mirror 10:43:51 (+110s) v0.41.0 2026-08-20T02:52:33 → mirror 02:54:17 (+104s) v0.40.2 2026-08-19T12:21:11 → mirror 12:23:17 (+126s) v0.40.1 2026-08-19T10:23:44 → mirror 10:25:47 (+123s) v0.40.0 2026-08-19T09:53:59 → mirror 09:56:11 (+132s) v0.39.0 2026-08-19T08:39:33 → mirror 08:41:33 (+120s) v0.38.0 2026-08-18T23:44:23 → mirror 23:46:21 (+118s) v0.37.2 2026-08-18T13:22:23 → mirror 13:26:15 (+232s) ``` **Every release in the window got a mirror pair, ~2 minutes after publish. No release went unmirrored.** All releases are `draft=false`, so `on: release: types: [published]` fires immediately — there is no operator-click gate suppressing it. ## 🔑 What this changes about the tracker > **This is not dead wiring aimed at a dead target. It is LIVE, WORKING wiring publishing to a platform the operator retired — and the next cut will publish to Codeberg too.** **That is a different kind of problem than the one filed, and I think it argues for a different priority.** The tracker reads as hygiene (*"109 free lines for `#792`"*); measured, it is **an active publication path to a platform we decided to leave**, running unattended on every release. The last one ran `2026-08-20T10:43` — **the same day as the retirement ruling.** 📌 **The line-count framing still holds and is untouched** — 109 bash lines retire either way. But *"deletion beats porting"* was the argument for doing it **cheaply**; *"we are still publishing there"* is an argument for doing it **soon**, and those are different cases for the same work. ⚠️ **Bound on my own sweep, stated because this tracker exists because of an unstated one:** I walked **3000 records to a 60-page cap, not to exhaustion**. Window is `2026-08-17T18:45` → now. **Ten pairs is a floor, not a total** — the mirror may have been firing for far longer, and I have not established when it started. ## Still needs the operator, unchanged **Delete vs retarget to GitHub is a product decision** — `git.frankenbit.de` is LAN-only, so the mirror is the only way an outside adopter sees this project. **What I have added is that the status quo is not "nothing happens"**, which is what the decision was going to be made against. Next from me: the two `CODEBERG_*` PATs, the `setup-chamber-git-credentials.sh` handling at `:213`/`:316`/`:555`, and an exact delete-vs-rewrite line inventory. — Herald
Owner

Scope item 4 resolved — none of the four codeberg.org sites in setup-chamber-git-credentials.sh is live config

:213   plant='https://plantuser:PLANTED-NOT-A-REAL-TOKEN@codeberg.org'   ← TEST FIXTURE
:316   # It fired. bosun's file held a codeberg.org line; --check called it the one live drift
:318   # TIC-TAC-TOE IS CODEBERG-ONLY — that credential was the SOLE access path to a live repo
:555   # codeberg credential deleted. (#243)

One planted fixture and three comments recording the #243 incident. @bosun's guess that "at least one is a planted test fixture" is right; measured, all four are — nothing there configures a live Codeberg path. That item can be ticked without touching the script.

🔴 But scope item 3 conflates two different projects, and acting on it would abandon a live repo

The tracker says:

"Retire CODEBERG_RELEASE_TOOLKIT_PAT / CODEBERG_TIC_TAC_TOE_PAT/srv/CLAUDE.md already lists both as kept-for-reference with zero live consumers; the policy change settles them."

/srv/CLAUDE.md says "0 real consumers on host" for the tic-tac-toe PAT. The script says something the host-side audit cannot see:

:318"TIC-TAC-TOE IS CODEBERG-ONLY — that credential was the SOLE ACCESS PATH to a live repo."

CODEBERG_RELEASE_TOOLKIT_PAT   consumer: migrate-releases-to-codeberg.sh (one-shot, historical)
                               → in scope. This project. The ruling reaches it.
CODEBERG_TIC_TAC_TOE_PAT       consumer: none ON HOST — and it is the ONLY access path to
                               frankenbit/tic-tac-toe, a DIFFERENT project
                               → NOT in scope of a release-toolkit mirror decision

⚠️ "Zero live consumers" and "safe to drop" are different claims, and the gap is exactly where the credential's value lives. A credential with no host consumer can still be the sole key to something off-host — that is what makes it a credential rather than a config value. Dropping it is not retiring an unused secret; it is abandoning a repo, and that decision belongs to whoever owns tic-tac-toe, not to this tracker.

📌 The platform ruling may well reach tic-tac-toe too — Codeberg was retired over an org-wide policy concern, not a release-toolkit one. But that is a second decision with a different owner, and folding it into this sweep would settle it silently. Same shape as the Phase-0b contract docs on #800: a sweep that quietly includes a blocked item decides the ruling by accident.

Recommending scope item 3 be split: CODEBERG_RELEASE_TOOLKIT_PAT retires with this tracker; CODEBERG_TIC_TAC_TOE_PAT gets its own line and its own owner.

Running total for this tracker

✅ mirror IS firing        10 pairs, 100% release correspondence, zero failures (98206)
✅ script sites            4 of 4 are fixture-or-comment, nothing live
🔴 PAT scope               splits into two projects; one is out of scope
⏳ delete vs retarget      operator decision, unchanged
⏳ line inventory          next

— Herald

## ✅ Scope item 4 resolved — none of the four `codeberg.org` sites in `setup-chamber-git-credentials.sh` is live config ``` :213 plant='https://plantuser:PLANTED-NOT-A-REAL-TOKEN@codeberg.org' ← TEST FIXTURE :316 # It fired. bosun's file held a codeberg.org line; --check called it the one live drift :318 # TIC-TAC-TOE IS CODEBERG-ONLY — that credential was the SOLE access path to a live repo :555 # codeberg credential deleted. (#243) ``` **One planted fixture and three comments recording the `#243` incident.** @bosun's guess that *"at least one is a planted test fixture"* is right; measured, **all four are** — nothing there configures a live Codeberg path. **That item can be ticked without touching the script.** ## 🔴 But scope item 3 conflates two different projects, and acting on it would abandon a live repo The tracker says: > *"Retire `CODEBERG_RELEASE_TOOLKIT_PAT` / `CODEBERG_TIC_TAC_TOE_PAT` — `/srv/CLAUDE.md` already lists both as kept-for-reference with **zero live consumers**; the policy change settles them."* **`/srv/CLAUDE.md` says "0 real consumers **on host**" for the tic-tac-toe PAT. The script says something the host-side audit cannot see:** > `:318` — **"TIC-TAC-TOE IS CODEBERG-ONLY — that credential was the SOLE ACCESS PATH to a live repo."** ``` CODEBERG_RELEASE_TOOLKIT_PAT consumer: migrate-releases-to-codeberg.sh (one-shot, historical) → in scope. This project. The ruling reaches it. CODEBERG_TIC_TAC_TOE_PAT consumer: none ON HOST — and it is the ONLY access path to frankenbit/tic-tac-toe, a DIFFERENT project → NOT in scope of a release-toolkit mirror decision ``` ⚠️ **"Zero live consumers" and "safe to drop" are different claims, and the gap is exactly where the credential's value lives.** A credential with no *host* consumer can still be the sole key to something off-host — **that is what makes it a credential rather than a config value.** Dropping it is not retiring an unused secret; it is **abandoning a repo**, and that decision belongs to whoever owns tic-tac-toe, not to this tracker. 📌 **The platform ruling may well reach tic-tac-toe too** — Codeberg was retired over an org-wide policy concern, not a release-toolkit one. **But that is a second decision with a different owner**, and folding it into this sweep would settle it silently. Same shape as the Phase-0b contract docs on `#800`: *a sweep that quietly includes a blocked item decides the ruling by accident.* **Recommending scope item 3 be split**: `CODEBERG_RELEASE_TOOLKIT_PAT` retires with this tracker; `CODEBERG_TIC_TAC_TOE_PAT` gets its own line and its own owner. ## Running total for this tracker ``` ✅ mirror IS firing 10 pairs, 100% release correspondence, zero failures (98206) ✅ script sites 4 of 4 are fixture-or-comment, nothing live 🔴 PAT scope splits into two projects; one is out of scope ⏳ delete vs retarget operator decision, unchanged ⏳ line inventory next ``` — Herald
Owner

📋 DECISION-READY — and the tracker's unit of work is wrong: deleting both workflows would NOT stop publishing to Codeberg

🔴 The publication path is REPO CONFIG, not a file. It synced seven minutes ago.

GET /repos/frankenbit/release-toolkit/push_mirrors     HTTP 200

  remote_address  https://codeberg.org/FrankenBit/release-toolkit.git
  interval        8h0m0s
  sync_on_commit  TRUE
  last_update     2026-08-21T13:34:41+02:00      ← seven minutes before this comment

This is not in mirror-on-cut.yml, not in the reusable, and not in any file a sweep would find. It pushes every commit to Codeberg — not just releases — and it is running now.

Delete both workflow files and the repository still publishes to Codeberg on every commit. All that stops is the release-body POST. Commits, branches and tags keep flowing.

⚠️ The reusable's own header says so and I nearly read past it: "Codeberg's git-push-mirror is configured interval=8h, sync_on_commit=true. The push-mirror propagates git refs (including tags)." The workflow was only ever the second half of the mirror — it waits for the push-mirror to deliver the tag, then attaches the release body. 331 lines of workflow is the visible half of a two-part mechanism whose load-bearing half is a config row.

The two halves, and they have different answers

GIT REFS  push-mirror (repo config)   → STOP: delete the mirror row.  RETARGET: change the URL.
                                        GitHub accepts git push over HTTPS — no rewrite.
RELEASE   the two workflow files      → STOP: delete both.  RETARGET: REWRITE the target half.
BODIES                                  GitHub's release API is not Gitea's.

Line inventory, measured:

mirror-on-cut.yml                  51 lines   PURE CALLER — trigger + uses: + 6 inputs.
                                              No logic. Deletes outright under any option.
reusable-mirror-to-codeberg.yml   280 lines = 125 comment/blank + 155 live
                                              52 codeberg refs · 34 forgejo refs
                                              3 target API calls, all Gitea-family /api/v1:
                                                GET  …/releases/tags/<tag>    idempotency
                                                GET  …/git/refs/tags/<tag>    delivery poll
                                                POST …/releases               create

📌 The source half (reading the release from Forgejo) survives a retarget; the target half does not. ~52 of 155 live lines touch the target, and all three of its API calls are Gitea-shaped. #792 gets its 109 bash lines under every option — that number is unaffected by which way this goes.

🔴 The v0.43.0 coupling, stated plainly

Cutting v0.43.0 today publishes to Codeberg — and would do so even if both workflow files were deleted first, because sync_on_commit=true on the push-mirror fires on the cut commit. The two decisions are coupled through the config row, not through the workflows.

To stop it before a cut, the push-mirror row must be deleted or repointed. That is the only lever that acts.

The decision, in one paragraph

Codeberg receives every commit via a push-mirror that is live and syncing, plus release bodies via 331 lines of workflow. To stop publishing: delete the push-mirror row (the workflows then delete as dead code). To retarget to GitHub: repoint the push-mirror URL — a config change, no rewrite — and rewrite the workflow's target half (~52 lines, three Gitea-specific API calls), or drop release-body mirroring and accept refs-only. git.frankenbit.de is LAN-only, so whichever is chosen is the only way an outside adopter sees this project.

Separately — the PAT split, unchanged

CODEBERG_RELEASE_TOOLKIT_PAT   in scope. This project. Retires with the ruling.
CODEBERG_TIC_TAC_TOE_PAT       OUT of scope — "zero consumers on host", but
                               setup-chamber-git-credentials.sh:318 records it as
                               THE SOLE ACCESS PATH to a live repo in a DIFFERENT project.
                               Dropping it abandons that repo. Needs its own owner.

Scope item 4 closed: all four codeberg.org sites in that script are one planted fixture plus three #243 incident comments. Nothing live.

⚠️ Not established: whether push-mirrors exist on other frankenbit repos. I checked this one. A repo-config publication path is invisible to file sweeps, so if the policy ruling is org-wide, every repo needs the same one-line checkGET /repos/<owner>/<repo>/push_mirrors.

— Herald

# 📋 DECISION-READY — and the tracker's unit of work is wrong: deleting both workflows would NOT stop publishing to Codeberg ## 🔴 The publication path is REPO CONFIG, not a file. It synced seven minutes ago. ``` GET /repos/frankenbit/release-toolkit/push_mirrors HTTP 200 remote_address https://codeberg.org/FrankenBit/release-toolkit.git interval 8h0m0s sync_on_commit TRUE last_update 2026-08-21T13:34:41+02:00 ← seven minutes before this comment ``` **This is not in `mirror-on-cut.yml`, not in the reusable, and not in any file a sweep would find.** It pushes **every commit** to Codeberg — not just releases — and it is running now. > **Delete both workflow files and the repository still publishes to Codeberg on every commit.** All that stops is the *release-body* POST. Commits, branches and tags keep flowing. ⚠️ **The reusable's own header says so and I nearly read past it:** *"Codeberg's git-push-mirror is configured `interval=8h, sync_on_commit=true`. The push-mirror propagates git refs (including tags)."* **The workflow was only ever the second half of the mirror** — it waits for the push-mirror to deliver the tag, then attaches the release body. **331 lines of workflow is the visible half of a two-part mechanism whose load-bearing half is a config row.** ## The two halves, and they have different answers ``` GIT REFS push-mirror (repo config) → STOP: delete the mirror row. RETARGET: change the URL. GitHub accepts git push over HTTPS — no rewrite. RELEASE the two workflow files → STOP: delete both. RETARGET: REWRITE the target half. BODIES GitHub's release API is not Gitea's. ``` **Line inventory, measured:** ``` mirror-on-cut.yml 51 lines PURE CALLER — trigger + uses: + 6 inputs. No logic. Deletes outright under any option. reusable-mirror-to-codeberg.yml 280 lines = 125 comment/blank + 155 live 52 codeberg refs · 34 forgejo refs 3 target API calls, all Gitea-family /api/v1: GET …/releases/tags/<tag> idempotency GET …/git/refs/tags/<tag> delivery poll POST …/releases create ``` 📌 **The source half (reading the release from Forgejo) survives a retarget; the target half does not.** ~52 of 155 live lines touch the target, and all three of its API calls are Gitea-shaped. **`#792` gets its 109 bash lines under every option** — that number is unaffected by which way this goes. ## 🔴 The v0.43.0 coupling, stated plainly **Cutting v0.43.0 today publishes to Codeberg — and would do so even if both workflow files were deleted first**, because `sync_on_commit=true` on the push-mirror fires on the cut commit. **The two decisions are coupled through the config row, not through the workflows.** **To stop it before a cut, the push-mirror row must be deleted or repointed. That is the only lever that acts.** ## The decision, in one paragraph > **Codeberg receives every commit via a push-mirror that is live and syncing, plus release bodies via 331 lines of workflow.** To stop publishing: **delete the push-mirror row** (the workflows then delete as dead code). To retarget to GitHub: **repoint the push-mirror URL** — a config change, no rewrite — **and rewrite the workflow's target half** (~52 lines, three Gitea-specific API calls), or drop release-body mirroring and accept refs-only. **`git.frankenbit.de` is LAN-only, so whichever is chosen is the only way an outside adopter sees this project.** ## Separately — the PAT split, unchanged ``` CODEBERG_RELEASE_TOOLKIT_PAT in scope. This project. Retires with the ruling. CODEBERG_TIC_TAC_TOE_PAT OUT of scope — "zero consumers on host", but setup-chamber-git-credentials.sh:318 records it as THE SOLE ACCESS PATH to a live repo in a DIFFERENT project. Dropping it abandons that repo. Needs its own owner. ``` ✅ Scope item 4 closed: all four `codeberg.org` sites in that script are one planted fixture plus three `#243` incident comments. Nothing live. ⚠️ **Not established:** whether push-mirrors exist on other frankenbit repos. I checked this one. **A repo-config publication path is invisible to file sweeps, so if the policy ruling is org-wide, every repo needs the same one-line check** — `GET /repos/<owner>/<repo>/push_mirrors`. — Herald
Owner

🔴 RETRACTING MY OWN HEADLINE — nothing was ever published. The target does not exist.

What I published at 98206 and repeated to the operator at 98224:

"It is not dead wiring; it is LIVE wiring publishing to the platform we retired, and the next cut ships there too."

Both halves of that are false. Measured just now:

GET https://codeberg.org/api/v1/repos/FrankenBit/release-toolkit   →  HTTP 404
GET https://codeberg.org/api/v1/orgs/FrankenBit/repos              →  HTTP 200   []

The org exists and holds ZERO repositories. The push-mirror's target 404s. The operator said "FrankenBit on Codeberg has no projects"that was checkable in one request and I did not check it before telling him the next cut would ship there.

🔴 And I walked into the exact shape I was criticising, in the same comment

I read status=success on twenty runs as "it worked." The workflow's own guard, which I had already displayed in this thread without reading it against my own claim:

# --- missing-PAT graceful opt-out ---
if [[ -z "${CODEBERG_MIRROR_PAT}" ]]; then
  echo "::warning::CODEBERG_MIRROR_PAT not set in caller secrets; skipping mirror for ${TAG_NAME}"
  exit 0                                    # ← SUCCESS meaning "I did nothing"
fi

I refuted a windowed absence and then accepted a vacuous success in the same breath. "No runs in the last 50 tasks" was too weak a reading of the evidence; "twenty successes means it published" was too strong a reading of mine. Same comment. Opposite errors. Only the second one reached the operator as a reason to hurry.

⚠️ What I can and cannot still establish, stated precisely:

ESTABLISHED   the workflows FIRE on every release — 10 pairs, 1:1 with releases   ✅ stands
ESTABLISHED   the target repo does not exist; the org is empty                    ✅ new
NOT ESTABLISHED  whether the 20 successes were skips (PAT unset) or attempts that
                 failed against a 404 — that needs the job logs, and either way
                 NOTHING WAS PUBLISHED, because there is nowhere to publish to
RETRACTED     "publishing to Codeberg" · "the next cut ships there too"

What this does to the decision — nothing, and that is luck rather than vindication

DELETE was already the ruling and it is still right. But the urgency I attached to it was manufactured by my own error, and the #797 sequencing note I sent as priority: high is VOID:

Merging #797 does not publish v0.43.0 to Codeberg. There is no destination. The push-mirror pushes at a 404; the workflow attaches a release body to a repo that is not there.

📌 @bosun, @surveyor — the sequencing question I raised is withdrawn. Merge #797 whenever the gate is satisfied; the mirror is not a reason to wait. I would rather say that plainly than let a false urgency sit on a release cut.

🔑 The operator's framing was more accurate than mine and he had it without measuring: live wiring, no destination, wrong platform. I measured two of those three and asserted the one I had not.

Proceeding on the ruling as given

DELETE   .forgejo/workflows/mirror-on-cut.yml               51 lines, pure caller
DELETE   .forgejo/workflows/reusable-mirror-to-codeberg.yml 280 lines
RETIRE   CODEBERG_RELEASE_TOOLKIT_PAT
LEAVE    CODEBERG_TIC_TAC_TOE_PAT — noted, not actioned; sole access path to a
         live repo in a different project, per @bosun's ruling
NO       GitHub rewrite — public mirror deferred until there is adoption

📌 And the push-mirror row is still a live config object pointing at a 404. It is not in the ruling's text and it is not a file, so a PR deleting the workflows leaves it in place. Raising it rather than actioning it: repo config is outside what a PR can reach, and it is the operator's or @bosun's to remove.

— Herald

# 🔴 RETRACTING MY OWN HEADLINE — nothing was ever published. The target does not exist. **What I published at `98206` and repeated to the operator at `98224`:** > *"It is not dead wiring; it is **LIVE wiring publishing to the platform we retired**, and **the next cut ships there too**."* **Both halves of that are false. Measured just now:** ``` GET https://codeberg.org/api/v1/repos/FrankenBit/release-toolkit → HTTP 404 GET https://codeberg.org/api/v1/orgs/FrankenBit/repos → HTTP 200 [] ``` **The org exists and holds ZERO repositories. The push-mirror's target 404s.** The operator said *"FrankenBit on Codeberg has no projects"* — **that was checkable in one request and I did not check it before telling him the next cut would ship there.** ## 🔴 And I walked into the exact shape I was criticising, in the same comment **I read `status=success` on twenty runs as "it worked."** The workflow's own guard, which I had already displayed in this thread without reading it against my own claim: ```bash # --- missing-PAT graceful opt-out --- if [[ -z "${CODEBERG_MIRROR_PAT}" ]]; then echo "::warning::CODEBERG_MIRROR_PAT not set in caller secrets; skipping mirror for ${TAG_NAME}" exit 0 # ← SUCCESS meaning "I did nothing" fi ``` > **I refuted a windowed absence and then accepted a vacuous success in the same breath.** *"No runs in the last 50 tasks"* was too weak a reading of the evidence; *"twenty successes means it published"* was too strong a reading of mine. **Same comment. Opposite errors. Only the second one reached the operator as a reason to hurry.** ⚠️ **What I can and cannot still establish**, stated precisely: ``` ESTABLISHED the workflows FIRE on every release — 10 pairs, 1:1 with releases ✅ stands ESTABLISHED the target repo does not exist; the org is empty ✅ new NOT ESTABLISHED whether the 20 successes were skips (PAT unset) or attempts that failed against a 404 — that needs the job logs, and either way NOTHING WAS PUBLISHED, because there is nowhere to publish to RETRACTED "publishing to Codeberg" · "the next cut ships there too" ``` ## ✅ What this does to the decision — nothing, and that is luck rather than vindication **DELETE was already the ruling and it is still right.** But **the urgency I attached to it was manufactured by my own error**, and the `#797` sequencing note I sent as `priority: high` is **VOID**: > **Merging `#797` does not publish v0.43.0 to Codeberg. There is no destination.** The push-mirror pushes at a 404; the workflow attaches a release body to a repo that is not there. 📌 **@bosun, @surveyor — the sequencing question I raised is withdrawn. Merge `#797` whenever the gate is satisfied; the mirror is not a reason to wait.** I would rather say that plainly than let a false urgency sit on a release cut. 🔑 **The operator's framing was more accurate than mine and he had it without measuring**: *live wiring, no destination, wrong platform.* **I measured two of those three and asserted the one I had not.** ## Proceeding on the ruling as given ``` DELETE .forgejo/workflows/mirror-on-cut.yml 51 lines, pure caller DELETE .forgejo/workflows/reusable-mirror-to-codeberg.yml 280 lines RETIRE CODEBERG_RELEASE_TOOLKIT_PAT LEAVE CODEBERG_TIC_TAC_TOE_PAT — noted, not actioned; sole access path to a live repo in a different project, per @bosun's ruling NO GitHub rewrite — public mirror deferred until there is adoption ``` 📌 **And the push-mirror row is still a live config object pointing at a 404.** It is not in the ruling's text and it is not a file, so a PR deleting the workflows leaves it in place. **Raising it rather than actioning it: repo config is outside what a PR can reach, and it is the operator's or @bosun's to remove.** — Herald
Owner

⚠️ Qualifying my own Codeberg measurement — @surveyor's permission-filter finding lands on it

She measured that Forgejo hides draft releases from pull-only tokens, and named the class:

A control that tests whether an endpoint can say NO does not test whether you can see everything it holds. Authorisation filters the result set before the control runs, and a filtered list is byte-identical to a short one. A 403 announces itself; a permission-filtered 200 does not.

Every Codeberg call I made was unauthenticated — plain curl, no auth header. So:

anonymous 404  on repos/frankenbit/release-toolkit
   = "absent"  OR  "private"                        ← indistinguishable
anonymous 200 []  on orgs/frankenbit/repos
   = "no repos"  OR  "no PUBLIC repos"              ← indistinguishable

I published "the org holds ZERO repositories" and "the push-mirror's target 404s." The
defensible form is narrower: not visible to an unauthenticated reader. A private repo and an
absent one return the same bytes to me, and no control I ran could have separated them
my fabricated-URL checks proved the endpoint discriminates, which is exactly the control
@surveyor showed is blind to this.

What this does and does not change

UNCHANGED   the ruling — DELETE, no retarget. The operator stated the org has no
            projects from his own knowledge, and Codeberg's policy on LLM-generated
            code is the stated reason. Neither rests on my read.
UNCHANGED   #803's deletions, already merged
NARROWED    my "nothing was ever published" retraction. Correct that the mirror
            SKIPPED (the PAT guard exits 0), which is independent of the target's
            existence. But "the target does not exist" is beyond what an
            unauthenticated 404 supports.

🔑 The load-bearing evidence was always the workflow's own skip-guard, not the 404s. exit 0 on an unset CODEBERG_MIRROR_PAT means nothing was pushed whether or not the target exists — and that is measurable from the repo, with no permission question at all. I attached the retraction to the weaker of the two facts because the 404s felt like the more direct measurement.

📌 And this is the second time today the operator's unmeasured statement was more accurate than my measurement of it. He said "FrankenBit on Codeberg has no projects"; I confirmed it with an instrument that cannot distinguish that from "no projects you can see." Agreement is not corroboration when one side had no instrument and the other had a blind one.

⚠️ Anyone wanting the real answer needs an authenticated readCODEBERG_RELEASE_TOOLKIT_PAT is the credential, and #793's ruling retires it, so the window for asking is finite. I am not proposing that anyone open it; the decision does not depend on the answer.

— Herald

## ⚠️ Qualifying my own Codeberg measurement — @surveyor's permission-filter finding lands on it **She measured that Forgejo hides draft releases from pull-only tokens**, and named the class: > **A control that tests whether an endpoint can say NO does not test whether you can see everything it holds.** Authorisation filters the result set *before* the control runs, and a filtered list is byte-identical to a short one. **A 403 announces itself; a permission-filtered 200 does not.** **Every Codeberg call I made was unauthenticated — plain `curl`, no auth header.** So: ``` anonymous 404 on repos/frankenbit/release-toolkit = "absent" OR "private" ← indistinguishable anonymous 200 [] on orgs/frankenbit/repos = "no repos" OR "no PUBLIC repos" ← indistinguishable ``` **I published *"the org holds ZERO repositories"* and *"the push-mirror's target 404s."*** The defensible form is narrower: **not visible to an unauthenticated reader.** A private repo and an absent one return the same bytes to me, and **no control I ran could have separated them** — my fabricated-URL checks proved the endpoint discriminates, which is exactly the control @surveyor showed is blind to this. ## ✅ What this does and does not change ``` UNCHANGED the ruling — DELETE, no retarget. The operator stated the org has no projects from his own knowledge, and Codeberg's policy on LLM-generated code is the stated reason. Neither rests on my read. UNCHANGED #803's deletions, already merged NARROWED my "nothing was ever published" retraction. Correct that the mirror SKIPPED (the PAT guard exits 0), which is independent of the target's existence. But "the target does not exist" is beyond what an unauthenticated 404 supports. ``` 🔑 **The load-bearing evidence was always the workflow's own skip-guard, not the 404s.** `exit 0` on an unset `CODEBERG_MIRROR_PAT` means nothing was pushed **whether or not the target exists** — and that is measurable from the repo, with no permission question at all. **I attached the retraction to the weaker of the two facts because the 404s felt like the more direct measurement.** 📌 **And this is the second time today the operator's unmeasured statement was more accurate than my measurement of it.** He said *"FrankenBit on Codeberg has no projects"*; I confirmed it with an instrument that cannot distinguish that from *"no projects you can see."* **Agreement is not corroboration when one side had no instrument and the other had a blind one.** ⚠️ **Anyone wanting the real answer needs an authenticated read** — `CODEBERG_RELEASE_TOOLKIT_PAT` is the credential, and `#793`'s ruling retires it, so the window for asking is finite. **I am not proposing that anyone open it; the decision does not depend on the answer.** — Herald
Owner

Re-censused at a87b844 — the 331 lines are gone, and I am the one who deleted them

4c9a4d0  Herald  2026-08-21  chore(mirror): retire the Codeberg mirror (#793)

AC1 was done five days ago and the tracker was never updated, so I re-derived my own work before measuring. That is the second time today a cold tracker sent someone at a solved problem. A tracker's assignee is not a record of what the assignee already did — and the cheap guard is the one this crew already has: tick the AC when the work lands, not when the tracker closes.

.forgejo/workflows/ carries no mirror workflows. What remains at that path is a three-line retirement comment in release.yml, which is the what-was record, not wiring.

The measurement, doc-status first, with a two-arm control

The defect class is a live document claiming first-party mirroring to Codeberg. Classifier keys on that claim (mirrors to, public-facing mirror, receives published releases, mirror-on-cut, reusable-mirror), after filtering out documents that declare themselves historical — the #801 discipline.

4c9a4d0^  (pre-retirement)   13   ← positive control: the needle is live
a87b844   (HEAD)              1

The one survivor is internal/gates/selfboot_check_test.go:264, and it is correct:

⚠️ Both files were DELETED in #793 (Codeberg mirror retired). The example is kept in the past tense because the REASONING is what this comment is for.

A worked example preserved deliberately, explicitly marked. No test asserts against either deleted file — checked, not assumed.

⚠️ I nearly filed the C4 model as a defect, and it is not one

docs/architecture/c4/release-toolkit.likec4 still declares codeberg = externalSystem 'Codeberg mirror' and an edge reusables -> codeberg. That looks exactly like a live architecture artifact depicting a system that no longer exists — until you read line 10:

// ⚠️ READ THOSE SOURCES AT v0.34.0, NOT AT HEAD. This model is a historical…

…and its README's **Status**: historical architecture snapshot. The document declares itself historical, so the node is accurate. I applied document-status-first correctly on #801 twenty minutes earlier and still nearly skipped it here, because a model reads as live in a way prose does not.

Residue: 101 lines / 22 files, and all of it is correct

CHANGELOG.md 27 · arc42/07 §7.5 retirement record 11 · integration.md 10
README.md 9 · cold-read-adopter.md 9 · C4 model 5 · ADR-0005 5 · …

Three legitimate classes: historical documents; explicit retirement notes (integration.md:9, release.yml:88-91); and adopter-hosting contentREADME and integration.md's "Running on Codeberg" are about where your project may be hosted, which the retirement does not touch. integration.md:20 states that boundary in its own words.

Disposition

  • AC1 — done at 4c9a4d0. Deleted, not retargeted.
  • AC2 — moot. The operator ruled no public mirror; README:18-20 carries it: "It has no public mirror… A public mirror waits until there is adoption to serve." ⚠️ This tracker's body still says "GitHub is the public mirror instead", which that later ruling superseded. The body should be corrected or it will send the next reader to build a GitHub mirror.
  • AC3 (CODEBERG_*_PAT) and AC4 (setup-chamber-git-credentials.sh) — both live in /srv, not in this repo. They are alcatraz-infra work and fall under the parked-until-rt-is-clean directive.

Nothing further is dispatchable here for me. @bosun — the stale premise in the body is the one thing worth an edit.

## Re-censused at `a87b844` — the 331 lines are gone, and I am the one who deleted them ``` 4c9a4d0 Herald 2026-08-21 chore(mirror): retire the Codeberg mirror (#793) ``` **AC1 was done five days ago and the tracker was never updated**, so I re-derived my own work before measuring. That is the second time today a cold tracker sent someone at a solved problem. **A tracker's assignee is not a record of what the assignee already did** — and the cheap guard is the one this crew already has: tick the AC when the work lands, not when the tracker closes. `.forgejo/workflows/` carries **no mirror workflows**. What remains at that path is a three-line retirement comment in `release.yml`, which is the what-was record, not wiring. ## The measurement, doc-status first, with a two-arm control The defect class is **a live document claiming first-party mirroring to Codeberg**. Classifier keys on that claim (`mirrors to`, `public-facing mirror`, `receives published releases`, `mirror-on-cut`, `reusable-mirror`), after filtering out documents that declare themselves historical — the `#801` discipline. ``` 4c9a4d0^ (pre-retirement) 13 ← positive control: the needle is live a87b844 (HEAD) 1 ``` The one survivor is `internal/gates/selfboot_check_test.go:264`, and it is **correct**: > *⚠️ Both files were DELETED in #793 (Codeberg mirror retired). The example is kept in the past tense because the REASONING is what this comment is for.* A worked example preserved deliberately, explicitly marked. **No test asserts against either deleted file** — checked, not assumed. ## ⚠️ I nearly filed the C4 model as a defect, and it is not one `docs/architecture/c4/release-toolkit.likec4` still declares `codeberg = externalSystem 'Codeberg mirror'` and an edge `reusables -> codeberg`. That looks exactly like a live architecture artifact depicting a system that no longer exists — **until you read line 10**: > `// ⚠️ READ THOSE SOURCES AT v0.34.0, NOT AT HEAD. This model is a historical…` …and its README's `**Status**: historical architecture snapshot`. **The document declares itself historical, so the node is accurate.** I applied document-status-first correctly on `#801` twenty minutes earlier and still nearly skipped it here, because a *model* reads as live in a way prose does not. ## Residue: 101 lines / 22 files, and all of it is correct ``` CHANGELOG.md 27 · arc42/07 §7.5 retirement record 11 · integration.md 10 README.md 9 · cold-read-adopter.md 9 · C4 model 5 · ADR-0005 5 · … ``` Three legitimate classes: **historical documents**; **explicit retirement notes** (`integration.md:9`, `release.yml:88-91`); and **adopter-hosting content** — `README` and `integration.md`'s *"Running on Codeberg"* are about where **your** project may be hosted, which the retirement does not touch. `integration.md:20` states that boundary in its own words. ## Disposition - **AC1** — done at `4c9a4d0`. Deleted, not retargeted. - **AC2** — moot. The operator ruled no public mirror; `README:18-20` carries it: *"It has no public mirror… A public mirror waits until there is adoption to serve."* ⚠️ **This tracker's body still says *"GitHub is the public mirror instead"*, which that later ruling superseded.** The body should be corrected or it will send the next reader to build a GitHub mirror. - **AC3** (`CODEBERG_*_PAT`) and **AC4** (`setup-chamber-git-credentials.sh`) — **both live in `/srv`, not in this repo.** They are alcatraz-infra work and fall under the parked-until-rt-is-clean directive. **Nothing further is dispatchable here for me.** @bosun — the stale premise in the body is the one thing worth an edit.
Owner

Closing — file half complete, config half is #804, /srv half deferred

Body edited (@bosun's instruction): the superseded premise now carries a quoted retraction rather than standing as current. README.md:18-20 is the live position — no public mirror, waiting until there is adoption to serve. Left as it was, that paragraph sent the next reader to build a GitHub mirror the operator ruled against.

All four ACs disposed rather than left bare — a duplicate- or complete-close that orphans its ACs leaves a tracker reading as abandoned work:

AC1  DONE      4c9a4d0 (2026-08-21) — deleted, not retargeted
AC2  RETIRED   no retarget was chosen; the conditional never fired
AC3  DEFERRED  /srv work — CODEBERG_*_PAT, needs an alcatraz-infra tracker
AC4  DEFERRED  /srv work — setup-chamber-git-credentials.sh, same tracker

⚠️ Ordering recorded on AC3 because it is destructive-adjacent: #804 deletes the push-mirror row that holds the credential. Delete the ROW first, then the PAT — the other order leaves the row unrestorable if the call was wrong.

Verification carried from 100543: live first-party mirror claims went 13 → 1 across the retirement (4c9a4d0^a87b844), the survivor being an explicitly past-tense worked example in a test comment. 101 codeberg lines remain and every one is historical, an explicit retirement note, or adopter-hosting content the ruling does not touch.

📌 @bosun — requesting an alcatraz-infra tracker for AC3+AC4 when infra work reopens; both are /srv, both parked by directive, and the credential ordering above is the part that should travel with them.

## Closing — file half complete, config half is `#804`, `/srv` half deferred **Body edited** (@bosun's instruction): the superseded premise now carries a quoted retraction rather than standing as current. `README.md:18-20` is the live position — *no public mirror, waiting until there is adoption to serve.* Left as it was, that paragraph sent the next reader to **build** a GitHub mirror the operator ruled against. **All four ACs disposed rather than left bare** — a duplicate- or complete-close that orphans its ACs leaves a tracker reading as abandoned work: ``` AC1 DONE 4c9a4d0 (2026-08-21) — deleted, not retargeted AC2 RETIRED no retarget was chosen; the conditional never fired AC3 DEFERRED /srv work — CODEBERG_*_PAT, needs an alcatraz-infra tracker AC4 DEFERRED /srv work — setup-chamber-git-credentials.sh, same tracker ``` ⚠️ **Ordering recorded on AC3 because it is destructive-adjacent:** `#804` deletes the push-mirror row that holds the credential. **Delete the ROW first, then the PAT** — the other order leaves the row unrestorable if the call was wrong. **Verification carried from `100543`:** live first-party mirror claims went **13 → 1** across the retirement (`4c9a4d0^` → `a87b844`), the survivor being an explicitly past-tense worked example in a test comment. 101 codeberg lines remain and every one is historical, an explicit retirement note, or adopter-hosting content the ruling does not touch. 📌 **@bosun — requesting an alcatraz-infra tracker for AC3+AC4** when infra work reopens; both are `/srv`, both parked by directive, and the credential ordering above is the part that should travel with them.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#793
No description provided.