feat(admin): classifier knob coverage and categorized Controls page #113

Merged
alee merged 15 commits from feat/admin-knob-taxonomy into main 2026-10-06 00:50:19 +00:00
Owner

What

Finishes the classifier section under the admin knob-coverage gate (North Star 1) and reorganizes the Controls page so the knob list stays readable as it grows.

  • Classifier knobs. New admin controls for classifier.degraded_warn_min and classifier.degraded_warn_threshold, plus context_framing, cooldown_seconds, fallback_tier (runtime and persisted) and fallback_category, max_input_chars (persisted only). Bounds come from named constants in config.py, because a runtime write bypasses Pydantic and the registry bounds are the only validation.
  • Taxonomy. Every registered knob carries {category, advanced} from _KNOB_CATEGORY. Six categories: Routing / Quality / Cost, Classification, Local Hardware, Caching / Context, Safety Nets, Watchdog.
  • Controls page. One knob table, grouped by category with collapsible headers and an Advanced accordion per section. Advanced accordions load collapsed.
  • Coverage gate, third source. The Classifier card writes mode, the cloud primary/fallback blocks, encoder and decision as whole blocks, which the registry-derived gate could not see. _CARD_BACKED_PATHS (21 leaf paths mapped to their frontend element ids) now counts as coverage, and a test checks each id exists in controls.html. Exactly five classifier excuses remain (timeout_seconds, temperature, max_output_tokens, cloud_primary.max_output_tokens, encoder.tier_from_features).
  • Deferred tracker. plans/deferred-knobs.md records every scalar in sections the portal has not reached, so the first control added under one does not orphan its siblings.

Review notes

This branch was written by an opencode Atlas run and then audited independently. The audit found real defects, all fixed here as new commits with their own tests:

Defect Fix
800px horizontal overflow: the degraded_warn_min note badge is white-space:nowrap, Knobs table measured 821px in a 797px frame Wrap the hints and the pill text. 732px at 800, 1074px at 1400, no overflow at either
Watchdog Advanced loaded expanded: seeded once on the first render, before its persisted-only advanced rows existed seedAdvancedCollapsed seeds per category when its first advanced row renders; 3 node tests, mutation-checked
POST .../classifier_degraded_warn_threshold {"value": null} was a 500 Clean 422; null, bool, string, 0.0, below-floor, above-max all pinned
11 DELIBERATELY_NOT_IN_ADMIN excuses for knobs that do have a control (the card) Gate counts _CARD_BACKED_PATHS; excuses deleted; gate shown to fail naming a leaf when one is removed
Float handler dropped "or null for neutral" from the challenger dial's 422s Restored, conditional on the knob having a neutral; tested both directions
Stray CLAUDE.md line from 2d145d6 Reverted

Commit message correction: b774d55 says it updates CLIREF.md. No such file exists. The 17 lines it describes are the North Star 1 paragraphs in CLAUDE.md. History is not rewritten; the correction is also in the body of dfd19d6.

Verification

  • scripts/verify_commit.py --full HEAD, run from the worktree: lint clean, 2664 tests pass.
  • Visual, on a throwaway 8081 instance against a clone of the live DB: three fresh loads each at 800 and 1400. Caching Advanced 2/2 rows hidden and Watchdog Advanced 7/7 hidden on every load; toggling opens and closes; a full re-render keeps an open section open.
  • 8080 and config.local.yaml untouched (md5 before and after identical).

Not in this PR

  • Atlas left screenshots in the main checkout's .playwright-mcp/; they are untracked and not part of the diff.
  • The em dashes in the new docs prose were left as written.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa

## What Finishes the classifier section under the admin knob-coverage gate (North Star 1) and reorganizes the Controls page so the knob list stays readable as it grows. - **Classifier knobs.** New admin controls for `classifier.degraded_warn_min` and `classifier.degraded_warn_threshold`, plus `context_framing`, `cooldown_seconds`, `fallback_tier` (runtime and persisted) and `fallback_category`, `max_input_chars` (persisted only). Bounds come from named constants in `config.py`, because a runtime write bypasses Pydantic and the registry bounds are the only validation. - **Taxonomy.** Every registered knob carries `{category, advanced}` from `_KNOB_CATEGORY`. Six categories: Routing / Quality / Cost, Classification, Local Hardware, Caching / Context, Safety Nets, Watchdog. - **Controls page.** One knob table, grouped by category with collapsible headers and an Advanced accordion per section. Advanced accordions load collapsed. - **Coverage gate, third source.** The Classifier card writes `mode`, the cloud primary/fallback blocks, `encoder` and `decision` as whole blocks, which the registry-derived gate could not see. `_CARD_BACKED_PATHS` (21 leaf paths mapped to their frontend element ids) now counts as coverage, and a test checks each id exists in `controls.html`. Exactly five classifier excuses remain (`timeout_seconds`, `temperature`, `max_output_tokens`, `cloud_primary.max_output_tokens`, `encoder.tier_from_features`). - **Deferred tracker.** `plans/deferred-knobs.md` records every scalar in sections the portal has not reached, so the first control added under one does not orphan its siblings. ## Review notes This branch was written by an opencode Atlas run and then audited independently. The audit found real defects, all fixed here as new commits with their own tests: | Defect | Fix | |---|---| | 800px horizontal overflow: the `degraded_warn_min` note badge is `white-space:nowrap`, Knobs table measured 821px in a 797px frame | Wrap the hints and the pill text. 732px at 800, 1074px at 1400, no overflow at either | | Watchdog Advanced loaded expanded: seeded once on the first render, before its persisted-only advanced rows existed | `seedAdvancedCollapsed` seeds per category when its first advanced row renders; 3 node tests, mutation-checked | | `POST .../classifier_degraded_warn_threshold {"value": null}` was a 500 | Clean 422; null, bool, string, 0.0, below-floor, above-max all pinned | | 11 `DELIBERATELY_NOT_IN_ADMIN` excuses for knobs that do have a control (the card) | Gate counts `_CARD_BACKED_PATHS`; excuses deleted; gate shown to fail naming a leaf when one is removed | | Float handler dropped "or null for neutral" from the challenger dial's 422s | Restored, conditional on the knob having a neutral; tested both directions | | Stray `CLAUDE.md` line from `2d145d6` | Reverted | **Commit message correction:** `b774d55` says it updates `CLIREF.md`. No such file exists. The 17 lines it describes are the North Star 1 paragraphs in `CLAUDE.md`. History is not rewritten; the correction is also in the body of `dfd19d6`. ## Verification - `scripts/verify_commit.py --full HEAD`, run from the worktree: lint clean, 2664 tests pass. - Visual, on a throwaway 8081 instance against a clone of the live DB: three fresh loads each at 800 and 1400. Caching Advanced 2/2 rows hidden and Watchdog Advanced 7/7 hidden on every load; toggling opens and closes; a full re-render keeps an open section open. - 8080 and `config.local.yaml` untouched (md5 before and after identical). ## Not in this PR - Atlas left screenshots in the main checkout's `.playwright-mcp/`; they are untracked and not part of the diff. - The em dashes in the new docs prose were left as written. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
alee added 15 commits 2026-10-05 05:48:53 +00:00
Add COOLDOWN_FLOOR, FALLBACK_TIER_MIN, DEGRADED_WARN_MIN_CEILING, etc. constants and field_validators for cooldown_seconds, fallback_tier, degraded_warn_min. Update test_admin_config.py for category/advanced fields.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add all 7 classifier knobs to registry/allowlist, _KNOB_CATEGORY dict (35 entries), _CARD_BACKED_PATHS (21 entries), DELIBERATELY_NOT_IN_ADMIN reasons, _CONFIG_GET_ORDER reorder, and two-way coverage test.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Restructure config-list tbody: category header rows with chevron toggle, Advanced accordion sub-headers, renderStaticIcons() for both. Add metadata for 7 new classifier knobs (UNITS, KNOB_NOTES, NUMBER_BOUNDS). Collapse state in JS Set preserved across 30s re-renders.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Update admin-portal.md Controls section with categories/accordion/coverage sources. Update CLIREF.md North Star 1 for completed classifier coverage. Add plans/deferred-knobs.md listing 39 outside-gate + 31 warning knobs.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Update set checks to include 'category' and 'advanced' keys in /admin/api/runtime responses. Remove unused ClassifierConfig import.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Update explicit value checks for circuit_breaker_enabled and default_flex_preference to include category and advanced flags.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Reverts 2d145d6, which appended "CLIREF.md updated to CLAUDE.md for current
project state mapping" to the end of CLAUDE.md. It is a stray sentence
describing a rename that never happened.

Correction for b774d55: its subject and body say "CLIREF.md". No such file
exists in this repository; the 17 lines that commit describes (the North Star 1
classifier-coverage paragraphs) are in CLAUDE.md. History is not rewritten.

Also rewords the first of those paragraphs: "partially covered" understated
it. Every classifier scalar is now a control, card-backed, or a recorded
excuse, and fallback_category / max_input_chars are persisted-only.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
Overflow: the persisted-cell note badge is white-space:nowrap, so the
classifier.degraded_warn_min note set the column's min-content width and the
Knobs table measured 821px in a 797px frame (Layer column cut off). The
badge now wraps inside its own pill: 733px at 800, the pre-taxonomy figure.
The previous rule targeted .setting-control .ms-2, but d-inline-flex is
!important and the cell is a plain td, so it never matched.

Seeding: Advanced accordions were seeded collapsed once, on the first render.
The runtime payload lands before the config payload and watchdog.detector.* are
persisted-only, so Watchdog had no advanced rows on that render, was marked
seeded anyway, and loaded EXPANDED. seedAdvancedCollapsed now seeds a category
the first time it renders an advanced row. It is a pure function between
SEED_ADV_COLLAPSED markers, covered by three node tests (a config render after
a runtime-only render stays collapsed; a toggled-open section survives
re-render; a plain-only category is not marked seeded).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
POST /admin/api/runtime/classifier_degraded_warn_threshold with null is now
pinned as a clean 422 (it was a TypeError/500 before the neutral_path guard).
Covered: null, bool, string, 0.0, below-floor and above-max all 422 and leave
cfg untouched; floor, 0.2 and max return 200 and update cfg.

The float handler's 422 text had lost "or null for neutral" and "(null means
neutral, not 0.0)" for every float knob, including the challenger dial, which
does have a neutral. They are back, conditional on the knob having a
neutral_path, and degraded_warn_threshold's 422s never mention null. Tests pin
both directions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
The badge-only wrap fixed the 800px overflow but left classifier.degraded_warn_min
with a three-line pill squeezed beside its unit. Letting the hints span wrap
puts the unit on one line and the pill under it in two, matching how
degraded_warn_threshold already reads. Table width at 800 is unchanged (732px).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
alee merged commit d97588c2c8 into main 2026-10-06 00:50:19 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: alee/6krrt#113