feat: capability-aware ceiling warnings + reactive rejection detector #31
Reference in New Issue
Block a user
Delete Branch "feat/capability-aware-ceiling-warnings"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Capability-aware ceiling warnings + reactive rejection detector
Closes the observability gap behind the 2026-09-04 incident, where the overall tier-1 ceiling looked healthy (782,324, served by non-vision models) while the vision-only sub-ceiling had collapsed to 192,500 — image requests 422'd with zero warnings. Observability only: warn, never act. No routing changes, no auto-revert of admin overrides, no model re-activation.
What's added
1. Capability sub-ceilings + demand warnings (
src/metrics.py):capability_ceilings(conn, cfg)— per-dimension{vision, json_mode} × (tier, latency_tolerance)context ceilings, computed through the samerouting.select_candidatespath live routing uses (admin deprecations included). Returns{dimension: {(tier, lat_tol): {"ceiling", "count"}}}.capability_demand_warnings— demand-relative only: firesvision-capable tier 1 context ceiling (0) is below observed max demand (200000, window: last 30d) for requests carrying images — requests above this may return 422when observed demand for capability-carrying requests exceeds the sub-ceiling (7d/30d window fallback mirroringdemand_ceiling_warnings). No absolute tier-ordering check (that's a theorem), no vision+json_mode combination bucket.2. Reactive rejection detector (
rejection_warnings):selected_model IS NULL) by(task_tier, normalized_reason)— tier from the structured column, never parsed from the string. Digit normalization (context >= 242486 tokens→context >= N tokens) merges token-count noise; only the column keepstier >= 1vstier >= 3separable.NOVEL_GROUP_MIN_COUNT, incident citation in the comment) warns asnew rejection pattern:; a familiar group warns asrejection rate:only at count ≥ 6 (2× the measured routine peak). n=1 novel is silent (a genuinely impossible request SHOULD 422); zero rejections → zero warnings.objective.rejection_warning_window_hours: 1,rejection_warning_baseline_hours: 24,rejection_warning_min_count: 6(config.yaml comment records the live-DB measurements justifying all three).rejection_warning_baseline_hoursto suppress.Both warning classes flow through the existing
scoring_coverage.warningslist →/metrics,/admin/api/snapshot, the admin bell, and the TUI automatically. 13 new regression tests (incident reproduction with both halves — new check fires AND existing(tier, latency_tolerance)checks stay silent on the same state; tier discrimination; novelty/rate fire+silence matrix; window boundary; custom window; endpoint integration).Verification evidence (
.omo/evidence/capability-aware-ceiling-warnings/)Fail-on-main first (T3-pytest.log) — every new regression test was run against unmodified main (
deeaf80) in a throwaway reference worktree BEFORE passing on the fix:Suite result (T4-final.txt) — with
local_energy.enabledtrue (worktree overlay copy):1212 passed; toggled to false (worktree copy only):1212 passed.Root overlay integrity —
config/config.local.yaml(gitignored, user tariff) bookended by sha256 at every step, final value:Real manual QA (F3-manual-qa.txt) — throwaway uvicorn on 127.0.0.1:8081 from isolated run dirs with seeded DBs:
coverage.warnings= 4 (2 pre-existing + both new types verbatim).Review verdicts (Final Verification Wave): F1 plan compliance APPROVE (8/8) · F2 code quality APPROVE (9/9, 2 cosmetic nits) · F3 manual QA APPROVE · F4 scope fidelity APPROVE (exactly 5 files:
src/metrics.py,src/config.py,config/config.yaml,tests/test_metrics.py,tests/test_metrics_endpoint.py;routing.pyanddispatcher.pybyte-identical to main; metrics reads-only — zero INSERT/UPDATE/DELETE).Notes for reviewers
test_rejection_warnings_old_rejections_not_countedseeds two 3-day-old rows instead of the plan's one — a single old row is silent even with broken window filtering (n=1 < novelty floor); two at the floor make the silence provably age-dependent. Test strengthened, assertion unchanged.capability_ceilingsdelegates tocontext_ceilings_with_rows, which gained backward-compatiblerequire_vision/require_json_modeparams (defaults preserve old behavior) — one copy of the filter logic, identicalselect_candidatescalls to the plan's spec.