Skip to content

Return 400 for invalid precoder_type or M_ant<K on /api/mimo/compute - #687

Merged
rwydaegh merged 1 commit into
masterfrom
claude/boundary-validation-optimize-mimo
Apr 20, 2026
Merged

rwydaegh merged 1 commit into
masterfrom
claude/boundary-validation-optimize-mimo

Conversation

@rwydaegh

Copy link
Copy Markdown
Owner

Summary

  • The /api/mimo/compute catchall at mimo.py:374-376 wraps every exception from compute_mimo_scene_with_bodies as HTTP 500, including user-input ValueErrors raised by the precoder code.
  • Two concrete 500 sources: ZF / zf_exposure with M_ant < K raises ValueError(\"M_ant (M) must be >= K (K) for ZF precoding\") at precoders.py:44, and any unknown precoder_type raises ValueError(\"Unknown precoder type: ...\") at precoders.py:148.
  • Flagged by PR Allow MMSE precoder at M<K in MIMO panel #676 author in the 2026-04-19 bulletin entry as the remaining MIMO backend 500 — frontend PR Allow MMSE precoder at M<K in MIMO panel #676 only stops the UI from greying out MMSE at M<K; the backend still surfaced 500 for any direct ZF call at M<K.
  • Fix validates precoder_type and M_ant >= K upfront and returns 400 with a specific message before invoking compute. Generic Exception path still returns 500 (test_compute_exception_returns_500 preserved).

Test plan

  • Added 5 parametrized cases for unknown precoder_type (returns 400)
  • Added 2 parametrized cases for ZF / zf_exposure at M_ant<K (returns 400, compute never called)
  • Added 1 case confirming MMSE is NOT blocked at M_ant<K (200)
  • test_compute_exception_returns_500 still passes for RuntimeError
  • 66/66 mimo route tests pass (was 58); 2839 fast tests pass across the suite
  • ruff check and ruff format --check clean

🤖 Generated with Claude Code

Previously any ValueError from compute_mimo_scene_with_bodies was caught
as a generic 500, including user-facing validation errors raised by the
precoder code (e.g. "M_ant (M) must be >= K (K) for ZF precoding" from
precoders.zf, or "Unknown precoder type: X" from compute_precoder).

Those are user input errors — a 400 tells the frontend exactly what the
caller sent wrong. Validate upfront so compute is never reached with a
bad precoder choice.

Keeps the existing 500-on-Exception path for genuine internal failures
(tested by test_compute_exception_returns_500 with RuntimeError).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@rwydaegh
rwydaegh merged commit fd4893b into master Apr 20, 2026
5 of 7 checks passed
@rwydaegh
rwydaegh deleted the claude/boundary-validation-optimize-mimo branch April 20, 2026 00:44
@github-actions github-actions Bot added viewer 3D viewer frontend/backend tests labels Apr 20, 2026
rwydaegh added a commit that referenced this pull request Apr 20, 2026
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
rwydaegh added a commit that referenced this pull request Apr 20, 2026
Bulletin entry for the MIMO compute 400/500 fix.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@sentry

sentry Bot commented Apr 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Apr 20, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 15 untouched benchmarks


Comparing claude/boundary-validation-optimize-mimo (bd046c0) with master (312032e)

Open in CodSpeed

rwydaegh added a commit that referenced this pull request Apr 20, 2026
## Summary

Install `StrictJSONProvider` on the Flask app so `NaN` / `Infinity` /
`-Infinity` in JSON request bodies are rejected at decode time with a
400, instead of slipping past every scalar-float field's `math.isfinite`
guard and either crashing downstream or silently propagating into
numeric pipelines.

Python's stdlib `json` accepts those literals by default as a
non-standard RFC 8259 extension; overriding `parse_constant` at the
provider level is a single choke point for the whole API surface.

## Why

The QA/feature/code-review agents have been shipping one
`isfinite`/`isnan` guard PR after another (#668, #670, #675, #685, #687,
#693, #694, #698, #701, #707…), each patching a new scalar field on a
new route. Whack-a-mole. This replaces the pond with the drain.

## Changes

- `src/aegis/viewer/server/_app.py` — `StrictJSONProvider` subclass of
`DefaultJSONProvider`, installed in `create_app`. Only `loads` is
overridden so routes that intentionally emit NaN (masked spatial
averaging) are unaffected on the output side.
- `src/aegis/viewer/routes/_helpers.py` — `get_json_dict` now
distinguishes empty body (returns `{}`) from parse failure (returns 400
with the parser's message), so provider rejections surface as 400
instead of silently falling through to route defaults.
- `tests/test_viewer_strict_json_nonfinite.py` — 37-case regression
matrix covering 12 routes × 3 literals (NaN / Infinity / -Infinity) +
nested-NaN + happy-path sanity.
- Two existing per-field NaN tests relaxed
(`test_power_dbm_nan_rejected`, `test_freq_nan_rejected`) to accept
either the legacy field-level message or the new parser-level message —
the protection now fires earlier.

## Test plan

- [x] `pytest tests/test_viewer_strict_json_nonfinite.py
tests/test_viewer_non_dict_body_guards.py` — 68/68 pass
- [x] `pytest tests/ -m "not slow" --deselect <network-flaky-overpass>`
— 2842 pass, 32 skipped
- [x] `ruff check` / `ruff format --check` on touched files — clean
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests viewer 3D viewer frontend/backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant