Skip to content

ID-68 purge execution-readiness (S372 recon snapshot)

EXECUTION-READINESS REPORT — ID-68 history purge + public flip + v1.0.0 cut + Phew repoint

Section titled “EXECUTION-READINESS REPORT — ID-68 history purge + public flip + v1.0.0 cut + Phew repoint”

Compiled: 2026-06-17 (read-only recon; no mutations performed) Repo: ai-solution-hub/knowledge-hub (currently private, default branch main, HEAD d6e222e96) Live state verified via gh api / supabase projects list (sandbox-bypassed — api.github.com + Supabase API are not on the sandbox allowlist; in-sandbox calls fail with x509: OSStatus -26276).


ItemStateBlocker to start
Inv-38 flip-readiness gates (G1-4, 6-8)ALL PASS (per {68.30} final journal 2026-06-16T14:59:53)None — “Remaining is the switch only.”
Repo visibilityprivate (correct pre-flip)
Dependabot high-sev open (Gate 8)0 (live-verified)None
Branch protection (Gate 4)identity-guard (classic API) + ci-summary (via ruleset 15785019) both requiredNone
git-filter-repo2.47.0 present (python3 -m git_filter_repo)None
Pre-purge backupDOES NOT EXIST — no ~/Backups, no mirror anywhereMANDATORY before purge (AC-E5)
OQ-H (redaction-map ratification)OPEN — Liam has not ratified the mapBLOCKS {68.10} purge
OQ-G(b) (seeded client-name row disposition)OPEN — selects Branch A/BBLOCKS {68.10} purge
Explicit Liam GONOT recorded for either {68.9} or {68.10}BLOCKS both
OrderingPURGE-THEN-FLIP is canonical (3 independent sources; S371 prompt’s “flip then purge” is wrong)
Squash impactClient migration GONE from HEAD; path-rename step now obsolete; --replace-text/--replace-message still required for historyRedaction map still needed; replay-proof now N/A
v1.0.0 release cutGreen-CI precondition MET (main fully green); not yet cut; no vX.Y.Z tags existOwner GO
Phew repoint targetsVercel knowledge-hub-phew; Supabase ref rovrymhhffssilaftdwd (live-confirmed)

Critical cross-dependency: the purge rewrites EVERY SHA. The v1.0.0 tag ({113.8}) must be cut AFTER the purge completes, against the post-purge main SHA — never before (a pre-purge tag would point at orphaned history). See §7.


SECTION 1 — ID-68 history-purge artefacts ({68.29} = DONE)

Section titled “SECTION 1 — ID-68 history-purge artefacts ({68.29} = DONE)”

1.1 File locations (all in PRIVATE docs-site — never copy into public repo)

Section titled “1.1 File locations (all in PRIVATE docs-site — never copy into public repo)”
ArtefactPathOwner
Purge runbook/Users/liamj/Documents/development/knowledge-hub-docs-site/src/content/docs/runbooks/id68-filter-repo-purge.md{68.29}
Redaction map (DRAFT)/Users/liamj/Documents/development/knowledge-hub-docs-site/src/content/docs/runbooks/id68-purge-redaction-map-draft.md{68.29}
Canonical identity denylist (PC-31)/Users/liamj/Documents/development/knowledge-hub-docs-site/ops/identity-denylist.json (v1, 2026-06-07){68.14}
Path-inventory generator/Users/liamj/Documents/development/knowledge-hub/scripts/generate-purge-path-inventory.ts (PUBLIC repo, run at purge time){68.29}

1.2 The exact git filter-repo command sequence (verbatim from the runbook)

Section titled “1.2 The exact git filter-repo command sequence (verbatim from the runbook)”

STATUS: EXECUTION HELD. Runs only after {68.30} gates pass (DONE) + OQ-H/OQ-G(b) close + explicit Liam GO.

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

Verify: git rev-list --all --count matches live; git tag -l pre-id68-purge returns the tag. Journal the absolute storage location (AC-E5). Storage location is itself client-confidential (retains un-redacted history permanently).

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

