docs: replace the cutover plan with the phased one that matches reality

Phase 1 requires nothing from anyone: agents keep pushing to GitHub, a timer
syncs GitHub -> Windy Git every 15 minutes, CI runs on Veron against current
code. Phase 2 flips one repo at a time, only when that repo is idle.

Records the direction mistake honestly: the source of truth is wherever people
are actually typing, not wherever the plan says it should be.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Grant Whitmer
2026-08-12 22:14:21 -04:00
parent 51acf9f86e
commit b2f00821d7

View File

@@ -1,63 +1,65 @@
# Windy Git is the daily driver — 2026-08-13
# Migration plan — GitHub first, Windy Git second, flip per repo
Grant's call, 2026-08-12: **push to Windy Git; GitHub is the second copy.**
**Superseded the 2026-08-13 "daily driver" cutover, which was premature.**
you ──push──▶ Windy Git (Veron 1) ──▶ CI on 24 cores
│
└──push-mirror on every commit──▶ GitHub
## What went wrong, recorded so it is not repeated
## 🔴 The one rule this creates
Nine repos were migrated writable with **push-mirrors pointed at GitHub**. At
the same time a dozen agent sessions on the Mac mini were pushing to GitHub
continuously — so GitHub, not Windy Git, was where the live work actually was.
**Do not push directly to GitHub for a migrated repo.**
A push-mirror force-updates refs. On its 8-hour timer it would have pushed Windy
Git's stale copy **over live work, silently, with no conflict to notice.**
A push mirror makes GitHub match Windy Git. Anything committed straight to
GitHub is **overwritten on the next sync**, silently, with no conflict and no
warning. That is the cost of having one writer, and one writer is the point —
two writers with no reconciliation is how you lose work you thought was saved.
All nine mirrors were removed before the first timer fired, and every GitHub
repo was verified untouched (latest push predated the mirrors). **No work was
lost.** The mistake was direction, and the lesson is: *the source of truth is
wherever people are actually typing, not wherever the plan says it should be.*
If you must hotfix on GitHub: push there, then immediately pull that commit into
Windy Git *before* anything triggers a mirror sync. Better: don't.
## Phase 1 — now. Nothing changes for anyone.
## Migrated (9)
Mac mini agents ──push──▶ GitHub ──sync every 15 min──▶ Windy Git ──▶ CI on Veron
`windy-calendar` · `windy-search` · `windy-registry` · `Windy-Clone` ·
`WindyCloud` · `windy-cloud-sites` · `windy-mind` · `eternitas` · `windy-agent`
- **You do not have to tell your agents anything.** No remote changes, no
coordination, no "everyone stop pushing." They keep working exactly as they
are.
- `windygit-sync.timer` runs `scripts/sync_from_github.sh` every 15 minutes.
- Windy Git is **force-updated** on purpose: it holds nothing anyone depends on,
so GitHub always wins and there is **no merge to reconcile**. That is the
whole point of not flipping until a repo is quiet.
- CI runs on Veron 1 against current code, on the 36 workflows that already say
`runs-on: [self-hosted, linux, x64]`.
All writable (`mirror=false`), all push-mirroring to GitHub with
`sync_on_commit: true`. Clone from `https://app.windygit.com/windyadmin/<repo>.git`.
Tracked repos live in `REPOS` in the script (currently 9 of 141).
**Not migrated on purpose:** `windy-pro`. Six checkouts exist, the build counter
has forked three ways (main 12 / overnight 34 / wave-44 56), and two sessions
recorded different HEADs hours apart. Resolve which is current and write it
down first (G11.5). The import script refuses it by name.
## Phase 2 — later, one repo at a time, only when that repo is idle
## CI
For a single repo, when nobody is mid-work on it:
The runner advertises `veron-1`, `linux-x64`, `self-hosted`, `linux`, `x64`.
**36 of 36 active workflows in the fleet already say
`runs-on: [self-hosted, linux, x64]`** — they were written for the self-hosted
runners that died when the repos went private, so they run **as-is, unedited**.
1. Remove it from `REPOS` in `sync_from_github.sh` — **first**, or the sync will
fight its authors and win.
2. Point that repo's sessions at Windy Git:
`git remote set-url origin https://app.windygit.com/windyadmin/<repo>.git`
3. Add a push-mirror back to GitHub with `sync_on_commit: true`, so GitHub stays
a current second copy.
Proven: `windy-calendar`'s existing `.github/workflows/ci.yml` ran on Veron 1
and reported success with no changes.
**Never flip more than one repo at a time, and never while an agent is working
in it.** A dozen parallel sessions is exactly the situation where a big-bang
cutover produces the dirty-branch mess this plan exists to avoid.
**Per-repo secrets are not imported.** A repo whose CI needs a database URL or
an API key will fail until those are set in its Gitea repo settings. Set them as
each repo needs them, not speculatively.
## What is NOT on Windy Git
**9 of 141 repos.** The whole GitHub account is 1.58 GB, so the rest is a
capacity non-issue — it simply has not been imported yet. Add repos to the
import list as they become useful to build.
**`windy-pro` is excluded on purpose.** Six checkouts, a build counter forked
three ways, two sessions recording different HEADs hours apart. Resolve which is
current and write it down before importing (G11.5). The import script refuses it
by name.
## Backups
Nightly `windygit-backup.timer` at 04:17 (`Persistent=true`, so a window missed
while the workstation is off is caught up rather than skipped). Every repo is
bundled `--all`, verified, and uploaded to R2 with the `windgit` schema.
**Restore is rehearsed, not assumed:** a bundle was pulled back from R2, cloned,
and its HEAD matched live `origin/main` exactly.
## Verify the loop yourself
```bash
git clone https://app.windygit.com/windyadmin/windy-calendar.git
cd windy-calendar && git commit --allow-empty -m "probe" && git push
# CI runs on Veron 1; GitHub receives the commit within ~20s
```
`windygit-backup.timer`, nightly 04:17, `git bundle --all` + verify + `windgit`
schema dump to R2, 30-day retention. **Restore rehearsed:** a bundle was pulled
from R2, cloned, and its HEAD matched live `origin/main` exactly.