# The public-flip runbook — Phase 33 Block C (the history rewrite, the push, the purge gate, the flip) > **Status:** written at C3 (S87, 2026-09-06). The procedure is the approved Phase-33 plan's Block C, made operational: > every step names its actor (**Claude** in the WSL clone, **Drew** for every push / GitHub action — rule R6), its exact > commands, its checks (each an exit code or a count), and what to do if it fails. The tools it names under > `tools/public_rewrite/` are written in C1; until then only `purge_set.txt` exists. Scratch for the whole block lives > in `.run/public_rewrite/` (gitignored — it holds old hashes and personal addresses; never commit it). ## 0. The decisions this runbook implements (Drew, 2026-09-06 — binding) 1. **Flip IN PLACE with the full history.** `Druthulu/BFM-decomp` is force-pushed with a rewritten history: every commit, date, message and order preserved; hashes change; the one commit that touched only purged paths ("session archive update", 2026-08-12) drops as empty. No new repo. 2. **Purge from ALL history** — `tools/public_rewrite/purge_set.txt` is the single source of truth (filter-repo syntax): the retail EXE at both historical paths, `dumps/*.bin` (28 RAM images), `ghidra/` (the Ghidra project — its database embeds the EXE's bytes under the page XOR mask), `tools/psyq/` (Sony's SDK), `session archive/` (3 parts, ~271 MB, the only blobs > 50 MiB, ~260k lines of game disassembly inside), `tools/ghidra-ext/*.zip` (re-downloadable; sha256s in `docs/SETUP.md` §2.3/§2.4), `tools/brave-CUE/brave.exe` (a compiled GPL tool; the source stays). 3. **In-history hash scrub.** Every old commit hash cited in any historical blob or commit message becomes an inert token `commit:NNNN` (NNNN = the commit's ordinal on `main`); a token→new-hash map is committed (`docs/commit-map.tsv`); one tip commit resolves the tokens at HEAD to the new hashes so current docs stay navigable. **Fixed-point rule:** a new hash can never be written into a historical blob (it would change every descendant hash) — tokens in history, real hashes only at the tip. 4. **Identity:** both personal e-mail identities → `Drew T <50529377+Druthulu@users.noreply.github.com>` (`--mailmap`); names and dates untouched. The three tracked files that mention the addresses are literal-replaced in the same pass. 5. **Everything else stays public**, including `phase-ends/` (PhaseEnds AND `logs/`), `CLAUDE.md`, `PROJECT_CONTEXT.md`, the decision log, the accelerators and the campaign tooling. 6. **The archive:** `Druthulu/BFM-decomp-archive` (private, created EMPTY — never a fork or an import, those share GitHub's object store) receives the current, unrewritten history (all refs + the `S76-pre-scrub-backup` tag) BEFORE the rewrite. The tag is dropped from the public history. 7. **The flip is gated** on GitHub no longer serving the old hashes (Support purge, probed by script); delete-and-recreate under the same name is the fallback if Support stalls. **Data-loss hazard, in force from C3 onward:** the purged paths are now *ignored but present* on disk. `git clean -x` / `git clean -fdx` would delete the Ghidra database, the dumps and the SDK. `make clean` is the only clean. (CLAUDE.md fail-safe rule; R20 amendment proposed at PhaseEnd_Phase33.) ## 1. Who does what | Actor | Steps | |---|---| | **Claude** (WSL clone `~/bfm-decomp`) | C3 ✓, C1, C2, the bundle (C4a), the bare clone + rewrite (C4c), C5, C6, C7, C8, the C9 gate, C11's local part, every doc | | **Drew** | creates the archive repo and pushes the mirror (C4b), the force-push + remote tag deletion (C9), the Support ticket, the probe re-runs, the visibility flip (C10), the other clones' resets (C11) | Claude never runs `git push` (R6). Every Drew step is announced with its exact command and its check, and the session WAITS for Drew's word that it ran (never inferred from a later `git fetch`). ## 2. C3 — the preparatory commit (DONE, S87) `git rm --cached` of the purge set (index only; the files stay on disk, already ignored — checked with `git check-ignore --no-index` on every path BEFORE they became untracked, because a blanket `git add -A` would otherwise re-add ROM bytes). `tools/psyq/CHECKSUMS.sha256` had already moved to `tools/psyq_CHECKSUMS.sha256` (B4) — nothing project-authored remained under a purged directory (census: 189 Sony/third-party files under `tools/psyq/`, 27 Ghidra DB files, 3 archive parts, 2 zips, `brave.exe`, the EXE). Controls: `git ls-files -- ` empty; `tools/audit_public.py` → OK (it FAILED by design until this commit); `make check-env` 0; main byte-identical with `tools/psyq/` + `.run/obj40` + `.run/obj42` moved aside (the public WITHOUT leg needs none of it). A5's recorded run stays valid for this tip: a `--cached` removal changes no tracked-content byte. ## 3. C1 — the tooling (Claude; design Max, execution xHigh) `.venv/bin/pip install git-filter-repo==2.47.0` (SETUP row, R21). `tools/public_rewrite/`: | Tool | Does | Measured (S87; C2 re-measures on the final tree) | |---|---|---| | `purge_set.txt` | the purge paths (exists since B7) | 8 rules | | `gate_scan.py --all \| --refs [--worktree] [--expect-fail tools/public_rewrite/expected_offenders.txt]` | the first-push gate over history: scans every blob reachable from the refs (and the worktree) for purge-path prefixes, content SHA1s in the known-ROM set (the EXE, the redump Track 1, every `sha1` in `extracted/retail/manifest.jsonl`, every `config/check.*.sha`), byte signatures (`PS-X EXE` at offset 0; the 2,097,152-byte RAM image with the resident's first words at 0xCEDF8; the EXE entry code; PsyQ `LIB\x01` / `LNK\x02` magics), any blob > 50 MiB; emits `rom_blob_ids.txt`. `--expect-fail FIXTURE` = the R39 negative control: the fixture lists `rulemin offending paths`; exit 0 only when every rule has at least that many AND no content/signature/size offender sits outside the purge rules; `rom_blob_ids.txt` = content hits ∪ every blob ever under a purge path | measured S87 on the current repo: 112,390 blobs / 16.8 GB in 2 m 25 s; paths ever: ghidra/ 42, tools/psyq/ 190, dumps 28, archive 3, zips 2, brave.exe 1, the EXE 1+1; 0 strays; 52 content/signature ids + 269 blobs ever under a purge path | | `hash_dict.py [--write-mailmap]` | every commit hash → ordinal (`git rev-list --reverse main`); off-main commits (the tag lineage, 126) → the ordinal of their (tree, author-timestamp, subject) twin, else `orphan-NNN`; all prefixes 7..40; asserts 0 ambiguous prefixes and 0 collisions with the content-hash set | measured S87: 4,420 commit objects (4,030 main, 339 twins incl. the 126 tag-lineage re-authorings, 51 orphans), 150,280 prefixes, 0 ambiguous, 0 content collisions | | `scrub.py` | THE one scrub function: `\b[0-9a-f]{7,40}\b` → dictionary lookup → `commit:NNNN` (non-hex in the first 7 chars, so it can never re-match; no Markdown side effects); NUL-sniff binary skip; idempotent | at HEAD (S87): 731 distinct resolving tokens, 1,238 replacements in 98 files, git's own lookup agrees exactly; 397 MB in 7.9 s | | `run_filter.py [--sample]` | composes the `git filter-repo` call (below); refuses to run outside a bare repo under `.run/public_rewrite/`; logs versions + wall time; `--sample` first (R37) = `scrub.py --sample`, the independent-oracle check | the trial run's numbers are in phase-ends/CURRENT_PHASE.md (S87 C1) | | `build_commit_map.py [--out PATH]` | public `docs/commit-map.tsv` (`ordinal new_hash author_date committer_date subject`, no old hash anywhere — asserted by running scrub over its own output); private `.run/public_rewrite/old-to-new.tsv` for the probe | 4,0xx rows, exactly one mapped to zeros (the pruned archive-upload commit) | | `resolve_tokens.py [--check] [--map PATH]` | at HEAD of the adopted checkout: `commit:NNNN` → the unique 9-char new abbreviation (asserted by `git cat-file --batch-check`); `--check` asserts zero resolvable tokens remain and lists the orphan residue | ≥1 orphan: `tools/verify_worktree.py` cites a dropped TEMP commit | | `verify_rewrite.py --old ~/bfm-decomp --new .run/public_rewrite/repo.git` | the pairwise proof (C5): old commits from the ORIGINAL repo (the clone gc's them away), new from the clone | every pair | | `absent_scan.py [--repo PATH] [--tree HEAD]` | every text blob + every message + every ref → 0 old-hash prefixes, 0 personal addresses, 0 session URLs, 0 trailer lines, every identity = noreply, no replace/original/tag refs (≈7 min over all objects; INFO: bare UUID count) | 0 offenders (the current repo: FAIL, 82,362 — its positive control) | | `probe_github.sh` | Drew's post-purge probe (C10) | — | | `mailmap` (scratch, `.run/public_rewrite/mailmap`, written by `hash_dict.py --write-mailmap` from the log's identities) | the two personal identities → the noreply identity | never committed | Budget: regex+lookup ≈2.2 CPU-min over 16.8 GB of blobs; the filter-repo stream dominates (10–30 min). Disk: a bare `--no-local` clone ≈0.6 GB + the bundle ≈0.6 GB; no working-tree copy (the WSL disk is capped at 75 GB, ≈13 GB free). ## 4. C2 — negative control, dictionary, sample (Claude) 1. `gate_scan.py --all --worktree --expect-fail tools/public_rewrite/expected_offenders.txt` on the CURRENT repo → must report PASS (= the scan fails exactly as the fixture says; the scan that PASSES with 0 offenders in C5 is this same tool). 2. `hash_dict.py` → prints the counts; assert 0 ambiguous, 0 collisions. 3. `run_filter.py --sample` → the sample numbers above. ## 5. C4 — backups, then the rewrite **C4a (Claude) — the bundle:** `git bundle create .run/public_rewrite/pre-rewrite.bundle --all --reflog && git bundle verify .run/public_rewrite/pre-rewrite.bundle` (≈0.6 GB). This is the local restore point for everything below. **C4b (Drew) — the archive mirror:** ```bash # on GitHub: New repository → Druthulu/BFM-decomp-archive → Private → EMPTY (no README, no .gitignore, no license; # NOT "import" and NOT a fork) cd ~/bfm-decomp git remote add archive https://github.com/Druthulu/BFM-decomp-archive.git git push --mirror archive ``` Check (Claude): `git for-each-ref --format='%(objectname) %(refname)' | sort` equals `git ls-remote archive | sort` (same ref set, same hashes; the tag included). Only then may the rewrite start. **C4c (Claude) — the bare clone + the rewrite:** ```bash git clone --no-local --bare ~/bfm-decomp .run/public_rewrite/repo.git cd .run/public_rewrite/repo.git git tag -d S76-pre-scrub-backup # the archive keeps it (decision 11) git for-each-ref # must be exactly refs/heads/main at the prep commit git count-objects -v # one pack, zero loose objects .venv/bin/python ../../../tools/public_rewrite/run_filter.py # composes: # git filter-repo --invert-paths --paths-from-file purge_set.txt --strip-blobs-with-ids rom_blob_ids.txt \ # --blob-callback --message-callback \ # --prune-empty auto --replace-refs delete-no-add --mailmap .run/public_rewrite/mailmap ``` Why each flag: `--prune-empty auto`, never `always` (main carries one pre-existing empty commit from 2026-08-25 that must survive); `--replace-refs delete-no-add` (no `refs/replace/` names may be minted — they would leak old hashes); `--strip-blobs-with-ids` catches the EXE wherever it was renamed; the message callback also strips the 60 remaining `Claude-Session:` trailer lines the S76 scrub missed. Checks: exit 0; the commit map has (old main count) rows; the rows mapped to zeros are EXACTLY the commits whose every change was a purge path (`verify_rewrite` derives that set — 1 on this history: "session archive update"); no `refs/replace`; one pack. Pack size: filter-repo's own gc leaves ≈500 MB (trial #1: 534 → 520 MB — the 16 GB of scrubbed text history re-deltas poorly; the purged binaries ARE gone: the archive/ghidra/dump blobs are absent from the store); C9's local gc uses an aggressive repack — measured on trial #2: `git -c pack.threads=16 repack -adf --window=250 --depth=50` took the 500 MB pack to **80 MB in 166 s**. **Lesson from trial #1 (S87):** stripping blobs BY ID must never include a blob that also lives under a non-purge path — the EMPTY blob (an empty file once sat under `ghidra/`) was in the list, and `--strip-blobs-with-ids` then dropped every "file emptied" change in history: those files silently kept their previous content and a later restore commit became empty and was pruned. `gate_scan` now excludes shared blobs from `rom_blob_ids.txt` (content/signature hits are always kept), and `verify_rewrite` asserts both that no purge path survives in any new tree and that the pruned set equals the derived purge-only set. ## 6. C5 — verification on the rewritten clone (Claude; every check an exit code) `verify_rewrite.py` — for every (old, new) pair: names/e-mails (post-mailmap) and BOTH timestamps equal; `new.message == scrub(old.message)`; new parents = map(old parents) with the pruned commit spliced out; `git diff-tree -r --no-renames old new`: every `D` is a purge path or a ROM blob id, every `M` satisfies `hash-object(scrub(old_blob)) == new_blob`, any `A` FAILS; prints the pair count. Then `gate_scan.py --refs --all` → PASS (the same tool that failed in C2); `absent_scan.py` → 0/0/0; `git rev-list --count main` = old count − pruned; the `%at %ct` lists match with the pruned commits removed; the pre-existing empty commit's twin exists; no purge path in any new tree; the pruned set == the purge-only commits. A commit the rewrite leaves BYTE-IDENTICAL keeps its hash (old == new — the noreply-authored "Initial commit", which cites no hash and touches no purge path): `build_commit_map` records those in `.run/public_rewrite/unchanged_commits.txt`, `absent_scan` does not count their prefixes as old hashes, and `probe_github.sh` skips them (they legitimately still resolve on GitHub). Measured trial #1: filter 274 s, verify 385 s, absent_scan 346 s over 16.2 GB of text. ## 7. C6 — adoption in `~/bfm-decomp` (Claude; NO gc yet) ```bash git fetch .run/public_rewrite/repo.git +refs/heads/main:refs/heads/main-rewritten git diff --stat main main-rewritten # ONLY text files (≈93) and no purge path git reset --hard main-rewritten # on main; the purged paths are untracked+ignored since C3 → they stay on disk git status --porcelain # empty git branch -D main-rewritten; git stash drop (each); git tag -d S76-pre-scrub-backup; git update-ref -d refs/original/... (if any) ``` `git ls-files | wc -l` equals the clone's tree count. No `gc` yet: `origin/main` still pins the old lineage until Drew pushes, and the old objects are the local safety net until C9's counts pass. ## 8. C7 — commit map + tip resolution (Claude) `git config user.email 50529377+Druthulu@users.noreply.github.com` (the noreply identity, before the tip commit). `build_commit_map.py` → `docs/commit-map.tsv`; `resolve_tokens.py`; checks: `git grep -c 'commit:[0-9]' HEAD` = 0; the orphan residue listed in the commit message; every inserted 9-char hash resolves uniquely; `absent_scan.py --tree HEAD` = 0. Commit: `docs(phase-33): commit-map + citations resolved to the rewritten history`. ## 9. C8 — R22 on the adopted tree (Claude, ≈15 min here) `tools/verify_contract.sh` (the A5 script) → `.run/P33/verify/` refreshed; `check-all: 218 passed, 0 failed of 218`; report still 100.00 / 100.0 / 100.0. Commit the refreshed evidence (a content-preserving rewrite changed no tracked byte — this run proves it). ## 10. C9 — final gate, force-push, gc **Claude:** `gate_scan.py --refs main --worktree` → PASS. **Drew:** ```bash git ls-remote --tags origin # is S76-pre-scrub-backup on origin? git push --force origin main git push origin :refs/tags/S76-pre-scrub-backup # if it was git fetch --prune origin && git rev-parse origin/main main # equal ``` **Claude, after Drew's word — measured S87:** (1) `git remote remove archive` (its remote-tracking ref pins the old lineage; the mirror push is done); (2) **`git worktree list` — every linked worktree's HEAD counts as REACHABLE**: S87 found 12 stale campaign worktrees (`.run/S74/wt_*`, `.run/pgate/wt*`, `.run/S69_fable3/…`, `~/bfm-verify`) at old commits — 12 GB of old-history checkouts, each holding the SDK/EXE/Ghidra on disk — `git worktree remove --force ` each, then `git worktree prune` (`rev-list --all` went 8,146 → 7,763 after the remote, → 4,034 only after the worktrees); (3) `git reflog expire --expire=now --all && rm -f .git/objects/info/commit-graph && git -c pack.threads=16 repack -adf --window=250 --depth=50 && git prune --expire=now && git commit-graph write --reachable` (a stale commit-graph names pruned commits and makes `fsck` fail; 163 s). Checks: `git rev-list --all --count` == `git rev-list --count main` (4,034), objects in store == reachable (176,056), `git fsck` clean, `gate_scan.py --all --worktree` PASS, `absent_scan.py --repo ~/bfm-decomp` PASS (≈7 min), `.git` size (S87: one 80 MB pack, `.git` 93 MB, from 1.5 GB). Then the probe baseline (`probe_github.sh`): every sampled old hash still ALIVE is expected until the Support purge — that count (S87: 31 of 33) is what the ticket asks GitHub to make zero. ## 11. C10 — the Support purge, the probe, the flip (Drew; decision Max) **The ticket** (GitHub Support → "Remove data from a repository" / force-push cleanup), text: > Repository: `Druthulu/BFM-decomp` (private; no forks — network_count 0; no pull requests). On 2026-09-07 05:55 UTC I > force-pushed a rewritten history to `main` that removes proprietary game binaries (a PlayStation executable, RAM dumps and > a vendor SDK) and personal session data from every commit; an earlier force-push on 2026-09-03 22:05 UTC left a second > unreachable lineage. The old commits are still served by SHA — for example the two pre-force-push tips > `3a8af85160f1ae837d9c1b24e78d6fc7530d27fd` and `71fc1600397e93c78850e2079832d62b1a8bf664` (both listed as "before" in the > repository's Activity view) return 200 from the commits API and can be fetched. Please garbage-collect all unreachable > objects in this repository and purge cached views (commit pages, raw blob URLs, API lookups by SHA) so those SHAs return > 404. The repository will be made public only after that; please let me know when it is done so I can re-verify. Thank you. Filing — the route that worked (2026-09-07, ticket **#4736982**): signed in as the owner → "Remove data from a repository I own or control" → "Clear cached views" → on the Repositories form press the blue **"Clear cached views with our Virtual Agent"** button (NOT the static form) → "Yes, but I need help removing of cached commits" → "No - Just the repository" (repository-wide, not one commit) → `Druthulu/BFM-decomp` → in a pull request? No → the reason, in ≤500 characters: third-party proprietary binaries were in the history, removed and force-pushed, purge the whole repository's unreachable objects before it goes public. The agent files the ticket itself. **Trap:** the static form's "Deletes" sub-option is the delete-the-whole-repository flow (asks for the URL to delete and a purge confirmation) — never submit it. Turnaround is days, not hours, and GitHub publishes no GC schedule; the long text above is for a human follow-up. **Why the flip cannot precede the purge (measured S89, 2026-09-07 — the "no one has the old hashes" premise is false):** GitHub's repository **Activity view** (`GET /repos/Druthulu/BFM-decomp/activity`, the Activity tab in the UI) lists every ref update since the repo was created — 157 rows back to 2026-06-11, including both force-pushes with their `before` SHA (the pre-rewrite tip `3a8af85160…` and the S76 tip `71fc1600…`) and 154 pushes whose before/after are old-lineage SHAs. Unlike the events API (whose rows carry `public: false`), these rows carry no visibility flag; assume every reader of a public repo sees them. From those SHAs, today, the API serves the commit, `extracted/retail/SLUS_007.26` (413,696 bytes, download URL present), all 28 `dumps/*.bin` and the 3 `session archive` parts, and `git fetch origin ` succeeds (the probe: 31 of 33 sampled old hashes ALIVE; the S76 lineage has been unreachable for 4 days with no GC). So a clean tree and a clean history are necessary but not sufficient: until the objects are gone from GitHub's store, the repo's own Activity tab is a one-click path to the purged binaries. A hash that resolves to 404 is harmless, so the purge alone closes it. **The deterministic alternative** (the fallback below, promoted): delete the repository and recreate it under the same name, then push the rewritten `main` — a new repository is a new object network AND a fresh Activity log, so the probe passes by construction; nothing registered against the repo id yet (no issues/PRs/stars/wiki/secrets; the Actions runs re-run on the new push; the archive repo, `Druthulu/xsig` and the permuter fork are separate). `gh repo delete` needs the `delete_repo` scope (`gh auth refresh -h github.com -s delete_repo`); a deleted repo stays owner-restorable for 90 days, not servable. **The probe** (`tools/public_rewrite/probe_github.sh`, needs `gh auth login` in Drew's shell): for 30 sampled full old hashes + the pruned commit + the old tag tip: `gh api repos/Druthulu/BFM-decomp/commits/` must return 404 and a `git fetch` of the sha must fail; positive control: the PUSHED tip (`origin/main`) must succeed (the local `main` may carry unpushed commits, S88). After the flip, also probe 7-char prefixes unauthenticated at `github.com/Druthulu/BFM-decomp/commit/<7>`. While ANY probe returns 200: wait and re-run daily. **R57 (S88, 2026-09-07): the fetch check runs in a THROWAWAY bare repo** (`.run/public_rewrite/ probe_scratch.git`, `--filter=blob:none --depth=1`, deleted on exit) — a successful `git fetch origin ` pulls that commit's whole closure (the purged EXE, dumps, Ghidra DB, SDK) into the repository that runs it. The S87 baseline run and the first S88 run did exactly that to `~/bfm-decomp` (30 packs / 5.97 GiB of unreachable old objects on top of C9's one 80 MB pack); the probe now ends with a self-check that names any sampled old commit the working repo still holds and prints the C11 recipe (`git reflog expire --expire-unreachable=now --all && git gc --prune=now`) — run it (Drew; the auto-mode classifier refuses it from a Claude shell) and the store returns to one pack. **Fallback** if Support stalls: delete the repository and recreate it under the same name, push the same rewritten history (nothing else exists to lose — the archive repo and the bundle hold the old history). **The flip:** Settings → General → Danger Zone → Change visibility → Public — ONLY after the probe exits 0 (and enable Settings → General → Features → Wikis first: `has_wiki` read false on 2026-09-07, and F3's push needs it) and Blocks D (README, LICENSE, NOTICE, THIRD_PARTY, badges), E and F have landed on the still-private repo. Then D3's outward actions (frogress slug, decomp.dev registration), E1 (the decomp.me preset — Drew's six steps are in `docs/decompme-preset.md` §5 and the phase checkpoint §3; bundle `.run/decompme/drew_bundle/`), E2 (the Archipelago message), F3 (the wiki push: create the first page in the GitHub UI — Wiki → "Create the first page" — then `tools/wiki_sync.sh --push`; the pages are authored in `docs/wiki/` + `docs/how-to-ai-decomp/` and the script replaces the wiki's pages with the rendered set). **The one recorded residue (P33.5 task 8, 2026-09-07).** After the force-push, the Phase-33.5 audit found two tracked notes files under `.run/giants/fable_cd4/` (`mine_full.txt`, `target_full.txt`: 398 and 399 lines of one matched function's disassembly, re-included by a `*.txt` allow-list line) — class 3 of the firewall, invisible to a path-and-hash audit. Owner decision: untrack them, list them in `tools/public_rewrite/untracked_after_rewrite.txt` (an audit-only sibling of the purge set — the purge set is also `gate_scan.py`'s history census, so adding a rule there without a rewrite would make the history gate red forever), and add `audit_public.py` check 4 (a contiguous run of disassembly-shaped lines, threshold 64) so the class is caught from now on; **no second rewrite** for 8 KB that the tracked C and the pinned compiler reproduce, at the cost of another set of old tips for Support to purge. The residue exists in the published history and is named on the wiki page *The ROM firewall*. **Drew's post-flip checklist (consolidated S89, 2026-09-07):** (0) probe daily until PASS; push; Actions green → (1) the flip + `--after-flip` probe + enable Wikis → (2) E1: `docs/decompme-preset.md` §5 (scratch → 100% → preset-request issue → manual search) → (3) E2: the issue in `docs/outreach/archipelago.md` §4 → (4) D3 outward: decomp.dev `manage/new`, frogress slug `bfm` + key, `frogress_upload.py --push` → (5) wiki: first page in the UI, `tools/wiki_sync.sh --push` → (6) tell Claude → C11 → G2. The same list is `phase-ends/CURRENT_PHASE.md` §0b. ## 12. C11 — aftercare - **Every other clone of the old history** (the Windows tree, any other machine): `git fetch origin && git reset --hard origin/main && git reflog expire --expire=now --all && git gc --prune=now` — or re-clone. **Never `git pull`** (an 8,000-commit merge of two unrelated lineages). - `git remote remove archive` — done at C9 (it pinned the old lineage); never re-add it to the working repo. - `.run/` (38 GB) → prune regenerables; `.run/public_rewrite/` (old hashes, the mailmap, the bundle) → keep until the probe has passed, then delete the clone and the dictionary; keep the bundle off-machine if wanted. - Docs: `phase-ends/DIGEST.md` §1 (H1 in force again; R1/R20 historical), `docs/decision-log.md` (R31), SETUP's posture section (D4), `CLAUDE.md` (the `git clean -x` guard — in place since C3). ## 13. Risk register (condensed) | Risk | Guard | |---|---| | A new hash written into a historical doc (changes every descendant hash) | tokens in history, real hashes only in the tip commit (fixed-point rule) | | `git clean -x` after C3 deletes the RE database | CLAUDE.md fail-safe line; the bundle; the archive repo; the text export `config/ghidra/` + `ghidra_rebuild.sh --proof` | | GitHub keeps serving force-pushed-away objects | the Support purge + the probe gate; the earlier S76 pre-scrub lineage sits unreachable on GitHub too and is covered by the same purge | | A fork/import-created archive shares the object store | the archive is created EMPTY and receives a `--mirror` push | | A scrub false positive (a binary SHA1 abbreviation coinciding with a commit prefix; expected < 1 over 44k tokens) | the content-hash exclusion set; ambiguous prefixes become `commit:amb-N`, never a wrong hash | | An old clone `git pull`s the new history | the C11 reset recipe; announced before the force-push | | Disk (13 GB free) / time | clone 0.6 + rewrite 0.2 + bundle 0.6 GB; rewrite 10–30 min; C8 ≈15 min | | The mailmap is cosmetic unless the 3 tracked files mentioning the addresses are also replaced | they are (the blob callback) — and `absent_scan.py` asserts 0 occurrences everywhere | ## 14. Rollback - **Before C9's force-push:** nothing on GitHub has changed; `git reset --hard origin/main` (or restore from the bundle: `git clone .run/public_rewrite/pre-rewrite.bundle`) returns the clone to the old history. - **After the force-push:** the archive repo and the bundle hold the complete old history; `git push --mirror` from the archive into `BFM-decomp` restores it (it would then need the Support purge again). Old objects on GitHub are unreachable, not gone, until the purge — which is why the flip waits for the probe.