Skip to content

Allow grandfathered orgs to use platform AI - #259

Open
JoachimLK wants to merge 1 commit into
mainfrom
fix/grandfather-byok
Open

Allow grandfathered orgs to use platform AI#259
JoachimLK wants to merge 1 commit into
mainfrom
fix/grandfather-byok

Conversation

@JoachimLK

@JoachimLK JoachimLK commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Enable access to the platform's OpenRouter key for grandfathered accounts.
Previously, these accounts were blocked from the platform fallback, leaving
any user without a custom AI config unable to perform analyses. Monthly budget caps and daily spend limits remain in effect to ensure safe usage.

Additionally, switch the default platform model to gemini-3.5-flash-lite for improved cost-efficiency.

Summary

  • What does this PR change?
  • Why is this needed?

PR title must follow Conventional Commits — e.g. feat(jobs): add bulk import or fix: handle null salary. The squash-merged title is what release-please uses to generate the changelog and pick the next version. PRs with non-conventional titles are blocked by CI.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Chore

Validation

  • I tested locally
  • I added/updated relevant documentation
  • I verified multi-tenant scoping and auth behavior for affected API paths

DCO

  • All commits in this PR are signed off (Signed-off-by) via git commit -s

Summary by CodeRabbit

  • New Features

    • Grandfathered accounts now receive a $2 monthly platform-funded AI budget.
    • Platform AI access can fall back for all account tiers when enabled and within budget limits.
  • Improvements

    • Gemini 3.5 Flash Lite is now the default AI model, with GPT-5.4 Mini available as an alternative.
    • Added pricing support and validation for the default model and available models.

Enable access to the platform's OpenRouter key for grandfathered
accounts.
Previously, these accounts were blocked from the platform fallback,
leaving
any user without a custom AI config unable to perform analyses. Monthly
budget caps and daily spend limits remain in effect to ensure safe
usage.

Additionally, switch the default platform model to gemini-3.5-flash-lite
for improved cost-efficiency.
@railway-app

railway-app Bot commented Aug 7, 2026

Copy link
Copy Markdown

🚅 Deployed to the reqcore-pr-259 environment in applirank

Service Status Web Updated (UTC)
applirank ✅ Success (View Logs) Aug 7, 2026 at 2:10 pm

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR changes the default platform model to google/gemini-3.5-flash-lite, adds its pricing, and validates provider pricing coverage. It also enables platform-key fallback for grandfathered organizations and assigns them a $2 monthly budget.

Changes

Platform AI configuration

Layer / File(s) Summary
Model pricing and defaults
server/utils/ai/pricing.ts, server/utils/ai/provider.ts, server/utils/env.ts, .env.example, tests/unit/ai-cost-pricing.test.ts
The platform default changes to google/gemini-3.5-flash-lite. Its input and output prices are added to MODEL_PRICING. Tests validate the default and registered OpenRouter models.
Grandfathered platform fallback
server/utils/ai/budget.ts, server/utils/ai/platformConfig.ts, server/utils/ai/resolveProvider.ts, tests/unit/ai-provider-grandfathered.test.ts
Grandfathered organizations can use the platform key when BYOK is missing. Their monthly platform-paid budget is set to $2. Existing disabled-platform behavior remains covered by tests.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Organization
  participant resolveProvider
  participant canUsePlatformAi
  participant OpenRouter
  Organization->>resolveProvider: Request AI provider without BYOK
  resolveProvider->>canUsePlatformAi: Check OpenRouter API key
  canUsePlatformAi-->>resolveProvider: Platform AI available
  resolveProvider->>OpenRouter: Use platform-key fallback with Gemini model
  OpenRouter-->>Organization: Return response under budget controls
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the change, but the required Summary, Type of change, Validation, and DCO sections remain incomplete. Complete the template sections by adding the change rationale, selecting the change type, recording validation, and confirming DCO sign-off.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary change: allowing grandfathered organizations to use platform AI.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/grandfather-byok

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

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
tests/unit/ai-cost-pricing.test.ts (1)

31-34: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Check rate equality, not only price presence.

The test confirms that every registry model has a pricing entry, but it does not compare the registry rates in server/utils/ai/provider.ts Lines 112-113 with the billing rates. A mismatch would pass this test and make displayed cost differ from budget calculation. Compare both input and output rates, or use one source of truth.

Suggested assertion
 for (const model of PROVIDER_REGISTRY.openrouter.models) {
-  expect(getModelPrice(model.id), `unpriced model: ${model.id}`).not.toBeNull()
+  const price = getModelPrice(model.id)
+  expect(price, `unpriced model: ${model.id}`).not.toBeNull()
+  expect(price?.inputPer1m).toBe(model.inputPricePer1m)
+  expect(price?.outputPer1m).toBe(model.outputPricePer1m)
 }
