Skip to content

fix: simplify free-only detection, add regression tests for Zen subscriber downgrade#1

Closed
mrosnerr wants to merge 2 commits into
MoerAI:fix/free-model-fallback-v2from
mrosnerr:fix/free-model-fallback-regression-tests
Closed

fix: simplify free-only detection, add regression tests for Zen subscriber downgrade#1
mrosnerr wants to merge 2 commits into
MoerAI:fix/free-model-fallback-v2from
mrosnerr:fix/free-model-fallback-regression-tests

Conversation

@mrosnerr
Copy link
Copy Markdown

Building on the work in this branch — we ran into a few edge cases while reviewing and wanted to contribute tests and a simplified approach. Feel free to merge, cherry-pick what's useful, or close if you'd prefer to take a different direction.

What changed

Simplified free-only detection

  • Replaced the two-point interception (gate category default + replace fallback chain) with an append strategy — free models are added as a tail to the existing chain rather than replacing it
  • Removed getFreeOnlyCategoryDefaultModel and getFallbackChainForFreeOnlyProviders in favor of a single appendFreeModelFallbacks that preserves chain ordering and deduplicates
  • Restored connectedProviders to its original conditional read (only on cold cache), matching dev branch behavior

Fixed Zen subscriber downgrade

  • On warm cache, free-only is now derived from availableModels (checks if all models are free) rather than connectedProviders, which can't distinguish free-tier from paid Zen users
  • Paid users whose only provider is opencode are no longer force-downgraded to free models

Tests lean on production code instead of hardcoding models

  • Test fixtures reference CATEGORY_MODEL_REQUIREMENTS.ultrabrain.fallbackChain and FREE_ONLY_FALLBACK_CHAIN directly — no hardcoded model names that break when the model roster rotates
  • Added a coupling invariant test ensuring FREE_ONLY_FALLBACK_CHAIN providers stay in sync with FREE_ONLY_PROVIDER_IDS
  • Added ordering assertion for appendFreeModelFallbacks to guarantee original chain comes first, then free models in priority order

Cold cache limitation noted

  • The connectedProviders-based detection is still used on cold cache (no model list available). Left a TODO noting this is removable once subagent-resolver falls back to a free model instead of matchedAgent.model on cold cache.

…rade and chain degradation

Add tests proving 4 issues in the free-only fallback implementation:

P0: Paid Zen subscribers (connectedProviders=["opencode"]) get
force-downgraded to free models because isFreeOnlyProviderConfiguration
cannot distinguish free-tier from paid Zen users.

P0: Category defaults for paid models are silently dropped via
getFreeOnlyCategoryDefaultModel when the free-only gate fires.

P0: Fallback chains are rewritten to the free-only chain even when
availableModels proves the user has paid model access.

P2: getFallbackChainForFreeOnlyProviders returns a degraded single-entry
filtered chain instead of the full FREE_ONLY_FALLBACK_CHAIN when the
original chain contains exactly one free model.

Also adds coupling invariant test ensuring FREE_ONLY_FALLBACK_CHAIN
providers stay in sync with FREE_ONLY_PROVIDER_IDS.

All 4 regression tests fail on the current PR code, confirming the bugs.
…criber downgrade

Replace the two-point interception approach (gate category default +
replace fallback chain) with a simpler append strategy:

- Restore connectedProviders to conditional read (matches dev branch)
- Remove getFreeOnlyCategoryDefaultModel (was silently dropping paid
  category defaults)
- Remove getFallbackChainForFreeOnlyProviders (was replacing chains
  wholesale, degrading to single-entry filtered chains)
- Add appendFreeModelFallbacks: appends free models as a tail to the
  existing chain with deduplication
- Derive freeOnly from availableModels on warm cache (correct signal),
  fall back to connectedProviders on cold cache only
- Skip non-free entries in cold-cache loop when freeOnly

Paid users are completely unaffected (warm-cache path identical to dev).
Free users get free models appended as a safety net after the system
chain naturally exhausts.
@github-actions
Copy link
Copy Markdown

Thank you for your contribution! Before we can merge this PR, we need you to sign our Contributor License Agreement (CLA).

To sign the CLA, please comment on this PR with:

I have read the CLA Document and I hereby sign the CLA

This is a one-time requirement. Once signed, all your future contributions will be automatically accepted.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@mrosnerr mrosnerr closed this Apr 30, 2026
@mrosnerr mrosnerr deleted the fix/free-model-fallback-regression-tests branch April 30, 2026 14:04
@mrosnerr mrosnerr restored the fix/free-model-fallback-regression-tests branch April 30, 2026 14:38
@mrosnerr mrosnerr reopened this Apr 30, 2026
@MoerAI MoerAI deleted the branch MoerAI:fix/free-model-fallback-v2 May 6, 2026 11:03
@MoerAI MoerAI closed this May 6, 2026
MoerAI pushed a commit that referenced this pull request May 20, 2026
- Add focused ROADMAP with TOC and clear priorities
- Pin package layering refactor as #1 urgent work
- Add multi-harness agent OS notice to all language READMEs
- Add ROADMAP section to root README with contributor guidance
- All ROADMAP-related PRs should use the ROADMAP label
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.

2 participants