Skip to content

feat: add DEPLOYMENT_MODE=tenant support - #795

Merged
2witstudios merged 4 commits into
masterfrom
pu/epic-4-tenant-mode
Mar 17, 2026
Merged

2witstudios merged 4 commits into
masterfrom
pu/epic-4-tenant-mode

Conversation

@2witstudios

@2witstudios 2witstudios commented Mar 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Add isTenantMode() and isBillingEnabled() to deployment mode utilities (server + client)
  • Update isCloud() to exclude tenant mode — tenant is neither cloud nor onprem
  • Block billing/OAuth/signup routes in middleware for tenant mode (same as onprem)
  • Return business-tier storage limits for all tenant mode users regardless of subscription
  • Exclude Stripe/Google CSP directives from security headers in tenant mode
  • Update billing visibility to use isBillingEnabled() instead of isOnPrem()

Context

Tenant mode is for managed team instances where billing is handled externally by the control plane. All users get max-tier (business) features. This is ~20 lines of logic across 7 files.

Files Changed

File Change
packages/lib/src/deployment-mode.ts Add isTenantMode(), isBillingEnabled(), update isCloud()
packages/lib/src/index.ts Export new functions
apps/web/src/lib/deployment-mode.ts Client-side mirror of above
apps/web/middleware.ts Block cloud-only routes in tenant mode
apps/web/src/middleware/security-headers.ts Exclude Stripe/Google CSP in tenant mode
packages/lib/src/services/subscription-utils.ts Business-tier override for tenant mode
apps/web/src/lib/billing/billing-visibility.ts Use isBillingEnabled()

Test Plan

  • 11 new tests in packages/lib/src/__tests__/deployment-mode.test.ts covering all mode combinations
  • 1 new test in packages/lib/src/services/__tests__/subscription-utils.test.ts for tenant business-tier override
  • All 32 related tests pass
  • No regressions in existing subscription-utils tests (21 tests)
  • Verify DEPLOYMENT_MODE=tenant blocks /api/stripe/ routes (manual/E2E)
  • Verify tenant users see business-tier storage limits (manual/E2E)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a tenant deployment mode alongside cloud and on‑prem.
  • Bug Fixes

    • Blocked cloud‑only routes (including certain auth endpoints) in non‑cloud deployments to prevent access.
  • UI/Behavior

    • Billing UI, subscription/storage visibility, quotas and usage limits now follow a billing‑enabled flag (cloud vs non‑cloud) so billing features are hidden where disabled.
  • Security

    • Content/security policies adjusted for cloud deployments to allow specific third‑party origins.

Add tenant deployment mode for managed team instances where billing
is handled at the control plane level, not in-app. All users in a
tenant instance get business-tier features.

Changes:
- Add isTenantMode() and isBillingEnabled() to deployment-mode utils
- Update isCloud() to exclude tenant mode (both server and client)
- Block billing/OAuth routes in middleware for tenant mode
- Return business-tier storage limits for tenant mode users
- Exclude Stripe/Google CSP directives in tenant mode
- Update billing-visibility to use isBillingEnabled()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Mar 16, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92b5e70b-0245-4b62-9bfe-f96d93726db3

📥 Commits

Reviewing files that changed from the base of the PR and between 3d19cdf and 5c92a71.

📒 Files selected for processing (3)
  • apps/web/src/lib/subscription/__tests__/rate-limit-middleware.test.ts
  • apps/web/src/lib/subscription/rate-limit-middleware.ts
  • apps/web/src/lib/subscription/usage-service.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/lib/subscription/tests/rate-limit-middleware.test.ts
  • apps/web/src/lib/subscription/rate-limit-middleware.ts

📝 Walkthrough

Walkthrough

Adds tenant-mode detection and a billing-enabled gate; replaces on-prem-only checks with billing-aware logic across middleware, security headers, subscription/usage, and billing-visibility; expands cloud-only route blocking and aligns tenant with on-prem behavior and limits.

Changes

