Files
6krrt/tests/test_admin_classifier_config.py
adlee-was-taken 10a0e56753 feat(admin): a Classifier card, reusing the write path and the live resolver
Applies the profiles-page lesson before relearning it: the admin surface
must report what dispatch actually does, using the same functions dispatch
uses, never a second guess that can drift. GET /admin/api/classifier-config
calls routing.cheapest_classifier_candidate directly against the live
catalog for cloud_primary_auto's resolved_primary -- the same function
classify() calls -- so the card never shows a static "auto" label while the
real resolution is doing something else.

admin.py's _persist_to gains a sibling, _persist_many_to, generalizing its
existing load/validate/backup/write sequence from one mutation to a list
applied together before the single validate-then-write pass. Needed because
classifier.mode: cloud_llm and its companion (cloud_primary or
cloud_primary_auto) are only valid TOGETHER -- writing them as two separate
_persist_to calls would validate, and could fail, after the first one
alone, since RouterConfig's own cross-field validators reject exactly that
intermediate state. _persist_to becomes a one-line wrapper; every existing
caller (profile CRUD, the persisted-config-key endpoint) is unchanged.

A dedicated "Classifier" card in controls.html, not a row in the generic
Runtime Knobs list -- the same reasoning that gave gaming mode its own card
elsewhere: this config has cross-field structure a flat scalar/boolean
input can't safely represent.

Two real bugs found and fixed during a browser smoke test against a
throwaway 8081 instance (8080 is the live router), not caught by the
initial test suite:

- Toggling the "auto" checkbox called the same full-regenerate handler the
  mode <select> uses, which rebuilds the fields from the ORIGINAL loaded
  config and so discarded the checkbox state (and anything already typed
  into the pinned fields) the instant it was ticked. Split into
  onClassifierModeChange (full regenerate, for the mode switch) and
  onClassifierCloudAutoToggle (visibility only, for the checkbox).
- A blank base_url/model was sent as {"base_url": "", "model": ""}, a
  truthy object, so the backend's `if not body.cloud_primary` check missed
  it and silently saved an unusable config. Fixed on both sides: the
  frontend now sends null when either field is blank, and the backend
  independently checks both fields are non-empty rather than trusting
  presence alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRQXz5SYZYVWscxS1QqF6U
2026-09-05 23:54:43 -04:00

10 KiB