feat(server): limites et erreurs 413/422/504, validation WAV (T-017) #17

Merged
ahitosly merged 1 commit from feat/T-017-server-dictate-limits into main 2026-09-03 00:40:47 +02:00
Owner

Tâche

T-xxx — (titre dans docs/STATUS.md)

Changements

Contrat modifié ?

  • Non
  • Oui : packages/contract / supabase/migrations — docs/API.md / docs/DATA.md mis à jour, label contract, approbation humaine requise

Tests

  • scripts/check.sh vert
  • Test manuel (OS, scénario) :

Impacts cross-domaine

Fichiers hors périmètre de l'agent (et pourquoi) :

Suivi

  • docs/STATUS.md mis à jour (ligne de la tâche)
  • docs/CHANGELOG.md : entrée ajoutée sous Unreleased
  • docs/DECISIONS.md / docs/ECARTS.md mis à jour si décision ou écart
## Tâche T-xxx — (titre dans docs/STATUS.md) ## Changements - ## Contrat modifié ? - [ ] Non - [ ] Oui : `packages/contract` / `supabase/migrations` — `docs/API.md` / `docs/DATA.md` mis à jour, label `contract`, approbation humaine requise ## Tests - [ ] `scripts/check.sh` vert - [ ] Test manuel (OS, scénario) : ## Impacts cross-domaine Fichiers hors périmètre de l'agent (et pourquoi) : ## Suivi - [ ] `docs/STATUS.md` mis à jour (ligne de la tâche) - [ ] `docs/CHANGELOG.md` : entrée ajoutée sous Unreleased - [ ] `docs/DECISIONS.md` / `docs/ECARTS.md` mis à jour si décision ou écart
feat(server): limites et erreurs 413/422/504, validation WAV (T-017)
All checks were successful
ci / server (pull_request) Successful in 14s
ci / desktop-ts (pull_request) Successful in 26s
ci / desktop-rust (pull_request) Successful in 2m26s
3021bc97d2
- api/wav.py::validate_wav : parseur RIFF/WAVE minimal, rejette 422
  audio_not_wav / audio_invalid_format quand le WAV n'est pas PCM 16 bits /
  16 kHz / mono (docs/API.md), au lieu de la simple vérification du magic
  RIFF.
- STTTimeoutError (sous-classe de STTError) : un timeout provider STT est
  désormais distingué d'un échec générique -> 504 stt_timeout au lieu de
  502 stt_failed sur /v1/dictate et /v1/agent.
- Starlette spoole tout fichier multipart au-delà de 1 Mo (spool_max_size)
  sur un vrai fichier temporaire disque, en contradiction avec la règle
  "l'audio n'est jamais écrit sur disque" dès qu'une dictée dépasse ~31 s :
  seuil relevé au démarrage au-delà de max_audio_bytes (vérifié : plus
  aucun fichier temporaire pour un audio de ~1.8 Mo).
- Middleware limit_upload_size : rejette 413 sur la base du Content-Length
  avant même le parsing multipart, en complément du contrôle post-lecture
  existant.

145 tests passed (123 avant), scripts/check.sh --server vert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MU4G6EMeKVyxx9eBSahggu
Author
Owner

