fix(proxy): resolve force-model provider against live availability (#874) - #875
fix(proxy): resolve force-model provider against live availability (#874)#875NathanLively wants to merge 1 commit into
Conversation
…orkweave#874) resolveForceModelWithEffort picked a model's provider from Providers[0] unconditionally, ignoring excluded/unregistered providers. /force-model and x-weave-force-model could silently pin to a dead provider (e.g. makora when ROUTER_EXCLUDED_PROVIDERS excludes it), which the next turn's pin-lookup then dropped as if no pin existed (reason:"no_pin") — no error, no warning, and the ack message falsely reported success the whole session. - resolveForceModelWithEffort/resolveForceModel now take the deployment's live enabled-provider set and walk a model's bindings in catalog order for the first available one (same pattern as cluster.Scorer's resolveProviderFor), instead of hard-picking the primary binding. A known model with no available binding now returns provider="" so callers can reject cleanly. - applyForceModelHeader / handleForceModelCommand thread the enabled-provider set through and reject with a clear ack/log message when no binding is available, instead of writing a pin that will silently die on the next turn. - turnloop's forced-pin fall-through now logs a Warn when a stored pin's provider became unavailable, so the drop is distinguishable from ordinary no-pin automatic routing. Deliberately did not touch ROUTER_HARD_PIN_MODEL/PROVIDER as a fix — that disables the cluster scorer entirely for main-loop turns and was ruled out as a real solution.
|
PR author is not in the allowed authors list. |
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR threads the deployment's live enabled-provider set into the /force-model resolution path. Previously resolveForceModel/resolveForceModelWithEffort unconditionally picked a model's primary binding (Providers[0]); now they accept an available map[string]struct{} and delegate to a new resolveForcedBinding helper that walks a model's bindings in catalog order to find the first available provider (with nil meaning unrestricted, preserving old behavior). The surface area covers the force-model helpers (resolveForceModel, resolveForceModelWithEffort, applyForceModelHeader, handleForceModelCommand) plus a new sortedProviderKeys logging helper, along with call sites like runAgentShadowEvaluationRoute that pass nil. It also adds a warning path when a recognized model has no available binding (routing falls back to automatic) and includes new internal tests exercising forced-model behavior against disabled/unavailable providers. Note: the changed-symbols list is much broader than the shown diff (touching pin stores, telemetry, routers, etc.), so there may be additional edits beyond the truncated portion I reviewed.
No blocking issues surfaced. 6 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 890 functions depend on the 292 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
NewService()— 156 callers, 3 callees - new:
TestRunTurnLoop_ForcedPin_FallsThroughWhenProviderUnavailable()— 0 callers, 6 callees
Verification — 890 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 854 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).
| // req.EnabledProviders (excluded mid-session, BYOK creds removed, etc.) must | ||
| // fall through to automatic routing rather than silently serving as if no | ||
| // pin existed — the scorer must still see it as ineligible and route around it. | ||
| func TestRunTurnLoop_ForcedPin_FallsThroughWhenProviderUnavailable(t *testing.T) { |
There was a problem hiding this comment.
TestRunTurnLoop_ForcedPin_FallsThroughWhenProviderUnavailable()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
Thank you so much for this, @NathanLively — this is a genuinely excellent contribution. You found a real and subtle bug (a force-model pin landing on an excluded binding, then reading back as To keep everything consistent with some of our internal conventions, I've opened a rewrite at #876 that keeps your production logic byte-for-byte and only adjusts three small things. Please don't read this as anything you did wrong — these are internal, still-evolving conventions that live in our What changed and why:
None of that touches your logic, your control flow, or your assertions. Really appreciate the care that went into this one — including the "Explicitly not done" note about |
Summary
Fixes #874:
/force-modelandx-weave-force-modelresolved a model's provider viacatalog.Model.Providers[0]unconditionally, ignoring the deployment's actual enabled/excluded provider set. If the primary binding was excluded (e.g.makoraviaROUTER_EXCLUDED_PROVIDERS), the pin silently pointed at a dead provider — the next turn's pin lookup treated it as equivalent to no pin (reason:"no_pin") and the cluster scorer silently overwrote it, with no error, no warning, and a force-model ack that falsely reported success.Changes
resolveForceModelWithEffort/resolveForceModelnow take the deployment's live enabled-provider set and walk a model's bindings in catalog order for the first available one — the same patterncluster.Scorer'sresolveProviderForalready uses for automatic routing — instead of hard-pickingProviders[0]. A known model with no available binding now returnsprovider=""so callers can reject cleanly rather than pin a dead one.applyForceModelHeader/handleForceModelCommandthread the enabled-provider set through (computed earlier in the request path so it's available before force-model handling runs) and reject with a clear ack message + Warn log when no binding is available.turnloop's forced-pin fall-through now logs aWarnwhen a stored pin's provider became unavailable mid-session, so that drop is distinguishable in logs from ordinary no-pin automatic routing.Testing
TestResolveForceModelextended with availability-aware cases (primary binding excluded → falls to fallback binding; no binding available → empty provider).TestRunTurnLoop_ForcedPin_FallsThroughWhenProviderUnavailablereproduces the issue's repro shape and asserts the pin is dropped rather than served, with the new Warn log confirmed in output.go build ./...,go vet ./...,go test ./...all pass.Explicitly not done
Did not set
ROUTER_HARD_PIN_MODEL/ROUTER_HARD_PIN_PROVIDERas "the fix" — that's a global override disabling the cluster scorer entirely for main-loop turns, discussed and rejected in the issue as a real solution.🤖 Generated with Claude