docs(migration): operator-window runbook (pilot gate + batch order)
RUNBOOK.md: the single doc the operator follows in the authorized window. Pilot gate on graceful-handoff with the B3 ordering fix (flip the catalog entry to external BEFORE the install-smoke, else the Forgejo/HTTPS/ref chain is untested). Forgejo repo creation via API (auto_init:false, public), per-repo create->push->flip->install-smoke loop, batch order (design-system -> high-churn -> low-churn), thin-catalog last, ref-update flow, and a reversible-until-thinning rollback. References the prior scripts; push-window-aware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3a5f558a48
commit
629e468983
1 changed files with 190 additions and 0 deletions
190
docs/marketplace-polyrepo-migration/migration/RUNBOOK.md
Normal file
190
docs/marketplace-polyrepo-migration/migration/RUNBOOK.md
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
# Polyrepo migration — operator-window RUNBOOK
|
||||
|
||||
The single document the operator follows in the authorized window. Everything before this (Steps 1–9,
|
||||
plus the Step 11 dry-run) was authored and verified locally with **NULL push (D8)**. This runbook is the
|
||||
**only** place live Forgejo repos are created and the live `marketplace.json` is mutated.
|
||||
|
||||
## Preconditions
|
||||
|
||||
- All migration scripts committed locally; the Step 11 dry-run is green (it exercises every script below).
|
||||
- `$FORGEJO_TOKEN` is exported (macOS Keychain → `~/.zshenv`) with `write:org` + `repo` scope.
|
||||
- The `pre-polyrepo-archive` tag exists locally (D2) — the rollback anchor for the whole operation.
|
||||
- **Push window (private-work policy):** this runbook pushes to Forgejo. Run it only inside the allowed
|
||||
push window — weekdays 20:00–23:00 (Europe/Oslo), or any time on weekends / Norwegian holidays.
|
||||
- `WORK=/tmp/polyrepo-migration` (the mirror + per-target extracts the scripts build).
|
||||
- Visibility: **public** (§9) — every repo is created `"private": false`.
|
||||
|
||||
Paths below assume you are in the catalog repo root unless stated. Scripts live in
|
||||
`docs/marketplace-polyrepo-migration/migration/` (referred to as `MIG/`).
|
||||
|
||||
---
|
||||
|
||||
## (0) Pilot gate — `graceful-handoff`
|
||||
|
||||
The pilot proves the highest-stakes unverified link: **HTTPS + external `source` + `ref` pin + Forgejo
|
||||
resolution** (F2 / Assumption 1). Pilot = `graceful-handoff` (low-churn, has tests, no design-system
|
||||
vendor, no blob bloat).
|
||||
|
||||
> **B3 ordering — non-negotiable:** the catalog entry MUST be flipped to external *before* the
|
||||
> install-smoke. If you install while the entry is still `./plugins/graceful-handoff`, the install
|
||||
> resolves the **local** copy and the test is vacuous — it proves nothing about the Forgejo chain.
|
||||
|
||||
**(0a) Create the repo on Forgejo** (`auto_init:false` so the first push defines history — an
|
||||
auto-initialised repo would create a divergent root commit the rename-aware extract cannot fast-forward):
|
||||
|
||||
```bash
|
||||
curl -fsS -X POST "https://git.fromaitochitta.com/api/v1/orgs/open/repos" \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"graceful-handoff","private":false,"auto_init":false,"default_branch":"main"}'
|
||||
```
|
||||
|
||||
**(0b) Push the extracted repo** (the extract was prepared + validated by the harness; re-run it to be
|
||||
sure it is green, then push all branches + tags):
|
||||
|
||||
```bash
|
||||
WORK=$WORK bash MIG/40-validate-standalone.sh graceful-handoff # must print: graceful-handoff: PASS (...)
|
||||
cd "$WORK/graceful-handoff"
|
||||
git remote add origin https://git.fromaitochitta.com/open/graceful-handoff.git
|
||||
git push origin --all
|
||||
git push origin --tags # publishes v2.1.0 — the ref the catalog will pin
|
||||
cd - # back to the catalog repo
|
||||
```
|
||||
|
||||
**(0c) Flip the catalog entry to external and push the catalog in its still-mixed state**
|
||||
(9× `./plugins/x` + 1× external; `plugins/` is still present — thinning is last):
|
||||
|
||||
```bash
|
||||
node MIG/60-rewrite-marketplace.mjs --only graceful-handoff \
|
||||
--in .claude-plugin/marketplace.json --out /tmp/mp.json
|
||||
# review /tmp/mp.json: graceful-handoff is now {source:"url", url:..., ref:"v2.1.0"}, the other 9 unchanged
|
||||
cp /tmp/mp.json .claude-plugin/marketplace.json
|
||||
git add .claude-plugin/marketplace.json
|
||||
git commit -m "chore(marketplace): externalise graceful-handoff (pilot)"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
**(0d) Install-smoke from a FRESH Claude Code session** — the actual Forgejo-chain test:
|
||||
|
||||
```text
|
||||
/plugin marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git
|
||||
/plugin install graceful-handoff@ktg-plugin-marketplace
|
||||
```
|
||||
|
||||
Because the entry is now external, the install resolves the Forgejo repo over **HTTPS at `ref: v2.1.0`**.
|
||||
Confirm the plugin's commands/skills load.
|
||||
|
||||
> **If 0d fails: STOP.** Revert the flip (§ Rollback) — graceful-handoff goes back to
|
||||
> `./plugins/graceful-handoff`, the marketplace stays live — and diagnose the HTTPS/ref/Forgejo chain
|
||||
> before touching any of the other 10. Do not proceed past a failed pilot.
|
||||
|
||||
---
|
||||
|
||||
## (1)–(3) Roll out the rest, one repo at a time
|
||||
|
||||
Order (lower risk last so problems surface while the blast radius is small):
|
||||
|
||||
1. **Design-system first** — `playground-design-system` (the 2 consumers, llm-security + ms-ai-architect,
|
||||
vendor it; standing it up first means their vendored copies have an upstream to point at).
|
||||
2. **High-churn** — `voyage` → `llm-security` → `linkedin-studio` → `ms-ai-architect`.
|
||||
- `ms-ai-architect` carries the 148 MB screenshot blob-bomb (F3); the extract strips >1 MB blobs when
|
||||
`blob_strip_safe` (verified true), so its push is a normal size. Confirm the push completes.
|
||||
3. **Low-churn** — `config-audit` → `okr` → `ai-psychosis` → `human-friendly-style` → `claude-design`.
|
||||
|
||||
**Per-repo procedure (identical for every target):**
|
||||
|
||||
```bash
|
||||
KEY=<target> # e.g. voyage
|
||||
|
||||
# a. extract + validate standalone (config-audit uses its dedicated SC2 gate)
|
||||
if [ "$KEY" = "config-audit" ]; then
|
||||
WORK=$WORK bash MIG/50-config-audit-sc2.sh # config-audit: SC2 PASS (...)
|
||||
else
|
||||
WORK=$WORK bash MIG/40-validate-standalone.sh "$KEY" # <KEY>: PASS (...)
|
||||
fi
|
||||
|
||||
# b. create the Forgejo repo (auto_init:false, public)
|
||||
curl -fsS -X POST "https://git.fromaitochitta.com/api/v1/orgs/open/repos" \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" -H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"$KEY\",\"private\":false,\"auto_init\":false,\"default_branch\":\"main\"}"
|
||||
|
||||
# c. push the extracted repo
|
||||
cd "$WORK/$KEY"
|
||||
git remote add origin "https://git.fromaitochitta.com/open/$KEY.git"
|
||||
git push origin --all
|
||||
git push origin --tags
|
||||
cd -
|
||||
|
||||
# d. flip this entry in the catalog (cumulative — previously-flipped entries pass through unchanged)
|
||||
node MIG/60-rewrite-marketplace.mjs --only "$KEY" \
|
||||
--in .claude-plugin/marketplace.json --out /tmp/mp.json
|
||||
cp /tmp/mp.json .claude-plugin/marketplace.json
|
||||
git add .claude-plugin/marketplace.json
|
||||
git commit -m "chore(marketplace): externalise $KEY"
|
||||
git push origin main
|
||||
|
||||
# e. INSTALL-SMOKE (SC8) before moving to the next target — fresh Claude Code session:
|
||||
# /plugin marketplace update
|
||||
# /plugin install $KEY@ktg-plugin-marketplace
|
||||
```
|
||||
|
||||
**Install-smoke order by surface** (largest command surface first, so the most likely to expose a
|
||||
resolution problem is caught early): `linkedin-studio` (29 commands) → `llm-security` → `config-audit`
|
||||
→ `ms-ai-architect` → `voyage` → the rest. For each: confirm commands/skills/agents register and one
|
||||
representative command runs.
|
||||
|
||||
> Do not start a target until the previous target's install-smoke has passed. A mixed-source catalog
|
||||
> (some `./plugins/x`, some external) is a fully valid live state (SC3/SC8) — that is exactly what makes
|
||||
> the one-at-a-time rollout safe.
|
||||
|
||||
---
|
||||
|
||||
## (4) Thin the catalog — LAST
|
||||
|
||||
Only after **all 11 repos are pushed and every install-smoke has passed**:
|
||||
|
||||
```bash
|
||||
bash MIG/70-thin-catalog.sh --workspace /tmp/thin-catalog
|
||||
# review /tmp/thin-catalog: no plugins/ or shared/, CONVENTIONS.md present, README.md all-external
|
||||
```
|
||||
|
||||
Apply the thin state to the catalog repo (remove `plugins/`, `shared/`, `scripts/sync-design-system.mjs`,
|
||||
add `CONVENTIONS.md`, swap in the rewritten `README.md` + thinned `CLAUDE.md`), commit, then:
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
git push origin pre-polyrepo-archive # publish the rollback anchor tag (D2)
|
||||
```
|
||||
|
||||
The marketplace.json is now fully external; the catalog hosts only the manifest + landing/governance/
|
||||
conventions docs.
|
||||
|
||||
---
|
||||
|
||||
## Ref updates (steady state)
|
||||
|
||||
When a plugin cuts a new release, bump its `ref` in `.claude-plugin/marketplace.json` (re-run
|
||||
`60-rewrite-marketplace.mjs --only <key>` after updating the tag in `plugin-map.json`, or edit the `ref`
|
||||
by hand), push the catalog, and consumers pick it up with `/plugin marketplace update`.
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
A flip is **reversible right up until the `./plugins/<key>` source is removed** (i.e. until thinning).
|
||||
Order of preference:
|
||||
|
||||
- **Single bad flip (pre-thinning):** `git revert <the externalise commit>` and push — the entry returns
|
||||
to `./plugins/<key>`, which is still present, so the marketplace works immediately. Then fix the
|
||||
external repo and re-flip.
|
||||
- **Never remove a `./plugins/<key>` source before that plugin's external repo is pushed AND its
|
||||
install-smoke has passed** (R1 / SC8). Thinning (step 4) is the point of no easy return — do it only
|
||||
when every external repo is verified live.
|
||||
- **Whole-operation anchor:** the `pre-polyrepo-archive` tag is the pre-migration state of the monorepo.
|
||||
If the catalog needs to be reset wholesale, reset to that tag (local) before it was force-published.
|
||||
|
||||
## Failure stops
|
||||
|
||||
- Pilot (0d) fails → STOP, revert, diagnose the Forgejo/HTTPS/ref chain. Touch nothing else.
|
||||
- Any per-repo push or install-smoke fails → STOP at that target, revert its flip, leave the rest live.
|
||||
- Never thin the catalog while any plugin is still `./plugins/<key>`-only without a verified external repo.
|
||||
Loading…
Add table
Add a link
Reference in a new issue