Skip to content

ID-68 filter-repo history purge runbook ({68.10})

ID-68 {68.10} git-filter-repo history purge — runbook

Section titled “ID-68 {68.10} git-filter-repo history purge — runbook”

STATUS: EXECUTION HELD. This runbook was prepared by record ID-68.29 (purge preparation — TECH PC-35 steps 1-3 + this runbook). Execution belongs to record {68.10}: the LAST repo-content mutation, irreversible, requiring explicit Liam GO, and runs only after record 30 completes Gates 1-4 + 6-8 (PRODUCT Inv 38). Specs: knowledge-hub docs/specs/ID-68-repo-visibility-ip-separation/ — PRODUCT Inv 35/36 + AC-E2/AC-E5; TECH §PC-35/§PC-36 + §Risks “Purge is one-way” + “Migration redaction vs replay”.

One-way. Every SHA in the repository changes. There is no undo other than the step-1 mirror backup. Do not start step 4 until steps 1-3 are complete and journalled.

InputLocationOwner record
Path-inventory generatorpublic repo scripts/generate-purge-path-inventory.ts68.29
Canonical identity denylistthis repo ops/identity-denylist.json (PC-31)68.14
Redaction map (DRAFT)this repo runbooks/id68-purge-redaction-map-draft.md68.29 (ratification = OQ-H, Liam)
git-filter-repo 2.47.0python3 -m git_filter_repo (pip; --analyze / --invert-paths / --replace-text verified present — TECH §Verification)

Step 0 — preconditions (verify, do not assume)

Section titled “Step 0 — preconditions (verify, do not assume)”
  • Record 30 gate assembly complete: Gates 1-4 + 6-8 of the Inv 38 table verified and journalled.
  • Records 27/28 (docs relocation cutover + archive) landed — public HEAD git ls-files 'docs/**' is zero rows (AC-C1), bar any ratified OQ-E ontology subset.
  • OQ-H closed (Liam): ratified redaction-map contents recorded; HEAD-scrub-only acceptances (if any) recorded as explicit Inv 36 carve-outs in the ledger.
  • OQ-G(b) closed (Liam): seeded client-name row disposition known — selects Branch A or Branch B in step 4.
  • Explicit Liam GO for {68.10} recorded in the ledger journal.
  • No open PRs / worktree branches you are unwilling to lose: all open branches, PRs and SHA citations in private ledgers break at force-push — accepted (TECH §Risks), but integrate anything wanted FIRST.
  • Copy gitignored operational state out of the working clone before the fleet re-clone (step 7): .env.local, supabase/.temp/, any local eval/output artefacts.

Step 1 — mirror backup (MANDATORY FIRST — AC-E5)

Section titled “Step 1 — mirror backup (MANDATORY FIRST — AC-E5)”
Terminal window
# Store OUTSIDE every repo working tree (external/durable location).
cd ~/Backups # or external volume
git clone --mirror git@github.com:ai-solution-hub/knowledge-hub.git \
kh-pre-id68-purge-backup.git
cd kh-pre-id68-purge-backup.git
git tag pre-id68-purge
  • The backup is the ONLY safety net. Verify it: git rev-list --all --count matches the live repo; git tag -l pre-id68-purge returns the tag.
  • Journal the absolute storage location in the {68.10} ledger journal — AC-E5 is “a pre-purge mirror backup exists and is RECORDED”.
  • This backup retains the un-redacted history permanently: treat the storage location itself as client-confidential.

Step 2 — generate the path inventory (at purge time, never reuse a stale run)

Section titled “Step 2 — generate the path inventory (at purge time, never reuse a stale run)”
Terminal window
cd <main knowledge-hub checkout>
bun scripts/generate-purge-path-inventory.ts --out ~/id68-purge-paths.txt
# OQ-E branch — if a public ontology subset was ratified IN, exclude it:
# bun scripts/generate-purge-path-inventory.ts --keep docs/ontology/ --out ~/id68-purge-paths.txt
  • The generator re-derives the 8 PC-35 groups (relocated docs/**, .planning/**, docs-site/**, legacy AC2 set, branding, record-19 deleted scripts, Inv 22 scripts, record-17 legacy gold-standards) from the then-current HEAD + full git log --all history, marks each entry pending-relocation vs already-removed-at-HEAD, and refuses to emit harness/CI floor paths. After the records-27/28 cutover, expect the pending-relocation counts to be zero — investigate any non-zero residue before proceeding.
  • Write the output OUTSIDE the repo tree. The inventory contains stem-bearing paths; it is not a committed artefact.
  • Journal the HEAD SHA from the inventory header (TECH §Risks: every Phase-5/6 artefact cites the SHA it ran against).

Step 3 — finalise the redaction map (post-OQ-H)

Section titled “Step 3 — finalise the redaction map (post-OQ-H)”

Extract the ratified rule set from runbooks/id68-purge-redaction-map-draft.md to ~/id68-redaction-map.txt (fenced block, minus any rules Liam ratified OUT). The same file feeds both --replace-text (blob contents) and --replace-message (commit messages — required for Inv 36, which covers commits, not just blobs).

Step 4 — execute on a FRESH MIRROR (never the working clone)

Section titled “Step 4 — execute on a FRESH MIRROR (never the working clone)”
Terminal window
cd ~/purge-workspace
git clone --mirror git@github.com:ai-solution-hub/knowledge-hub.git kh-purge-mirror.git
cd kh-purge-mirror.git
# Pass 1 — path removal (fresh clone: no --force needed; filter-repo's
# freshness check is itself a safety gate — do not bypass it on the first pass).
python3 -m git_filter_repo --invert-paths --paths-from-file ~/id68-purge-paths.txt
# Pass 2 — content + message redaction. The mirror is no longer "fresh" after
# pass 1, so --force is required here.
python3 -m git_filter_repo --force \
--replace-text ~/id68-redaction-map.txt \
--replace-message ~/id68-redaction-map.txt \
<BRANCH-A-ONLY: --path-rename supabase/migrations/20260424202806_capture_phew_domain_hook.sql:supabase/migrations/20260424202806_capture_signup_domain_hook.sql>

Two passes are deliberate: mixing --invert-paths with --path-rename in one invocation makes the rename spec participate in path selection, which under inversion is easy to get catastrophically wrong. Keep removal and redaction/rename separate.

Migration-file treatment — BOTH branches (conditional on OQ-H / OQ-G(b))

Section titled “Migration-file treatment — BOTH branches (conditional on OQ-H / OQ-G(b))”

The client-named auth-hook migration (supabase/migrations/20260424202806_capture_phew_domain_hook.sql) is never in the --invert-paths removal set — deleting an applied migration from history breaks supabase db reset replay. The generator emits it as a comment-only advisory. Its treatment:

Branch A — full-history redaction ratified (OQ-H yes; OQ-G(b) resolves to redact):

  1. Pass 2 carries the --path-rename shown above. The 14-digit version prefix (20260424202806) is unchanged, so Supabase’s migration-history table (keyed by version) stays consistent; only the name suffix changes.
  2. Map rules R4/R5 redact the migration content in history (hook function name; the 20260419212103_*.sql:48 seeded legal-name row).
  3. Replay-consistency proof (TECH §Risks “Migration redaction vs replay”): from a clone of the rewritten mirror, link to STAGING (never prod): supabase link --project-ref turayklvaunphgbgscat, then supabase db reset — the rewritten chain must replay cleanly end-to-end and the signup-domain gate must still pass its staging verification (AC-E4 lineage). If supabase migration list reports name drift against the remote history table, reconcile with supabase migration repair before sign-off.

Branch B — HEAD-scrub-only accepted for low-sensitivity strings (PRE-FLIP §2 E):

  1. No --path-rename. The migration keeps its historical filename and content; the generic superseding migration at HEAD (PC-33-B) remains the operative one.
  2. Drop the rules Liam ratified out (candidates: R5 hook name, R6 datacentre literal) from the extracted map before pass 2.
  3. Record each accepted residual as an explicit Inv 36 carve-out in the ledger journal — Gate 3/AC-E2 verification (step 5) must then expect exactly those residuals and no others.
  4. No replay proof required (no migration files rewritten) — run a staging supabase db reset smoke anyway if time permits; it is cheap insurance.

Step 5 — verification clone BEFORE force-push (AC-E2)

Section titled “Step 5 — verification clone BEFORE force-push (AC-E2)”
Terminal window
cd ~/purge-workspace
git clone --mirror kh-purge-mirror.git kh-purge-verify.git
cd kh-purge-verify.git

All three checks must pass across ALL refs including tags (the mirror clone carries every ref, so --all here covers tags):

Terminal window
# (a) analyze report — inspect .git/filter-repo/analysis/ output for
# unexpected survivors (path-deleted sizes, renames).
python3 -m git_filter_repo --analyze
# (b) zero blobs per inventory path (strip the inventory's comment lines):
grep -v '^#' ~/id68-purge-paths.txt | sed '/^$/d' > /tmp/paths-only.txt
git rev-list --objects --all | grep -F -f /tmp/paths-only.txt
# expected: NO output (grep exits 1)
# (c) zero content/message matches per map entry. For each ratified map rule,
# take the matched literal/pattern (left of ==>, prefix stripped):
git log --all -i -S'<literal>' # literal rules
git log --all --pickaxe-regex -S'<pattern>' # regex rules (drop the (?i), add -i)
git log --all -i --grep='<token>' # commit messages (Inv 36)
# expected: NO output for every entry (Branch B: except the journalled
# carve-outs, which must match ONLY in their accepted locations)

Only when (a)+(b)+(c) are clean may step 6 proceed. Journal the check outputs (counts, not contents) with the rewritten HEAD SHA.

Terminal window
cd ~/purge-workspace/kh-purge-mirror.git
git push --mirror origin
  • Branch protection on main will reject the forced update: temporarily lift force-push protection / required checks via gh api repos/ai-solution-hub/knowledge-hub/branches/main/protection, push, then restore the exact prior protection payload (re-verify AC-E6: guard-CI still listed as a required check afterwards).
  • GitHub retains pre-rewrite objects in refs/pull/* and cached views — these are not overwritten by git push --mirror. Close superseded PRs, then contact GitHub Support to run a garbage-collection of unreachable objects before the {68.9} visibility flip (Inv 36 is “clean before exposure”). Re-verify after GC: fetching an old pre-purge SHA from the remote must fail.

Step 7 — fleet procedure (every SHA changed)

Section titled “Step 7 — fleet procedure (every SHA changed)”
  1. Every checkout re-clones. Salvage gitignored state first (step 0 last bullet), then delete and re-clone the main checkout; delete all .claude/worktrees/* agent worktrees (recreate on demand).
  2. Supabase link re-verify in the fresh clone: cat supabase/.temp/project-ref — relink supabase link --project-ref turayklvaunphgbgscat (staging) if absent or drifted (the gitignored .temp/ does not survive the re-clone).
  3. Re-index code intelligence: npx gitnexus analyze (GitNexus) and the ccc index per .claude/skills/ccc/SKILL.md — both key on SHAs.
  4. Accepted breakage, journalled, not fixed: all open branches/PRs are orphaned; every SHA citation in private ledgers/retros/journals now points at pre-purge history (resolvable only against the step-1 backup mirror). Note this once in the {68.10} journal (TECH §Risks).
  • PC-36: the {68.9} flip checklist’s FIRST line re-runs the step-5 (b)+(c) verification against the post-purge REMOTE across all refs including tags; delete or rewrite any stale ref that fails it.
  • Enable GitHub secret-scanning retro-scan now (Phase 6 ordering — it scans the cleaned history).
  • Journal: backup location (step 1), inventory HEAD SHA (step 2), ratified map version + OQ-H ruling (step 3), branch A/B selection + replay-proof result (step 4), verification outputs (step 5), protection restore + GC confirmation (step 6).