🤖 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 `@tests/unit/ai-cost-pricing.test.ts` around lines 31 - 34, Update the test
around “prices every model offered in the OpenRouter provider registry” to
compare each registry model’s input and output rates against the billing rates
returned by getModelPrice, rather than only asserting a non-null result. Ensure
both rate values must match so displayed pricing and budget calculation remain
consistent, or reuse a shared source of truth for the comparison.
server/utils/ai/budget.ts (1)

32-37: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add a regression test for the complete grandfathered budget path.

The new cap applies only when resolveOrgPlanId returns exactly grandfathered; free exits through a different count-based branch. Assert the $2 value and test that assertPlatformBudget blocks at the monthly boundary for a grandfathered organization.

🤖 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 `@server/utils/ai/budget.ts` around lines 32 - 37, Add regression coverage for
the grandfathered path in the budget tests: mock or configure resolveOrgPlanId
to return exactly grandfathered, assert the resolved budget is $2, and verify
assertPlatformBudget rejects usage at the monthly $2 boundary. Keep free-plan
coverage on its existing count-based branch.
server/utils/ai/resolveProvider.ts (1)

72-76: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add the platform budget gate to generate criteria paths.

server/api/ai-config/generate-criteria.post.ts resolves billingMode: 'platform' but does not check assertPlatformBudget. Add the same platform budget check there for normal and grandfathered organizations; assertPlatformBudget already includes the per-org monthly cap and global daily cap. Add integration coverage around the $2 grandfathered/free boundary.

🤖 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 `@server/utils/ai/resolveProvider.ts` around lines 72 - 76, Update the platform
billing path in generate-criteria handling to call assertPlatformBudget for both
normal and grandfathered organizations before generation proceeds, matching the
existing budget-gating behavior. Add integration coverage for the $2 boundary,
verifying grandfathered/free organizations are allowed below the limit and
rejected at or above it.
🤖 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 `@server/utils/ai/resolveProvider.ts`:
- Around line 72-76: The fallback in resolveProvider must apply only when
loadAiConfig reports genuinely missing AI configuration. In the catch path,
distinguish the expected missing-config condition from database, decryption,
read, or validation failures; re-throw all non-missing-config errors before
constructing or returning the platform-backed OpenRouter configuration.

In `@server/utils/env.ts`:
- Around line 204-205: Update server/utils/env.ts lines 204-205 to validate
OPENROUTER_MODEL against MODEL_PRICING at runtime and fail closed when the
configured model has no pricing entry, preserving the default only if it is
priced. Update .env.example lines 120-123 to document this enforced validation
rather than relying on manual configuration.

---

Nitpick comments:
In `@server/utils/ai/budget.ts`:
- Around line 32-37: Add regression coverage for the grandfathered path in the
budget tests: mock or configure resolveOrgPlanId to return exactly
grandfathered, assert the resolved budget is $2, and verify assertPlatformBudget
rejects usage at the monthly $2 boundary. Keep free-plan coverage on its
existing count-based branch.

In `@server/utils/ai/resolveProvider.ts`:
- Around line 72-76: Update the platform billing path in generate-criteria
handling to call assertPlatformBudget for both normal and grandfathered
organizations before generation proceeds, matching the existing budget-gating
behavior. Add integration coverage for the $2 boundary, verifying
grandfathered/free organizations are allowed below the limit and rejected at or
above it.

In `@tests/unit/ai-cost-pricing.test.ts`:
- Around line 31-34: Update the test around “prices every model offered in the
OpenRouter provider registry” to compare each registry model’s input and output
rates against the billing rates returned by getModelPrice, rather than only
asserting a non-null result. Ensure both rate values must match so displayed
pricing and budget calculation remain consistent, or reuse a shared source of
truth for the comparison.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 71349e21-007b-4f1c-99d2-02ae263007e8

📥 Commits

Reviewing files that changed from the base of the PR and between 2d970e4 and e35600e.

📒 Files selected for processing (9)
  • .env.example
  • server/utils/ai/budget.ts
  • server/utils/ai/platformConfig.ts
  • server/utils/ai/pricing.ts
  • server/utils/ai/provider.ts
  • server/utils/ai/resolveProvider.ts
  • server/utils/env.ts
  • tests/unit/ai-cost-pricing.test.ts
  • tests/unit/ai-provider-grandfathered.test.ts

Comment on lines 72 to +76
// 2: no org config — fall back to the platform key if one is configured.
// This applies to grandfathered orgs too: they used to be excluded here,
// which left any grandfathered org without its own key unable to run
// analysis at all. Platform spend is capped per-org and globally in
// budget.ts, so the fallback is money-safe for every tier.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 '\bloadAiConfig\s*\(|No AI config|createError|throw' server tests

Repository: reqcore-inc/reqcore

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate resolveProvider and ai config helpers =="
fd -a 'resolveProvider\.ts|.*ai.*|.*config.*' server | sed 's#^\./##' | head -200

echo
echo "== resolveProvider.ts =="
cat -n server/utils/ai/resolveProvider.ts | sed -n '1,180p'

echo
echo "== focused loadAiConfig occurrences in server =="
rg -n -C 6 '\bloadAiConfig\s*\(' server --glob '!**/node_modules/**'

echo
echo "== focused No AI config occurrences =="
rg -n -C 4 'No AI config|aiConfig|AI configuration|custom provider|platform.*key|billingMode' server/utils server/lib 2>/dev/null || true

Repository: reqcore-inc/reqcore

Length of output: 26999


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== loadConfig.ts full =="
cat -n server/utils/ai/loadConfig.ts | sed -n '1,90p'

echo
echo "== createError definition and Nuxt error status handling =="
rg -n -C 8 'export function createError|function createError|statusCode|statusMessage|createError' server --glob '!**/node_modules/**' | head -250

echo
echo "== deterministic behavior of current catch guards for representative errors =="
python3 - <<'PY'
def choose(err, platform_key, platform_override_enabled, platform_override):
    class BudgetExceededError(Exception):
        pass
    # Mimic server/utils/ai/resolveProvider.ts catch body logic.
    if platform_key:
        if not platform_override_enabled(platform_override):
            if isinstance(err, BudgetExceededError):
                raise BudgetExceededError("hidden and fallback?")
            raise err
        if platform_override:
            return "platform-pinned"
        return "platform-generic"
    if isinstance(err, BudgetExceededError):
        raise BudgetExceededError("hidden and fallback?")
    raise err

def platform_override_enabled(value):
    return value is not None and value != "false"

class DatabaseError(Exception): pass
class DecryptError(Exception): pass
class SchemaValidationError(Exception): pass
class MissingConfigError(Exception):
    status = 422

for err in [DatabaseError("db"), DecryptError("decrypt"), SchemaValidationError("schema"), MissingConfigError("config")]:
    result = choose(err, "key", platform_override_enabled, "override")
    print(type(err).__name__, "with platform key:", result)

try:
    choose(DatabaseError("db"), None, platform_override_enabled, None)
except Exception as e:
    print(type(e).__name__, "without platform key:", type(e).__name__)
PY

Repository: reqcore-inc/reqcore

Length of output: 20513


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package lock/import hints for h3/createError =="
if [ -f package.json ]; then cat package.json | sed -n '1,160p'; fi
if [ -f server/package.json ]; then cat server/package.json | sed -n '1,160p'; fi
rg -n '"h3"|"nuxt|nitropack|createError' package.json server/package.json 2>/dev/null || true

echo
echo "== h3/createError types/imports in repo =="
rg -n "from ['\"]h3['\"]|import createError|createError\\(" server --glob '!**/node_modules/**' | head -120

Repository: reqcore-inc/reqcore

Length of output: 17706


Only fall back when AI configuration is truly absent.

The catch path also handles database/read/decrypt/validation errors from loadAiConfig; those errors can be transformed into a platform-paid billingMode: 'platform' run. Re-throw non-missing-config errors before returning the platform-backed OpenRouter config.

🤖 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 `@server/utils/ai/resolveProvider.ts` around lines 72 - 76, The fallback in
resolveProvider must apply only when loadAiConfig reports genuinely missing AI
configuration. In the catch path, distinguish the expected missing-config
condition from database, decryption, read, or validation failures; re-throw all
non-missing-config errors before constructing or returning the platform-backed
OpenRouter configuration.

Comment thread server/utils/env.ts
Comment on lines +204 to +205
/** Default model for platform-paid runs, OpenRouter-prefixed. Defaults to google/gemini-3.5-flash-lite. */
OPENROUTER_MODEL: emptyToUndefined.pipe(z.string().min(1)).optional().default('google/gemini-3.5-flash-lite'),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Enforce the platform model pricing invariant at runtime.

An unpriced model can produce a null cost and bypass the monthly platform budget. Documentation alone cannot preserve the budget guarantee.

  • server/utils/env.ts#L204-L205: validate OPENROUTER_MODEL against MODEL_PRICING, or fail closed before the platform request.
  • .env.example#L120-L123: document the enforced validation instead of relying on manual configuration.
📍 Affects 2 files
  • server/utils/env.ts#L204-L205 (this comment)
  • .env.example#L120-L123
🤖 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 `@server/utils/env.ts` around lines 204 - 205, Update server/utils/env.ts lines
204-205 to validate OPENROUTER_MODEL against MODEL_PRICING at runtime and fail
closed when the configured model has no pricing entry, preserving the default
only if it is priced. Update .env.example lines 120-123 to document this
enforced validation rather than relying on manual configuration.

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