ADRs
ADR-0075 Per-Org Model Routing Policy
Org allow/deny model policies gated in front of Registry.For, with proxy Postgres + LRU+TTL cache
ADR-0075 — Per-Org Model Routing Policy
Status
Accepted (Milestone 4.C.2)
Context
Registry.For(model) is an exact platform map with no org awareness. Exit criteria require per-org allow/deny over requested models. Credentials use Auth gRPC because they are secrets (ADR-0074); org model policy is non-secret config closer to directives and rate-limit overrides. Auth's ValidateAgent does not return default_model (4.A.3 columns exist only in Postgres).
Decision
OrgAwareRegistrywraps*provider.Registry; do not changeFor(model)signature or exact-match semantics.- Precedence: trimmed request-body
modelif non-empty; else proxy Postgresagents.default_modelfor the verified agent; else 400model is required. Org policy is then a veto (priority ASC, firstfilepath.Matchwins). No match → allow any registered model. ThenRegistry.For.default_provideris not used for registry lookup. - Deny →
403 MODEL_NOT_ALLOWED. Unknown model after allow → existing501 PROVIDER_NOT_CONFIGURED. - Direct Postgres from the proxy (
packages/modelpolicystore + RLS). No Auth gRPC hop. - Cache: process-local LRU + TTL in
packages/modelpolicy(notpackages/authcache), 60s TTL, Redis pub/submodel_policy_updates:{org_id}viapackages/redissub.Loop. Invalidate advances a per-org generation under the same lock used for cache install so in-flight loads cannot repopulate a stale snapshot. A positive bloom filter is not used: false negatives would fail open on deny policies. Policy load errors fail closed and return 503SERVICE_DEGRADED; true policy denials remain 403. Agentdefault_modelreads use a separate short-TTL LRU. Management publish-after-commit is best-effort: if Redis publish fails, proxies may serve a stale allow/deny snapshot until the ~60s TTL expires (monitor publish failures; do not treat this as fail-open on the hot path). WhenPOSTGRES_DSNor a required model-policy dependency is unavailable, the proxy selects no provider, recordsibex_proxy_model_policy_enabled=0, and emits a warning. The removedIBEX_MODEL_POLICY_ALLOW_PASSTHROUGHcontrol cannot enable an ungoverned route. - No
fallback_chaincolumn in 4.C.2 — deferred to 4.C.4 when a reader exists. - Admin CRUD under
/v1/organizations/{org_id}/model-policieswithRequireOrgSettingson writes. Write-sidemodel_patternvalidation strips whitespace, enforces length ≤256, rejects the shared golden reject corpus, and runs a full Gofilepath.Matchgrammar scan (character classes, escapes) so the API refuses patterns the proxy would fail-closed on at load time. - Middleware change is required:
ProviderRoutingMiddlewarereadsauth.FromContextorg ID and callsForOrg— handlers remain registry-agnostic.
Consequences
- Chat parse allows empty
modelso agent-default fill can run before the policy gate. - Management API publishes invalidation events; proxy subscribers drop LRU entries for that org.
- Glob dialect is Go
filepath.Match(shell-style*,?, and character classes including ranges/^negation; malformed classes such as[]are rejected byValidatePattern/ management API golden reject list). filepath.Match*does not cross/. A sole deny pattern*does not match slashy catalog IDs (e.g.openai/gpt-4). Catch-alls for those IDs need*/*(and/or explicitprovider/*) in addition to*.
Was this page helpful?
Edit on GitHub
Last updated on