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).
0. ONE-PAGE VERDICT
Section titled “0. ONE-PAGE VERDICT”| Item | State | Blocker 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 visibility | private (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 required | None |
| git-filter-repo | 2.47.0 present (python3 -m git_filter_repo) | None |
| Pre-purge backup | DOES NOT EXIST — no ~/Backups, no mirror anywhere | MANDATORY before purge (AC-E5) |
| OQ-H (redaction-map ratification) | OPEN — Liam has not ratified the map | BLOCKS {68.10} purge |
| OQ-G(b) (seeded client-name row disposition) | OPEN — selects Branch A/B | BLOCKS {68.10} purge |
| Explicit Liam GO | NOT recorded for either {68.9} or {68.10} | BLOCKS both |
| Ordering | PURGE-THEN-FLIP is canonical (3 independent sources; S371 prompt’s “flip then purge” is wrong) | — |
| Squash impact | Client migration GONE from HEAD; path-rename step now obsolete; --replace-text/--replace-message still required for history | Redaction map still needed; replay-proof now N/A |
| v1.0.0 release cut | Green-CI precondition MET (main fully green); not yet cut; no vX.Y.Z tags exist | Owner GO |
| Phew repoint targets | Vercel 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)”| Artefact | Path | Owner |
|---|---|---|
| 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):
# 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-purgeVerify: 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):
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.txtGenerator 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):
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 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 migration20260424202806_capture_phew_domain_hook.sqlno longer exists at HEAD. The Branch-A--path-renameline 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-messagehistory redaction is STILL required — the stemphewis reachable across 798 commits ingit log --all -S'phew', including the squash commit’s own diff of the deleted content.
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 must pass across ALL refs including tags:
# (a) analyze reportpython3 -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.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: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 journalled carve-outs)Journal the check outputs (counts, not contents) with the rewritten HEAD SHA.
Step 6 — force-push:
cd ~/purge-workspace/kh-purge-mirror.gitgit push --mirror origin- Branch protection on
mainrejects 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 a required check afterwards).NUANCE (live-verified): the active protection is ruleset 15785019 (
production-protection), which hasOrganizationAdmin+RepositoryRolebypassalways. 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’snon_fast_forwardrule, 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 bygit 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):
- 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. - Supabase link re-verify:
cat supabase/.temp/project-ref→ relinksupabase link --project-ref turayklvaunphgbgscat(staging) if absent/drifted. - Re-index code intelligence:
npx gitnexus analyze+ the ccc index (both key on SHAs). - 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-pathsset (deleting an applied migration breakssupabase db resetreplay) — 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 emailregex:(?i)support@phew\.org\.uk==>support@client.example# R2 — production subdomainregex:(?i)kh\.phew\.org\.uk==>kh.client.example# R3 — bare client domainregex:(?i)phew\.org\.uk==>client.example# R4 — legal nameregex:(?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 literalregex:(?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-clientRules 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)”- R8 bare-stem rule in or out. Draft recommendation: in (strongest guarantee; AC-E2 trivially verifiable).
- 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.
- 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-textregardless 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:
-
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.”
-
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)”. -
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.”
-
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-178describes 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}retains68.10in 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)”| Gate | Requirement | Verification | State (live + {68.30}) |
|---|---|---|---|
| 1 | Docs + ledgers moved to private docs-site (no sanitisation) | AC-A3, AC-C1 | PASS (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) |
| 2 | Ledgers private via the ID-20/ID-90 cutover | cutover recorded | PASS (ledger JSONs = 0 in public; satisfied via {68.35} done) |
| 3 | Genuine client content out of public HEAD and history | AC-E1, AC-E2 | PASS (HEAD leg via CI sweep; history leg = Gate 5 = the purge itself, still pending) |
| 4 | guard-CI required branch-protection check live | AC-E6 | PASS — (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. |
| 5 | History purge {68.10} executed | AC-E2, AC-E5 | HELD — this IS the purge; runs in Phase 6. |
| 6 | Archive + workflow-eval corpus cold-storaged | AC-A4 | PASS (archive index-isolation) |
| 7 | Relocation sweep re-run against current HEAD (Inv 40) | sweep cites HEAD SHA | PASS — sweep-identity-relocation exit 0; 1541 docs-pointers, 10 runtime-read; clean. |
| 8 | Dependabot high-sev gate = 0 open (drifts) | gh api .../dependabot/alerts count = 0 | PASS (live-verified just now: 0 open high; 0 open critical) |
| 9 | Explicit Liam GO (outward-facing) | recorded in ledger journal | HELD — not granted. |
3.2 Is the repo flip-ready NOW?
Section titled “3.2 Is the repo flip-ready NOW?”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):
- 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).
- G9 = explicit Liam GO — for both {68.10} and {68.9}.
- Fix-forward carried to {68.9} (
ID-68.md:119, 832): widen the Dependabot gate query fromseverity=="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. - Hygiene (non-blocking): 172 dead
PENDING_DEIDexcludes inidentity-guard.yml(path-absent, deferred sentinel-gated cleanup); 4 stale:(exclude)ontology pathspecs.
SECTION 4 — {68.9} FLIP MECHANISM
Section titled “SECTION 4 — {68.9} FLIP MECHANISM”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)”# 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 visibility4.2 The flip command (verbatim from ledger details)
Section titled “4.2 The flip command (verbatim from ledger details)”gh repo edit ai-solution-hub/knowledge-hub --visibility publicFlip 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.
# 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):
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 == enabledSECTION 5 — BACKUP PRECONDITION
Section titled “SECTION 5 — BACKUP PRECONDITION”The ledger mandates “explicit Liam GO + backup” before purge (AC-E5).
A current full backup/mirror DOES NOT EXIST. Verified:
- No
~/Backupsdirectory. - No
~/*purge*,~/*backup*git mirror in home (only two unrelated MemPalace.tar.gzarchives). - No
pre-id68-purgetag anywhere.
Exact command to take the complete pre-purge backup (verbatim, AC-E5):
mkdir -p ~/Backupscd ~/Backupsgit clone --mirror https://github.com/ai-solution-hub/knowledge-hub.git \ kh-pre-id68-purge-backup.gitcd kh-pre-id68-purge-backup.gitgit tag pre-id68-purgeVerify:
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 tagRecommended 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 hitsgithub.comover 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 ishttps://. Use whichever auth the operator has;gh auth setup-gitmakes HTTPS work with theghtoken.
SECTION 6 — ID-113 LIVE-OPS RUNBOOKS
Section titled “SECTION 6 — ID-113 LIVE-OPS RUNBOOKS”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:
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=successDo 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:
# 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.ZTag 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:
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-FFTag 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/.
gh workflow run onprem-deploy.yml \ --ref vX.Y.Z \ --field job=tag-release \ --field source_sha=<full-sha> \ --field release_version=vX.Y.ZVerify in ghcr.io:
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.
# 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.
supabase db push --project-ref rovrymhhffssilaftdwdVerify schema parity:
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:
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=successAND 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_TAG → vX.Y.Z. Skip if Phew has n/a in the COCOINDEX_IMAGE_TAG pin column.
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_TAGif 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.
- Create a throwaway Vercel project connected to
ai-solution-hub/knowledge-hub(or reuse a non-client project). - Settings → Git → Required checks: set a deliberately wrong string, e.g.
ci-summary(missing theVercel - knowledge-hub:prefix). - Push a commit that triggers CI. Observe whether Vercel allows a production deploy when the correct
Vercel - knowledge-hub: ci-summarycheck passes but the required-check field does NOT match it. - 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.
- 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.
- Each client Vercel project → Settings → Git → Production Branch ==
release/vX.Y.Z(matching §1 pin), NOTmain/auto-updating. - Push a no-op commit:
git commit --allow-empty -m "chore: standup verification no-op (PI-3/PI-4 check)"git push origin main- Observe each client project — NO production deployment should trigger.
Expected PASS: no client shows a new Production deployment from the
mainpush; each Production Branch shows exactly onerelease/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.
- Open ≥2 client project settings side by side (Settings → Git → Required checks).
- Confirm each shows
Vercel - knowledge-hub: ci-summaryand 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.
6.D Live deployment topology (verified)
Section titled “6.D Live deployment topology (verified)”- Phew (
client-app-deploy.md:39-40):kh.phew.org.uk; Vercel projectknowledge-hub-phew; Supabase refrovrymhhffssilaftdwd(West Europe London, created 2026-03-03 — live-confirmed viasupabase projects list). - Platform control/demo deploy (
client-app-deploy.md:40-42):NEXT_PUBLIC_CLIENT_ID=default, its own dedicated Supabase projectknowledge-hub-platformrefzjqbrdctesqvouboziae(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.Zis immutable SoT (PI-3);release/vX.Y.Zis the Vercel handle pointing at the tag commit; on-premCOCOINDEX_IMAGE_TAGmirrors thevX.Y.Ztag. Repoint is always deliberate; Vercel never tracksmain. - Per-client model (
client-app-deploy.md:29-31): each client = a dedicated Supabase project + a dedicated Vercel projectknowledge-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:
- The
v1.0.0tag ({113.8} Step 4) MUST be cut AFTER the purge completes, against the post-purgemainSHA. A tag cut before the purge would point at an orphaned pre-purge commit (resolvable only against the backup mirror) and therelease/v1.0.0branch + Phew repoint would carry pre-purge (un-redacted) history into the client’s production deployment — defeating the entire ID-68 IP-separation purpose. - Recommended global sequence (combining Inv-36 purge-then-flip with the release cut):
- Backup (§5) — mandatory first.
- Purge ({68.10}, §1) on a fresh mirror → verify → force-push → GitHub-Support GC of
refs/pull/*. - Fleet re-clone + re-index (every SHA changed).
- Flip ({68.9}, §4) repo public + enable security suite + secret-scanning retro-scan (scans cleaned history).
- Cut
v1.0.0({113.8}, §6.A) against the post-purge, post-flipmainHEAD (re-confirm green ci-summary on the new SHA first). - 3 standup checks ({113.10}, §6.C).
- Phew repoint ({113.9}, §6.B) — snapshot-first → migrate
rovrymhhffssilaftdwd→ deploy-gate → repointknowledge-hub-phewVercel 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.
mainlineage note: the cut requires the target SHA be a first-parent ancestor ofmain(PI-2). Post-purge,main’s HEAD SHA is brand-new — re-rungh run list --branch mainto 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).
RISKS / OPEN-QUESTIONS
Section titled “RISKS / OPEN-QUESTIONS”BLOCKERS (must clear before the purge can start):
- No backup exists. AC-E5 mandatory. Run §5 first (bypassed; outside repo tree).
- OQ-H OPEN — redaction-map contents not ratified by Liam (R8 in/out; HEAD-scrub-only candidates; migration branch). Cannot extract
~/id68-redaction-map.txtuntil closed. - OQ-G(b) OPEN — seeded client-name row disposition (Branch A vs B). Largely collapsed post-squash but still formally open.
- 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/* retention — git 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.mdat 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+RepositoryRolealways) ;15785711 staging-protection security_and_analysis: code_securitydisabled, secret_scanningdisabled, secret_scanning_push_protectiondisabled, dependabot_security_updatesenabled- git-filter-repo:
2.47.0(python3 -m git_filter_repo) - Migrations at
origin/mainHEAD: ONE —supabase/migrations/20260617130000_squash_baseline.sql(client migration deleted by squash84b355ec2) git log --all -S'phew': 798 commits still reference the stem in history (purge--replace-textrequired)docs/**atorigin/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 (
~/Backupsabsent; no purge mirror anywhere) vX.Y.Zrelease tags: NONE (onlyv0.2.0-task-view, unrelated); norelease/*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.”