Tenant backend parity
Port the data layer and align tenant config so migrated UI runs on real data. Types flow
downstream only: Drizzle schema → Zod → Services → API routes → Hooks → UI.
Scope from $ARGUMENTS (e.g. org-audit, type-chain, services, hooks, config, full).
This skill orchestrates existing primitives — tenant-migrate (Supabase + config audit),
type-safety-chain (the six-layer generators/validators), validate, tenant-check.
Delegate to them; this skill sequences them for a tenant migration and enforces scoping.
Read first
TENANT_MANIFEST.md → {{TENANT_SLUG}}, {{TENANT_ORG_ID}}, {{SUPABASE_PROJECT_ID}},
{{REFERENCE_REPO}}, {{SOURCE_TENANT_NAME}}, {{AI_ASSISTANT_LABEL}}, {{CONTENT_RESEARCH}},
{{STORAGE_PREFIX}}, feature-flag table, leak grep patterns. Plus src/lib/config/tenant.config.ts,
tenant.schema.ts, .env.local.example.
11 — Supabase org & tenant identity (tenant-migrate Phase 1)
Via Supabase MCP on project {{SUPABASE_PROJECT_ID}} (public schema):
- Org row:
SELECT id, name, slug, description FROM organizations WHERE slug = '{{TENANT_SLUG}}';Confirmid == {{TENANT_ORG_ID}}. If missing → STOP and report; never create an org without approval. - Content counts (scoped by
organization_id): books, articles, courses, podcast_episodes, videos, pathways/themes. Report per type. - Storage: bucket
media-library, prefix{{STORAGE_PREFIX}}— note whether the tenant folder has assets. - AI/vector (if
features.chatwill be on): org-scoped corpus tables;OPENAI_VECTOR_STORE_IDmust be tenant-specific. - Env alignment + middleware (
src/middleware.ts— no hardcoded source-tenant paths).
Deliverable: docs/build/notes/supabase-org-audit-{{TENANT_SLUG}}-<date>.md with PASS/FAIL per
check and warnings for any features.* = true that has zero content. Do not INSERT/UPDATE orgs.
12 — Type-safety chain port (delegate to type-safety-chain)
Inventory first (report only): diff {{REFERENCE_REPO}} vs target for src/lib/db, scripts/,
src/hooks/simplified/, src/lib/services/; compare validate:/generate:/db: package scripts.
Then on branch slice/Sxx-backend-type-chain: port missing generator scripts, run generators
(pnpm generate:zod|services|routes|hooks; db:generate only with human coordination), and validate:
pnpm validate:all # or db:check / contracts:check / services:check / routes:check / hooks:check / ui:check
Never hand-edit src/hooks/simplified/* or generated zod — fix the generator and rerun.
Every service must filter by organization_id / {{TENANT_ORG_ID}}. Never modify Drizzle schema for UI.
13 — Services & API routes parity
diff -rq {{REFERENCE_REPO}}/src/app/api <target>/src/app/api (prioritize api/custom),
and .../src/lib/services. Port missing handlers; routes call services, not raw db.
Priority when courses enabled: courses/[slug]/progress|access|discussions, assessments/**;
plus /api/search and chat/agent routes when those features are on. Confirm organization_id
scoping; do not port source-tenant-only demo services. Verify pnpm routes:check, pnpm services:check.
14 — Hooks layer parity
diff -rq {{REFERENCE_REPO}}/src/hooks/{simplified,custom} <target>/.... Build the UI→hook
dependency map from COMPONENT_CHECKLIST.md. Generate simplified hooks via pnpm generate:hooks
(after services). Port priority custom hooks (use-course-learn, progress/enrollment, chat).
Hooks call API routes/services — never import Drizzle. After each hook ports, wire the
corresponding stitch-migrated section to use it instead of the static stub, and update the
checklist hook column. Zero DB rows ⇒ keep the hook wired, show empty state. Verify pnpm hooks:check.
15 — Tenant config & feature alignment (tenant-migrate Phases 2–5)
- A — Flags vs DB: for each
features.*/contentTypes.*.enabled, compare to the Prompt-11 counts. Count 0 ⇒ featurefalseor empty-state-only (no fake data). - B — Identity strings: replace name/tagline/description/copyright/logo, hero/about/contact/
newsletter,
chat.assistantLabel → {{AI_ASSISTANT_LABEL}}, search placeholder, themes/pathways, affiliations, author profile — tenant values only. - C — Content slugs:
contentTypes.courses.availableSlugsand featured books from research, never source-tenant slugs. - D — Leak grep (delegate to
tenant-check):rg '{{leak patterns}}' src --glob '!tenant.config.ts'; fix violations. - E — Env example:
TENANT_ORG_ID, documentOPENAI_VECTOR_STORE_ID(chat) and Stripe keys (checkout).
Deliverable: updated tenant.config.ts (tenant strings only — no unrelated refactors) +
docs/build/notes/tenant-config-alignment-<date>.md.
16 — Backend validation & cutover
pnpm validate:all && pnpm typecheck && pnpm lint && pnpm build
pnpm verify:tenant-org # if defined
rg '{{SOURCE_TENANT_NAME}}|{{REFERENCE_SLUG}}' src --glob '!tenant.config.ts' -l
rg 'PlaceholderPage|dangerouslySetInnerHTML' src ; rg '#111111|#666666' src/components --glob '*.tsx'
Re-run content counts for {{TENANT_ORG_ID}} vs the Prompt-11 baseline. Update
COMPONENT_CHECKLIST.md, REFERENCE_PAGE_COMPARISON.md, L5 page docs, and README. Emit a
migration report: org PASS/FAIL · validate:all PASS/FAIL · routes BUILT N/M · features aligned ·
screens converted · APIs/hooks ported · archived page-old files · production blockers (content
seeding, Vercel env, Stripe/chat) · recommended follow-up slices.
Human cutover (flag, don't do): Vercel TENANT_ORG_ID env, Supabase auth redirects, Stripe
keys, OPENAI_VECTOR_STORE_ID, Sentry DSN, DNS/custom domain.
Acceptance criteria
- Org row matches
{{TENANT_ORG_ID}}; audit committed -
pnpm validate:allgreen (or failures documented with layer owner) - No hand-edits in
simplified/; services scope byorganization_id - Course APIs/hooks exist if
features.coursestrue - Every
features.* = truehas DB content or a documented staging plan - Leak grep clean;
pnpm verify:tenant-orgpasses
Do not merge to main until this passes or blockers are explicit product decisions.
Related skills
tenant-migrate · type-safety-chain · validate · tenant-check ·
tenant-structural-port · tenant-migration-playbook