Skip to content

bugfix: issue-191 strict model routing bypass#197

Merged
thushan merged 11 commits into
mainfrom
bugfix/191-strict-model-routing-bypass
Jul 20, 2026
Merged

bugfix: issue-191 strict model routing bypass#197
thushan merged 11 commits into
mainfrom
bugfix/191-strict-model-routing-bypass

Conversation

@thushan

@thushan thushan commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Strict routing could be bypassed when a request used a model_aliases entry whose target model existed on no endpoint: the routing decision was logged as rejected, but the request was still proxied to a compatible backend and returned 200. Requests now fail fast with a decision-aware 404/503 and routing_action: rejected, matching how unknown models are already handled.

This resolves #191

Changes

  • Alias resolution no longer falls back to all profile-compatible endpoints when the routing decision is a rejection; it returns no endpoints so the request short-circuits before dispatch
  • Shared post-selection dispatch tail across /olla/proxy/ and provider-prefixed routes, so rejections behave identically on every route (incl. X-Olla-Routing-* headers)
  • Rejection short-circuit keys on the decision's status code rather than the action string, so base-registry rejections (no_model/no_healthy) can't slip through
  • Aliases that resolve to real models with no healthy serving candidate now record a routing decision (503 model_unavailable) instead of a generic 404 with no routing headers
  • Anthropic translation route honours the recorded routing decision on zero-endpoint rejection (correct 503 vs 404, routing headers, shared rejection log) while keeping Anthropic-shaped JSON error bodies
  • Short-circuited rejections are logged as Request rejected (warn) rather than Request completed
  • Docs updated: unroutable aliases fail fast under strict routing and non-all fallback behaviours

Behavioural notes

  • /olla/proxy/ with zero healthy endpoints now returns 503 (previously 502 from the proxy engine); adjust any monitors keyed on 502 which most folks won't have we think.
  • Under optimistic routing with fallback_behavior: none or compatible_only, unroutable aliases now also fail fast (consistent with the non-alias path)

Summary by CodeRabbit

  • Bug Fixes

    • Model alias requests that resolve to no routable endpoints now fail fast instead of being proxied to an incompatible backend.
    • Responses now use routing-aware status codes (e.g., 404/503) and return consistent routing decision headers and error details across proxy and translation paths.
    • Strict routing enforces correct rejection for both missing and unhealthy-only alias targets.
  • Documentation

    • Updated alias routing documentation to clarify fallback and rejection behaviour by routing strategy.
  • Tests

    • Added and expanded regression and strict-routing test coverage, including a routing strategy script check.

thushan added 9 commits July 2, 2026 17:38
Rewrites TestResolveAliasEndpoints_NoMatchingEndpoints to assert the fixed
empty-result behaviour instead of the old assert.Len(result,1) that codified
the bug, adds strict_strategy_test.go covering the three routing outcomes
(routed/rejected-503/rejected-404), and adds a full-handler httptest covering
both providerProxyHandler and proxyHandler with a real inspector chain to
prove a rejected alias never reaches the proxy engine. Also adds a guarded,
documented e2e case to test-model-routing-strategy.sh (needs a live Olla with
model_aliases + strict routing configured; not runnable in this environment).
@thushan thushan self-assigned this Jul 18, 2026
@thushan thushan added bug Something isn't working routing This issue is with routing labels Jul 18, 2026
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Unroutable model aliases now fail fast under strict routing instead of reaching compatible backends. Shared proxy and translation paths preserve routing decisions, statuses, headers, sticky-session data, and rejection logs, with unit, regression, documentation, and script coverage.

Changes

Strict alias routing