Cohort / File(s) Summary
Middleware & Routing
apps/web/middleware.ts
Renamed on-prem block list to CLOUD_ONLY_ROUTE_PREFIXES, added IS_TENANT / IS_BILLING_DISABLED flags, and block cloud-only routes when billing is disabled (applies before route handlers).
Security Headers
apps/web/src/middleware/security-headers.ts
Replaced IS_ONPREM checks with IS_CLOUD; adjust CSP to include Google/Stripe origins only when cloud.
Deployment Mode Utilities
packages/lib/src/deployment-mode.ts, apps/web/src/lib/deployment-mode.ts
Added isTenantMode(), changed isCloud() to exclude tenant mode, and added isBillingEnabled() alias.
Public Exports
packages/lib/src/index.ts
Re-exported isTenantMode and isBillingEnabled from deployment-mode.
Billing / Visibility Hooks
apps/web/src/lib/billing/billing-visibility.ts, apps/web/src/hooks/useBillingVisibility.ts
Switched gating from isOnPrem to isBillingEnabled; updated comments to reflect non-cloud (on-prem/tenant) behavior.
Subscription / Usage / Rate Limits
packages/lib/src/services/subscription-utils.ts, apps/web/src/lib/subscription/usage-service.ts, apps/web/src/lib/subscription/rate-limit-middleware.ts
Treat tenant like on-prem for storage limits; replace isOnPrem() checks with billing-enabled checks (isBillingEnabled() where appropriate) to determine unlimited usage or pro-requirement gating.
Tests
packages/lib/src/__tests__/deployment-mode.test.ts, packages/lib/src/services/__tests__/subscription-utils.test.ts, apps/web/src/lib/subscription/__tests__/*
Added tests for isTenantMode and isBillingEnabled; updated/added tests asserting tenant-mode behavior for storage, usage limits, and rate-limit middleware across deployment modes.

Sequence Diagram(s)

sequenceDiagram
participant Client
participant Edge as "Edge Middleware"
participant Mode as "Deployment Mode Service"
participant Handler as "Route Handler / API"

Client->>Edge: HTTP request (path)
Edge->>Mode: query isBillingEnabled / isTenantMode
Mode-->>Edge: billingEnabled? / tenant?
alt billing disabled AND path in CLOUD_ONLY_ROUTE_PREFIXES
    Edge-->>Client: 404 (blocked cloud-only route)
else forward
    Edge->>Handler: forward request
    Handler-->>Client: 200 / handler response
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 In the burrow the flags all align,
Tenant and on‑prem now share one sign,
Cloud doors close when billing’s turned low,
Routes and quotas hop where needs must go.
🥕 Hooray for tidy gates and light!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main objective: adding tenant deployment mode support to the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/epic-4-tenant-mode
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/web/src/lib/billing/billing-visibility.ts (1)

10-14: Update JSDoc to reflect tenant mode behavior.

The comment states billing returns false for "on-prem deployments", but with the switch to isBillingEnabled(), billing is now also hidden for tenant deployments. The documentation should be updated for accuracy.

📝 Proposed documentation fix
 /**
  * Determines whether billing UI should be shown.
- * Returns false for iOS Capacitor apps and on-prem deployments.
+ * Returns false for iOS Capacitor apps, on-prem, and tenant deployments.
  *
  * Use this for non-React contexts. For React components, use `useBillingVisibility` hook.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/billing/billing-visibility.ts` around lines 10 - 14, Update
the JSDoc for billing-visibility to accurately describe current behavior: note
that billing UI is hidden for iOS Capacitor apps and for both tenant and on-prem
deployments now that the logic uses isBillingEnabled(); reference the
isBillingEnabled() check and keep the guidance to use useBillingVisibility for
React components so the comment matches the implementation.
packages/lib/src/__tests__/deployment-mode.test.ts (1)

64-70: Test coverage for isOnPrem is incomplete.

The isOnPrem test suite only has one test case (tenant mode), while other functions have 3-4 cases covering various DEPLOYMENT_MODE values. Consider adding tests for cloud, onprem, and unset modes for consistency.

✅ Proposed additional test cases
   describe('isOnPrem', () => {
     it('given DEPLOYMENT_MODE=tenant, should return false', async () => {
       vi.stubEnv('DEPLOYMENT_MODE', 'tenant');
       const { isOnPrem } = await loadModule();
       expect(isOnPrem()).toBe(false);
     });
+
+    it('given DEPLOYMENT_MODE=onprem, should return true', async () => {
+      vi.stubEnv('DEPLOYMENT_MODE', 'onprem');
+      const { isOnPrem } = await loadModule();
+      expect(isOnPrem()).toBe(true);
+    });
+
+    it('given DEPLOYMENT_MODE=cloud, should return false', async () => {
+      vi.stubEnv('DEPLOYMENT_MODE', 'cloud');
+      const { isOnPrem } = await loadModule();
+      expect(isOnPrem()).toBe(false);
+    });
+
+    it('given DEPLOYMENT_MODE is unset, should return false', async () => {
+      vi.stubEnv('DEPLOYMENT_MODE', '');
+      const { isOnPrem } = await loadModule();
+      expect(isOnPrem()).toBe(false);
+    });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/lib/src/__tests__/deployment-mode.test.ts` around lines 64 - 70, The
isOnPrem test suite only covers DEPLOYMENT_MODE='tenant'; add tests to cover the
other modes by stubbing environment and asserting expected returns: add tests
that set vi.stubEnv('DEPLOYMENT_MODE', 'cloud') expect isOnPrem() false,
vi.stubEnv('DEPLOYMENT_MODE', 'onprem') expect isOnPrem() true, and a test with
no DEPLOYMENT_MODE (clear stub or unset) to assert the default behavior; use the
same pattern as the existing test (await loadModule() to import isOnPrem) so the
module reads the stubbed env each time.
packages/lib/src/deployment-mode.ts (1)

1-9: Update module JSDoc to document tenant mode.

The module documentation only describes DEPLOYMENT_MODE=onprem behavior, but this PR introduces DEPLOYMENT_MODE=tenant. Consider updating the JSDoc to explain tenant mode semantics for future maintainers.

📝 Proposed documentation update
 /**
  * Deployment mode detection utilities (server-side).
  *
  * When DEPLOYMENT_MODE=onprem, the application disables cloud-only features
  * (Stripe billing, OAuth, self-registration) and surfaces password auth,
  * local AI providers, and admin-managed user accounts.
  *
+ * When DEPLOYMENT_MODE=tenant, the application behaves similarly to on-prem
+ * (billing disabled, business-tier storage limits) but may have different
+ * authentication or infrastructure configurations.
+ *
  * All changes are inert unless the env var is explicitly set.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/lib/src/deployment-mode.ts` around lines 1 - 9, Update the module
JSDoc at the top of deployment-mode.ts to document the new
DEPLOYMENT_MODE=tenant option: add a concise description of tenant mode
semantics (how it differs from onprem and cloud), e.g., that tenant mode enables
multi-tenant behavior, keeps cloud-only features disabled for individual tenants
while allowing centralized management and tenant-scoped integrations, and list
any behavioral effects (auth, billing, provisioning) so future maintainers see
both onprem and tenant modes alongside the existing onprem description.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/lib/deployment-mode.ts`:
- Around line 12-22: The useBillingVisibility hook currently only checks
isOnPrem() and thus shows billing in tenant deployments; update
useBillingVisibility (the function using useCapacitor) to also call
isTenantMode() and treat tenant like on-prem by returning { showBilling: false,
hideBilling: true, isReady: true } when isOnPrem() || isTenantMode() is true,
and import isTenantMode from "@/lib/deployment-mode".

---

Nitpick comments:
In `@apps/web/src/lib/billing/billing-visibility.ts`:
- Around line 10-14: Update the JSDoc for billing-visibility to accurately
describe current behavior: note that billing UI is hidden for iOS Capacitor apps
and for both tenant and on-prem deployments now that the logic uses
isBillingEnabled(); reference the isBillingEnabled() check and keep the guidance
to use useBillingVisibility for React components so the comment matches the
implementation.

In `@packages/lib/src/__tests__/deployment-mode.test.ts`:
- Around line 64-70: The isOnPrem test suite only covers
DEPLOYMENT_MODE='tenant'; add tests to cover the other modes by stubbing
environment and asserting expected returns: add tests that set
vi.stubEnv('DEPLOYMENT_MODE', 'cloud') expect isOnPrem() false,
vi.stubEnv('DEPLOYMENT_MODE', 'onprem') expect isOnPrem() true, and a test with
no DEPLOYMENT_MODE (clear stub or unset) to assert the default behavior; use the
same pattern as the existing test (await loadModule() to import isOnPrem) so the
module reads the stubbed env each time.

In `@packages/lib/src/deployment-mode.ts`:
- Around line 1-9: Update the module JSDoc at the top of deployment-mode.ts to
document the new DEPLOYMENT_MODE=tenant option: add a concise description of
tenant mode semantics (how it differs from onprem and cloud), e.g., that tenant
mode enables multi-tenant behavior, keeps cloud-only features disabled for
individual tenants while allowing centralized management and tenant-scoped
integrations, and list any behavioral effects (auth, billing, provisioning) so
future maintainers see both onprem and tenant modes alongside the existing
onprem description.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8ce5f39-f549-487b-9c8c-63106e038424

📥 Commits

Reviewing files that changed from the base of the PR and between 9fde873 and 0715939.

📒 Files selected for processing (9)
  • apps/web/middleware.ts
  • apps/web/src/lib/billing/billing-visibility.ts
  • apps/web/src/lib/deployment-mode.ts
  • apps/web/src/middleware/security-headers.ts
  • packages/lib/src/__tests__/deployment-mode.test.ts
  • packages/lib/src/deployment-mode.ts
  • packages/lib/src/index.ts
  • packages/lib/src/services/__tests__/subscription-utils.test.ts
  • packages/lib/src/services/subscription-utils.ts

Comment thread apps/web/src/lib/deployment-mode.ts
- Update useBillingVisibility hook to use isBillingEnabled() instead
  of isOnPrem(), fixing tenant mode showing billing UI incorrectly
- Update JSDoc comments to document tenant mode behavior
- Add comprehensive isOnPrem test cases (onprem/cloud/tenant/unset)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

Addressed all CodeRabbit review feedback in e852e38

Actionable (major):

  • useBillingVisibility hook — Now uses isBillingEnabled() instead of isOnPrem(), correctly hiding billing UI for tenant deployments. File: apps/web/src/hooks/useBillingVisibility.ts

Nitpicks (all addressed):

  1. billing-visibility.ts JSDoc — Updated to mention tenant deployments alongside on-prem.
  2. isOnPrem test coverage — Added 3 missing test cases (onprem=true, cloud=false, unset=false). Now 14 tests total.
  3. deployment-mode.ts module JSDoc — Added paragraph documenting DEPLOYMENT_MODE=tenant semantics.

usage-service.ts and rate-limit-middleware.ts only checked isOnPrem(),
missing tenant mode. Tenant users would get free-tier rate limits
(50 standard/0 pro calls/day) instead of unlimited. Change isOnPrem()
to !isBillingEnabled() so both onprem and tenant modes bypass billing
rate limits, matching the storage override pattern.

Epic plan requirement: "Given DEPLOYMENT_MODE=tenant, AI model limits
should return business-tier allowances"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/src/lib/subscription/rate-limit-middleware.ts (1)

92-96: Update stale JSDoc to match billing-enabled behavior.

Line 93 says only on-prem bypasses subscription gating, but Line 96 bypasses whenever billing is disabled (on-prem and tenant). Please align the comment with the actual guard.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/subscription/rate-limit-middleware.ts` around lines 92 - 96,
The JSDoc above requiresProSubscription is stale: it claims only on-prem
bypasses subscription gating but the function returns false whenever
isBillingEnabled() is false (covering on-prem and non-billing tenants). Update
the comment to state that subscription gating is bypassed when billing is
disabled (i.e., both on-prem and tenants without billing), and mention the guard
uses isBillingEnabled() to short-circuit; reference the function
requiresProSubscription and the isBillingEnabled() check so readers know where
the behavior is enforced.
apps/web/src/lib/subscription/usage-service.ts (1)

14-18: Refresh “on-prem” comments to “billing-disabled” semantics.

These comments still describe unlimited usage as on-prem only, but Line 18 now applies to all billing-disabled modes (including tenant). Updating wording will keep this self-documenting.

Also applies to: 81-82, 145-146

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/subscription/usage-service.ts` around lines 14 - 18, Update
the misleading "on-prem" comment wording to reflect "billing-disabled"
semantics: change the comment above getUsageLimits (and the other occurrences
around lines with similar comments) so it says it returns -1 for
billing-disabled modes (i.e., when isBillingEnabled() is false) rather than
referencing “on-prem”; locate the comments near the getUsageLimits function and
the same comment blocks referenced by the reviewer and replace “on-prem” with
“billing-disabled” (or similar phrasing like “billing-disabled
deployments/modes, including tenant”) to accurately describe the behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/lib/subscription/__tests__/rate-limit-middleware.test.ts`:
- Around line 20-25: The test name/assertion mismatch: rename the existing case
to reflect that a non-pro model on cloud should NOT require a pro subscription,
and keep vi.stubEnv('DEPLOYMENT_MODE', 'cloud') and the call to
requiresProSubscription('pagespace', undefined, 'free') expecting false; then
add a new test that stubs cloud mode and passes a pro-tier model (use the same
symbol logic as getPageSpaceModelTier by supplying a model string that maps to
pro) to requiresProSubscription (e.g., requiresProSubscription('pagespace',
'<pro-model-string>', 'free')) and assert true to validate the pro-model path.
Ensure tests reference requiresProSubscription and rely on getPageSpaceModelTier
behavior.

---

Nitpick comments:
In `@apps/web/src/lib/subscription/rate-limit-middleware.ts`:
- Around line 92-96: The JSDoc above requiresProSubscription is stale: it claims
only on-prem bypasses subscription gating but the function returns false
whenever isBillingEnabled() is false (covering on-prem and non-billing tenants).
Update the comment to state that subscription gating is bypassed when billing is
disabled (i.e., both on-prem and tenants without billing), and mention the guard
uses isBillingEnabled() to short-circuit; reference the function
requiresProSubscription and the isBillingEnabled() check so readers know where
the behavior is enforced.

In `@apps/web/src/lib/subscription/usage-service.ts`:
- Around line 14-18: Update the misleading "on-prem" comment wording to reflect
"billing-disabled" semantics: change the comment above getUsageLimits (and the
other occurrences around lines with similar comments) so it says it returns -1
for billing-disabled modes (i.e., when isBillingEnabled() is false) rather than
referencing “on-prem”; locate the comments near the getUsageLimits function and
the same comment blocks referenced by the reviewer and replace “on-prem” with
“billing-disabled” (or similar phrasing like “billing-disabled
deployments/modes, including tenant”) to accurately describe the behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16bb678e-4e63-46c6-9019-b9e74b781292

📥 Commits

Reviewing files that changed from the base of the PR and between 0715939 and 3d19cdf.

📒 Files selected for processing (8)
  • apps/web/src/hooks/useBillingVisibility.ts
  • apps/web/src/lib/billing/billing-visibility.ts
  • apps/web/src/lib/subscription/__tests__/rate-limit-middleware.test.ts
  • apps/web/src/lib/subscription/__tests__/usage-service.test.ts
  • apps/web/src/lib/subscription/rate-limit-middleware.ts
  • apps/web/src/lib/subscription/usage-service.ts
  • packages/lib/src/__tests__/deployment-mode.test.ts
  • packages/lib/src/deployment-mode.ts

Comment thread apps/web/src/lib/subscription/__tests__/rate-limit-middleware.test.ts Outdated
…omments

Rename misleading test, add missing pro-model assertion, and replace
"on-prem" comments with "billing-disabled" to accurately cover both
on-prem and tenant deployment modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@2witstudios
2witstudios merged commit cc8b91d into master Mar 17, 2026
10 checks passed
@2witstudios
2witstudios deleted the pu/epic-4-tenant-mode branch March 17, 2026 02:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant