S331 4-OQ resolution dossier (ID-95 / {95.2} input) -- 11/06/2026
S331 4-OQ resolution dossier (ID-95 / {95.2} input)
Section titled “S331 4-OQ resolution dossier (ID-95 / {95.2} input)”Produced by a dispatched read-only analyst (sub-orchestrator session b65d396e, 11/06/2026) from RESEARCH.md + id95-supporting-info.md + client-app-deploy.md + DEPLOY-OVERLAY-DESIGN.md + the live repo corpus. Input artefact for {95.2} PRODUCT.md. Verbatim agent output below.
OQ-1 — What makes the current deploy a “LIVE CLIENT DEPLOY” (AC-E3 beyond current state)
Section titled “OQ-1 — What makes the current deploy a “LIVE CLIENT DEPLOY” (AC-E3 beyond current state)”ANSWER: The current deploy renders Phew branding only because the branding files are still committed to the public repo — lib/branding/clients/phew.json and public/clients/phew/{logo.webp,logo-dark.png,logo-dark.svg,favicon.svg,favicon.png} are tracked at HEAD, so the generate:branding prebuild glob picks them up with zero overlay involvement. ID-68 AC-E3 is verbatim: “git ls-files zero rows for lib/branding/clients/phew.json, public/clients/phew/**; the deploy-overlay supplies branding on the client deploy (deployed site still branded)” — i.e. AC-E3 fails today on its first clause, and the second clause (the overlay) is the unbuilt {68.22} half. What’s required beyond current state: (1) an overlay fetch step inserted before generate:branding in the client project’s build:vercel chain that copies phew.json + 5 assets from the private home into place; (2) a rehearsal deploy on a branch where the tracked files are removed, verified still-branded; (3) the git rm + redeploy + re-verify. Concrete AC-E3 verification definition: on the client Vercel project (today knowledge-hub, post-rename knowledge-hub-phew), production deployment at kh.phew.org.uk, with the public repo HEAD showing git ls-files zero rows for both paths, the page renders Phew logo/favicon/brand colour/product title (not default), AND a control build of the public repo with NEXT_PUBLIC_CLIENT_ID=phew but no overlay resolves default and stays green (DEPLOY-OVERLAY-DESIGN §6.1–6.3).
EVIDENCE:
- Branding files tracked at HEAD:
lib/branding/clients/phew.json(full client config incl.supportEmail: support@phew.org.uk, brand colours, tagline) andpublic/clients/phew/(5 assets) — confirmed by directory listing in the worktree. The §1 “done” claim in DEPLOY-OVERLAY-DESIGN refers to branchid68-relocation-phase23; it is not the current HEAD state. - Codegen seam already built:
scripts/generate-client-branding-map.ts:25-39,46-86globslib/branding/clients/*.json->client-branding-map.generated.ts; requiresdefault.json(:49-53); overlay only ever ADDS files. - Wiring:
package.jsonprebuild/predevrungenerate:branding;vercel.json:3buildCommand: "bun run build:vercel";build:vercelchainsgenerate:brandingbeforenext build. - Loader:
lib/client-config.ts:604-630—loadBranding()resolvesNEXT_PUBLIC_CLIENT_ID-> map ->defaultfallback;BRANDINGcomputed once at module init; schema + contrast validation run at build. lib/env-client.ts:59-64—NEXT_PUBLIC_CLIENT_IDREQUIRED (S196 incident guard), build-inlined.- AC-E3 text: ID-68
PRODUCT.md:330-332; overlay acceptance:DEPLOY-OVERLAY-DESIGN.md§6 (3 criteria); hard sequence:client-app-deploy.md:106-112(“untrack only after the overlay is wired and a client deploy has been verified”).
LIAM-FORK: One: where the overlay fetch executes and with what credential — Vercel-native build (needs a read-only private-docs-site token in the client Vercel project env — a NEW credential surface, contradicting DEPLOY-OVERLAY-DESIGN §4’s “no new credential surface”, which only holds for GitHub-Actions-side builds) vs a GitHub Action that overlays then deploys via vercel deploy --prebuilt. Functionally either satisfies AC-E3; the credential placement is an owner security call.
PRODUCT-IMPLICATION:
- {95.2} must pin the overlay execution locus (Vercel build env token vs GH-Actions prebuilt deploy) and name the private overlay home (
client-branding/<id>/in the docs-site repo — which does not exist yet; see OQ-4). - Pin the AC-E3 verification sequence as a gated checklist: preview-deploy rehearsal with files untracked -> verify branded ->
git rmon main -> production redeploy -> re-verify, plus the no-overlay regression guard (§6.1). - Pin that every future client = drop
<id>.json+ assets into the overlay home + setNEXT_PUBLIC_CLIENT_ID— no public-repo commit (this is the config-as-data principle applied to branding).
OQ-2 — Security lens on public supabase/migrations/
Section titled “OQ-2 — Security lens on public supabase/migrations/”ANSWER: The attack-surface lens does NOT change the round-1 “do not split migrations” verdict. Grounded in the actual corpus (123 files): there are zero secrets — every password/token/key grep hit is a comment, a column name, or the pipeline service account seeded with a deliberate non-bcrypt sentinel (“Cannot log in”, 20260416122127:14); client names appear in exactly 3 files (all Phew, zero Charnwood/Telehouse), and all three are already filter-repo purge targets (redaction map R4/R5). The policy-logic-exposure argument fails on Kerckhoffs grounds and on marginal-information grounds: PostgREST exposes schema introspection to any holder of the anon key (which ships in the public app bundle), database.types.ts already publishes every table/column/enum/function signature, and policy behaviour is probeable online via the REST API — so migrations add only the policy/function bodies, which help an attacker only if a policy is wrong. KH’s defences against wrong policies are real controls, not obscurity: 238 CREATE POLICY statements under role-based get_user_role(), 107 SECURITY DEFINER statements progressively converted to invoker across the ops43 batches, universal SET search_path on function definitions (the only 2 grep exceptions are comment-only mentions), PR-blocking REVOKE lint plus weekly prod drift audit, and nightly Supabase security advisors against a committed baseline. The residual client-identifying concern is covered by the purge (history) plus the config-as-data principle (future: no client literal can enter a migration — signup_policy proves it) plus the private per-client provisioning repo seam for genuinely client-specific artefacts.
EVIDENCE:
- Secrets sweep: no live credential in any of 123 files. Only flag-adjacent item is
supabase/migrations/20260416122127_seed_pipeline_service_account.sql:29(encrypted_password= documented sentinel, “:14 — Cannot log in (sentinel password, not a valid bcrypt hash)”; token columns''). - Client names:
20260424202806_capture_phew_domain_hook.sql:1,16,28,34,44,47(client-named hook fn +phew.org.ukliteral — the highest-signal leak, already superseded),20260419212103_...sql:48(‘Phew Design Limited’ seeded row),20260609093000_id68_21_drop_superseded_signup_hook.sql:21(DROP by name). Purge map covers them:id68-purge-redaction-map-draft.md:41(R5capture_phew_domain_hook==>capture_signup_domain_hook),:61(R4 legal name incl. the20260419212103:48blob),:43(R6 telehouse — not present in migrations). - Genericised present state:
20260603121652_hook_restrict_signup_to_allowed_domain.sql— no domain literal, fail-closed;20260609160000_config_table_signup_domain_policy.sql—signup_policytable, RLS deny-all,supabase_auth_admin-only SELECT; per-client value inserted out-of-band (client-app-deploy.md:70-78). - Policy/SECDEF surface: 238
CREATE POLICYstatements; 107SECURITY DEFINERstatements across 26 files (large share are the ops43 secdef->invoker remediation batches, e.g.20260502195036,20260506115705,20260506091039,20260502232856— they document fixed states, not live weaknesses). - Enforcement already in CI:
.github/workflows/migration-revoke-guard.yml— PR-blocking lint onsupabase/migrations/**diffs (missing REVOKEs block merge) + Monday 06:00 UTC prod cron audit;.github/workflows/supabase-advisors.yml— nightly 02:00 UTC + on-push security/performance advisor diff vs committed baseline. - RESEARCH §4.2-4.4 still holds: types-gen reads the live DB (
ci.ymlsupabase-types-parity); the only PR-blocking job needing migration FILES is the revoke-guard, so a split forces it private or breaches Inv 30.
LIAM-FORK: NONE on the split verdict. One adjacent ID-68 ratification remains (not new): the Branch A-vs-B treatment of migration blobs inside the filter-repo purge (id68-purge-redaction-map-draft.md:96-98, OQ-G(b)) — i.e. whether the 3 phew-bearing migration files are rewritten in history or path-preserved with token redaction. That is Liam’s call because rewriting applied-migration blobs changes file hashes operators may diff against live schema_migrations.
PRODUCT-IMPLICATION:
- {95.2} pins: migrations stay public; the invariant is “no client literal, no secret, no environment-specific value in any migration” (config-as-data), with the revoke-guard + advisors named as the standing enforcement and the purge as the history backstop.
- Pin the private per-client provisioning repo (RESEARCH §4.5) as the designated home for anything that would fail that invariant (per-client seeds, domains, Coolify compose, secrets) — this is what closes the attack-surface residual, not migration privacy.
- Optionally add a CI denylist lint (client-name tokens in
supabase/migrations/**) so a future regression is PR-blocked rather than purge-discovered.
OQ-3 — Vercel project topology per client
Section titled “OQ-3 — Vercel project topology per client”ANSWER: The model is CONFIRMED: rename knowledge-hub -> knowledge-hub-phew; each future client is a knowledge-hub-{client} Vercel project connected to the same public repo with per-project build-time env (NEXT_PUBLIC_CLIENT_ID, Supabase URL/keys, APP_URL) plus the branding overlay; plus a platform dev/demo project at kh.aisolutionhub.co.uk. Vercel natively supports N projects on one repo, and nothing in-repo references a Vercel project ID. However, the rename has a real blast radius: the auto-generated production domain knowledge-hub-seven-kappa.vercel.app (derived from the current project name) is hardcoded in 17+ places, including runtime code and CI, and will stop resolving after rename — these must be swept to APP_URL/custom-domain-derived values FIRST. The custom domain kh.phew.org.uk, project ID, env vars, and git integration all survive a rename untouched.
EVIDENCE: (in-repo couplings to the project name, via its derived domain)
.github/workflows/taxonomy-sync.yml:98,119— taxonomy-sync callback POSTs tohttps://knowledge-hub-seven-kappa.vercel.app/api/admin/taxonomy-sync/callback— functional CI breakage on rename.components/intelligence/rss-feed-panel.tsx:118— runtime fallback base URL;lib/intelligence/rss-generator.ts:37—KH_NAMESPACERSS namespace URI;lib/intelligence/content-extractor.ts:9+lib/intelligence/url-validation.ts:17— User-Agent strings;scripts/seed-procurement-test-data.ts:488— console output..claude/plugins/knowledge-hub/1.0.0/— 10 files givehttps://knowledge-hub-seven-kappa.vercel.app/api/mcp/mcpas the MCP server URL -> every configured Claude Desktop/claude.ai connector breaks on rename;lib/mcp/plugin-bundle.tsis generated from these (regenerate viabun run build:pluginafter the sweep)..github/workflows/ci.yml:1468-1472— deploy-gate dispatch named"Vercel - knowledge-hub: ci-summary"; the Vercel-side required-check config references this string — verify/update both sides at rename.vercel.jsonis project-name-agnostic (no ids) but repo-global:regions: ["lhr1"]and the fullcronsblock apply to EVERY project built from this repo — each client project + the platform project will each run all 8 crons against their own env (correct per-client behaviour, but multiplies cron-driven AI spend and requires per-projectCRON_SECRET+ Supabase env).APP_URLis consumed only asNEXT_PUBLIC_APP_URL(per-project env):lib/env-client.ts:47-49,lib/mcp/resource-url.ts,app/.well-known/oauth-protected-resource/route.ts,app/api/feeds/[workspaceId]/rss/*— all per-project-safe.- Exact step list: (1) sweep the seven-kappa literals ->
kh.phew.org.ukor env-derived, regenerate plugin bundle; (2) rename project in Vercel dashboard (ID/env/domains/git persist); (3) verify the deploy-gate check name on both sides; (4) createknowledge-hub-platform(or reuse the freed bare name) on the same repo:NEXT_PUBLIC_CLIENT_ID=default, own Supabase project keys (the platform dev/demo Supabase from RESEARCH §5.1 — does not exist yet),APP_URL=https://kh.aisolutionhub.co.uk, add domain + DNS; (5) re-point MCP connectors and taxonomy-sync callback target.
LIAM-FORK: Two small ones: (a) whether the platform project takes the freed bare name knowledge-hub or a -platform suffix (cosmetic, but fixes the *.vercel.app namespace permanently); (b) approving the new managed Supabase project for the platform dev/demo instance (cost: ~Micro compute on the org) and the kh.aisolutionhub.co.uk DNS record — both already leant-yes in his OQ-95-6 note, only the action is his.
PRODUCT-IMPLICATION:
- {95.2} pins the topology invariant: one public repo -> N Vercel projects; a Vercel project is the unit of client app isolation, configured exclusively via per-project env + overlay (no repo branches per client).
- Pin “no deploy-target literal in repo code” as an invariant (the seven-kappa sweep is its enforcement debt) — all self-URLs derive from
NEXT_PUBLIC_APP_URL. - Pin that
taxonomy-sync(and any future callback-style workflow) must take its target app URL from per-environment config, because “the prod app” stops being singular under this topology.
OQ-4 — Client-specific content in the private docs-site repo
Section titled “OQ-4 — Client-specific content in the private docs-site repo”ANSWER: Inventory result inverts the premise: there is currently NO client-specific artefact in the docs-site repo at all — no client-branding/ dir (the runbook’s “proposed home” is unbuilt), no Phew-named files or assets; what exists is only textual “Phew” mentions inside platform-operational runbooks/specs/session archives (16 runbooks, ~20 specs), which are platform IP about serving a client, not client content. The actual client artefacts live in the public repo today (phew.json + 5 assets — the OQ-1 untrack targets). So the answer to Liam’s Point-4 is: it does not need per-client private repos — the historic shape is an accident, and the requirement is private-vs-public separation, not client-vs-client separation, for as long as KH operates every deployment. Recommend: interim home = a root-level client-branding/<id>/ dir in the docs-site repo (beside ops/, NOT under src/content/docs/ so it never enters the published docs build), holding <id>.json + assets + the per-client ops crib (signup domain, Supabase ref, Vercel project, domain). The seam flips to a dedicated private per-client provisioning repo exactly when a client becomes a party to the artefacts: at Tier-3 BYOC stand-up (the Coolify GitOps delivery channel structurally requires a per-client repo — RESEARCH §8.2/§10.3), or earlier if a second client lands with contractual audit/access rights over their config, or when per-client artefacts grow beyond branding (seed data, corpus manifests, compose files). demo-bootstrap-spec.md (31/03/2026) is confirmed stale — it predates the config-table signup design, the overlay seam, and ID-95 topology; archive it to knowledge-hub-archive and let {95.2} + client-app-deploy.md supersede it.
EVIDENCE:
findover docs-sitesrc/+ops/for*client*/*phew*dirs: onlyspecs/id-69-client-corpus-onboardingandspecs/id-95-per-client-topology(platform specs); zero phew-named files repo-wide; noclient-branding/at root (root holdsops/,src/,harness/,e2e/, etc.).- Proposed home named but unbuilt:
client-app-deploy.md:95-97(“proposed home:client-branding/<id>/, beside the other root-homed private artefacts”) and:140(new-client checklist step 2). - The real client artefacts sit in the public repo:
lib/branding/clients/phew.json(support email, tagline, brand colours),public/clients/phew/*(5 assets). - RESEARCH §4.5 defers the private repo “created only when real per-client artefacts exist”; §8.2 makes a private per-client repo the BYOC delivery channel (one shared scoped GitHub App serving N client repos); §9.4 routes the canonical-content payload through that same repo at Tier 3.
- Staleness of
demo-bootstrap-spec.md: status Draft, created 31/03/2026, referencesdocs/operations/production-setup-guide.md(path relocated) and pre-datessignup_policy(09/06/2026) — matches Liam’s supporting-info note (id95-supporting-info.md:15).
LIAM-FORK: NONE on the mechanism. One scheduling preference: whether to archive demo-bootstrap-spec.md now or mine its demo-seed-data phases into {95.2} first (its demo-instance use case is the platform dev/demo project from OQ-3, which {95.2} will respecify anyway).
PRODUCT-IMPLICATION:
- {95.2} pins the interim per-client artefact home: docs-site root
client-branding/<id>/(outside the published content tree), and defines its minimum contents per client (branding JSON + assets + ops crib). - Pin the seam-flip trigger explicitly: “per-client private provisioning repo is created at Tier-3 BYOC stand-up for that client, or when a client contract grants the client access/audit over their config — not before.”
- Pin supersession of
demo-bootstrap-spec.md(archive + pointer), so the stale GUC-era onboarding flow can’t be followed by mistake.
Residual risks
Section titled “Residual risks”knowledge-hub-seven-kappa.vercel.apphardcoding (17+ refs) in runtime code, CI, and the distributed MCP plugin — the rename in OQ-95-6 breaks them all; neither RESEARCH nor the runbook mentions this. The plugin case is worst: external users’ configured connectors break silently. Sweep before rename; regeneratelib/mcp/plugin-bundle.ts.- Vercel deploy-gate check-name coupling (
ci.yml:1472“Vercel - knowledge-hub: ci-summary”) — half the config lives in the Vercel dashboard; a rename can silently un-gate prod deploys. - taxonomy-sync callback assumes a singular prod app (
taxonomy-sync.yml:98,119) — under N client projects, taxonomy sync needs a per-target design (it writes to one DB via one app); currently undesigned. - Overlay credential surface on Vercel builds — DEPLOY-OVERLAY-DESIGN §4’s “no new credential surface” claim only holds if the overlay runs in GitHub Actions; a Vercel-native build step needs a private-repo read token in each client project’s env. Undecided (OQ-1 fork) and uncovered by both documents.
- Per-project cron multiplication —
vercel.jsoncrons run in every project off this repo (client + platform): per-projectCRON_SECRET/env hygiene required, and AI-calling crons (classification-quality, intelligence-poll) multiply spend per client. Alsoregions: ["lhr1"]is repo-global — a non-UK-residency client would need a repo-level change. - Migration-blob purge treatment unratified (ID-68 OQ-G(b), Branch A/B) — until ratified, the 3 phew-bearing migrations persist in history; rewriting them changes blob hashes operators may compare against applied
schema_migrationsstate on live projects. - Live-DB residue is out of every doc’s scope: the prod DB still carries the seeded ‘Phew Design Limited’ guide row (
20260419212103:48) and the historical record of the dropped phew-named hook — purge cleans git, not databases; the per-client provisioning runbook should own DB-side de-identification for the platform/demo instance. docs/referencetask files mention the client (e.g.docs/reference/tasks/ID-68.mdper GitNexus) — the public-repo ledgers/continuation-prompts tree carries client names outsidesupabase/migrations/; confirm these are inside the ID-68 purge/relocation perimeter, since OQ-2’s clean verdict is migrations-only.