SkillPixel LMS — Multi-Tenant Roadmap

From single-tenant SkillPixel to safely serving multiple tenants — audit date 2026-08-17, epic SP-371

Last updated 2026-08-18 · v0.0.37 in production

Core multi-tenant plumbing is done. What is left is ordered into six phases.

The tenant model, host routing, query scoping, bootstrap API, platform-admin CRUD, feature gating, theming and storage namespacing are all in place.

The remaining work: release what is merged, close activation blockers, activate managed subdomains, make product surfaces tenant-grade, add custom domains, then harden and clear debt.

Ordering principle: security and correctness first, then first-tenant launch, then polish and debt.
Legend SP-xxxexisting Jira ticket NEW TICKET DECISION NEEDED SECURITY BETA-VERIFY SHIPPED v0.0.37
Phase 0 — now

Beta-verify what is already merged

GoalBeta-verify the merged code — much of it is already live after v0.0.37 — and clear the Testing column; everything later builds on it.
Why firstVerifying now avoids stacking new work on unverified code.
1 of 4 items shipped
  • Beta-verify tenantization phases 05–12

    Storage and media, enrollments and certificates, submissions and grading, commerce, platform-admin APIs and UI, ops surfaces, frontend bootstrap.

    SP-468SP-469SP-470 SP-471SP-472SP-473 SP-474SP-475 BETA-VERIFY
  • Beta-verify routing, authz and branding hardening

    Domain-aware URLs, tenant-scoped roles, branding and SEO in bootstrap, slug at provisioning, self-serve settings.

    SP-567SP-571SP-575 SP-583SP-584 BETA-VERIFY
  • Beta-verify feature gating

    Config-backed entitlements plus the feature matrix UI. The backend and frontend gating code (SP-586, SP-587) is live in production as of v0.0.37 — what remains is beta verification of the gating behaviour itself, plus SP-585's rollout decisions.

    SP-585 SP-586SHIPPED v0.0.37 SP-587SHIPPED v0.0.37 BETA-VERIFY
  • Platform-admin overhaul released

    Shipped to production in v0.0.37 on 2026-08-18, together with SP-595 and SP-596 moving platform-admin fully onto the /platform API behind a centralized guard, off direct DB access. Jira status still needs moving.

    SP-590SP-595SP-596 SHIPPED v0.0.37
Phase 1 — security + correctness

Close activation blockers

GoalMake it safe to turn on a second tenant at all. Mostly new tickets; some items fold into SP-476 scope.
Why nowThese are silent failure modes that get worse the moment real tenant traffic exists.
  • Stop signup trusting X-Tenant-Host

    Profile sync auto-creates a STUDENT membership in whatever tenant the client-sent header names (apps/backend/app/services/user.py), so any logged-in user can self-join any active tenant. The single biggest blocker.

    NEW TICKETSECURITY
  • Default entitlements at tenant creation

    create_tenant writes only the tenant row, so all 10 gated features 404 until an operator flips them by hand — this already bit HistoryClass. Needs a decided default set.

    NEW TICKETDECISION NEEDED
  • Reminder crons fan out over all active tenants

    Notification crons bind only the default tenant, so other tenants get no deadline or live-class reminders (apps/backend/app/routers/v1/notification.py).

    NEW TICKET
  • Cross-instance domain cache invalidation

    Domain activate and deactivate clear only the local process cache; other instances serve stale routing for up to 5 minutes.

    NEW TICKET
  • Harden the domain write path

    Create and update only lowercase the value. Add IDNA/punycode canonicalization to match the read path, plus a reserved and platform-label blocklist (apps/backend/app/schemas/platform.py).

    NEW TICKET
  • Fallback-write metric for hot-table defaults

    Courses, chapters, lessons and questions still carry a default-tenant server default. The metric that gates its later removal — zero fallback writes for 7 days — does not exist yet.

    SP-476
  • Decide and fix the RSA environment

    ENVIRONMENT=rsa resolves to DEV, giving wrong platform hostnames and dev-only behavior in production. Decide: real tenant, separate deployment, or a fourth Environment value.

    NEW TICKETDECISION NEEDED
Phase 2 — first tenant live

Activate managed subdomains and minimum launch polish

