PLAN -- Per-client topology + PI-18 propagation worker (ID-95.4)
PLAN — Per-client deployment/config + canonical-content propagation (ID-95.4)
Section titled “PLAN — Per-client deployment/config + canonical-content propagation (ID-95.4)”Task: ID-95 — Per-client Supabase/infra topology + platform-vs-client ownership
boundary.
Subtask: {95.4} PLAN — decomposition of the ratified {95.2} PRODUCT + {95.3}
TECH into sibling-only implementation Subtasks {95.5+}.
Author date: 12/06/2026.
Status: SPEC — decomposition. Fresh-Planner artefact (Q-PLANNER-2): this PLAN was
authored by a Planner instance that did NOT write PRODUCT or TECH; both were read in full
as the contract. UK English throughout (DD/MM/YYYY, “colour”, “organisation”,
“behaviour”). Cross-references by path; this document does NOT re-dump PRODUCT/TECH/RESEARCH
— it cites them.
This PLAN does two things the brief mandates: (1) it carries the branding/config remit the TECH fully specified (T-A/T-B/T-C/T-D/T-E) straight into implementable Subtasks, and (2) it DESIGNS, at TECH altitude, the PI-18 one-way platform->client propagation worker that PRODUCT pinned as behaviour and TECH explicitly scope-noted as “a substantial mechanism worth its own spec slice” — then decomposes it. The propagation worker is the centrepiece.
Code-intelligence orientation (cited, not paraphrased)
Section titled “Code-intelligence orientation (cited, not paraphrased)”Index knowledge-hub. Run this session for the propagation domain.
gitnexus_query({query: 'canonical content propagation taxonomy fan-out client database upsert'})-> top processproc_249_put“PUT -> Error” (priority 0.051), a single unrelated content-upload handler (handleQABatchDismiss,components/create-content/upload-tab-content.tsx:515-520). No propagation / fan-out / migration-orchestration execution flow exists in the corpus. Interpretation: the PI-18 worker is a GREENFIELD orchestration surface — there is no existing fan-out loop or catalog to extend; it is built fresh over the known config-as-data seam. The returned definitions are all cocoindex extraction tests + taxonomy-validation tests (scripts/cocoindex_pipeline/extraction.py,scripts/tests/test_cocoindex_extraction.py) — i.e. the taxonomy CONSUMERS, not a propagation producer.gitnexus_context({name: 'loadBranding', file_path: 'lib/client-config.ts'})-> incoming = {} (no callers indexed beyond the module-init at:630); outgoing.calls =validateBrandingContrast,ClientLogger.warn;processes: [](leaf). Verdict LOW. Confirms PRODUCT/TECH: the T-B build-time fetch can add<id>.jsonto the glob input with ZERO blast radius — it touches no symbol the loader imports.
Grounding reads this session (read-only, cited for the Checker):
supabase/migrations/20260609160000_config_table_signup_domain_policy.sql(read in full) — the config-as-data template T-A mirrors: single-row singleton CHECK,ENABLE ROW LEVEL SECURITY,REVOKE ALL ... FROM anon, authenticated, public, out-of-band INSERT documented in the header, NO client literal.package.json:13prebuild = generate:skills && generate:content-type-values && generate:branding;:58build:vercel = ... && generate:branding && build:mcp-apps && next build;:34sync:taxonomy = generate-classification-prompt-taxonomy.ts && generate-taxonomy-snapshot.ts && sync-plugin-taxonomy.ts && build:plugin. The T-B fetch inserts BEFOREgenerate:brandingin both build chains, never inpredev.lib/supabase/server.ts:44-61— the service-role construction reads URL fromclientEnv.NEXT_PUBLIC_SUPABASE_URLand key fromserverEnv.SUPABASE_SERVICE_ROLE_KEY, BOTH Zod-validated at boot. This confirms T-B’s hard rule: the fetch script must NOT reuse this construction (it would drag in theenv-serverZod parse that hard-requires the service-role key and breaks the CASE 1 control build); it readsprocess.envdirectly.components/settings/mcp-url.ts:11-17—getMcpUrl()derives the endpoint fromclientEnv.NEXT_PUBLIC_APP_URLwith awindow.location.originfallback; the PI-13 derivation precedent.- Table inventory via
mcp__supabase__list_tables(turayklvaunphgbgscat)— the client-class staging DB. The platform-curated canonical tables that are the PI-18 payload candidates:taxonomy_domains(15),taxonomy_subtopics(57),layer_vocabulary(4),application_types(6),form_types(8),form_template_requirements(96 — the catalogue requirements),reference_items(2 — global reference layer). These are platform-curated baseline, distinct from client-provenance rows (content_items, guides, entity_mentions, etc.) which the worker NEVER touches. seven-kappagrep (worktree): 7 sites — the 6 the TECH enumerates PLUS one the TECH missed,scripts/seed-procurement-test-data.ts:488. Folded into the PI-13 sweep Subtask.
The load-bearing grounding fact — the platform DB is EMPTY
Section titled “The load-bearing grounding fact — the platform DB is EMPTY”Verified this session (do not re-derive): the platform DB zjqbrdctesqvouboziae
(knowledge-hub-platform, org ptacqvoidhqznzdnzpbg, eu-west-2) public schema is EMPTY —
zero tables. “Initial config complete” meant Vercel project + domain + DSN, NOT DB
schema. The canonical SOURCE the PI-18 worker reads FROM does not yet exist and must be
stood up. This is the unresolved core of OQ-95-3 (“what IS the platform canonical
source”). It is surfaced here as an explicit decision and a [GATED] Subtask, because:
- Every DDL/schema change against
zjqbrdctesqvouboziaeis a CARE point. It is brand-new infra Liam just provisioned. Any platform-DB DDL requires parent GO via the OQ channel (oq_emit) BEFORE execution; DDL via CLI only, never MCP. Subtasks that prescribe platform-DB DDL are prefixed[GATED: platform-DB GO]. - The branding/config remit (T-A/T-B/T-C) does NOT touch the platform DB — those are client-side migrations + pure-TS scripts, buildable with NO platform GO. They are the unblocked critical path to the first-client cutover.
- The PI-18 worker’s client-side half (version table, fan-out target writes) is client-side and buildable; only the SOURCE bootstrap + the catalog-if-platform-resident are gated.
Dependency graph (bottom-up)
Section titled “Dependency graph (bottom-up)” {95.5} tenant_config table (client-side migration, T-A) | +---------------+----------------+ | | {95.6} fetch-client-branding {95.7} reseed-tenant-instance (T-B prebuild, PI-11) (T-C manifest, PI-20; creates bucket) | | +---------------+----------------+ | {95.8} AC-E3 gated verification (PI-11/PI-2)
-- PI-13/PI-14 lane (independent of the branding lane; ordering gate INTERNAL) -- {95.9} no-deploy-target-literal sweep (PI-13) -> {95.10} rename ordering-gate record (PI-14)
-- PI-18 propagation worker (centrepiece; designed at TECH altitude in this PLAN) -- {95.11} payload contract + version table migration (client-side, PI-18) | +--> {95.12} [GATED] platform canonical-source + catalog bootstrap (platform-DB DDL) | +--> {95.13} fan-out worker script (Tier-1 DB-direct, PI-18/PI-19) | (consumes both {95.11} version table and {95.12} source) v {95.14} OQ-6 durable-master restore-path wiring (T-E; rides {95.12}+{95.7})
-- docs lane (docs-only) -- {95.15} client-app-deploy.md runbook update (docs-site; PI-12/PI-20/OQ-95-7)Architecture decisions (PLAN-level, within the ratified topology)
Section titled “Architecture decisions (PLAN-level, within the ratified topology)”D-1: T-A/T-B/T-C are the unblocked critical path; PI-18 is the gated centrepiece
Section titled “D-1: T-A/T-B/T-C are the unblocked critical path; PI-18 is the gated centrepiece”The branding/config remit the TECH fully specified is buildable today with no platform GO. It ships first as the first-client cutover enabler. The PI-18 worker is designed here but its SOURCE bootstrap is GATED — so the worker decomposes into a buildable client-side half ({95.11}, {95.13} against staging/client targets) and a gated platform-DB half ({95.12}).
D-2: PI-18 worker mechanism (TECH-altitude design — the part TECH deferred)
Section titled “D-2: PI-18 worker mechanism (TECH-altitude design — the part TECH deferred)”RESEARCH §9.1/§9.2/§9.4 is the reference; this PLAN pins the mechanism so it can be decomposed. Tier-1 only (Tier-3 GitOps transport is a named, NON-decomposed follow-up). Four components:
- Catalog (client -> DB connection). Maps each client to its Supabase project-ref +
service-role connection. Decision: config-as-DATA, NOT a platform-DB table for the
Tier-1 build. The catalog is a gitignored, out-of-band operator input
(
scripts/.propagation-catalog.jsonor env-driven--targetslist of{ref, db_url}), exactly mirroring thesignup_policy/ re-seed out-of-band pattern. Rationale: (i) a platform-DB catalog table would be platform-DB DDL = an extra GATED dependency on the worker’s critical path, and (ii) a connection catalog holding service-role DSNs must never be committed anyway (config-as-data, PI-7). If/when the platform control-plane DB is bootstrapped ({95.12}), the catalog MAY graduate to a control-plane table — noted as a {95.12} extension, not a Tier-1 requirement. This keeps the fan-out worker ({95.13}) buildable without platform GO. - Per-client version table —
public.content_propagation_version(atenant_migrations-style ledger:payload_key text,version bigint,applied_at timestamptz,payload_checksum text). A CLIENT-SIDE public migration (genericised, no client literal), fanned to every client DB + staging exactly liketenant_config. NOT platform-DB DDL -> NOT gated. ({95.11}.) - Fan-out worker (
scripts/propagate-canonical-content.ts) — iterates the catalog; for each target reads the canonical payload from the SOURCE, applies upsert-by-stable-key + tombstone deletes (RESEARCH §9.2 Pattern 2), records the version incontent_propagation_version. One-way only: the worker NEVER reads client rows back into the source (PI-18). Nopostgres_fdw, no logical replication — a plain service-role upsert over a per-target connection creates no standing cross-DB dependency (PI-19). Each target connection applies thecat supabase/.temp/project-refrelink-verify discipline; the catalog carries the explicit ref so the target is never link-state-derived. ({95.13}.) - Payload definition — the platform-curated canonical table set:
taxonomy_domains,taxonomy_subtopics,layer_vocabulary,application_types,form_types,form_template_requirements(catalogue requirements),reference_items(global reference layer). Decision: these seven are the v1 payload, each upserted by a stable natural key (slug / canonical_name / normalised URL), with tombstone deletes keyed off a source-side “active” set. Client-provenance tables (content_items, guides, entity_mentions, q_a_pairs, form_responses, …) are EXPLICITLY EXCLUDED — the worker pushes baseline, never client data. The exact per-table stable-key + tombstone mapping is pinned in {95.11}‘s payload-contract artefact so {95.13} implements against a written contract, not a guess.
D-3: the platform canonical-source bootstrap is the gated core of OQ-95-3
Section titled “D-3: the platform canonical-source bootstrap is the gated core of OQ-95-3”Because the platform DB is empty, the SOURCE the worker reads from must be stood up:
the seven payload tables (+ the control-plane catalog if it graduates per D-2) created on
zjqbrdctesqvouboziae, then seeded with the platform-curated baseline. This is
platform-DB DDL -> [GATED: platform-DB GO]. It is also the OQ-6 (T-E) durable-master home:
once the source exists, the re-seed manifest ({95.7}) and the restore path ({95.14}) read
from it. Until it lands, T-E’s interim operator-archive stopgap stands (TECH T-E option 1).
D-4: OQ-6 (T-E) durable master rides the PI-18 source
Section titled “D-4: OQ-6 (T-E) durable master rides the PI-18 source”Per TECH T-E: the platform control-plane DB is the system-of-record for the durable
branding master, pushed to client tenant_config + bucket via the same one-way channel.
The restore path (control-plane master -> re-seed manifest -> fresh client tenant_config +
bucket -> build fetch -> branded deploy) is wired in {95.14}, which depends on both the
source bootstrap ({95.12}) and the re-seed manifest ({95.7}). Until {95.12} lands, {95.14}
documents the interim operator-archive stopgap and is NOT a blocker on the branding cutover.
D-5: Tier-3 BYOC GitOps transport is named, NOT decomposed
Section titled “D-5: Tier-3 BYOC GitOps transport is named, NOT decomposed”Per owner ratification (feedback + PI-6a): Tier-3 BYOC is OUT-OF-SCOPE-FOR-BUILD. The PI-18 design above is Tier-1 only (DB-direct service-role push). The Tier-3 GitOps transport (versioned payload via a private per-client repo reconciled by Coolify, RESEARCH §9.4 / §10) is named as a deferred follow-up and is NOT decomposed into buildable Subtasks. The Tier-1 design satisfies the PI-19 forward-compatibility requirement: one-way upsert with no fdw / no logical replication means the same payload contract serves the Tier-3 era unchanged — only the transport swaps.
D-6: Coolify framing correction (owner)
Section titled “D-6: Coolify framing correction (owner)”The existing deploy/coolify/** host was built FOR the first client (Phew) and re-points to
the client’s own domain at handover (the aisolutionhub.co.uk domain was interim DNS
scaffolding). The platform needs its OWN Coolify deployment (Tier-3 framing, build-timing
out of scope). No Subtask asserts “Phew has no Coolify”. No Coolify build Subtask is created
(out of scope per PI-6a).
Subtask catalogue (narrative; the TM-shape JSON is the RETURN payload)
Section titled “Subtask catalogue (narrative; the TM-shape JSON is the RETURN payload)”| ID | Title | Maps to | Gated? | Parallel group |
|---|---|---|---|---|
| {95.5} | tenant_config table + RLS deny-all + branding bucket note | T-A / PI-10,7,9 | no | A (schema) |
| {95.6} | scripts/fetch-client-branding.ts prebuild fetch | T-B / PI-11,2 | no | B (TS, disjoint) |
| {95.7} | scripts/reseed-tenant-instance.ts manifest + bucket create | T-C / PI-20 | no | B (TS, disjoint) |
| {95.8} | AC-E3 gated verification (rehearsal/rm/redeploy/control/fail-closed) | PI-11,2 / AC-E3 | no | C (verify) |
| {95.9} | no-deploy-target-literal sweep (7 sites) | PI-13 | no | D (disjoint files) |
| {95.10} | rename ordering-gate record (sweep+deploy-gate before rename) | PI-14 | no | D (record) |
| {95.11} | PI-18 payload contract + content_propagation_version migration | PI-18 / RESEARCH §9 | no | A (schema) |
| {95.12} | [GATED] platform canonical-source + catalog bootstrap | OQ-95-3 / T-E | YES | E (platform DDL) |
| {95.13} | PI-18 fan-out worker (Tier-1 DB-direct, one-way) | PI-18,19 / RESEARCH §9.4 | no | F (worker) |
| {95.14} | OQ-6 durable-master restore-path wiring | T-E / OQ-6 | partial* | F (worker) |
| {95.15} | client-app-deploy.md runbook update (docs-only) | PI-12,20 / OQ-95-7 | no | G (docs) |
| {95.16} | Archive demo-bootstrap-spec.md + leave pointer (docs-only) | PI-17 | no | G (docs) |
* {95.14} depends on the gated {95.12}; its interim-stopgap half is buildable, its control-plane restore half waits on {95.12}‘s GO.
Checker resolution (S344, checker-95-4 PASS_WITH_NOTES, 6 in-scope notes, 0 blockers). Applied before the Orchestrator returned
add-subtasksintents: (N1) PI-17 added as{95.16}(docs-only: archivedemo-bootstrap-spec.md, leave a superseding pointer) — PRODUCT PI-17 said “archive NOW” and had no tracing. (N2) PI-9 is NOT a net-new ID-95 CI workflow: the lint mechanism already exists as ID-68identity-guard.yml(full-treegit grep -FofKH_CLIENT_NAME_DENYLIST, PR-blocking onmain, coverssupabase/migrations/**because it scans the whole tracked tree). A client-name token in a new migration already fails CI. PI-9’s residual (client-DOMAIN-literal coverage + the R8 bare-stem rule) is routed to ID-68 below;{95.5}’s migration greps clean. Parent to confirm “PI-9 satisfied-by-ID-68” (see OQ). (N3){95.13}dep corrected[95.11,95.12]->[95.11];{95.12}is a LIVE-RUN gate (worker code + unit tests build now against a mock source), not a hard dispatch dep. (N4){95.14}dep corrected[95.7,95.12]->[95.7]; the interim operator-archive half is buildable now, the control-plane restore half is a{95.12}LIVE-RUN gate noted indetails. (N5) phantom{95.20}prose reference (was line ~292) corrected to{95.7}. (N6) per-subtask Executor code-intel blocks ARE present in the RETURNdetails(the Checker reviewed the summary list, not the JSON) — confirmed, no change.
Parallel-safe vs serialised
Section titled “Parallel-safe vs serialised”- Parallel-safe (disjoint file sets): {95.6} (
scripts/fetch-client-branding.ts+package.jsonchain), {95.7} (scripts/reseed-tenant-instance.ts), {95.9} (lib/intelligence/**+components/intelligence/**+scripts/seed-procurement-test-data.ts.github/workflows/taxonomy-sync.yml) can run concurrently after {95.5}/{95.11} land.
- Serialised (shared schema ordering): {95.5} and {95.11} are both
supabase/migrations/**additions — author them as two separate migration files (distinct timestamps) so they do not collide; land {95.5} first (branding critical path), {95.11} second. Both must fan out to staging + client + platform-equivalent. - {95.13} consumes {95.11} (version table) AND {95.12} (source) — serialise after both; but {95.13} can be built and unit-tested against a MOCK source + staging target before {95.12}‘s platform GO (the gated dependency is the LIVE source, not the worker code).
- {95.8} AC-E3 serialises after {95.6} + {95.7} (it exercises the fetch against a seeded
tenant_config+ bucket). - {95.10} records the ordering gate; it serialises after {95.9} and BEFORE any Vercel rename (the rename itself is an operator step, not a code Subtask).
Platform-DB-DDL gating summary
Section titled “Platform-DB-DDL gating summary”Exactly one Subtask is [GATED: platform-DB GO]: {95.12} (platform canonical-source +
catalog bootstrap on zjqbrdctesqvouboziae). Its details carry the standing block:
“BLOCKED pending parent GO (oq_emit) — platform-DB DDL on zjqbrdctesqvouboziae; DDL via CLI
only, never MCP.” {95.14}‘s control-plane restore half inherits the same gate transitively
(it reads from the source {95.12} stands up) but its interim operator-archive stopgap half
is buildable now. All other Subtasks are client-side migrations (tenant_config, version
table -> staging/client) or pure-TS / docs -> NOT gated.
Sibling-only dependency check (forcing function — §3.3 / A6)
Section titled “Sibling-only dependency check (forcing function — §3.3 / A6)”Every dependency in the RETURN JSON is another {95.x} sibling. No cross-Task Subtask
dependency is encoded. Where the spec genuinely depends on another Task, it is surfaced
here for the Orchestrator (NOT encoded as a dep):
- ID-71 ai-tooling owns: the MCP-plugin
seven-kapparesidue (plugin-bundle.ts+.claude/plugins/knowledge-hub/1.0.0/**, PI-13/PI-14 carve-out) AND the taxonomy point-of-use generator refactor (PI-15 seam: target-DB-parameterised generators). {95.9} EXCLUDES the plugin tree by construction (it is ID-71’s), so no cross-Task dep is needed — but the Orchestrator must confirm ID-71 carries the plugin-residue + taxonomy-generator Subtasks. No sibling-violation; flagged for routing. - ID-68 owns: the git-history purge backstop (the three Phew-bearing migration files),
the ledger/continuation-prompt perimeter (PI-23), AND the
main_session_output.mdde-ID/untrack finding (owner routed it to ID-68’s perimeter — it is a docs-site transcript-tooling fix, not ID-95). {95.5} and {95.11} produce NEW clean migrations; they do not depend on the purge. No sibling-violation; flagged for routing. - ID-45 re-ingest cutover produces the first-client handover DB; {95.7} re-seeds AFTER a recut but does not gate on the cutover mechanics. No dep encoded.
No Task-split or Task-merge escalation is required: the ID-95 boundary holds. Every implementation slice above lives inside ID-95’s sibling space.
Cross-Task routing appendix (for the Orchestrator — per owner directive)
Section titled “Cross-Task routing appendix (for the Orchestrator — per owner directive)”The owner directed that the S-current findings are NOT backlog — they move to the correct task-id as NEW Subtasks. Per the sibling-only constraint these do NOT become ID-95 Subtasks; the Orchestrator routes them to their owning Tasks:
| Finding | Owning Task | Note |
|---|---|---|
main_session_output.md de-ID + untrack (update the global-Claude tools/ generator to emit to a transient .session-transcript/ dir in docs-site, untracked) | ID-68 | Owner: easy fix, sub-agent-resolvable; transcript tooling, not load-bearing. NOT an ID-95 Subtask. |
R8 bare-stem rule + guard-list drift (ID-95.md, s328-s332 prompts, cmux-brief-s267 not in PENDING_DEID; migration 20260609093000 hook_restrict_signup_to_phew_domain literal) | ID-68 | Inside PI-23 / ID-68 purge perimeter. |
harness eval imports broken (harness/scripts/eval-classification.ts:383 imports non-existent ../lib/ai/classify) | ID-68 (docs-site harness) or its owning eval Task | docs-site harness lane; not ID-95. |
| taxonomy-sync automation-shell retirement + point-of-use generator refactor (PI-15 seam) | ID-71 | TECH SECONDARY names the seam: target-DB-parameterised generators, no new public-repo commit. |
| docs-site root vitest unwired in its CI | docs-site repo | docs-site CI config; not ID-95. |
MCP-plugin seven-kappa residue (plugin-bundle.ts + plugin tree) | ID-71 | PI-13/PI-14 carve-out; regenerate via bun run build:plugin when ID-71 lands its derivation. |
PI-9 CI denylist-lint residual: client-DOMAIN-literal coverage in supabase/migrations/** (client-NAME tokens already PR-blocked by ID-68 identity-guard.yml full-tree grep) | ID-68 | PI-9’s lint mechanism already exists (identity-guard, full tracked-tree scan incl. migrations). Residual = add client-domain literals to ops/identity-denylist.json + resolve the R8 bare-stem rule (already routed above). No net-new ID-95 CI workflow; {95.5} greps clean. Parent confirms “PI-9 satisfied-by-ID-68”. |
The PI-18 propagation-mechanics scheduling OQ (TECH “Open questions for the Orchestrator”): this PLAN decomposes the Tier-1 PI-18 worker INTO ID-95 Subtasks ({95.11}-{95.14}) rather than deferring to a new Task — because OQ-6 (the durable master) and the first recut both need it, and it fits ID-95’s sibling space. The Tier-3 GitOps transport remains a named, NON-decomposed follow-up (D-5). The Orchestrator should confirm this absorb-into-ID-95 decision (vs spinning a dedicated propagation Task) at wave planning.
Risks and mitigations
Section titled “Risks and mitigations”| Risk | Impact | Mitigation |
|---|---|---|
| Platform-DB DDL run before parent GO | High (mutates brand-new infra) | {95.12} [GATED]; standing block in details; DDL via CLI only, never MCP. |
.temp/project-ref drift on fan-out / worker writes to WRONG project | High (silent wrong-DB write) | Catalog carries explicit ref; every step cat supabase/.temp/project-ref + relink before write (CLAUDE.md gotcha). |
| Silent fetch failure ships default-branded client deploy (S196 class) | High | {95.6} CASE 2 fail-closed process.exit(1); {95.8} negative test is the single most important behavioural check. |
Fetch script imports lib/env-server.ts Zod parse and breaks CASE 1 | Medium | {95.6} reads process.env DIRECTLY; explicit must-not-import note (TECH T-B). |
| Worker reads client rows back into source (PI-18 violation) | High (isolation breach) | {95.13} one-way-only; payload contract excludes client-provenance tables; no fdw / no logical replication (PI-19). |
| Rename before sweep breaks deploy-gate auto-domain | High (prod gate) | {95.10} records sweep + deploy-gate verification BEFORE any rename. |
| Two migration files collide on timestamp | Low | {95.5} and {95.11} authored as distinct-timestamp files; {95.5} lands first. |
Open questions for the Orchestrator
Section titled “Open questions for the Orchestrator”- Absorb-vs-new-Task (PI-18): this PLAN absorbs the Tier-1 PI-18 worker into ID-95 Subtasks rather than spinning a dedicated propagation Task. Confirm at wave planning.
- {95.12} GO timing: the platform canonical-source bootstrap is gated. The branding
cutover (T-A/T-B/T-C) does NOT wait on it (interim operator-archive stopgap covers OQ-6).
But {95.12} must be GO’d before the first recut-to-new-project for the durable master to
exist. The Orchestrator sequences the
oq_emitGO relative to the first recut. - Catalog graduation (D-2): the Tier-1 catalog is config-as-data (gitignored). Whether it graduates to a control-plane table is a {95.12} extension; confirm whether to build the table form in {95.12} or keep config-as-data for v1.
- PI-9 satisfied-by-ID-68 (checker N2): confirm PI-9 needs no net-new ID-95 CI workflow —
ID-68
identity-guard.ymlalready PR-blocks client-name tokens across the full tracked tree (incl.supabase/migrations/**); PI-9’s residual (client-domain-literal coverage + R8 bare-stem) is routed to ID-68. If the parent wants a migrations-scoped standalone lint instead, that is a new ID-95 Subtask to add.