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.
-
Beta-verify routing, authz and branding hardening
Domain-aware URLs, tenant-scoped roles, branding and SEO in bootstrap, slug at provisioning, self-serve settings.
-
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. PR #276 (2026-08-19) centralized frontend gating into a
TenantFeatureenum withenabled_featuressanitized at a single choke point — including the sessionStorage cache — hardening the surface before beta verification. MERGED · UNRELEASED -
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.
Close activation blockers
-
Stop signup trusting X-Tenant-Host (stack 1/6)
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.SP-602SECURITY -
Default entitlements at tenant creation (stack 3/6)
create_tenantwrites only the tenant row, so all 10 gated features 404 until an operator flips them by hand — this already bit HistoryClass. Decided default (2026-08-20): new tenants start with certificates and DOCX import enabled, all other gated features disabled. -
Reminder crons fan out over all active tenants (stack 4/6)
Notification crons bind only the default tenant, so other tenants get no deadline or live-class reminders (
apps/backend/app/routers/v1/notification.py). -
Cross-instance domain cache invalidation (stack 6/6)
Domain activate and deactivate clear only the local process cache; other instances serve stale routing for up to 5 minutes.
-
Harden the domain write path (stack 5/6)
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). -
Fallback-write metric for hot-table defaults (stack 2/6)
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. It feeds the SP-478 Gate C criterion.
-
Decide and fix the RSA environment
Dropped 2026-08-20 — RSA is a separate deployment with a long-diverged codebase; not part of this rollout.
DROPPED
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.
-
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.
-
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.
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.
-
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.
-
Cloudflare custom hostnames and canonical-host rules
Registration on activate and deactivate, predictable canonical redirects, managed subdomain as the fallback.
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.
-
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.
-
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. Resolved 2026-08-20 — new tenants start with certificates and DOCX import enabled, all other gated features disabled. Implemented by SP-604. No longer blocks Phase 1
- RSA tenancy model — tenant, separate deployment, or new environment. Resolved 2026-08-20 — RSA stays a separate deployment with a long-diverged codebase; dropped from this rollout. No longer 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-20 — Phase 1 ticketed: SP-602–SP-607 created as a stacked-PR chain gating SP-476 (SP-603 also gates SP-478). Decided: new tenants start with certificates + DOCX import; RSA dropped (separate deployment).
- 2026-08-19 — Merged to main (unreleased): centralized frontend tenant feature registration —
TenantFeatureenum, sanitizedenabled_featureschoke point, unifiedFeatureRoute(PR #276). - 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.