feat(ai-proxy): routes table + catalog allow/deny (ADR-0030 §3)#79
Merged
feat(ai-proxy): routes table + catalog allow/deny (ADR-0030 §3)#79
Conversation
Adds glob-based dynamic model routing as a third resolution layer between ai.target context lookup and default_target/flat fallthrough. Each routes entry binds a glob pattern (e.g. claude-*, gpt-4o*, o[1-4]*) to a provider + credentials. First match wins. Catalog policy lives on the target via optional allow/deny glob lists. Critically, allow/deny applies on every resolution path that produces a target carrying those rules — including ai.target-driven dispatch — so a cel misconfig that sets ai.target to a target whose deny covers the requested model still gets 403. Catalog policy is a property of the target, not the resolution path. Resolution precedence (4-step ladder, ADR-0030 §3): 1. ai.target context key (set by upstream cel) 2. routes glob match against client model 3. default_target → targets[name] 4. flat provider config Failure modes: - 400 model_required (PR-2): client omitted model - 400 no_route (new): routes configured but no entry matched and no fallthrough — operator's catalog doesn't cover the requested model - 403 model_not_permitted (new): allow/deny rejected the model. Does NOT fall through to fallback or to another route — that would silently escalate a denied model to a different provider. Escape hatch: tighten the route's pattern so non-matching models miss the route entirely and reach the catch-all. - 500 misconfiguration: nothing configured, or a route's glob fails to compile (surfaced from ensure_compiled_routes at first dispatch). Implementation: - New Route struct (pattern + provider + credentials + allow/deny). - New CompiledRoute caching the precompiled GlobMatcher per route. - ensure_compiled_routes runs once per plugin instance, lazily on first dispatch — same pattern as cel's compiled CEL program. - New ResolveOutcome enum distinguishes Resolved / NoRouteMatch / NotConfigured so dispatch can map each to the right HTTP shape. - evaluate_catalog_policy compiles per-target allow/deny on the fly (lists are typically <5 entries; cheap). Fails closed on a glob compile error rather than silently bypassing the policy. - Glob library: `globset` 0.4 with case-sensitive, anchored matching. default-features = false to keep the WASM binary small. - Schema: TargetConfig gains optional allow/deny; new RouteEntry; new GlobPattern referenced from both. Pattern allowlist regex ^[A-Za-z0-9_*?\[\]\-:.+/]+$ pins glob characters at lint time so vacuum surfaces nonsense like regex syntax before runtime. - New resolution_total counter with `resolution=context|routes|default| flat` label and a debug log (ai-proxy: resolved provider=X via=Y) for the "why did my request go there?" debugging case. Tests: 67/67 (was 50; +17 covering routes first-match-wins, catch-all fallthrough, no_route when no fallthrough, default/flat fallthrough, ai.target overrides routes, invalid glob compile error, allow pass/ reject, deny pass/match, allow+deny ordering, end-to-end 400 no_route, end-to-end 403 model_not_permitted, no-fallthrough on deny, the ai.target+deny subtlety, and resolution_total label emission). 14/14 ruleset tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Re-opened replacement for #76 after its base branch was auto-deleted on the merge of #75. Same content; rebased onto main.
See original PR: #76