Layer / File(s) Summary
Strict routing outcomes
internal/adapter/registry/routing/strict_strategy_test.go, internal/app/handlers/handler_proxy_model_test.go
Strict routing tests and fixtures cover healthy matches, unavailable models returning 503, and missing models returning 404.
Alias resolution fail-fast handling
internal/app/handlers/handler_proxy.go, internal/app/handlers/handler_proxy_alias_test.go, internal/app/handlers/handler_proxy_alias_strict_test.go
Alias fallback uses alias-name routing, records routing decisions, rejects status-coded failures, and verifies that strict requests are not proxied while optimistic fallback remains available.
Shared rejection and response handling
internal/app/handlers/handler_provider_common.go, internal/app/handlers/handler_proxy.go, internal/app/handlers/handler_translation.go, internal/app/handlers/*strict_rejection_test.go, test/scripts/logic/test-model-routing-strategy.sh, docs/content/...
Proxy and translation flows centralise zero-endpoint handling, emit decision-aware statuses and routing headers, preserve telemetry, and document and exercise strict alias rejection.

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

Possibly related PRs

  • thushan/olla#99: Introduced the alias resolution path extended here with strict fail-fast handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.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 references issue 191 and the strict routing bypass fix.
Linked Issues check ✅ Passed The changes make unroutable aliases fail fast without proxying, matching the issue’s expected strict-routing behaviour.
Out of Scope Changes check ✅ Passed The documentation, tests, and handler changes all support the alias routing fix and do not दिख appear unrelated.
✨ 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 bugfix/191-strict-model-routing-bypass

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

🤖 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 `@internal/app/handlers/handler_proxy.go`:
- Around line 719-727: Update the alias no-candidate branch near
profile.RoutingDecision to consult the configured routing strategy and
fallback_behavior before rejecting. Preserve the healthy fallback path for
optimistic with fallback_behavior set to all, and only create the
model_unavailable rejected decision and return an empty endpoint list when the
active policy disallows fallback.

In `@test/scripts/logic/test-model-routing-strategy.sh`:
- Around line 91-101: Update the rejection check in the response-validation
block to require both status_code 404/503 and a decision_header containing
“X-Olla-Routing-Decision: rejected”; otherwise increment the script’s failure
counter or return a non-zero status so a proxied 200 fails the regression test.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9352dcf7-c2fc-48c3-8ec8-de1816030b84

📥 Commits

Reviewing files that changed from the base of the PR and between ff40d06 and c76556c.

📒 Files selected for processing (11)
  • docs/content/concepts/model-aliases.md
  • docs/content/configuration/reference.md
  • internal/adapter/registry/routing/strict_strategy_test.go
  • internal/app/handlers/handler_provider_common.go
  • internal/app/handlers/handler_proxy.go
  • internal/app/handlers/handler_proxy_alias_strict_test.go
  • internal/app/handlers/handler_proxy_alias_test.go
  • internal/app/handlers/handler_proxy_model_test.go
  • internal/app/handlers/handler_translation.go
  • internal/app/handlers/handler_translation_strict_rejection_test.go
  • test/scripts/logic/test-model-routing-strategy.sh

Comment thread internal/app/handlers/handler_proxy.go Outdated
Comment thread test/scripts/logic/test-model-routing-strategy.sh

@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.

🧹 Nitpick comments (2)
internal/app/handlers/handler_proxy_alias_test.go (2)

258-266: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the test documentation.

The comment explains what changed alongside PR history and uses spaced hyphens as em-dashes. As per coding guidelines, "Comment on why, not what. Concise and direct." and "No em-dashes."

♻️ Proposed refactor
-// TestResolveAliasEndpoints_NoIntersectionWithCandidates_OptimisticFallsBack covers the
-// other side of the same fix: under a registry that returns a fallback decision with
-// endpoints (optimistic routing, fallback_behavior: all) instead of a rejection, the
-// no-intersection branch must let that fallback through rather than unconditionally
-// returning empty. This is the behaviour CodeRabbit flagged as missing - previously this
-// branch always rejected, so optimistic/all could never recover here. The returned
-// endpoints don't serve the alias's actual target models (they're the routing strategy's
-// substitute, not an alias match), so the alias rewrite map must NOT be set - the proxy
-// has to forward the original request body unchanged.
+// TestResolveAliasEndpoints_NoIntersectionWithCandidates_OptimisticFallsBack ensures that optimistic
+// routing allows fallback when an alias has no compatible candidates. The fallback endpoints
+// are substitutes rather than direct matches, so the alias rewrite map must not be set.
🤖 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 `@internal/app/handlers/handler_proxy_alias_test.go` around lines 258 - 266,
Condense the comment above
TestResolveAliasEndpoints_NoIntersectionWithCandidates_OptimisticFallsBack to
state only why the no-intersection case must allow optimistic fallback endpoints
through without setting the alias rewrite map. Remove PR history, implementation
narration, and spaced-hyphen em-dash phrasing.

Source: Coding guidelines


196-204: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the test documentation.

The comment explains what changed by detailing its PR history and uses spaced hyphens as em-dashes. As per coding guidelines, "Comment on why, not what. Concise and direct." and "No em-dashes."

♻️ Proposed refactor
-// TestResolveAliasEndpoints_NoIntersectionWithCandidates_StrictRejects covers fix 4 from
-// the `#191` follow-up review, CodeRabbit round 2: the alias resolves to real target models,
-// but none of the endpoints serving those models are in the healthy/compatible candidate
-// list. Rather than the handler synthesising its own rejection, this must fall through to
-// the configured routing strategy for the alias name itself - exactly like the "resolved to
-// no endpoints at all" branch - so the strategy's decision (not a hardcoded one) governs the
-// outcome. Under a strict/rejecting registry that decision is still a fail-fast rejection,
-// just reported as model_not_found/404 (an unknown model name) rather than the
-// alias-specific model_unavailable/503 the handler used to synthesise.
+// TestResolveAliasEndpoints_NoIntersectionWithCandidates_StrictRejects ensures that when an alias
+// has no compatible endpoints, it falls back to the routing strategy's decision. Under a
+// strict registry, this correctly yields a 404 rejection instead of a hardcoded 503.
🤖 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 `@internal/app/handlers/handler_proxy_alias_test.go` around lines 196 - 204,
Simplify the documentation comment for
TestResolveAliasEndpoints_NoIntersectionWithCandidates_StrictRejects by removing
PR/review history and implementation-detail narration. Briefly state the
behavior being verified: when no candidate endpoint serves the resolved alias
targets, routing falls back to the alias strategy and strict rejection returns
model_not_found/404. Use direct punctuation without em-dashes.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@internal/app/handlers/handler_proxy_alias_test.go`:
- Around line 258-266: Condense the comment above
TestResolveAliasEndpoints_NoIntersectionWithCandidates_OptimisticFallsBack to
state only why the no-intersection case must allow optimistic fallback endpoints
through without setting the alias rewrite map. Remove PR history, implementation
narration, and spaced-hyphen em-dash phrasing.
- Around line 196-204: Simplify the documentation comment for
TestResolveAliasEndpoints_NoIntersectionWithCandidates_StrictRejects by removing
PR/review history and implementation-detail narration. Briefly state the
behavior being verified: when no candidate endpoint serves the resolved alias
targets, routing falls back to the alias strategy and strict rejection returns
model_not_found/404. Use direct punctuation without em-dashes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a0e7f393-5dfd-41da-b85c-bfa6db212149

📥 Commits

Reviewing files that changed from the base of the PR and between c76556c and 8317cc5.

📒 Files selected for processing (3)
  • internal/app/handlers/handler_proxy.go
  • internal/app/handlers/handler_proxy_alias_test.go
  • test/scripts/logic/test-model-routing-strategy.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/scripts/logic/test-model-routing-strategy.sh
  • internal/app/handlers/handler_proxy.go

@thushan
thushan merged commit 31f0716 into main Jul 20, 2026
8 checks passed
@thushan
thushan deleted the bugfix/191-strict-model-routing-bypass branch July 20, 2026 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working routing This issue is with routing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: strict model routing is bypassed when an alias resolves to no endpoints

1 participant