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-hubdocs/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.
Inputs
Section titled “Inputs”| Input | Location | Owner record |
|---|---|---|
| Path-inventory generator | public repo scripts/generate-purge-path-inventory.ts | 68.29 |
| Canonical identity denylist | this repo ops/identity-denylist.json (PC-31) | 68.14 |
| Redaction map (DRAFT) | this repo runbooks/id68-purge-redaction-map-draft.md | 68.29 (ratification = OQ-H, Liam) |
git-filter-repo 2.47.0 | python3 -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)”# Store OUTSIDE every repo working tree (external/durable location).cd ~/Backups # or external volumegit clone --mirror git@github.com:ai-solution-hub/knowledge-hub.git \ kh-pre-id68-purge-backup.gitcd kh-pre-id68-purge-backup.gitgit tag pre-id68-purge- The backup is the ONLY safety net. Verify it:
git rev-list --all --countmatches the live repo;git tag -l pre-id68-purgereturns 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)”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 + fullgit log --allhistory, marks each entrypending-relocationvsalready-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)”cd ~/purge-workspacegit clone --mirror git@github.com:ai-solution-hub/knowledge-hub.git kh-purge-mirror.gitcd 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):
- Pass 2 carries the
--path-renameshown 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. - Map rules R4/R5 redact the migration content in history (hook function
name; the
20260419212103_*.sql:48seeded legal-name row). - 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, thensupabase 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). Ifsupabase migration listreports name drift against the remote history table, reconcile withsupabase migration repairbefore sign-off.
Branch B — HEAD-scrub-only accepted for low-sensitivity strings (PRE-FLIP §2 E):
- No
--path-rename. The migration keeps its historical filename and content; the generic superseding migration at HEAD (PC-33-B) remains the operative one. - Drop the rules Liam ratified out (candidates: R5 hook name, R6 datacentre literal) from the extracted map before pass 2.
- 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.
- No replay proof required (no migration files rewritten) — run a staging
supabase db resetsmoke 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)”cd ~/purge-workspacegit clone --mirror kh-purge-mirror.git kh-purge-verify.gitcd kh-purge-verify.gitAll three checks must pass across ALL refs including tags (the mirror
clone carries every ref, so --all here covers tags):
# (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.txtgit 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 rulesgit 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.
Step 6 — force-push
Section titled “Step 6 — force-push”cd ~/purge-workspace/kh-purge-mirror.gitgit push --mirror origin- Branch protection on
mainwill reject the forced update: temporarily lift force-push protection / required checks viagh 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 bygit 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)”- 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). - Supabase link re-verify in the fresh clone:
cat supabase/.temp/project-ref— relinksupabase link --project-ref turayklvaunphgbgscat(staging) if absent or drifted (the gitignored.temp/does not survive the re-clone). - Re-index code intelligence:
npx gitnexus analyze(GitNexus) and the ccc index per.claude/skills/ccc/SKILL.md— both key on SHAs. - 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).
Step 8 — post-purge actions
Section titled “Step 8 — post-purge actions”- 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).