ENG-736 - Delete model_prefixes: route models off the harness's own fetched-or-shipped list#82
Conversation
There was a problem hiding this comment.
Routing rewrite matches the ticket verbatim and the AC coverage is complete, but CI is red: uv run ruff check backend fails with I001 on backend/tests/test_harness_model_routing.py because the import block is not sorted/formatted (stdlib → third-party → first-party grouping is broken by the blank line placement between fastapi and the druks.* imports). Reorganize the imports so ruff is happy — uv run ruff check backend --fix locally will do it — and push. Nothing else is blocking.
Follow-up recommendations
- The change from
text.startswith("```")totext[:3] == "```"inbackend/druks/harnesses/subprocess.pyis outside the ticket scope (that call operates on markdown-fence text, not a model name, so AC2 does not require it) and is semantically identical. Consider reverting to keep the diff scoped to routing. Push back if you had a reason — I might be missing context.
Superseded by bc950be.
There was a problem hiding this comment.
All nine acceptance criteria are met and CI is green.
- AC1:
model_prefixesandHarness.has_modelare gone; no overrides remain inclaude.py/codex.py. - AC2: only
startswithin the harnesses/user_settings tree is the pricing lookup atbackend/druks/harnesses/codex.py:232. - AC3:
get_harness_for_modelinbackend/druks/harnesses/registry.pymatches the ticket's verbatim shape. - AC4/AC5/AC6/AC9: tests seed real
HarnessSettingsrows and cover shipped-fallback, fetched-list, bare-name, and miss paths (including theclaude-opus-99case that proves prefix pass-through is gone). - AC7: both validators in
routes.pycollapse ontoget_harness_for_modelwith the exact 422 detail strings. - AC8:
test_settings_reject_model_missing_from_lists_returns_422asserts the 422 shape.
Round 2 diff is just the ruff I001 import sort fix that blocked round 1; uv run ruff check backend now passes locally and the On Pull Request Backend check is green. Backend pytest was not run locally (no Postgres in this sandbox) — CI covers it.
Follow-up recommendations
backend/druks/harnesses/subprocess.pyswappedtext.startswith("\``")fortext[:3] == "```"`. Semantically equivalent and out of the stated scope (markdown fence, not a model name), so AC2 doesn't require it — worth a revert on the next unrelated touch for readability, but not a blocker.
Push back if any of this reads wrong.
|
Code review: the cross-harness model rejection branch in |
bc950be to
2346817
Compare
2346817 to
eabe04e
Compare
Linear ticket: ENG-736
Plan
Goal
Delete the hand-maintained
Harness.model_prefixes/Harness.has_modelprefix-matching path and route models entirely off the per-rowHarnessSettings.allowed_modelslist already populated by ENG-701 (provider fetch on connect +RefreshModelscron, with a shipped-tuple fallback). Namespace pass-through dies deliberately: a brand-new model becomes routable only after a fetch, and misses loudly raiseHarnessError.Files touched
backend/druks/harnesses/base.py— deletemodel_prefixes: ClassVar[tuple[str, ...]](:76) and the wholehas_modelclassmethod (:90–:94); trim the docstring at :69–:72 that referencesmodel_prefixes.backend/druks/harnesses/claude.py— delete themodel_prefixes = ("claude-",)line (:39).backend/druks/harnesses/codex.py— delete themodel_prefixes = ("gpt-", "o1", "o3", "o4")line (:274). Leave_rate_for'sstartswithat :232 alone — it is pricing, not routing.backend/druks/harnesses/registry.py— rewriteget_harness_for_modelto iterateHarnessSettings.all()per the ticket's verbatim snippet. Update the docstring to describe fetched-or-shipped routing and the loud-miss consequence.backend/druks/user_settings/routes.py— collapse the twohas_modelcallsites onto the new routing function:update_harness_settings(:70) callsget_harness_for_model(updates["model"])inside atry/except HarnessErrorand additionally rejects (422) when the resolved harness is notharnessitself, preserving the "not a {name} model" wording._validate_model(:119–:124) callsget_harness_for_model(value)inside atry/except HarnessErrorand re-raises as the existing 422 with wording"No installed harness runs model {value!r}.".backend/tests/test_harness_model_routing.py— full rework against seededHarnessSettingsrows (see Tests below).Routing rewrite (verbatim from the brief)
HarnessSettings.allowed_models(models.py:118–:124) already returns the fetched list whenmodels_fetchedis truthy and falls back toharness.modelsotherwise, so both cases are covered by the same iteration. ImportingHarnessSettingsat module top level is fine —harnesses/registry.pyhas no import cycle risk withuser_settings/models, but if a cycle appears at import time, keep the import local to the function body (mirroring the localget_harnessimport already used inmodels.py:110).Route validator collapse
Both import
get_harness_for_model(andHarnessError) fromdruks.harnesses.registry/druks.harnesses.exceptions— the file already imports fromdruks.harnesses.registry, so it's an additional symbol on an existing import line.Tests
Rework
backend/tests/test_harness_model_routing.pyto seed realHarnessSettingsrows via thedb_sessionfixture (the same pattern used intests/test_user_settings.pyandtests/test_harness_models_parsing.py). The autouse test setup already seeds one row per registered harness with shipped defaults; tests mutatemodels_fetchedexplicitly to cover the fetched-list arm.Cases (each an independent test):
test_shipped_tuple_fallback_routes_shipped_models— with a freshly-seeded row (nomodels_fetched),get_harness_for_model("claude-opus-4-7")returnsClaudeHarnessandget_harness_for_model("gpt-5.5")returnsCodexHarness. Verifies the fallback arm (models come fromharness.models).test_fetched_list_routes_provider_models— setHarnessSettings.require("claude").models_fetched = [{"id": "claude-fable-5", "label": "Claude Fable 5"}], flush;get_harness_for_model("claude-fable-5") is ClaudeHarness. Verifies the fetched-list arm.test_bare_harness_name_routes—get_harness_for_model("claude") is ClaudeHarnessandget_harness_for_model("codex") is CodexHarness. Confirms themodel == row.namearm survives.test_unknown_model_raises_harness_error—pytest.raises(HarnessError)for"llama-3-70b"and for a claude-prefixed model that is neither inmodels_fetchednor shipped (e.g."claude-opus-99"), proving namespace pass-through is gone.test_settings_reject_model_missing_from_lists_returns_422— hits the FastAPI test client forPATCH /api/settings/harnesses/claudewith a fabricated model (or reuse the existing test client fixture, if any); asserts 422 with the "not a claude model" detail. If no HTTP client fixture exists in this test module's neighborhood, unit-call_validate_modelandupdate_harness_settings's guard block directly and assert theHTTPException.status_code == 422. Verification: covers the "existing 422 shape on rejection" acceptance criterion.Drop the old
test_new_model_in_known_namespace_routes_without_a_release— its behavior is the deliberate regression.Non-goals
POST /api/settings/harnesses/{name}/models/refresh) and no refresh button on the picker — explicitly out of scope per the ticket.RefreshModelscron cadence orHarnessSettings.refresh_models._rate_for'sstartswithincodex.py:232— pricing, not routing.Risk & mitigation
RefreshModelstick (or a reconnect) will raiseHarnessErrorwhere it previously would have been passed through by prefix. The ticket accepts this trade explicitly; the follow-up manual-refresh endpoint is out of scope."is not a"and"No installed harness runs"inbackend/tests/before merging; keep wording verbatim to avoid unrelated snapshot churn.registry.pyimportingHarnessSettingsat module scope should be safe — the harness modules that register subclasses import frombaseonly — but if pytest collection surfaces a cycle, fall back to a function-local import (mirroringHarnessSettings.harnessat models.py:110).Acceptance Criteria
model_prefixesand the classmethodHarness.has_modelare removed frombackend/druks/harnesses/base.py, and nomodel_prefixesoverride remains inbackend/druks/harnesses/claude.pyorbackend/druks/harnesses/codex.py.grep -rn "model_prefixes\|has_model" backend/druksreturns no matches;backend/druks/harnesses/base.pyno longer defineshas_modelor themodel_prefixesClassVar.startswithcall is used for model→harness routing or for model validation. The only survivingstartswithon a model name in the harnesses/user_settings tree is the pricing lookup atbackend/druks/harnesses/codex.pyinside_rate_for.grep -n "startswith" backend/druks/harnesses backend/druks/user_settingsreports only the_rate_forline incodex.py.get_harness_for_modelinbackend/druks/harnesses/registry.pyiteratesHarnessSettings.all()and matchesmodel == row.nameorany(m["id"] == model for m in row.allowed_models), returningrow.harnesson match and raisingHarnessError(f"no harness runs model {model!r}")on miss.registry.pymatches the shape specified in the ticket brief verbatim; nohas_modelor prefix logic is reintroduced.get_harness_for_model("claude")returnsClaudeHarnessandget_harness_for_model("codex")returnsCodexHarnesswhen theirHarnessSettingsrows are seeded.test_bare_harness_name_routesinbackend/tests/test_harness_model_routing.py.get_harness_for_modelroutes viaHarnessSettings.allowed_modelsin both branches: a model listed inHarnessSettings.models_fetchedroutes, AND a model appearing only in the harness's shippedmodelstuple (row withmodels_fetchedunset) still routes.backend/tests/test_harness_model_routing.py—test_fetched_list_routes_provider_models(seedsmodels_fetchedand asserts routing) andtest_shipped_tuple_fallback_routes_shipped_models(asserts routing withoutmodels_fetchedset).allowed_modelsraisesHarnessErrorfromget_harness_for_model. In particular, a namespace-shaped miss like"claude-opus-99"(not in shipped or fetched lists) raises, proving prefix pass-through is gone.test_unknown_model_raises_harness_errorinbackend/tests/test_harness_model_routing.pyusingpytest.raises(HarnessError)for"llama-3-70b"and for an unlisted claude-shaped model.backend/druks/user_settings/routes.pyno longer callhas_model.update_harness_settings(:70) rejects a model with 422 when eitherget_harness_for_model(...)raisesHarnessErroror the resolved harness'snamedoes not equal the path harness'sname, keeping the existing detail wording"{model!r} is not a {harness.name} model."._validate_model(:119) rejects with 422 and existing detail wording"No installed harness runs model {value!r}."whenget_harness_for_modelraises.get_harness_for_model(imported fromdruks.harnesses.registry) and raiseHTTPException(status_code=422, ...)with the exact detail strings above.detailstring).backend/tests/test_harness_model_routing.py(or an existing route test file) exercises_validate_modelorupdate_harness_settingsand asserts the raisedHTTPExceptionhasstatus_code == 422and the expecteddetailsubstring.backend/tests/test_harness_model_routing.pyis reworked to seedHarnessSettingsrows via thedb_sessionfixture; the file must contain a test coveringmodels_fetchedpopulated, a test covering the shipped-tuple fallback (nomodels_fetched), a test covering bare-name routing, and a test assertingHarnessErroron miss.HarnessSettingsand thedb_sessionfixture, contains at least the four tests above, and no longer relies on prefix behavior.