Skip to content

feat(devpass): default service tier setting - #3071

Merged
steebchen merged 5 commits into
mainfrom
claude/devpass-service-tier-setting-c1yn5q
Jul 15, 2026
Merged

steebchen merged 5 commits into
mainfrom
claude/devpass-service-tier-setting-c1yn5q

Conversation

@steebchen

@steebchen steebchen commented Jul 15, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds a Default service tier setting to the DevPass dashboard so users can opt their dev-plan routing into cheaper flex processing to save on plan credits. Defaults to standard; flex is optional.

Dashboard (apps/code)

  • New "Default service tier" select on the DevPass settings page (Standard (recommended) / Flex), wired to PATCH /dev-plans/settings with optimistic state + rollback on failure, linking to the service-tiers docs page.

API (apps/api)

  • devPlanServiceTier ("default" | "flex") added to GET /dev-plans/status and PATCH /dev-plans/settings (with audit logging of changes), and to the organization response schema.

DB (packages/db)

  • New organization.devPlanServiceTier column (text enum, not null, default "default") + generated migration.

Gateway (apps/gateway)

  • When a dev-plan org has the flex default enabled and the request does not specify service_tier itself, the gateway applies service_tier: "flex" before service-tier provider filtering. The org fetch (and isDevPlan) was moved ahead of that filtering block to make the setting available there; the end-user-wallet credits substitution (withWalletCredits) stays at its original site, since orgs backing end-user wallets are always regular PAYG/credits orgs — never dev-plan orgs — so it cannot affect the dev-plan default (invariant documented in AGENTS.md).
  • The default is applied softly: only when at least one candidate provider mapping supports flex and has a credential that reaches the provider's real upstream (mirroring the existing service-tier key-eligibility rules). Models/providers without flex support stay on standard processing instead of failing the explicit-tier validation.
  • An explicit service_tier on the request always wins over the org default.
  • used_service_tier is now surfaced in response metadata whenever a premium tier was actually served, even without a client-requested tier, so default-applied flex stays visible. Billing already keys off the served tier, so the flex multiplier applies automatically.

Tests

  • Gateway specs: default flex is applied and billed/logged (usedServiceTier: "flex", requestedServiceTier: null), explicit service_tier overrides the default, and the default is skipped for models without flex support.
  • Test harness setDevPlan now accepts serviceTier; existing mock org literals updated for the new column.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HfQCYRgCLi3GvW5ymvTkDE

Summary by CodeRabbit

  • New Features
    • Added a DevPlan service-tier setting with Default and Flex options.
    • DevPlan organizations configured for Flex now automatically use Flex service tier when supported and no tier is explicitly requested.
    • Added service-tier details to organization and DevPlan status responses.
    • Explicit request-level service tiers continue to take precedence over organization defaults.
  • Bug Fixes
    • Improved service-tier metadata accuracy in API responses.

Add a DevPass dashboard setting to choose the default service tier for
dev-plan routing. Defaults to standard; can be set to flex to save plan
credits. The gateway applies the org-level flex default only when the
request doesn't specify service_tier itself and only for provider
mappings that support flex with an upstream-eligible credential, so
unsupported models stay on standard processing instead of failing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HfQCYRgCLi3GvW5ymvTkDE
Copilot AI review requested due to automatic review settings July 15, 2026 12:38
@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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

Failed to post review comments.

We encountered an issue with GitHub. Use @coderabbitai full review to retry the review.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d368e519-148e-4278-8826-2c9ff5ef68f3

📥 Commits

Reviewing files that changed from the base of the PR and between 771dbce and f15f134.

📒 Files selected for processing (4)
  • packages/db/migrations/1784125968_yummy_strong_guy.sql
  • packages/db/migrations/meta/1784125968_snapshot.json
  • packages/db/migrations/meta/_journal.json
  • packages/db/src/schema.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/db/src/schema.ts
⏰ Context from checks skipped due to timeout. (16)
  • GitHub Check: test / run
  • GitHub Check: e2e-shards (6)
  • GitHub Check: e2e-shards (5)
  • GitHub Check: e2e-shards (1)
  • GitHub Check: e2e-shards (9)
  • GitHub Check: e2e-shards (8)
  • GitHub Check: e2e-shards (4)
  • GitHub Check: e2e-shards (2)
  • GitHub Check: e2e-shards (7)
  • GitHub Check: e2e-shards (3)
  • GitHub Check: e2e-shards (10)
  • GitHub Check: lint / run
  • GitHub Check: generate / run
  • GitHub Check: build / run
  • GitHub Check: autofix
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

Use tabs for indentation.

Files:

  • packages/db/migrations/meta/1784125968_snapshot.json
  • packages/db/migrations/meta/_journal.json
