ID-68 {68.13} — Pre-Flip Client De-Identification + Private-Repo Maturation Plan
ID-68 {68.13} — Pre-Flip Client De-Identification + Private-Repo Maturation Plan
Section titled “ID-68 {68.13} — Pre-Flip Client De-Identification + Private-Repo Maturation Plan”Type: Implementation design (RESEARCH + TECH, no PRODUCT — ID-54/ID-68 precedent). Authored: 2026-06-03 (S301a) by the relocation Orchestrator, from the S301a de-identification-status investigation (3 read-only agents: spec-corpus, live-triage, memory). Status: RATIFIED (S301a, Liam) with notes — D3 adopted + a ledger-CLI client-name guard added (§2 D, §4); C largely resolved (classification-prompt is
kb_pipeline-only and retires with it — §2 C). No code/ledger/migration edits made by this document. Governing inputs:RELOCATION-IMPLEMENTATION-PLAN.md(S-B routing, ratified S301);RELOCATION-INVESTIGATION-V2.md(OQ-R10/R12); ledgerdocs/reference/tasks/ID-68.md({68.11}deferral journal); the S291 “Pre-Flip De-Identification + Client-Configurability Scoping Report” (MemPalace,sessions/technical, 2026-06-01). Implements: a NEW subtask{68.13}to be opened under Task ID-68, inserted as a dependency of{68.9}(flip) — see §7.
0. Why this exists — the flip-blocking gap
Section titled “0. Why this exists — the flip-blocking gap”The S301a investigation established that the ratified ID-68 design is sound and ~60% executed, but the pre-flip client de-identification is incomplete, and the remaining work was explicitly owner-deferred and never ratified or re-homed into a tracked subtask.
Terminology (ratified, RESEARCH §11): Phew (Phew Design Limited) = the first CLIENT.
The platform is Knowledge Hub; the company is AI Solution Hub (ai-solution-hub org).
Client identity must not leak into the public application repo; client-name references
inside the private knowledge-hub-internal repo are accepted (sensitivity routing, S-B).
The gap, precisely: {68.11} (de-identify shipped code — DONE, S292 f917f88d) scoped
itself to confidential worked-examples + parameterising lib/client-config.ts defaults, and
its journal (tasks/ID-68.md:149) listed under “DEFERRED (separate, not code-scope)”:
phew.json + public/clients/phew/* untracking; the capture_phew_domain_hook migration;
the kh.phew.org.uk sweep (~40 files); export-phew-articles.ts filename;
classification-eval-gold-standard.json; cloudrun/*phew*.yaml. The {68.9} flip AC
(tasks/ID-68.md:113-116) gates only on Dependabot clearance + Liam GO — it carries none
of these. Flipping today would publish the real client name (Phew Design Limited),
support@phew.org.uk, the client domain, and the domain-gate migration — the exact
IP-separation concern ID-68 exists to prevent.
Cross-session note: a concurrent session is implementing the canonical pipeline on
canonical-pipeline-setup. Item C below (classification-prompt scrub) and the ledger
writes intersect that work — execution must be coordinated, not raced.
1. Current status — what is done vs what remains
Section titled “1. Current status — what is done vs what remains”| Area | Status | Evidence |
|---|---|---|
| Channel-B client content HEAD-removed | ✅ DONE | {68.5} S290 b6e35683 |
scripts/ client literals parameterised → client-config/CLIENT_* env | ✅ DONE | {68.6} S291 |
| ITT one-shot seeds relocated out of build context | ✅ DONE | {68.7} |
| ghcr build context clean post-scrub | ✅ DONE | {68.8} |
Classification worked-examples scrubbed in lib/ai/skills/classification*.md + inlined.generated.ts; client-config defaults genericised | ✅ DONE | {68.11} S292 f917f88d |
Cloud Run *phew* yamls | ✅ DONE (0 tracked) | Cloud Run retired S298 — deferred-item 6 already resolved |
| Dependabot high-sev gate | ✅ GREEN (re-verify live pre-flip) | S291: 0 open high |
A — phew.json + public/clients/phew/* (5 assets) | ❌ OPEN — tracked in public HEAD | lib/branding/clients/phew.json, public/clients/phew/* |
B — capture_phew_domain_hook migration + config.toml | ❌ OPEN — names real client domain | supabase/migrations/20260424202806_*.sql, supabase/config.toml:72-78 |
| B2 — 2nd migration seeds real client name | ❌ OPEN | 20260419212103_*.sql:48 ("…for Phew Design Limited") |
C — confidential client content in fixtures + classification-prompt.md | ❌ OPEN — real bid prose | 4 eval gold-standards; docs/reference/classification-prompt.md:194-758 |
| D — staying-public ledgers/specs/ontology name the client | ❌ OPEN — ~82 docs files | docs/specs/ID-68-*, docs/reference/*.json + mirrors, docs/ontology/18-entity-aliases.md |
F — kh.phew.org.uk prod domain in public source | ❌ OPEN | CLAUDE.md, scripts/tests/*.py (×7 webhook URLs) |
| Operator-move (docs-site + operators → private; remove from public) | ⏸ NOT STARTED | {68.12} Phase 2e/3 remainder — see §3 |
Private repo (knowledge-hub-internal) maturation | ⏸ NOT a functioning repo | no package.json/vitest/CI; bridges unwired |
Already shipped this session (S301a): client-IP scrub of the mammoth test (synthetic
.docx fixture), quality-gate.test.ts filenames, and an ICO-number placeholder — commit
83525b84 on id68-relocation-phase23.
2. Decisions A–F (Liam-steered) — design + open investigations
Section titled “2. Decisions A–F (Liam-steered) — design + open investigations”Each carries Liam’s ratified steer (verbatim intent), the proposed design, the open investigation, and risk.
A — phew.json + brand assets → untrack → deploy-inject
Section titled “A — phew.json + brand assets → untrack → deploy-inject”- Steer: “untrack → deploy-inject.”
- Current:
lib/client-config.tsstaticallyimportsphew.jsonand registers it inCLIENT_BRANDING_MAP = { default, phew }— so the real client config is compiled into the bundle regardless ofNEXT_PUBLIC_CLIENT_ID.default.jsonis already a generic fallback. - Design: (1)
git rm lib/branding/clients/phew.json+public/clients/phew/*; (2) drop the staticphewimport + map entry fromclient-config.ts, so the map ships{ default }only and the loader resolvesdefaultwhen no client overlay is present; (3) providephew.json+ assets at deploy time via a private overlay (mounted/copied intolib/branding/clients/+public/clients/during the client build) — the path/loader mechanism is unchanged, only the data is injected.NEXT_PUBLIC_CLIENT_ID=phewat deploy selects it. - Open investigation: confirm the loader’s behaviour when
CLIENT_BRANDING_MAP[id]is absent — it already falls back todefault(verified L584-607), so this is low-risk; verify no other static import ofphew.jsonexists and thatpublic/clients/phew/*is referenced only via the config-driven asset paths. - Risk: LOW. Reversible. Deploy overlay mechanism must exist before the public asset removal lands in a deployed build (else the client deploy loses branding).
B — capture_phew_domain_hook migration → rename + parameterise
Section titled “B — capture_phew_domain_hook migration → rename + parameterise”- Steer: “rename + parameterise.”
- Current:
20260424202806_capture_phew_domain_hook.sqlhardcodeshook_restrict_signup_to_phew_domain,IF domain = 'phew.org.uk', the user-facing error message, and a prod project-ref.config.toml:72-78wires the hook by that name. A 2nd migration (20260419212103) seeds a DB row namingPhew Design Limited. - Design: migrations are append-only history — do not edit the applied file. Author a
new migration that (a)
CREATE OR REPLACEs the function under a generic name (hook_restrict_signup_to_allowed_domain) reading the allowed domain from a config value (aapp_settings/GUC row or env-injected setting), (b) drops/renames the old client-named function, (c) repointsconfig.tomlto the generic hook name. The real domain becomes deploy-config, not source. The 2nd migration’s seeded client-name row: supersede via a follow-up migration or accept (it is data, not identity in the function surface) — decide in ratification. - Open investigation: whether Supabase auth-hook wiring (
config.toml) can reference a config-driven domain without a redeploy; whether the old migration’s text must also be history-purged (§E) since it names the domain in all history. - Risk: MEDIUM. Touches live auth (signup domain gate). Must be tested against staging before the public-app change. The migration is the single highest-signal leak — it proves Phew is the live production tenant (memory: “this connection is the IP-separation concern ID-68 exists to prevent”).
C — confidential client content (fixtures + classification-prompt.md) → scrub to synthetic, AFTER confirming live-usage
Section titled “C — confidential client content (fixtures + classification-prompt.md) → scrub to synthetic, AFTER confirming live-usage”- Steer: “Scrub to synthetic, but first check whether the classification prompt in its current form is still used once the canonical pipeline implementation is complete.”
- Current — two prompt sources:
docs/reference/classification-prompt.md— read byscripts/kb_pipeline/config.py:15(PROMPT_PATH), bundled into the MCP plugin (bundle-plugin.ts:174), taxonomy-codegen target (generate-classification-prompt-taxonomy.ts). Contains real client bid content at L194–758 (Phew portfolio, CREST pen-test, Phew Audit System, PI insurance, G-Cloud positioning, client listPhew Design Limited, NHS, HMRC, …) — the same confidential worked-examples{68.11}already scrubbed fromlib/ai/skills/classification.md.scripts/cocoindex_pipeline/prompts.py— the canonical pipeline’s own prompt source.
- The coordination question (Liam’s caveat): if the canonical pipeline (
cocoindex_pipeline) supersedeskb_pipelineand inlines its own prompts, thenclassification-prompt.mdmay be retired by the canonical work — in which case it should be relocated-private/deleted rather than scrubbed-in-place, and the scrub effort should target the canonical prompt source if that carries any client examples. - RESOLVED (S301a):
classification-prompt.mdis consumed bykb_pipelineonly (config.py:15,bundle-plugin.ts:174, taxonomy codegen) — the canonicalcocoindex_pipelinedoes NOT reference it (verified: noclassification-prompt/PROMPT_PATH/get_system_promptinscripts/cocoindex_pipeline/**). Andkb_pipelineis due to retire (ID-66 PRODUCT:93,277“kb_pipeline retirement — a separate Curator carry”; Liam: see07-collapse-list.md/ the v1-sequencing doc for the exact timing). Thereforeclassification-prompt.mdretires WITHkb_pipeline— its client bid content leaves the public surface whenkb_pipelineis removed, not via an in-place scrub. - Design: (1) Do NOT scrub
classification-prompt.mdin place — confirm thekb_pipelineretirement timing (v1-sequencing) and either (a) gate the{68.9}flip onkb_pipeline(+classification-prompt.md+ the MCP-plugin classification surface) retirement, or (b) if the flip must precede retirement, relocateclassification-prompt.md(and repoint the plugin bundle/codegen) rather than scrub. (2) The 4 eval gold-standards (classification/entity/procurement-drafting/summarisation) carry real bid prose (ISO 27001, Telehouse data centre, DBS, employee counts) and are notkb_pipeline-coupled → scrub to synthetic regardless. - Risk: MEDIUM.
classification-prompt.mdis the MCP plugin’s classification surface +reference-doc-paths.jsoncarve-out; scrubbing must keep codegen markers + path-manifest valid. Coordination risk with s302 if scrubbed prematurely.
D — staying-public ledgers / specs / ontology → RATIFIED: D3 hybrid + ledger-CLI guard (§4)
Section titled “D — staying-public ledgers / specs / ontology → RATIFIED: D3 hybrid + ledger-CLI guard (§4)”- Steer: “My preference would be to move these across to the private repo … the implications relate to our dev workflow … ledgers and specs are internal workflow related, not platform related. For ontology this should be sanitised, but the ontology itself is core platform functionality … clients [will] customise/configure it.” Liam explicitly requested my recommendation. See §4.
E — {68.10} history purge → avoid sharing any client-specific content
Section titled “E — {68.10} history purge → avoid sharing any client-specific content”- Steer: “Whichever approach avoids unnecessarily sharing any client specific content.”
- Current:
{68.10}as specced is path-removal only (git filter-repo --invert-paths) — it does not content-scrub the string “phew” from blobs that stay (the migration, Python tests, fixtures). So identity strings scrubbed at HEAD would still survive in history. - Design: extend
{68.10}withgit filter-repo --replace-text(a redaction map:phew.org.uk→client.example,Phew Design Limited→Example Client Ltd,support@phew.org.uk,kh.phew.org.uk, etc.) applied across all history, in addition to the path-removal set. Mandatory first action: a pre-purge mirror backup (git clone --mirror+ tagpre-id68-purge) — the only safety net (purge is irreversible; memory Q1). Run on a fresh mirror; verify zero residual matches before force-push. - Open investigation: the exact redaction map (must not break staying code that legitimately
needs a value — e.g. generic
defaultconfig); whether HEAD-scrub-only is acceptable for any low-sensitivity strings (memory: config strings “may be acceptable to leave in history if scrubbed at HEAD”). - Risk: HIGH (irreversible). Backup-first is non-negotiable.
F — kh.phew.org.uk prod domain → genericise public refs; keep a private/deploy-side reference
Section titled “F — kh.phew.org.uk prod domain → genericise public refs; keep a private/deploy-side reference”- Steer: “Genericise, but keep a reference to the URL given that it’s the current focus for all implementation work … I’d benefit from you knowing at the start of each session the current production domain … unless there’s a better way more closely aligned to how we’ll deploy to clients, which would make the URL a non-issue.”
- Design: (1) genericise hardcoded public refs —
scripts/tests/*.pywebhook URLs and anyCLAUDE.mdprod-URL line read from an env/config value (APP_URL/NEXT_PUBLIC_APP_URL) with a generic example in public source; (2) the deploy-aligned answer Liam intuits: the production domain is inherently per-client deploy config — each client deploy sets its ownAPP_URL, so the URL is a deploy variable, not source → a non-issue publicly; (3) session-start awareness: record the current live domain in a private/local location thatstart-sessionreads — options: the gitignored.env.local(APP_URL=https://kh.phew.org.uk), or a private-repo ops doc (knowledge-hub-internal/docs/runbooks/current-deploy.md). Recommend.env.local+start-sessionsurfacing it, so it tracks the active deploy without any public reference. - Risk: LOW. Mostly mechanical genericisation + one start-session convention.
3. Operator-move true scope + private-repo maturation (folds {68.12} remainder)
Section titled “3. Operator-move true scope + private-repo maturation (folds {68.12} remainder)”The continuation under-scoped the operator move. The actual move-set (verified S301a):
- 7 workflows (not 6):
docs-site-deploy,missing-docs,docs-seo-audit,review-docs-pr,sync-source-docs,docubot,check-for-broken-links. .github/actions/docubot/(composite action:action.yml+prompt.txt) — used only bydocubot.yml, moves with it. Absent from private repo — must copy first.- 8 skills: the 6 docs skills +
evaluate-workflow+evaluate-findings. workflow-evaluatoragent,scripts/skills/run-skill.ts,scripts/docubot/run-agent.ts..claude/workflows/evaluator-efficiency-sweep.js— dispatches the (moving) evaluator agent; moves with the eval lane (overrides PLAN §2d’s stale “repoint”). Absent from private.- ~15 coupled tests — relocate (pure operator-shape/loading) or edit (mixed):
skill-workflow-shape,skill-inventory,docubot-action-shape,docubot-workflow-shape,docubot-prompt-loading,run-skill-loading,docs-seo-audit-cron,docubot-prompt-shape,session-a-acceptance,check-for-broken-links,docs-seo-audit,review-docs-pr,sync-source-docs,missing-docs-audit,keep-docs-in-sync-shape,claude-agent-sdk(driver-import test). (umbrellas-schema.test.tsis a false positive — comment only.) tsconfig.jsondocs-siteexcludeline — remove (dead after move).@anthropic-ai/claude-agent-sdk— after the drivers move, it is an unused public devDependency (its only consumers are the moving drivers + docubot action). Either remove frompackage.json(+ lockfile regen) or knip-baseline it.
Private-repo maturation (the real blocker for the move’s gate): knowledge-hub-internal
is not a functioning repo — no package.json, no vitest, no CI, bridges unwired. To make
the move’s gate (private docs-site dry-run + relocated-guard coverage) real, the private repo
needs: (1) a package.json + vitest harness + the test helpers the relocated guards import;
(2) the 2b/2f bridges wired (the moved sync-source-docs/docubot/docs-site-deploy
workflows mint a GitHub App installation token to checkout the public repo — App + repo-level
secrets are installed); (3) a buildable docs-site/ (node_modules + resolvable ../docs
spaces) for the dry-run gate; (4) its own CI to actually run the relocated guards.
Recommendation: treat the operator-move as two tracked phases: (i) public-side removal
(clean, reversible, low-risk — everything copied to private first) and (ii) private-repo
standup (harness + CI + bridges + docs-site build). Phase (i) can land independently; phase
(ii) is the larger “make knowledge-hub-internal a real repo” effort, and it is entangled
with D (if ledgers/specs also move private, the private repo must also support the SDLC
write-path).
4. The D decision — ledgers / specs / ontology routing (recommendation)
Section titled “4. The D decision — ledgers / specs / ontology routing (recommendation)”Liam asked for my recommendation. The tension: S-B (ratified) deliberately kept ledgers + raw
specs public-sanitised precisely to avoid re-introducing the S299 SDLC write-path
parameterisation cost. Moving them private is the PLAN’s named “max-security variant”
(RELOCATION-IMPLEMENTATION-PLAN.md §0.2.5 sub-note, §3.2 note) and re-incurs that cost.
The cost of move-private (what S-B avoided): scripts/ledger-cli.ts --ledger-dir
threading; scripts/regen-mirrors.sh parameterisation; ~39 ledger-cli skill invocations
across .claude/skills/*/SKILL.md; 4 ledger roundtrip guards. The KH_DOCS_DIR read-knob
already exists (Phase 0) and the GitHub App is installed (cross-repo CI writes possible)
— so the residual cost is the write-path parameterisation + the per-session discipline of
the orchestrator writing to the sibling/private checkout.
Three options:
| Option | What | Pro | Con |
|---|---|---|---|
| D1 — move ledgers + specs private | Relocate docs/reference/*.json + mirrors + docs/specs/ to knowledge-hub-internal; SDLC writes via KH_DOCS_DIR/App | Aligns with “internal workflow, not platform”; true separation; specs keep full fidelity | Re-incurs S299 write-path parameterisation; high-churn ledgers written every session → ongoing cross-repo writes; collides with concurrent ledger writers until they settle |
| D2 — sanitise ledgers + specs in public | Replace client name with a placeholder (Client-1/Example Client) in-place; keep public | Lowest infra cost; no write-path change | Ongoing discipline (every new entry must be client-clean); specs that structurally describe client work lose fidelity; still exposes internal SDLC process/IP |
| D3 — hybrid (recommended) | Specs → private (lower churn, structurally client-named, genuinely internal IP). Ledgers → sanitise-in-public with a placeholder (high churn; client name is incidental to process-tracking). | Balances cost vs separation; avoids parameterising the hottest write-path; removes identity from both | Two mechanisms; specs move needs the private repo standup (§3.ii) |
RATIFIED: D3 (hybrid) — S301a, Liam. Specs are the genuine internal-IP/process artefacts and
are low-churn → clean fit for the private repo (and they already mirror there). Ledgers are
written continuously and by concurrent sessions → moving them private now is operationally
risky and costly; sanitising the client name to a placeholder in-place removes the identity at
a fraction of the cost. Either way, execution must wait for concurrent ledger writers to
settle (the s302 canonical session is actively writing task-list.json).
Ledger-CLI client-name guard (Liam, S301a) — the forcing function for D3’s ledger half. Add
a check to scripts/ledger-cli.ts that rejects (or warns on) any write whose content contains
the first client’s name (Phew/Phew Design Limited), at the mutation entry points
(journalBlock :2108, promote :3551, updateUmbrella :4023, and the open/add/status
paths). This ensures nothing re-introduces the client name after the one-time sanitise
sweep. Implement as a small denylist (single value now, list-shaped for future multi-client) so
the guard is trivially adaptable. Order: sanitise sweep → land the guard in the same change, so
the ledger stays clean by construction.
Ontology: stays public, sanitised — Liam confirms it is core platform functionality
(not internal), with client-configurability in scope (clients customise/configure the
ontology at deploy or in-use). So: sanitise the residual client reference in
docs/ontology/18-entity-aliases.md (and confirm 03-layer-vocabulary/14-bid-states prose
done in Phase-0 0f), and design the de-ID so the ontology’s client-customisation seam is
preserved (per-client ontology overlays, analogous to the client-config mechanism) — flag as
a forward design note, not a pre-flip blocker.
5. Sequencing / dependency DAG
Section titled “5. Sequencing / dependency DAG”[done] {68.5,6,7,8,11} (de-ID code-scope, content removal, Cloud Run teardown) │ ▼{68.13} pre-flip de-ID ── A (phew.json untrack) ─┐ │ B (migration rename) │ (all worker-safe, reversible, │ C (scrub — AFTER s302 │ land on id68-relocation-phase23) │ canonical coord) │ │ D3 (specs→private; │ │ ledgers sanitise) │ │ F (domain genericise) │ │ ▼{68.12} operator-move ── (i) public-side removal ──► Gate: public test+build green │ (ii) private-repo standup (harness+CI+bridges+docs-site) │ │ └──────────────────────┬──────────────────────────┘ ▼ Pre-flip sanitisation AUDIT (git grep clean on staying public set) ▼ {68.9} FLIP (HUMAN GO) — reversible ▼ {68.10} PURGE (HUMAN GO, LAST) — backup-first + --invert-paths + --replace-text (E)Coordination gates: C (classification-prompt) and D (ledgers) both intersect the concurrent
canonical-pipeline-setup session — sequence them after that work settles, not concurrently.
6. Open investigations to resolve before execution
Section titled “6. Open investigations to resolve before execution”- C / canonical-pipeline — RESOLVED in principle:
classification-prompt.mdiskb_pipeline-only (cocoindex doesn’t touch it) andkb_pipelineis due to retire. Remaining: confirm the retirement timing in the v1-sequencing /07-collapse-list.mddoc and decide whether{68.9}flip waits for that retirement or relocates the prompt if it must precede it. (Eval gold-standards scrub regardless.) - A / loader: confirm the only static consumer of
phew.jsonisclient-config.tsand thatpublic/clients/phew/*is reached only via config-driven paths; design the deploy overlay injection. - B / auth hook: can
config.tomlreference a config-driven signup domain without redeploy; staging-test the superseding migration before the public-app change. - D / write-path: if specs move private, confirm
KH_DOCS_DIRcovers the spec read-paths the spec-driven skills use; scope the ledger placeholder-sanitise sweep. - E / redaction map: finalise the
--replace-textmap; confirm no staying code depends on a redacted literal. - Operator-move: decide
claude-agent-sdkdep removal vs knip-baseline; design the private-repo vitest harness for the relocated guards.
7. {68.9} flip-gate update (the AC this plan adds)
Section titled “7. {68.9} flip-gate update (the AC this plan adds)”Add to {68.9}’s acceptance criteria (currently Dependabot + Liam GO only):
AC-DEID (new): the public HEAD is client-de-identified —
git grep -i 'phew'over the public flip-surface returns only (a) parameterised-mechanism config keys (defaultconfig, slug examples) and (b) intentionally-retained generic placeholders. Specifically:phew.json
public/clients/phew/*untracked (A); the signup-domain migration genericised (B); the classification prompt + eval fixtures scrubbed-or-retired (C); ledgers sanitised + specs relocated-private (D3);kh.phew.org.ukgenericised in public source (F); and the operator move-set (docs-site + operators) absent from public HEAD ({68.12}).
{68.10} extends with the --replace-text redaction map + mandatory pre-purge mirror backup
(E).
Sources
Section titled “Sources”- S301a investigation (this session): spec-corpus agent, live-triage agent, memory agent — three read-only reports synthesised in the S301a handoff.
RELOCATION-IMPLEMENTATION-PLAN.md(S-B routing, Phase 4 gate, §3.2 purge),RELOCATION-INVESTIGATION-V2.md(OQ-R10/R12), ledgerdocs/reference/tasks/ID-68.md({68.11}deferral journal:149, cross-cutting flag:60),RESEARCH.md §11(Phew=client terminology).- MemPalace
sessions/technical2026-06-01: “Pre-Flip De-Identification + Client-Configurability Scoping Report” (owner framing; NOW/LATER split) + “Three Decision-Gating Questions” (flip reversible / purge irreversible; eval-fixture-vs-live-tenant distinction). - Live verification S301a:
lib/client-config.ts:580-607(staticphew.jsonimport + map),scripts/kb_pipeline/config.py:15(classification-prompt.mdPROMPT_PATH),docs/reference/classification-prompt.md:194-758(client bid content),supabase/config.toml:72-78.