Generator re-derives 8 PC-35 groups from then-current HEAD + full git log --all. At S321 run it emitted 8 groups / 4,651 paths (relocated docs/** 2,672; .planning/** 1,855; docs-site/** 66; legacy AC2 43; branding 6; record-19 deleted scripts 3; Inv-22 scripts 2; record-17 legacy gold-standards 4). Write OUTSIDE the repo tree. Journal the HEAD SHA from the inventory header.

Step 3 — finalise the redaction map (post-OQ-H): Extract the ratified rule set from id68-purge-redaction-map-draft.md to ~/id68-redaction-map.txt (fenced block minus any rules Liam ratified OUT). Same file feeds both --replace-text (blobs) and --replace-message (commit messages — required for Inv 36).

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 makes the rename spec participate in path selection, catastrophic under inversion. Keep removal and redaction/rename separate.

⚠ SQUASH-INDUCED CHANGE (NOT yet reflected in the runbook — see §RISKS): the migration squash (84b355ec2) collapsed all 143 migrations to ONE file (20260617130000_squash_baseline.sql). The client migration 20260424202806_capture_phew_domain_hook.sql no longer exists at HEAD. The Branch-A --path-rename line above targets a file that is absent from current refs — at minimum it is now a no-op at HEAD (it would only rename historical occurrences before the squash deletion). The replay-consistency proof in the runbook’s “Branch A” block is also moot at HEAD (there is no rewritten migration chain to replay; the squash baseline already replaces it). The --replace-text/--replace-message history redaction is STILL required — the stem phew is reachable across 798 commits in git log --all -S'phew', including the squash commit’s own diff of the deleted content.

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 must pass across ALL refs including tags:

Terminal window
# (a) analyze report
python3 -m git_filter_repo --analyze
# inspect .git/filter-repo/analysis/ for unexpected survivors
# (b) zero blobs per inventory path:
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:
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 journalled carve-outs)

Journal the check outputs (counts, not contents) with the rewritten HEAD SHA.

Step 6 — force-push:

Terminal window
cd ~/purge-workspace/kh-purge-mirror.git
git push --mirror origin
  • Branch protection on main rejects 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 a required check afterwards).

    NUANCE (live-verified): the active protection is ruleset 15785019 (production-protection), which has OrganizationAdmin + RepositoryRole bypass always. If the owner pushes as an org admin, the bypass may permit the force-push without lifting the ruleset. Decide at execution: either (a) push under the admin bypass, or (b) explicitly disable the ruleset’s non_fast_forward rule, push, re-enable. The runbook assumes the classic-API path; reconcile with the ruleset.

  • GitHub retains pre-rewrite objects in refs/pull/* + cached views (NOT overwritten by git push --mirror). Close superseded PRs, then contact GitHub Support to GC unreachable objects BEFORE the {68.9} flip (Inv 36 = “clean before exposure”; old-SHA fetches must fail afterwards). This is a hard ordering constraint between purge and flip.

Step 7 — fleet procedure (every SHA changed):

  1. Every checkout re-clones. Salvage gitignored state first (.env.local, supabase/.temp/, eval artefacts), then delete + re-clone the main checkout; delete all .claude/worktrees/* agent worktrees.
  2. Supabase link re-verify: cat supabase/.temp/project-ref → relink supabase link --project-ref turayklvaunphgbgscat (staging) if absent/drifted.
  3. Re-index code intelligence: npx gitnexus analyze + the ccc index (both key on SHAs).
  4. Accepted breakage, journalled, not fixed: all open branches/PRs orphaned; every SHA citation in private ledgers now points at pre-purge history (resolvable only against the step-1 backup).

Step 8 — post-purge actions:

  • PC-36: the {68.9} flip checklist’s FIRST line re-runs step-5 (b)+(c) verification against the post-purge REMOTE across all refs incl. tags; delete/rewrite any stale ref that fails.
  • Enable GitHub secret-scanning retro-scan (scans the cleaned history).
  • Journal: backup location, inventory HEAD SHA, ratified map version + OQ-H ruling, branch A/B + replay-proof, verification outputs, protection restore + GC confirmation.

1.3 The EXACT redaction map — set of strings/blobs/paths it targets

Section titled “1.3 The EXACT redaction map — set of strings/blobs/paths it targets”

(a) PATHS removed via --invert-paths (8 groups, ~4,651 paths at S321): relocated docs/**; .planning/**; docs-site/**; legacy AC2 set (client corpus/briefs/personas/feedback, the CSP under docs/testing/test-data/, scripts/catalogue-*-itt.ts); lib/branding/clients/phew.json + public/clients/phew/**; the 3 deleted client-named scripts (record 19); scripts/run-1m-context.ts + scripts/wf-export.py; the 4 legacy gold-standard paths. Plus (S322 annotations) scripts/ingest_stage2_markdown.py (dead legacy, carries client bid-library refs).

The client auth-hook migration is NEVER in the --invert-paths set (deleting an applied migration breaks supabase db reset replay) — the generator emits it as a comment-only advisory. (Now moot post-squash anyway.)

(b) CONTENT/MESSAGE rules via --replace-text + --replace-message (DRAFT v1, ordered most-specific-first):

# id68-redaction-map (DRAFT v1 — OQ-H OPEN; rendered from ops/identity-denylist.json v1)
# R1 — support email
regex:(?i)support@phew\.org\.uk==>support@client.example
# R2 — production subdomain
regex:(?i)kh\.phew\.org\.uk==>kh.client.example
# R3 — bare client domain
regex:(?i)phew\.org\.uk==>client.example
# R4 — legal name
regex:(?i)phew\s+design\s+limited==>Example Client Ltd
# R5 — old auth-hook name (superseded at HEAD by PC-33-B)
regex:(?i)capture_phew_domain_hook==>capture_signup_domain_hook
# R6 — datacentre literal
regex:(?i)telehouse==>example-datacentre
# R7 — historical ICO registration number (case-sensitive literal)
Z8143724==>ZA123456
# R8 — bare client-name stem (BROAD; ordered last). OQ-H DECISION POINT: include or drop.
regex:(?i)phew==>example-client

Rules apply in file order (filter-repo applies sequentially per blob — most-specific first, else R8 would consume the structured forms and leak shape).

1.4 OQ-H open decision points (Liam must ratify before purge)

Section titled “1.4 OQ-H open decision points (Liam must ratify before purge)”
  1. R8 bare-stem rule in or out. Draft recommendation: in (strongest guarantee; AC-E2 trivially verifiable).
  2. HEAD-scrub-only for low-sensitivity strings (PRE-FLIP §2 E). Candidates: R5 (hook name, already superseded), R6 (datacentre literal). Any accepted residual must be recorded as an explicit Inv 36 carve-out in the ledger journal.
  3. Migration-file treatment (interacts with OQ-G(b)): Branch A (full-history redaction + path-rename + staging replay-proof) vs Branch B (HEAD-scrub-only). Post-squash, this decision largely collapses — there is no migration file at HEAD to rename; only the historical content (R4/R5 inside pre-squash migration blobs) is in scope, handled by --replace-text regardless of branch.

SECTION 2 — ORDERING RECONCILIATION: PURGE-THEN-FLIP (DEFINITIVE)

Section titled “SECTION 2 — ORDERING RECONCILIATION: PURGE-THEN-FLIP (DEFINITIVE)”

CANONICAL ORDERING = PURGE FIRST ({68.10}), THEN FLIP ({68.9}).

The brief’s S371 continuation-prompt §2 (“flip then purge”) is WRONG / superseded. It is a loose restatement of the legacy ledger dependency encoding, not the spec. The S371 prompt (line 43) literally reads: “{68.9} flip (reversible repo-public) THEN {68.10} history purge (irreversible, LAST)” — but this contradicts the ratified PRODUCT invariant. Trust the spec + ledger status_note over the continuation prompt.

FOUR independent canonical sources all say purge-then-flip:

  1. PRODUCT.md Invariant 36 (the normative source), specs/id-68-repo-visibility-ip-separation/PRODUCT.md:316-318:

    “36. Public-visibility precondition: at the instant the repo becomes public, no commit reachable from any ref contains identity-set matches outside the carve-out — history is clean before exposure, not after.”

  2. TECH.md §10 (specs/.../TECH.md:10-11):

    “C6→C7 ordering (flip, then purge) is reversed by PRODUCT Invariant 36/Gate 5 (history is clean before exposure).” And TECH.md Phase 6 (:733-735): “PC-35 purge (…) → PC-36 all-refs re-verification → {68.9} flip (gh repo edit --visibility public)”.

  3. Ledger status_note (ID-68 record, ledgers/tasks/ID-68.md:15):

    “68.9 flip + 68.10 purge HELD — purge-then-flip, Liam GO; OQ-H open.”

  4. V1 Completion Sequence ordering board (themes/canonical-pipeline/reference/v1-completion-sequence.md:283):

    “{68.9} flip + {68.10} purge HELD — Inv-36 purge-then-flip, explicit Liam GO + backup”.

The {68.30} journal records the live correction explicitly (ledgers/tasks/ID-68.md:794):

“SEQUENCE NOTE (PC-36 / Inv 36 — supersedes the legacy order encoded in the current ledger deps): the purge ({68.10}) + its verification clone complete BEFORE the flip ({68.9}) executes — history is clean before exposure. … existing {68.10}.dependencies includes 9 (legacy flip-then-purge) — recommend removing 9 from {68.10}.dependencies and appending [10, 30] to {68.9}.dependencies via ledger-cli.”

⚠ LEDGER DEPENDENCY DEFECT (verified live, NOT yet corrected):

  • {68.9} deps = [68.5, 68.6, 68.7, 68.8, 68.12, **68.10**, 68.30] — correctly lists 68.10 as a prerequisite (purge-first). ✅
  • {68.10} deps = [68.5, 68.6, 68.7, 68.8, 68.12] — does NOT list 9, so no cycle. ✅
  • So the current deps actually already encode purge-then-flip correctly on {68.9} (68.10 is in its deps). The PLAN’s recommended “remove 9 from {68.10}” is already true (9 isn’t there). The stale-PLAN warning at PLAN.md:174-178 describes a state that has since been partially fixed. Net: the deps are NOT cyclic and DO encode purge-then-flip. No ledger correction is strictly required to execute in the right order, but the Orchestrator should confirm {68.9} retains 68.10 in its deps before flipping.

Note also PRE-FLIP-DEID-PLAN.md §5 DAG (:318-320) shows flip-then-purge — but that document is dated S301a (2026-06-03), predates the Inv-36 reversal, and is explicitly superseded by PRODUCT Inv 36 + the {68.30} sequence note. Do not use the PRE-FLIP DAG for ordering.


SECTION 3 — {68.30} Inv-38 GATE STATE (= DONE)

Section titled “SECTION 3 — {68.30} Inv-38 GATE STATE (= DONE)”

Source: ID-68.30 final journal entry, ledgers/tasks/ID-68.md:831-833 (S363 re-verify, HEAD 7fe481f74, 2026-06-16T14:59:53).

3.1 The Inv-38 nine-gate table (PRODUCT.md:340-353)

Section titled “3.1 The Inv-38 nine-gate table (PRODUCT.md:340-353)”
GateRequirementVerificationState (live + {68.30})
1Docs + ledgers moved to private docs-site (no sanitisation)AC-A3, AC-C1PASS (NOTE: git ls-files docs/ = 9 residual = skill-routing-map.md + 8 public-tender fixtures, no client IP; AC-C1 literal-zero unmet but substance met)
2Ledgers private via the ID-20/ID-90 cutovercutover recordedPASS (ledger JSONs = 0 in public; satisfied via {68.35} done)
3Genuine client content out of public HEAD and historyAC-E1, AC-E2PASS (HEAD leg via CI sweep; history leg = Gate 5 = the purge itself, still pending)
4guard-CI required branch-protection check liveAC-E6PASS — (a) ci-summary required via ruleset 15785019 (admin-bypassable — note for flip journal); (b) identity-guard required via classic API. 172 dead PENDING_DEID excludes are path-absent = hygiene-only, non-blocking.
5History purge {68.10} executedAC-E2, AC-E5HELD — this IS the purge; runs in Phase 6.
6Archive + workflow-eval corpus cold-storagedAC-A4PASS (archive index-isolation)
7Relocation sweep re-run against current HEAD (Inv 40)sweep cites HEAD SHAPASSsweep-identity-relocation exit 0; 1541 docs-pointers, 10 runtime-read; clean.
8Dependabot high-sev gate = 0 open (drifts)gh api .../dependabot/alerts count = 0PASS (live-verified just now: 0 open high; 0 open critical)
9Explicit Liam GO (outward-facing)recorded in ledger journalHELD — not granted.

YES for all readiness gates. The {68.30} journal’s final line:

“Remaining is the switch only: G5/G9 → {68.9} flip + {68.10} purge, HELD on Liam GO.”

Residual / outstanding (the ONLY things between here and flip):

  1. G5 = the purge ({68.10}) must execute first (purge-then-flip). Which itself is gated on:
    • OQ-H (redaction-map ratification) — OPEN.
    • OQ-G(b) (seeded client-name row disposition / Branch A vs B) — OPEN.
    • Mandatory mirror backup — does not exist yet (§5).
  2. G9 = explicit Liam GO — for both {68.10} and {68.9}.
  3. Fix-forward carried to {68.9} (ID-68.md:119, 832): widen the Dependabot gate query from severity=="high" only to (high or critical) before flip — a future critical would otherwise silently pass (critical=0 today, so verdict unaffected). Apply this in BOTH the {68.9} flip pre-check and the gate query.
  4. Hygiene (non-blocking): 172 dead PENDING_DEID excludes in identity-guard.yml (path-absent, deferred sentinel-gated cleanup); 4 stale :(exclude) ontology pathspecs.

Source: ID-68.9 details (ledgers/tasks/ID-68.md:104-120) + runbook step 8.

4.1 Live pre-flight (re-verify immediately before flip — state drifts)

Section titled “4.1 Live pre-flight (re-verify immediately before flip — state drifts)”
Terminal window
# Gate 8 re-verify — MUST return 0 (widen to high-or-critical per fix-forward):
gh api repos/ai-solution-hub/knowledge-hub/dependabot/alerts \
--jq '[.[]|select((.security_advisory.severity=="high" or .security_advisory.severity=="critical") and .state=="open")]|length'
# expected: 0 (live now: 0)
# Confirm still private before flip:
gh repo view ai-solution-hub/knowledge-hub --json visibility

4.2 The flip command (verbatim from ledger details)

Section titled “4.2 The flip command (verbatim from ledger details)”
Terminal window
gh repo edit ai-solution-hub/knowledge-hub --visibility public

Flip is reversible until {68.10}. (Since the canonical order is purge-then-flip, the purge has already happened by the time you flip — so practically the flip is the last step. The “reversible until purge” framing is from the legacy order; under purge-then-flip the flip is genuinely the final irreversible-exposure act, so treat it as one-way for GO purposes.)

4.3 Enable the security suite (live state: ALL of these are currently OFF except dependabot_security_updates)

Section titled “4.3 Enable the security suite (live state: ALL of these are currently OFF except dependabot_security_updates)”

Live security_and_analysis: code_security: disabled, secret_scanning: disabled, secret_scanning_push_protection: disabled, dependabot_security_updates: enabled.

Terminal window
# Enable CodeQL/code-scanning + secret-scanning + push-protection in one PATCH:
gh api -X PATCH repos/ai-solution-hub/knowledge-hub \
-f 'security_and_analysis[secret_scanning][status]=enabled' \
-f 'security_and_analysis[secret_scanning_push_protection][status]=enabled' \
-f 'security_and_analysis[dependabot_security_updates][status]=enabled'

code_security (the GHAS/CodeQL default-setup toggle) and secret-scanning RETRO-scan are typically configured via the code-scanning default-setup API / repo Settings → Code security after the repo is public (many of these toggles are only available once visibility = public on the free tier — the whole point of the flip). The runbook step 8 explicitly defers secret-scanning retro-scan to after the purge+flip so it scans cleaned history. Sequence: flip → enable secret-scanning + push-protection → enable code-scanning/CodeQL default setup → trigger secret-scanning historical scan.

Verification (test strategy, ID-68.9:112):

Terminal window
gh repo view ai-solution-hub/knowledge-hub --json visibility # == "public"
gh api repos/ai-solution-hub/knowledge-hub --jq '.security_and_analysis'
# CodeQL + secret scanning + push protection == enabled

The ledger mandates “explicit Liam GO + backup” before purge (AC-E5).

A current full backup/mirror DOES NOT EXIST. Verified:

  • No ~/Backups directory.
  • No ~/*purge*, ~/*backup* git mirror in home (only two unrelated MemPalace .tar.gz archives).
  • No pre-id68-purge tag anywhere.

Exact command to take the complete pre-purge backup (verbatim, AC-E5):

Terminal window
mkdir -p ~/Backups
cd ~/Backups
git clone --mirror https://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

Verify:

Terminal window
git rev-list --all --count # must match live repo (live main lineage ≈ 6,329 commits on this checkout)
git tag -l pre-id68-purge # must return the tag

Recommended safe destination (OUTSIDE the repo tree): ~/Backups/kh-pre-id68-purge-backup.git.

Even better for durability: an external volume or a private GitHub mirror, since the local sandbox write-allowlist does not include ~/Backups (the backup clone must run bypassed — it hits github.com over the network and writes outside the repo). Treat the storage location as client-confidential — this mirror retains the full un-redacted history permanently (the very thing the purge removes). Journal the absolute path in the {68.10} ledger journal (AC-E5 is “a pre-purge mirror backup exists and is RECORDED”).

SSH vs HTTPS: the runbook uses git@github.com: (SSH). The live remote on this checkout is https://. Use whichever auth the operator has; gh auth setup-git makes HTTPS work with the gh token.


Status clarification: ALL ID-113 subtasks ({113.1}–{113.10}) are ledger-status done — but these are runbook/spec AUTHORING records ({113.8} = “Cut-a-release runbook”, {113.9} = “Upgrade-a-client runbook”, {113.10} = “Verification sweep at standup”). The runbooks are written + ratified; the live actions they describe are the owner-gated execution that has NOT happened (no vX.Y.Z tag exists; the 3 standup checks are “pending first standup”). The parent ID-113 is done because its deliverable was the machinery + docs, not the first live cut.

Source files:

  • /Users/liamj/Documents/development/knowledge-hub-docs-site/src/content/docs/runbooks/per-client-release-pins.md
  • /Users/liamj/Documents/development/knowledge-hub-docs-site/src/content/docs/runbooks/client-app-deploy.md

6.A {113.8} CUT A RELEASE (per-client-release-pins.md §3, lines 84-255)

Section titled “6.A {113.8} CUT A RELEASE (per-client-release-pins.md §3, lines 84-255)”

Step 1 — Verify main is known-good (PI-1 precondition) — GREEN ci-summary:

Terminal window
gh run list --branch main --limit 5
# Identify the target SHA. Then:
gh api repos/ai-solution-hub/knowledge-hub/commits/<sha>/check-runs \
--jq '.check_runs[] | select(.name == "Vercel - knowledge-hub: ci-summary") | {status, conclusion}'
# Expected: status=completed, conclusion=success

Do not proceed if absent/pending/failed. (Live: main is fully green — both identity-guard + ci-summary pass; precondition MET.)

Step 2 — Choose the semver bump: MAJOR (breaking client-facing change OR any migration rollback-safety: destructive OR old-code-tolerant: no); MINOR (additive/back-compatible); PATCH (fix-only, no schema/API change).

Step 3 — Classify the migration set — old-code-tolerant field (BLOCKING): for each migration in the delta, tolerant vs intolerant. ANY intolerant → release classified old-code-tolerant: no → FORCES MAJOR bump + coordinated cutover. Record + justify in Step 8.

Step 4 — Tag the release:

Terminal window
# Confirm the target SHA is a first-parent ancestor of main (PI-2):
git merge-base --is-ancestor <sha> main && echo "OK — on main lineage" || echo "STOP — not on main"
git tag vX.Y.Z <known-good-main-sha>
git push origin vX.Y.Z

Tag is immutable once pushed. NEVER retag an existing vX.Y.Z. Defect after tagging → ship fix as new PATCH (vX.Y.(Z+1)).

Step 5 — Create or fast-forward the release/vX.Y.Z Vercel handle branch:

Terminal window
git checkout -b release/vX.Y.Z <known-good-main-sha>
git push origin release/vX.Y.Z
# If the branch already exists (aborted cut):
git push origin release/vX.Y.Z --force-with-lease # FF-only; fails if non-FF

Tag is source of truth (PI-3); branch is the Vercel handle only, must point at exactly the tag commit. Never commit directly to release/.

Step 6 — On-prem image re-tag sha-<sha>vX.Y.Z (pipeline releases only): skip if no changes to scripts/cocoindex_pipeline/, requirements.txt, or deploy/coolify/.

Terminal window
gh workflow run onprem-deploy.yml \
--ref vX.Y.Z \
--field job=tag-release \
--field source_sha=<full-sha> \
--field release_version=vX.Y.Z

Verify in ghcr.io:

Terminal window
gh api /orgs/ai-solution-hub/packages/container/kh-cocoindex-pipeline/versions \
--jq '.[] | select(.metadata.container.tags[] | contains("vX.Y.Z")) | .id'

Step 7 — AGPL / copyleft discharge — THIRD-PARTY-NOTICES (RATIFIED REV 5): for any AGPL/GPL dependency/image shipped per-client: (1) identify components (requirements.txt, deploy/coolify/ compose, upstream images); (2) generate/refresh THIRD-PARTY-NOTICES at repo root (each component, licence, upstream URL, version, Corresponding-Source pointer); (3) commit to main + include in release/vX.Y.Z (cherry-pick if landed after tagging); (4) record location in Step 8. If none: record THIRD-PARTY-NOTICES: n/a — no AGPL/copyleft components in vX.Y.Z.

Step 8 — Record the release in the crib changelog (per-client-release-pins.md §1 pin table if any client immediately pinned; then append a release-changelog row): Version, SHA (short), Date, Notable changes, Migration delta, Rollback-safety class + justification, Old-code-tolerant + justification, THIRD-PARTY-NOTICES path.

6.B {113.9} UPGRADE A CLIENT — Phew repoint (per-client-release-pins.md §4, lines 264-480)

Section titled “6.B {113.9} UPGRADE A CLIENT — Phew repoint (per-client-release-pins.md §4, lines 264-480)”

CRITICAL ORDERING: migrate-DB-first, THEN repoint Vercel. Reverse = hard runtime skew fault.

Step 1 — Pre-flight: review migration delta + classify upgrade safety. For every migration from Phew’s current pin → target vX.Y.Z: rollback-safety (safe/risky/destructive, OQ-113-3); confirm release was old-code-tolerant: yes in §3 changelog (if no → coordinated cutover/maintenance window, NOT standard migrate-first). Record both before Step 2.

Step 2 — (BLOCKING — RATIFIED REV 1) Take a Supabase snapshot BEFORE any migration.

MANDATORY AND BLOCKING. The upgrade MUST NOT proceed without a confirmed snapshot. No snapshot = no migration.

Terminal window
# Phew's Supabase project ref = rovrymhhffssilaftdwd (live-confirmed):
supabase snapshots create --project-ref rovrymhhffssilaftdwd
# Note the snapshot ID and timestamp returned.

Record snapshot ID + UTC time (needed for the §2 crib row, Step 8).

Step 3 — (MIGRATE-DB-FIRST — OQ-113-2) Apply the migration set to Phew’s Supabase project.

Terminal window
supabase db push --project-ref rovrymhhffssilaftdwd

Verify schema parity:

Terminal window
supabase db diff --project-ref rovrymhhffssilaftdwd
# Expected: no diff.

Old deploy keeps serving during this compatibility window (forward-only additive ⇒ window, NOT downtime). Do NOT repoint Vercel before confirming schema parity — reverse ordering is a hard runtime skew fault (build-time-baked config, no graceful degradation).

Step 4 — (PI-9 / BLOCKING) Verify the deploy gate BEFORE any repoint. Confirm Vercel - knowledge-hub: ci-summary required check is intact on BOTH:

Terminal window
gh api repos/ai-solution-hub/knowledge-hub/commits/<release-branch-tip-sha>/check-runs \
--jq '.check_runs[] | select(.name == "Vercel - knowledge-hub: ci-summary") | {status, conclusion}'
# Expected: status=completed, conclusion=success

AND in Phew’s Vercel project: Settings → Git → Required checks must read EXACTLY Vercel - knowledge-hub: ci-summary (a mismatch silently un-gates production). Do not proceed if absent/pending/failed/mismatched.

Step 5 — Repoint Phew’s Vercel Production Branch to release/vX.Y.Z. In the knowledge-hub-phew Vercel project: Settings → Git → Production Branch; change from release/vX.Y.(Z-1) to release/vX.Y.Z; trigger a Vercel redeploy (PI-4 — deliberate, never automatic). Vercel never tracks main.

Step 6 — (Pipeline clients only) Repoint Coolify COCOINDEX_IMAGE_TAGvX.Y.Z. Skip if Phew has n/a in the COCOINDEX_IMAGE_TAG pin column.

Terminal window
curl -s -X PATCH "${COOLIFY_BASE_URL}/api/v1/applications/${COOLIFY_APP_UUID}/envs" \
-H "Authorization: Bearer ${COOLIFY_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"key":"COCOINDEX_IMAGE_TAG","value":"vX.Y.Z","is_buildtime":true,"is_runtime":true}'

Then trigger the Coolify deploy; confirm success before proceeding.

Step 7 — Verify: deploy gate, smoke test, version field. (1) re-run Step-4 check against the live deployment SHA; (2) smoke-test the Phew URL kh.phew.org.uk — branded render (AC-E3); (3) curl <client-url>/api/health | jq .version"vX.Y.Z" (the {113.5} build-time stamp — DONE, so this hard check is available).

Step 8 — (SAME-STEP — PI-7/PI-12) Update the crib in the same act as the repoint.

A repoint without a same-step crib update is a checklist FAILURE. APPEND a row to §2 upgrade-history (append-only): client token, From, To, date, migration delta, ordering (migrate-first (OQ-113-2)), rollback-safety + justification, old-code-tolerance + justification, snapshot ID from Step 2. UPDATE Phew’s §1 pin-table row in place: “Pinned release vX.Y.Z” + “Last-upgrade date” (+ COCOINDEX_IMAGE_TAG if applicable).

Step 9 — Rollback guidance. Additive (safe): repoint Vercel back to release/vX.Y.(Z-1) + redeploy (forward-compatible schema; no schema rollback). Forward-only (destructive): roll FORWARD — ship vX.Y.(Z+1) PATCH + upgrade to it (never revert schema). Emergency data restore (last resort): restore from the Step-2 snapshot (warning: post-snapshot writes lost). Record every rollback/restore in §2 (append-only correction-note).

6.C {113.10} THE 3 DEFERRED LIVE-STANDUP CHECKS (per-client-release-pins.md §5, lines 484-676)

Section titled “6.C {113.10} THE 3 DEFERRED LIVE-STANDUP CHECKS (per-client-release-pins.md §5, lines 484-676)”

All three are pending first standup (require live Vercel infra).

PI-9 — deploy-gate NEGATIVE test (the HEADLINE HAZARD; lines 556-581): a misconfigured required-check name silently un-gates production.

  1. Create a throwaway Vercel project connected to ai-solution-hub/knowledge-hub (or reuse a non-client project).
  2. Settings → Git → Required checks: set a deliberately wrong string, e.g. ci-summary (missing the Vercel - knowledge-hub: prefix).
  3. Push a commit that triggers CI. Observe whether Vercel allows a production deploy when the correct Vercel - knowledge-hub: ci-summary check passes but the required-check field does NOT match it.
  4. Expected PASS: production deploy is BLOCKED (Vercel treats the required check as unmet on name mismatch). If it proceeds → gate is silently misconfigured → FAIL → audit the required-check string in EVERY live client project immediately.
  5. Restore the throwaway’s required-check field to Vercel - knowledge-hub: ci-summary (or delete the project).

PI-3 — no-op-main-merge / pin-not-main-tracking (lines 528-555): a no-op merge to main MUST NOT trigger a client production redeploy; each client’s Production Branch must resolve to exactly one release/vX.Y.Z.

  1. Each client Vercel project → Settings → Git → Production Branch == release/vX.Y.Z (matching §1 pin), NOT main/auto-updating.
  2. Push a no-op commit:
Terminal window
git commit --allow-empty -m "chore: standup verification no-op (PI-3/PI-4 check)"
git push origin main
  1. Observe each client project — NO production deployment should trigger. Expected PASS: no client shows a new Production deployment from the main push; each Production Branch shows exactly one release/vX.Y.Z.

OQ-113-1 — multi-project shared ci-summary check (lines 642-661): confirm multiple per-client Vercel projects can each configure the same Vercel - knowledge-hub: ci-summary required check without conflict.

  1. Open ≥2 client project settings side by side (Settings → Git → Required checks).
  2. Confirm each shows Vercel - knowledge-hub: ci-summary and independently gates its own production deployments. Expected PASS: all client projects show the same check name; each deployment history shows it was independently gated; no “check name conflict” warning. Resolves OQ-113-1.
  • Phew (client-app-deploy.md:39-40): kh.phew.org.uk; Vercel project knowledge-hub-phew; Supabase ref rovrymhhffssilaftdwd (West Europe London, created 2026-03-03 — live-confirmed via supabase projects list).
  • Platform control/demo deploy (client-app-deploy.md:40-42): NEXT_PUBLIC_CLIENT_ID=default, its own dedicated Supabase project knowledge-hub-platform ref zjqbrdctesqvouboziae (created 2026-06-11).
  • Staging (.env.local / CLAUDE.md): turayklvaunphgbgscat (the ref the purge replay-proof links to — STAGING, never prod).
  • Release-branch convention: tag vX.Y.Z is immutable SoT (PI-3); release/vX.Y.Z is the Vercel handle pointing at the tag commit; on-prem COCOINDEX_IMAGE_TAG mirrors the vX.Y.Z tag. Repoint is always deliberate; Vercel never tracks main.
  • Per-client model (client-app-deploy.md:29-31): each client = a dedicated Supabase project + a dedicated Vercel project knowledge-hub-{client} against the single public app repo.

SECTION 7 — CROSS-DEPENDENCY: PURGE ↔ v1.0.0 TAG

Section titled “SECTION 7 — CROSS-DEPENDENCY: PURGE ↔ v1.0.0 TAG”

The purge rewrites EVERY SHA in the repo. Therefore:

  1. The v1.0.0 tag ({113.8} Step 4) MUST be cut AFTER the purge completes, against the post-purge main SHA. A tag cut before the purge would point at an orphaned pre-purge commit (resolvable only against the backup mirror) and the release/v1.0.0 branch + Phew repoint would carry pre-purge (un-redacted) history into the client’s production deployment — defeating the entire ID-68 IP-separation purpose.
  2. Recommended global sequence (combining Inv-36 purge-then-flip with the release cut):
    1. Backup (§5) — mandatory first.
    2. Purge ({68.10}, §1) on a fresh mirror → verify → force-push → GitHub-Support GC of refs/pull/*.
    3. Fleet re-clone + re-index (every SHA changed).
    4. Flip ({68.9}, §4) repo public + enable security suite + secret-scanning retro-scan (scans cleaned history).
    5. Cut v1.0.0 ({113.8}, §6.A) against the post-purge, post-flip main HEAD (re-confirm green ci-summary on the new SHA first).
    6. 3 standup checks ({113.10}, §6.C).
    7. Phew repoint ({113.9}, §6.B) — snapshot-first → migrate rovrymhhffssilaftdwd → deploy-gate → repoint knowledge-hub-phew Vercel Production Branch → crib.

    Note: cutting the release can technically precede the flip (the tag/branch are git refs; visibility is orthogonal). The hard constraint is purge-BEFORE-tag and purge-BEFORE-flip. Cutting v1.0.0 after the flip is cleanest (single post-purge SHA basis, public repo for client transparency), but if the owner wants v1.0.0 cut while still private that is permissible as long as it is post-purge.

  3. main lineage note: the cut requires the target SHA be a first-parent ancestor of main (PI-2). Post-purge, main’s HEAD SHA is brand-new — re-run gh run list --branch main to confirm ci-summary is green on the rewritten HEAD before tagging (CI must re-run on the rewritten commit; the old green run is against an orphaned SHA).

BLOCKERS (must clear before the purge can start):

  1. No backup exists. AC-E5 mandatory. Run §5 first (bypassed; outside repo tree).
  2. OQ-H OPEN — redaction-map contents not ratified by Liam (R8 in/out; HEAD-scrub-only candidates; migration branch). Cannot extract ~/id68-redaction-map.txt until closed.
  3. OQ-G(b) OPEN — seeded client-name row disposition (Branch A vs B). Largely collapsed post-squash but still formally open.
  4. No explicit Liam GO recorded for {68.10} or {68.9}.

SQUASH-DRIFT RISKS (the runbook predates the 2026-06-17 squash): 5. The --path-rename Branch-A step targets a now-deleted file (20260424202806_capture_phew_domain_hook.sql is gone; HEAD has only 20260617130000_squash_baseline.sql). At HEAD it is a no-op; it only affects pre-squash history. The runbook’s “Branch A replay-consistency proof” (staging supabase db reset) is moot at HEAD — there is no rewritten migration chain to replay. Recommend: drop the --path-rename from pass 2 (or keep it harmlessly for historical blobs) and skip the replay-proof; rely on --replace-text R4/R5 for the historical migration content. Confirm with Liam / re-run the inventory generator at purge time — it re-derives from current HEAD and will reflect the squash. 6. The path inventory must be regenerated at purge time (never reuse the S321 4,651-path run) — the squash changed the migration tree; the generator’s “already-removed-at-HEAD” vs “pending-relocation” marking will differ. 7. docs/** is NOT literally zero at HEAD (9 files on origin/main: docs/reference/skill-routing-map.md + 8 ITT/RFP public-tender fixtures). {68.30} accepted this as “substance met, AC-C1 literal-zero unmet — no client IP.” These 9 are NOT client-confidential, but confirm they are either in the purge --invert-paths set or deliberately retained before flip (the public-tender fixtures are public-domain procurement docs; skill-routing-map.md is internal-process-but-not-client). Flag for Liam: is skill-routing-map.md acceptable to publish?

EXECUTION HAZARDS: 8. refs/pull/* retentiongit push --mirror does NOT overwrite GitHub’s PR-ref cache. A GitHub-Support GC must complete BEFORE the flip or old-SHA fetches leak un-redacted history (violates Inv-36 clean-before-exposure). This is a support-ticket lead-time dependency — initiate early. 9. Branch protection / ruleset reconciliation — the purge force-push must get past ruleset 15785019 (non_fast_forward + deletion + required_status_checks). Org-admin bypass is always, so the owner MAY force-push without lifting it; otherwise disable the ruleset’s non_fast_forward rule, push, re-enable. The runbook references only the classic branches/main/protection API — reconcile with the ruleset at execution. 10. Security-suite toggles are visibility-gated — most security_and_analysis toggles (CodeQL default-setup, secret-scanning) only become available once the repo is public (the free-tier benefit motivating the flip). Enable them AFTER the flip; sequence secret-scanning retro-scan last so it scans cleaned history. 11. CI must re-run green on the post-purge main SHA before cutting v1.0.0 — the existing green run is against the soon-orphaned SHA; do not tag against a SHA whose ci-summary hasn’t re-run. 12. Widen the Dependabot gate query to (high or critical) in the {68.9} flip pre-check (fix-forward carried from {68.30}); current query checks high only (critical=0 today, so verdict unaffected, but a future critical would silently pass).

SANDBOX / OPERATIONAL: 13. All gh/gh api/supabase/git push/git clone --mirror calls need dangerouslyDisableSandbox: true (the in-sandbox tell is x509: OSStatus -26276). Set SUPABASE_TELEMETRY_DISABLED=1 for supabase CLI. 14. $TMPDIR differs in-sandbox (/tmp/claude-501) vs bypassed (/var/folders/...) — a file written bypassed is invisible to an in-sandbox grep. This report is written to BOTH $TMPDIR/flip-readiness-report.md and the repo copy ./.flip-readiness-report.md.

OPEN QUESTIONS FOR LIAM (decision gates):

  • OQ-H: ratify the redaction map (R8 in/out; any HEAD-scrub-only acceptances).
  • OQ-G(b): seeded client-name row disposition (likely N/A post-squash — confirm).
  • Post-squash --path-rename / replay-proof: drop or retain? (recommend drop).
  • docs/reference/skill-routing-map.md at public HEAD — publish or purge?
  • Confirm v1.0.0 is cut post-purge (timing relative to flip is operator preference; purge-first is the hard constraint).
  • Explicit GO for: (1) backup, (2) purge, (3) flip, (4) v1.0.0 cut, (5) Phew repoint.

APPENDIX — verified live facts (2026-06-17)

Section titled “APPENDIX — verified live facts (2026-06-17)”
  • Repo: {"archived":false,"default_branch":"main","private":true,"visibility":"private"}
  • Dependabot open high-sev: 0; open critical: 0
  • Classic branch protection on main: required checks ["identity-guard"], allow_force_pushes:false, allow_deletions:false, enforce_admins:false, strict:false
  • Rulesets: 15785019 production-protection (active, branch; rules: deletion + non_fast_forward + required_status_checks; bypass OrganizationAdmin+RepositoryRole always) ; 15785711 staging-protection
  • security_and_analysis: code_security disabled, secret_scanning disabled, secret_scanning_push_protection disabled, dependabot_security_updates enabled
  • git-filter-repo: 2.47.0 (python3 -m git_filter_repo)
  • Migrations at origin/main HEAD: ONE — supabase/migrations/20260617130000_squash_baseline.sql (client migration deleted by squash 84b355ec2)
  • git log --all -S'phew': 798 commits still reference the stem in history (purge --replace-text required)
  • docs/** at origin/main: 9 files (skill-routing-map.md + 8 public-tender fixtures)
  • Supabase projects (org ptacqvoidhqznzdnzpbg): knowledge-hub-platform = zjqbrdctesqvouboziae; knowledge-hub-phew = rovrymhhffssilaftdwd
  • Existing backups: NONE (~/Backups absent; no purge mirror anywhere)
  • vX.Y.Z release tags: NONE (only v0.2.0-task-view, unrelated); no release/* branches
  • ID-113 subtasks {113.1}–{113.10}: all ledger-status done (runbook AUTHORING; live execution still owner-gated)
  • {68.30} final journal: all readiness gates PASS; “Remaining is the switch only: G5/G9 → {68.9} flip + {68.10} purge, HELD on Liam GO.”