feat: self-serve tier — PR 10/12 — Trial Lifecycle (emails, archival, upgrade)
loading diff…
Stack order: Merge after PR 9 — Enterprise Tier Gates.
Adds trial lifecycle management: automated email notifications, hard-lock enforcement, expired org archival, trial extension, and enterprise upgrade scripts. Also wires the tier gates and trial UI components (introduced in PRs 8-9) into their final integration points across the dashboard.
Day 0: Org created (free_trial, 14-day expiry)
Day 7: Warning email — "7 days left"
Day 11: Urgent email — "3 days left"
Day 14: Expiry email — "Trial expired" (soft-lock: UI interstitial, can dismiss)
Day 21: Hard-lock (7 days after expiry — MCP tool calls blocked, dashboard mutations blocked)
Day 44: Auto-archive (30 days after expiry — FusionAuth users deactivated, org archived)
Hard-lock grace period is hardcoded at 7 days. Archive delay is configurable via
TRIAL_ARCHIVE_AFTER_DAYS(default: 30 days after expiry).
| File | Purpose |
|---|---|
trial-lifecycle-emails.ts | Queries orgs approaching/past expiry, sends 7d/3d/expired emails |
trial-lifecycle-emails.test.ts | Unit tests for email trigger logic |
archive-expired-trials.ts | Hard-lock (7d grace) + archive (30d) + FusionAuth user deactivation |
archive-expired-trials.test.ts | Unit tests for hard-lock and archive logic |
cron-auth.ts | CRON_SECRET validation middleware for internal API routes |
cron-auth.test.ts | Unit tests for cron auth |
org-capacity.test.ts | Tests for assertEnterpriseTier() and seat cap enforcement |
| Route | Frequency | Purpose |
|---|---|---|
POST /api/internal/trial-lifecycle-emails | Daily cron | Sends 7d/3d/expiry emails |
POST /api/internal/archive-expired-trials | Daily cron | Hard-locks (7d) and archives (30d) expired orgs |
POST /api/internal/extend-trial | Manual | Staff-only trial extension |
POST /api/internal/scim-projection-queue | Daily cron | SCIM sync projection |
All require Authorization: Bearer <CRON_SECRET> header.
provisioning/upgrade-to-enterprise.ts: Converts a trial org to enterprise tier (removes limits, updates settings)provisioning/upgrade-to-enterprise.test.ts: Tests for upgrade scriptprovisioning/assign-org-admin.ts: CLI tool for granting org_admin role to any userFiles from PRs 8-9 (Trial UI + Enterprise Gates) are modified here to wire them into their final integration points:
| Category | Files | What changed |
|---|---|---|
| Pages (6) | page.tsx, page-content.tsx, sso/_components.tsx, tool-config/page.tsx, _components.tsx, trial-components/page.tsx | Added tier-aware conditionals, upgrade CTAs, enterprise gates |
| Layout components (4) | app-frame.tsx, app-sidebar.tsx, app-sidebar-nav.tsx, app-sidebar-sections.tsx | Trial interstitial rendering, seat count badge, nav gating |
| Trial UI (4) | trial-banner.tsx, trial-expired-interstitial.tsx, upgrade-dialog.tsx, upgrade-gate.tsx | Refined states, hard-lock vs soft-lock logic, copy updates |
| Feature components (1) | member-actions.tsx | Seat cap enforcement in invite UI |
| Enterprise gates (8) | set-enabled-toolkits.ts, create-auth-config.ts, regenerate-scim-credentials.ts, set-audit-mode.ts, set-audit-mode.test.ts, add-member.ts, add-members.ts, update-tool-config.ts | assertEnterpriseTier() + seat cap checks wired in |
| # | PR | Link |
|---|---|---|
| 1 | Foundation | #458 |
| 2 | Core orgId Resolution | #459 |
| 3 | Routers: orgs, audit, onboarding | #460 |
| 4 | Routers: teams, connections, me | #461 |
| 5 | Routers: apps, auth-configs, misc | #462 |
| 6 | Self-Serve Signup Flow | #463 |
| 7 | Sign-In & Auth Broker | #464 |
| 8 | Trial UI | #465 |
| 9 | Enterprise Tier Gates | #466 |
| 10 | Trial Lifecycle | this PR |
| 11 | Proxy Trial Lock | #468 |
| 12 | Tests & Fixtures | #469 |