GoalFirst non-default tenant (HistoryClass, SP-542) live on a managed subdomain.
Why hereActivation is only safe after Phase 1; the polish items are what its users see on day one.
NoteHistoryClass content work (SP-549) already shipped in v0.0.37 — this phase is what stands between it and a live tenant site.
  • Activate managed subdomain routing

    Browser hostname pass-through, tenant activation gates, audit queries, monitoring. Default-tenant traffic must stay stable.

    SP-476
  • Fix default banner branding

    Default banner components hardcode SkillPixel imagery and copy on shared surfaces. Also add a uniqueness guard against duplicate seeded default banners.

    SP-557
  • De-brand the frontend shell

    A static index.html title and meta is served to every tenant; the hardcoded default-tenant snapshot and fallback hosts in resolution.ts must derive from config.

    NEW TICKET
  • Tenant landing pages to launch quality

    Landing strategy is a hardcoded build-time UUID map holding only the local seed UUID, and the shared template lacks hero copy, announcements and featured courses (frontend phase-3 WP3–WP6).

    NEW TICKET
  • Staff invitation flow

    Memberships 404 unless the user already self-registered. Add invite tokens, a pending state and an invite email — or accept the manual workaround for launch and do this early in Phase 3.

    NEW TICKET
Phase 3 — product surfaces

Tenant-grade product surfaces

GoalA tenant's users never see SkillPixel branding, legal identity, or SkillPixel-only behavior.
Why hereNot needed to flip the switch, but needed before a tenant runs a real cohort.
  • Per-tenant email

    One global SMTP identity (info@skillpixel.vn) and SkillPixel-branded templates and subjects everywhere. Add a per-tenant from-identity and branding, plus tenant_id on email templates.

    NEW TICKET
  • Per-tenant legal pages

    Privacy, terms, refund and company address are shared i18n copy naming SkillPixel's legal entity. Decide: tenant data, per-tenant i18n, or external links.

    NEW TICKETDECISION NEEDED
  • Tenant-aware payments

    Payment and cart routes cannot be feature-gated naively — webhooks resolve to the default tenant after the gateway already charged — and provider config is a hardcoded TODO. Decide: provider list entitlement, or per-tenant merchant credentials.

    NEW TICKETDECISION NEEDED
  • Backend-backed localisations

    In progress. Per-tenant copy overrides fit naturally alongside the email and legal work.

    SP-531
Phase 4 — custom domains

Verified custom domains

GoalCustomer-owned domains served safely (SP-477).
Why hereThe spec sequences this strictly after managed subdomains are stable.
  • Domain verification flow

    TXT/CNAME challenge, with activation requiring a verified domain. Today ACTIVE can be set with no verification and no external side effects.

    SP-477
  • Firebase authorized-domain automation and guardrails

    The spec assumes under 50 active custom domains, warns at 75 and blocks at 90. No counter or guardrail exists in code.

    SP-477
  • Cloudflare custom hostnames and canonical-host rules

    Registration on activate and deactivate, predictable canonical redirects, managed subdomain as the fallback.

    SP-477
Phase 5 — hardening

Hardening and debt cleanup

GoalRemove compatibility layers and add defense in depth (SP-478, SP-573 and small items).
Why lastNeeds runtime evidence from real multi-tenant traffic to do safely.
  • Finish tenant-scoped authorization

    The contest service and the admin chat WebSocket still authorize on global Firebase claims. Add wrong-tenant 403 tests per router family.

    SP-573
  • Audit trail and scoped platform-admin

    One is_platform_admin boolean controls every tenant's entitlements, with no append-only log of config writes.

    NEW TICKET
  • Remove hot-table defaults and phased RLS

    Drop default-tenant stamping once the Phase-1 metric shows 7 clean days, add PostgreSQL RLS for prioritized tenant tables, and reduce global roles to platform-wide semantics.

    SP-478
  • Small items sweep

    Banner uploads missing a tenant prefix, the last direct-Postgres read in the platform-admin activity route, a CORS wildcard revisit, and cron tenant-binding as a dependency instead of a convention.

    NEW TICKET

Open decisions

Each one blocks or shapes a phase below.

  1. Default entitlement set for a new tenant. Blocks Phase 1
  2. RSA tenancy model — tenant, separate deployment, or new environment. Blocks Phase 1
  3. Tenant legal identity ownership. Blocks Phase 3
  4. Payment provider model per tenant. Blocks Phase 3
  5. Cross-tenant identity policy — users are global, and membership-add errors leak email existence. Informs the invitation flow

Changelog