# Git History Rewrite Runbook (audit R-1 tail)

Prepared and **measured** 2026-07-18. Execution requires a force-push to `main`,
which is forbidden without the owner driving the window — everything short of
that is done here.

## Measured outcome (dry run, 2026-07-18)

A fresh `--single-branch main` clone was rewritten with `git-filter-repo`
(31ebad4c8fb3) using the path set below: **pack 1.2 GiB → 889 MiB (−28%)**,
filter 17s + repack 31s on the dev box. The audit's ~850 MB estimate was
working-tree bytes; compressed pack savings are smaller because the SwiftPM
`.pcm`/`.mdb` blobs delta-compress well. The remaining pack is dominated by
legitimate source history.

## Purge path set (tip-absent paths only — verified against the

forbidden-paths guard's carve-outs)

```
regex:^libs/psyche/ios-sdk/\.build/
regex:^apps/bellona/remote-host/macos/\.build/
regex:^backups/
regex:^apps/lilith/contracts/artifacts/build-info/
regex:^docs-center/.*\.html$
regex:^vdocs-index\.html$
regex:^vdocs-search-index\.js$
regex:^docs/(?!.*(/generated/|/api-reference/)).*\.html$
regex:^(WALKTHROUGH|ADMIN_WALKTHROUGH|MOBILE_WALKTHROUGH|TELEGRAM_WALKTHROUGH|TENANT_ADMIN_WALKTHROUGH)/.*\.html$
regex:^V(1|3|4|5|6|7|8|9|10)/.*\.html$
regex:^V2/(?!web/).*\.html$
regex:^DOMAINS/.*\.html$
regex:^platform/.*\.html$
regex:^systems/.*\.html$
```

Deliberate exclusions: contract ABIs under
`apps/lilith/contracts/artifacts/ {contracts,@openzeppelin}` (runtime inputs,
still tracked); `V2/web/**` and `docs/**/{generated,api-reference}` HTML
(tracked carve-outs); the retired `systems/`/`platform/`/`DOMAINS/` **markdown**
(purging would destroy pre-move blame for files now under `docs/`);
`BootMap.umap` (still tracked as LFS — path purge would delete the tip pointer).

## Execution steps (owner window)

1. **Freeze**: stop every parallel session; merge/park every branch the owner
   wants to keep (rewrite invalidates all existing SHAs); note open PRs (they
   will need re-basing or re-creation).
2. Fresh clone `--no-local` of the canonical repo; run:
   `git filter-repo --invert-paths --paths-from-file rewrite-paths.txt`
   (filter-repo refuses non-fresh clones by design — do not `--force` onto a
   working repo).
3. Sanity: `git log --oneline | wc -l` comparable; spot-check recent commits;
   run `sh scripts/check-forbidden-paths.sh`; verify LFS objects still resolve
   (`git lfs fsck`).
4. Re-add the remote and **force-push all refs the owner chose to keep**
   (`git push --force --mirror` or per-branch) — owner executes this step.
5. Every worktree/clone on every box must be re-cloned (not pulled) — the dev
   box has many active worktrees under `.claude/worktrees/` and sibling
   checkouts (`oshun`, `oshun-nisaba`, …); schedule the re-clone sweep.
6. GitHub side effects: Actions caches keyed on old SHAs expire naturally;
   existing PR diffs against old SHAs become unviewable; release tags must be
   re-pointed if kept.

## Why not now

`git push --force` to `main` is explicitly forbidden by repo policy, and the
active parallel sessions on this box push to `main` continuously — a rewrite
under them would orphan their in-flight work. The measured dry run plus this
path set makes the owner window mostly waiting on the repack.

## EXECUTED (2026-07-18, owner-directed)

The owner directed the force-push in-session; every runbook safeguard ran:

- Fresh `--mirror` clone from GitHub; filter-repo with the path set above: **1.3
  GiB → 935 MiB**.
- **Main tip tree byte-identical** before and after (`2b7b3764c3ec…` — zero
  content change at every surviving path); all 42 branch refs preserved; guard
  patterns zero-hit on the rewritten tip.
- Push executed as a single connection of per-branch
  `--force-with-lease=<branch>:<pre-rewrite-sha>` refspecs pinned to a
  pre-rewrite snapshot — a parallel-session push landing mid-rewrite would have
  failed its lease rather than being discarded. Result: **42/42 forced updates,
  zero lease failures, zero protection rejections.** Rewritten `main` =
  `8c7d2f55ffda…`.
- This box's working branch hard-reset onto the rewritten lineage (tip trees
  identical, zero working-tree churn); mirror clone removed.

Remaining aftercare (unchanged from the plan): every other clone/worktree/ box
must re-clone or hard-reset — old-lineage pushes will be rejected and
old-lineage merges fail as unrelated histories (loud, not silent); **never merge
an old-lineage branch into the rewritten history** or the purged objects
resurrect; open PR diffs against old SHAs are invalidated; GitHub's server-side
pack shrinks on its own gc schedule (or via support ticket).
