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.
Beta-verify what is already merged
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
/platformAPI behind a centralized guard, off direct DB access. Jira status still needs moving.SP-590SP-595SP-596 SHIPPED v0.0.37
Close activation blockers
-
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_tenantwrites 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=rsaresolves 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
Activate managed subdomains and minimum launch polish
-
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.htmltitle and meta is served to every tenant; the hardcoded default-tenant snapshot and fallback hosts inresolution.tsmust 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
Tenant-grade product surfaces
-
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, plustenant_idon 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
Verified custom domains
-
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
Hardening and debt cleanup
-
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_adminboolean 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.
- Default entitlement set for a new tenant. Blocks Phase 1
- RSA tenancy model — tenant, separate deployment, or new environment. Blocks Phase 1
- Tenant legal identity ownership. Blocks Phase 3
- Payment provider model per tenant. Blocks Phase 3
- Cross-tenant identity policy — users are global, and membership-add errors leak email existence. Informs the invitation flow
Changelog
- 2026-08-18 — v0.0.37 to production: platform-admin overhaul (SP-590), per-tenant feature gating live (SP-586, SP-587), platform-admin fully on
/platformAPI (SP-595, SP-596), History class content (SP-549). - 2026-08-17 — Initial roadmap from code audit + Jira epic SP-371.