packages/db/migrations/**

📄 CodeRabbit inference engine (AGENTS.md)

Never manually create migrations or manually resolve conflicts in migration, journal, or snapshot files; regenerate migrations with pnpm migrations after resetting migrations before a merge.

Files:

  • packages/db/migrations/meta/1784125968_snapshot.json
  • packages/db/migrations/1784125968_yummy_strong_guy.sql
  • packages/db/migrations/meta/_journal.json
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Run pnpm format before committing and run pnpm build after feature work; run pnpm build when API routes are modified.
Commit completed feature or bug-fix changes; use conventional commit messages with titles no longer than 50 characters.
Do not amend commits after pushing; never force-push the main/default branch.
Open pull requests as ready-for-review rather than draft unless explicitly requested; always provide and update both the conventional title and description.

Files:

  • packages/db/migrations/meta/1784125968_snapshot.json
  • packages/db/migrations/1784125968_yummy_strong_guy.sql
  • packages/db/migrations/meta/_journal.json

Walkthrough

Adds a persisted DevPlan service-tier setting, exposes it through APIs and the dashboard, applies eligible DevPlan-flex defaults in chat routing, and updates service-tier metadata plus gateway test coverage.

Changes

DevPlan service tier

Layer / File(s) Summary
Tier storage and API contracts
packages/db/..., apps/api/src/routes/dev-plans.ts, apps/api/src/routes/organization.ts
Adds the non-null devPlanServiceTier organization field and exposes, updates, audits, and validates it through the DevPlan and organization APIs.
Dashboard service-tier settings
apps/code/src/app/dashboard/...
Passes the effective tier into DevPlanSettings and adds a selectable setting with persistence, loading state, notifications, and rollback on failure.
Gateway default routing
apps/gateway/src/chat/chat.ts, AGENTS.md
Loads organization state earlier and applies the DevPlan flex default only when no premium tier was requested and eligible credentials support flex routing.
Tier metadata and routing validation
apps/gateway/src/chat/tools/..., apps/gateway/src/api.spec.ts, apps/gateway/src/test-utils/..., apps/gateway/src/lib/rate-limit.spec.ts
Separates requested and used service-tier metadata and tests defaults, explicit overrides, unsupported models, and updated organization fixtures.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Dashboard
  participant DevPlanAPI
  participant Database
  participant ChatClient
  participant Gateway
  participant ProviderCredentials

  Dashboard->>DevPlanAPI: update devPlanServiceTier
  DevPlanAPI->>Database: persist organization tier
  ChatClient->>Gateway: submit chat request
  Gateway->>Database: load organization DevPlan tier
  Gateway->>ProviderCredentials: check flex routing eligibility
  ProviderCredentials-->>Gateway: return credential eligibility
  Gateway-->>ChatClient: return response with requested and used service tiers
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a default service tier setting for DevPass organizations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/devpass-service-tier-setting-c1yn5q

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.

@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/code/src/app/dashboard/components/DevPlanSettings.tsx (1)

134-142: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Invalidate the query cache to keep global state in sync.

Consider calling await invalidateStatus() here after the successful mutation, similar to how it is done in handleRetentionToggle. This ensures that any other components relying on useDevPlanStatus reflect the updated service tier immediately without waiting for a refetch (e.g., on window focus).

♻️ Proposed refactor
 		try {
 			await updateSettingsMutation.mutateAsync({
 				body: { devPlanServiceTier: tier },
 			});
+			await invalidateStatus();
 			toast.success(
 				tier === "flex"
 					? "Requests default to flex processing"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/code/src/app/dashboard/components/DevPlanSettings.tsx` around lines 134
- 142, After the successful updateSettingsMutation.mutateAsync call in the tier
update handler, await invalidateStatus() before displaying the success toast,
matching the existing handleRetentionToggle flow so useDevPlanStatus consumers
receive the updated service tier immediately.
apps/gateway/src/chat/chat.ts (2)

2017-2019: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reuse serviceTierOrgKeys to avoid redundant cache/database calls.

If serviceTierOrgKeys is already declared in the outer scope at this point (as seen utilized later down in enforceServiceTierKeyEligibility), consider populating and reusing it here to save an unnecessary findActiveProviderKeys cache/DB call.

♻️ Proposed refactor
-		const orgKeysForDefaultTier = await findActiveProviderKeys(
-			project.organizationId,
-		);
+		if (serviceTierOrgKeys === undefined) {
+			serviceTierOrgKeys = await findActiveProviderKeys(project.organizationId);
+		}

(and update the reference inside providerHasEligibleTierCredential to use serviceTierOrgKeys! instead of orgKeysForDefaultTier)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/gateway/src/chat/chat.ts` around lines 2017 - 2019, Reuse the
outer-scope serviceTierOrgKeys when determining default-tier provider keys
instead of calling findActiveProviderKeys again, populating it only when needed
and preserving the existing lookup result. Update
providerHasEligibleTierCredential to reference serviceTierOrgKeys! rather than
orgKeysForDefaultTier, while keeping enforceServiceTierKeyEligibility aligned
with the shared value.

2000-2002: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify boolean expression and remove redundant optional chaining.

Since organization is already checked and guaranteed to be non-null above (it throws a 500 otherwise), the optional chaining ?. is unnecessary. Additionally, the explicit Boolean() wrapper can be safely removed, as the expression inherently evaluates to a boolean.

♻️ Proposed refactor
-	const isDevPlan = Boolean(
-		organization?.kind === "devpass" && organization.devPlan !== "none",
-	);
+	const isDevPlan =
+		organization.kind === "devpass" && organization.devPlan !== "none";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/gateway/src/chat/chat.ts` around lines 2000 - 2002, Update the isDevPlan
assignment to remove the redundant Boolean wrapper and optional chaining, using
the already validated organization object directly in the boolean comparison.
Preserve the existing devpass and devPlan !== "none" conditions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/gateway/src/chat/tools/transform-response-to-openai.ts`:
- Around line 61-63: Update the conditional spread in the response
transformation to check requestedServiceTier and usedServiceTier directly
against undefined rather than relying on truthiness, so an explicitly assigned
null usedServiceTier still emits used_service_tier: null.

---

Nitpick comments:
In `@apps/code/src/app/dashboard/components/DevPlanSettings.tsx`:
- Around line 134-142: After the successful updateSettingsMutation.mutateAsync
call in the tier update handler, await invalidateStatus() before displaying the
success toast, matching the existing handleRetentionToggle flow so
useDevPlanStatus consumers receive the updated service tier immediately.

In `@apps/gateway/src/chat/chat.ts`:
- Around line 2017-2019: Reuse the outer-scope serviceTierOrgKeys when
determining default-tier provider keys instead of calling findActiveProviderKeys
again, populating it only when needed and preserving the existing lookup result.
Update providerHasEligibleTierCredential to reference serviceTierOrgKeys! rather
than orgKeysForDefaultTier, while keeping enforceServiceTierKeyEligibility
aligned with the shared value.
- Around line 2000-2002: Update the isDevPlan assignment to remove the redundant
Boolean wrapper and optional chaining, using the already validated organization
object directly in the boolean comparison. Preserve the existing devpass and
devPlan !== "none" conditions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2a5a7584-b287-42b7-940b-77a79112c207

📥 Commits

Reviewing files that changed from the base of the PR and between 434a9fc and b28db0a.

📒 Files selected for processing (13)
  • apps/api/src/routes/dev-plans.ts
  • apps/api/src/routes/organization.ts
  • apps/code/src/app/dashboard/(main)/settings/page.tsx
  • apps/code/src/app/dashboard/components/DevPlanSettings.tsx
  • apps/gateway/src/api.spec.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/chat/tools/transform-response-to-openai.ts
  • apps/gateway/src/lib/rate-limit.spec.ts
  • apps/gateway/src/test-utils/gateway-api-test-harness.ts
  • packages/db/migrations/1784117115_lonely_the_liberteens.sql
  • packages/db/migrations/meta/1784117115_snapshot.json
  • packages/db/migrations/meta/_journal.json
  • packages/db/src/schema.ts

Comment on lines +61 to 63
...(extras.requestedServiceTier || extras.usedServiceTier
? { used_service_tier: extras.usedServiceTier ?? null }
: {}),

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check against undefined to safely include null values.

If the intent is to surface used_service_tier: null when an organization-default tier is downgraded by the provider, this condition will fail to do so. Since null is falsy, extras.requestedServiceTier || extras.usedServiceTier evaluates to false when requestedServiceTier is undefined and usedServiceTier is null, causing the field to be omitted rather than emitted as null.

To safely include explicitly assigned null values, compare directly against undefined.

♻️ Proposed fix
-		...(extras.requestedServiceTier || extras.usedServiceTier
+		...(extras.requestedServiceTier !== undefined || extras.usedServiceTier !== undefined
			? { used_service_tier: extras.usedServiceTier ?? null }
			: {}),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
...(extras.requestedServiceTier || extras.usedServiceTier
? { used_service_tier: extras.usedServiceTier ?? null }
: {}),
...(extras.requestedServiceTier !== undefined || extras.usedServiceTier !== undefined
? { used_service_tier: extras.usedServiceTier ?? null }
: {}),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/gateway/src/chat/tools/transform-response-to-openai.ts` around lines 61
- 63, Update the conditional spread in the response transformation to check
requestedServiceTier and usedServiceTier directly against undefined rather than
relying on truthiness, so an explicitly assigned null usedServiceTier still
emits used_service_tier: null.

claude added 4 commits July 15, 2026 14:23
End-user wallets are always backed by regular PAYG/credits orgs, never
dev-plan orgs, so the withWalletCredits substitution has no bearing on
the dev-plan default service tier and stays where credit gating needs
it. Document the invariant in AGENTS.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HfQCYRgCLi3GvW5ymvTkDE
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HfQCYRgCLi3GvW5ymvTkDE
@steebchen
steebchen enabled auto-merge July 15, 2026 14:41
@steebchen
steebchen added this pull request to the merge queue Jul 15, 2026
Merged via the queue into main with commit fc2f5f1 Jul 15, 2026
22 of 23 checks passed
@steebchen
steebchen deleted the claude/devpass-service-tier-setting-c1yn5q branch July 15, 2026 14:59
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.

3 participants