qa-regression — verdict : APPROVE

  1. Périmètre : uniquement apps/server/** (api/routes.py, api/wav.py nouveau, main.py, providers/stt/base.py+openrouter.py, tests/*) + docs/API.md, docs/CHANGELOG.md, docs/STATUS.md. Aucun fichier hors périmètre backend, rien à signaler en Impacts cross-domaine.

  2. Contrats : packages/contract/openapi.json non modifié (vérifié : seuls 200/422 y étaient déjà déclarés pour /v1/dictate, les detail de HTTPException n'y sont pas typés) ; supabase/migrations non touché. docs/API.md (ligne "Erreurs :") ne fait que préciser les valeurs de detail pour des codes 413/422/502/504 déjà documentés avant cette PR — pas une PR contract/*, pas d'approbation humaine requise.

  3. Sécurité :

    • apps/server/aria_server/api/wav.py:20-46 (validate_wav) : parseur RIFF/WAVE en lecture seule sur les bytes déjà en mémoire, aucune écriture disque, valide PCM 16 bits/16 kHz/mono comme documenté API.md.
    • apps/server/aria_server/main.py:26-34 : correctif réel — MultiPartParser.spool_max_size (Starlette, 1 Mo par défaut) relevé au-delà de max_audio_bytes, sinon toute dictée > ~31 s était spoolée sur disque en violation de la règle CLAUDE.md "jamais écrire l'audio sur disque". Vérifié manuellement (hors tests unitaires) : un WAV de ~1.8 Mo ne crée plus aucun fichier temporaire alors qu'il en créait un avant le correctif.
    • Pas de secret touché, pas de log INFO du texte dicté (inchangé).
  4. Robustesse :

    • apps/server/aria_server/providers/stt/openrouter.py:36-41 : httpx.TimeoutException catché avant httpx.HTTPError générique (sous-classe) → STTTimeoutError distinct.
    • apps/server/aria_server/api/routes.py:82-83 et :101-102 : /v1/dictate et /v1/agent mappent STTTimeoutError → 504 avant STTError → 502 (ordre des except respecté, sous-classe en premier).
    • apps/server/aria_server/main.py:37-49 (limit_upload_size) : défense en profondeur sur Content-Length, ne bloque pas les requêtes sans cet en-tête (chunked) — _read_audio reste le filet de sécurité, comportement documenté en commentaire.
  5. Cross-platform : N/A (backend seul).

  6. Tests : scripts/check.sh --server vert, 145 passed (123 avant), 1 skipped (intégration OpenRouter opt-in inchangée). Une branche par cas : WAV absent/tronqué/mauvais magic/mauvais nombre de canaux/mauvaise fréquence/mauvaise profondeur/format non-PCM/padding de chunk impair (tests/test_wav.py, 13 tests) ; 413 au niveau route avec limite réduite (tests/test_dictate.py::test_dictate_audio_too_large_is_413) ; 504 distinct de 502 au niveau route et provider (test_dictate_stt_timeout_is_504, test_timeout_raises_stt_timeout_error, test_connect_timeout_also_raises_stt_timeout_error) ; middleware testé en isolation sans transfert réseau réel (tests/test_main_limits.py, 5 tests, dont la non-régression du spool_max_size).

  7. Suivi : STATUS.md (T-017 done, note détaillée incluant la découverte spool_max_size), CHANGELOG.md (Unreleased, Added ×3 + Fixed ×1). Pas de nouvel écart ouvert dans ECARTS.md — le problème de spoolage disque découvert pendant cette tâche est corrigé dans la même PR, pas laissé en dette. Pas de nouvelle ADR nécessaire (détail d'implémentation, ne contredit aucune décision existante).

  8. Latence : middleware = un lookup de path + parse d'un entier, coût négligeable sur le chemin hotkey-up → paste ; le monkeypatch spool_max_size s'exécute une seule fois à l'import.

Aucun point bloquant. Prêt à merger.

qa-regression — verdict : APPROVE 1. Périmètre : uniquement apps/server/** (api/routes.py, api/wav.py nouveau, main.py, providers/stt/base.py+openrouter.py, tests/*) + docs/API.md, docs/CHANGELOG.md, docs/STATUS.md. Aucun fichier hors périmètre backend, rien à signaler en Impacts cross-domaine. 2. Contrats : packages/contract/openapi.json non modifié (vérifié : seuls 200/422 y étaient déjà déclarés pour /v1/dictate, les `detail` de HTTPException n'y sont pas typés) ; supabase/migrations non touché. docs/API.md (ligne "Erreurs :") ne fait que préciser les valeurs de `detail` pour des codes 413/422/502/504 déjà documentés avant cette PR — pas une PR contract/*, pas d'approbation humaine requise. 3. Sécurité : - apps/server/aria_server/api/wav.py:20-46 (`validate_wav`) : parseur RIFF/WAVE en lecture seule sur les bytes déjà en mémoire, aucune écriture disque, valide PCM 16 bits/16 kHz/mono comme documenté API.md. - apps/server/aria_server/main.py:26-34 : correctif réel — `MultiPartParser.spool_max_size` (Starlette, 1 Mo par défaut) relevé au-delà de `max_audio_bytes`, sinon toute dictée > ~31 s était spoolée sur disque en violation de la règle CLAUDE.md "jamais écrire l'audio sur disque". Vérifié manuellement (hors tests unitaires) : un WAV de ~1.8 Mo ne crée plus aucun fichier temporaire alors qu'il en créait un avant le correctif. - Pas de secret touché, pas de log INFO du texte dicté (inchangé). 4. Robustesse : - apps/server/aria_server/providers/stt/openrouter.py:36-41 : `httpx.TimeoutException` catché avant `httpx.HTTPError` générique (sous-classe) → `STTTimeoutError` distinct. - apps/server/aria_server/api/routes.py:82-83 et :101-102 : `/v1/dictate` et `/v1/agent` mappent `STTTimeoutError` → 504 avant `STTError` → 502 (ordre des except respecté, sous-classe en premier). - apps/server/aria_server/main.py:37-49 (`limit_upload_size`) : défense en profondeur sur Content-Length, ne bloque pas les requêtes sans cet en-tête (chunked) — `_read_audio` reste le filet de sécurité, comportement documenté en commentaire. 5. Cross-platform : N/A (backend seul). 6. Tests : `scripts/check.sh --server` vert, 145 passed (123 avant), 1 skipped (intégration OpenRouter opt-in inchangée). Une branche par cas : WAV absent/tronqué/mauvais magic/mauvais nombre de canaux/mauvaise fréquence/mauvaise profondeur/format non-PCM/padding de chunk impair (tests/test_wav.py, 13 tests) ; 413 au niveau route avec limite réduite (tests/test_dictate.py::test_dictate_audio_too_large_is_413) ; 504 distinct de 502 au niveau route et provider (test_dictate_stt_timeout_is_504, test_timeout_raises_stt_timeout_error, test_connect_timeout_also_raises_stt_timeout_error) ; middleware testé en isolation sans transfert réseau réel (tests/test_main_limits.py, 5 tests, dont la non-régression du spool_max_size). 7. Suivi : STATUS.md (T-017 done, note détaillée incluant la découverte spool_max_size), CHANGELOG.md (Unreleased, Added ×3 + Fixed ×1). Pas de nouvel écart ouvert dans ECARTS.md — le problème de spoolage disque découvert pendant cette tâche est corrigé dans la même PR, pas laissé en dette. Pas de nouvelle ADR nécessaire (détail d'implémentation, ne contredit aucune décision existante). 8. Latence : middleware = un lookup de path + parse d'un entier, coût négligeable sur le chemin hotkey-up → paste ; le monkeypatch spool_max_size s'exécute une seule fois à l'import. Aucun point bloquant. Prêt à merger.
ahitosly scheduled this pull request to auto merge when all checks succeed 2026-09-03 00:39:01 +02:00
ahitosly deleted branch feat/T-017-server-dictate-limits 2026-09-03 00:40:47 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
cloud-rennais/aria!17
No description provided.