Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
8.8 KiB
8.8 KiB
Scratchpad — Tenant Tier System
- Plan slug:
tenant-tiers - Created:
2026-03-03 - Updated:
2026-03-05(narrowed to 2-tier model: Pro/Premium)
What This Is
Keep a lightweight, continuously-updated log of discoveries and decisions made while implementing this plan.
Decisions
- (2026-03-05) 2-tier model only: Pro and Premium. No basic tier — users who want only basic features can run the open-source CE. This simplifies the tier system significantly.
- (2026-03-05) Existing customers grandfathered at Pro with $0 base fee. They keep full access to all standard features.
- (2026-03-05) Invoice Designer is the POC gated feature. Only premium tenants get the visual drag-and-drop designer. Code view remains available to all.
- (2026-03-05) Add-ons infrastructure built.
tenant_addonstable + constants/utilities ready for future per-tenant purchasable features. - (2026-03-05) Phase C removed. There's no basic tier to gate self-registration into.
- (2026-03-03) NULL plan → error state (pro access + warning banner), NOT a lower tier. Rationale: prevents accidentally restricting features for misconfigured customers.
- (2026-03-03) No DB column DEFAULT on
plan. NULL is intentional error state caught by the UI. - (2026-03-03) CE tenants always get
plan: 'pro'— CE has no Stripe/tiers, should never be gated. - (2026-03-03) Temporal workflow updated in Phase A (not deferred). All EE tenant creation paths funnel through
createTenantInDB(). - (2026-03-03) Pre-map future Stripe product names (
alga-psa-pro,alga-psa-premium) so they work automatically when created. - (2026-03-03) Three horizons: (1) Stripe product mapping now, (2) new Stripe products per tier later, (3) internal contracts way later.
- (2026-03-03) Plan throttle at 5 min (separate from session revocation check at 30s).
Discoveries / Constraints
- (2026-03-03)
ITenantalready hasplan?: stringfield — just needs narrowing toTenantTier. - (2026-03-03)
Tenant.updatePlan(knex, tenant, plan)already exists inpackages/db/src/models/tenant.ts. - (2026-03-03) Dev seeds at
server/seeds/dev/01_tenants.cjsalready setplan: 'pro'— no change needed. - (2026-03-03) Test factory at
server/test-utils/testDataFactory.tssetsplan: 'test'— needs update to'pro'. - (2026-03-03) Two
buildAuthOptionspaths innextAuthOptions.ts— BOTH must be updated (enterprise and non-enterprise). - (2026-03-03) Nine Minds reporting extension →
/api/v1/tenant-management/create-tenant→ Temporal workflow →createTenantInDB(). Same path as Stripe checkout. - (2026-03-03) Provisioning API (
/api/provisioning/tenants) also uses Temporal workflow. Same path. - (2026-03-03) CE AccountManagement stub returns null. Account page enhancements are EE-only.
- (2026-03-03)
isEnterprisecheck inserver/src/lib/features.tsusesEDITION/NEXT_PUBLIC_EDITIONenv vars. - (2026-03-03) Existing
FeaturePlaceholdercomponent shows "under construction" — distinct from newUpsellPlaceholder. - (2026-03-03) License count gating uses
if (limit !== null && used >= limit)— NULL = no limit. Tier gating is additive.
Commands / Runbooks
- Verify migration:
SELECT tenant, plan FROM tenants WHERE plan IS NULL OR plan = '';(should return 0 rows after migration) - Test misconfigured state:
UPDATE tenants SET plan = NULL WHERE tenant = '<test-tenant-id>'; - Test premium gating:
UPDATE tenants SET plan = 'premium' WHERE tenant = '<test-tenant-id>'; - Inspect JWT: Browser DevTools → Application → Cookies → next-auth.session-token → decode at jwt.io
- Force session refresh: Call
refreshTier()from TierContext or wait 5 min
Links / References
- Detailed design reference:
.ai/tiers/tenant-tier-system-plan.md - Tenant model:
packages/db/src/models/tenant.ts - Auth types:
packages/auth/src/types/next-auth.ts - Auth options:
packages/auth/src/lib/nextAuthOptions.ts - Layout client:
server/src/app/msp/MspLayoutClient.tsx - Tenant operations:
ee/temporal-workflows/src/db/tenant-operations.ts - Stripe service:
ee/server/src/lib/stripe/StripeService.ts - Account management:
ee/server/src/components/settings/account/AccountManagement.tsx - Registration:
packages/auth/src/actions/useRegister.tsx - Edition check:
server/src/lib/features.ts - Dev seeds:
server/seeds/dev/01_tenants.cjs - Test factory:
server/test-utils/testDataFactory.ts
Tenant Creation Paths Coverage
| Path | How Plan Is Set | Phase |
|---|---|---|
| Stripe checkout → Temporal workflow | createTenantInDB() resolves tier from Stripe product |
A |
| Nine Minds reporting extension | Same Temporal workflow → same createTenantInDB() |
A |
| Provisioning API | Same Temporal workflow | A |
CE self-registration (useRegister.tsx) |
plan: 'pro' in Tenant.insert() |
A |
| Dev seeds | Already plan: 'pro' |
— |
| Test data factory | plan: 'test' → plan: 'pro' |
A |
| Migration backfill | NULL/empty → 'pro' |
A |
Implementation Log
Phase A Complete (2026-03-03)
- Created
packages/types/src/constants/tenantTiers.tswith TENANT_TIERS ['pro', 'premium'], TenantTier, TIER_LABELS, isValidTier(), resolveTier() - Created
packages/types/src/constants/tierFeatures.tswith TIER_FEATURES enum (INVOICE_DESIGNER), TIER_FEATURE_MAP, tierHasFeature(), FEATURE_MINIMUM_TIER - Created
packages/types/src/constants/addOns.tswith ADD_ONS enum (empty placeholder), tenantHasAddOn() - Updated ITenant.plan to use TenantTier type in both interface files, added addons?: string[]
- Updated useRegister.tsx to set plan: 'pro' in both Tenant.insert() calls
- Created backfill migration: 20260303100000_backfill_tenant_plan_to_pro.cjs
- Created add-ons table migration: 20260305100000_create_tenant_addons.cjs
- Updated testDataFactory.ts: plan 'test' → 'pro'
- Updated next-auth types with plan in User, Session.user, and JWT interfaces
- Updated ExtendedUser interface in nextAuthOptions.ts
- Added plan fetching on initial sign-in in both buildAuthOptions paths
- Added 5-minute throttled plan refresh in JWT callback
- Added plan propagation from JWT to session in session callback
- Created TierContext with TierProvider and useTier() hook
- Wrapped MspLayoutClient content with TierProvider
- Created stripeTierMapping.ts with STRIPE_PRODUCT_TIER_MAP and tierFromStripeProduct()
- Wired tier resolution in createTenantInDB() from Stripe product
- Updated handleCheckoutCompleted() and handleSubscriptionUpdated() to set tenant plan
Phase B Complete (2026-03-03)
- Created UpsellPlaceholder in
packages/ui/src/components/tier-gating/UpsellPlaceholder.tsxwith icon, heading, description, CTA to /msp/account - Created TierGate client component in
server/src/components/tier-gating/TierGate.tsxusing TierContext - Created ServerTierGate in
server/src/lib/tier-gating/ServerTierGate.tsxreading session directly - Created assertTierAccess utility with TierAccessError class in
server/src/lib/tier-gating/assertTierAccess.ts - Applied assertTierAccess to invoice template save/delete actions (invoiceTemplates.ts)
- Gated Invoice Designer visual tab in InvoiceTemplateEditor with canUseVisualDesigner prop
- Computed canUseVisualDesigner server-side in billing page from session tier
- Updated AccountManagement to use useTier() hook for tier display
- Added tier badge with TIER_LABELS[tier] to account page
- Added tier features list showing TIER_FEATURE_MAP[tier] features
Tests Implemented (2026-03-03)
- T001-T011: Tier constants unit tests in
packages/types/src/constants/tenantTiers.test.ts - T012-T017: Tier features unit tests in
packages/types/src/constants/tierFeatures.test.ts - T018: Export verification in
packages/types/src/constants/tierExports.test.ts - T019: ITenant.plan type check in
packages/types/src/interfaces/tenant.interface.typecheck.test.ts - T020: Test data factory check in
server/src/test/unit/testDataFactory.test.ts - T021-T024: Stripe tier mapping tests in
ee/server/src/__tests__/unit/stripeTierMapping.test.ts - T025-T027: assertTierAccess tests in
server/src/lib/tier-gating/assertTierAccess.test.ts - Total: 28 unit tests implemented and passing (verified with
vitest run)
Remaining Tests (Integration/E2E)
The remaining tests require:
- Database connections for migration tests
- NextAuth session mocking for JWT/session tests
- React component rendering for UI tests
- Playwright for E2E tests These are out of scope for initial unit test coverage and should be implemented as integration tests.
Open Questions
- (resolved) NULL plan handling → error state with pro access + warning banner
- (resolved) CE mode → always Pro, never gated
- (resolved) Dev seeds → already set
plan: 'pro' - (resolved) Nine Minds extension → same Temporal workflow, covered by A8/A9
- (resolved, 2026-03-05) No basic tier → users wanting basic features use open-source CE