Skip to content

fix: factory accounting, retry, and fallback contracts#36

Merged
stackbilt-admin merged 1 commit intomainfrom
fix-factory-contract-bugs
Apr 12, 2026
Merged

fix: factory accounting, retry, and fallback contracts#36
stackbilt-admin merged 1 commit intomainfrom
fix-factory-contract-bugs

Conversation

@stackbilt-admin
Copy link
Copy Markdown
Member

Summary

Fixes four contract bugs in the factory orchestration layer:

Also adds requestForProvider() helper so fallback-routed requests carry the rule-specified model, and expands factory + provider test coverage (346 insertions).

Closes #31, closes #32, closes #33, closes #34

Test plan

  • npm test — 184 tests pass across 11 suites
  • npm run typecheck — clean
  • Verify ledger spend tracking with costOptimization: false, ledger: <CreditLedger>
  • Verify enableRetries: false prevents provider-level retries
  • Verify fallback rules with explicit fallbackProvider/fallbackModel route correctly

🤖 Generated with Claude Code

Copy link
Copy Markdown
Member Author

@stackbilt-admin stackbilt-admin left a comment

Choose a reason for hiding this comment

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

CodeBeast Review — PR #36 (fix: factory accounting, retry, and fallback contracts)

Verdict: APPROVE with 2 observations

What's good

  1. Ledger wiring fix (#31) — Passing ledger to CostTracker at construction and tracking spend when ledger OR costOptimization is configured is the correct fix. Previously ledger-only consumers silently lost all cost tracking. The CreditLedger integration test at the bottom validates the full pipe: factory → response → accumulator → rate limit counters.

  2. enableRetries: false (#32) — The maxRetries: 0 injection when enableRetries === false && providerConfig.maxRetries === undefined is clean. Preserves explicit per-provider overrides. Good that updateConfig re-initialises providers when enableRetries changes — prevents stale provider instances holding onto old retry counts.

  3. Tool-capable model validation (#33) — Moving from "silently strip tools" to "throw ConfigurationError" is a better contract. Silent stripping masked bugs where a caller expected tool output and got freeform text instead. The Groq and Cerebras implementations are consistent.

  4. Fallback routing (#34)getFallbackDecision()applyFallbackDecision() is a solid refactor. The providerModels map + requestForProvider() helper ensures the fallback-specified model actually reaches the provider. The splice logic in applyFallbackDecision correctly:

    • Skips if target === failed provider
    • Skips if target already tried (index ≤ currentIndex)
    • Deduplicates if target appears later in the chain
    • Inserts at nextIndex so it's the immediate next attempt
  5. isLedgerLimited() extraction — Consolidates the 4-dimension rate-limit check into one method. Minor but removes duplication.

Observations (non-blocking)

O-1: Custom fallback rules evaluated before error-type checks. In getFallbackDecision(), custom rules are evaluated after the AuthenticationError/ConfigurationError gates but before the CircuitBreaker/RateLimit/ServerError catches. This means a custom rule with condition: 'error' will match on a RateLimitError and potentially override the default fallback behavior (which would have returned { shouldFallback: true } without specifying a provider). This is probably the desired behavior — custom rules are more specific — but it's a subtle ordering dependency worth a comment.

O-2: KV race on concurrent consume. The existing checkHourlyRateLimit (not changed in this PR, just context) does read-then-write on KV without atomic increment. Two concurrent requests can both read count=99, both write 100, and both pass a limit of 100. This is pre-existing and out of scope for this PR — KV doesn't support atomic increments — but worth noting for the #102 anonymous quota work that just landed in edge-auth (same pattern, same limitation, acceptable for the use case).

Tests

  • Factory tests cover all 4 issues with targeted assertions
  • Mock provider beforeEach reset is thorough (prevents inter-test leakage)
  • Cerebras + Groq tool validation tests correctly assert ConfigurationError + mockFetch not called

Ship it.

🤖 Reviewed by CodeBeast (0xDEBT666F)

@stackbilt-admin stackbilt-admin marked this pull request as ready for review April 12, 2026 14:49
@stackbilt-admin stackbilt-admin merged commit 16e4771 into main Apr 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant