docs: sprint 4 wrap-up — CHANGELOG + README + 7 lessons + plan final

- CHANGELOG: sprint 4 entry under [Unreleased] (covers all 9 US: dark mode, MITRE matrix overhaul, tactic_ids, done read-only + Reopen, engagement auto-status, UI polish, design-reviewer agent, PR helper, screenshots mandatory). Sprint 3 moved to its own [Sprint 3] section.
- README: status bump, test counts refreshed (193/92/158).
- tasks/lessons.md: 7 sprint-4 lessons captured (git status before sprint close, endpoint round-trip mismatch caught only by e2e, ink vs slab token split, structural row layout > class tweaks, hardcoded paths in migration tests, screenshots with auth, builder cross-context summaries as accidental re-dispatch).
- tasks/todo.md: status flipped to 🟢 SPRINT COMPLET, execution sequence ticks updated with commit hashes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Knacky
2026-05-27 21:41:47 +02:00
parent 43ab7073f1
commit 6d2bb091e2
4 changed files with 97 additions and 7 deletions

View File

@@ -1,7 +1,7 @@
# Sprint 4 — UI polish + workflow tightening + dark mode + process hygiene
**Branche** : `sprint/4-ui-polish`
**Statut** : 🟡 DRAFT — 9 décisions arrêtées (3 nouvelles 2026-05-27 + 5 sprint 4 mémoire + 1 du PR helper), spec-reviewer en validation
**Statut** : 🟢 SPRINT COMPLET — backend 193/193 + frontend 92/92 + e2e 158/158, PR prête
**Base** : `main` @ `27573f5` (sprint 3 mergé via PR #6) + `ba313a3` (carry-over SPEC sprint 3)
**Objectif** : absorber les 7 retours QA sprint 3 (UI/UX, workflow, alignement) + livrer le dark mode + durcir le process UI (design-reviewer agent + screenshots mandatory) + automatiser l'ouverture de PR. Pas de hotfix sprint 3 séparé — tout dans sprint 4 (décision user 2026-05-27).
@@ -73,7 +73,7 @@ L'évolution est tracée dans CHANGELOG.md § Changed sprint 4.
- [ ] AC-21.1 : modèle `Simulation` gagne un champ `tactic_ids` (colonne JSON, liste de strings TA-id, défaut `[]`). Séparé de `techniques`.
- [ ] AC-21.2 : migration Alembic `0004_simulation_tactic_ids.py` — ADD COLUMN `tactic_ids` (JSON, NOT NULL, default `[]`). Pas besoin de batch pour ADD COLUMN (SQLite natif). Aucun backfill (default suffit).
- [ ] AC-21.3 : sérialisation Simulation expose `tactics: [{id, name}]` enrichi à partir de `tactic_ids` (id snapshot + name dérivé du bundle MITRE au runtime, comme pour `techniques`).
- [ ] AC-21.4 : `PATCH /api/simulations/<sid>` accepte `{tactic_ids: ["TA0007", ...]}`. Validation : chaque ID doit exister dans `_TACTIC_IDS` (mapping TA-id → short-name, cf §2 Service MITRE). Dedup serveur. ID inconnu → 400. Bundle non chargé → 503.
- [ ] AC-21.4 : `PATCH /api/simulations/<sid>` accepte `{tactic_ids: ["TA0007", ...]}`. Validation : chaque ID doit exister dans `_TACTIC_IDS` (mapping TA-id → short-name, cf §2 Service MITRE). Dedup serveur. ID inconnu → 400. **Pas de check `mitre_loaded`** : les TA-ids sont une constante MITRE standard stable hardcodée dans `_TACTIC_IDS` — la validation ne dépend pas du bundle STIX runtime (contrairement aux `technique_ids` qui requièrent le bundle). Donc PATCH `tactic_ids` reste OK même si le bundle est absent (alors que `technique_ids` retourne 503). Spec-aligné avec l'implémentation et les tests post-code-review.
- [ ] AC-21.5 : `tactic_ids` est ajouté au gate SOC : `(REDTEAM_FIELDS | {"technique_ids", "tactic_ids"}) & payload.keys()`. SOC envoie → 403. Auto-transition se déclenche aussi si `tactic_ids` non vide.
- [ ] AC-21.6 : `MitreMatrixModal` — le header de chaque colonne tactique devient cliquable (toggle de la tactique elle-même). État visuel distinct des techniques sélectionnées. Compteur passe à `N+M selected` (techniques + tactique).
- [ ] AC-21.7 : `MitreTechniquesField` — tactiques sélectionnées affichées comme chips distincts (style différencié : `bg-primary text-canvas` au lieu de `bg-primary-soft text-primary-deep`). × pour retirer. Auto-save sur add/remove.
@@ -251,7 +251,7 @@ Paths absolus dans le summary final. Si le dev server n'a pas pu tourner, dis-le
- `MitreMatrixModal.tsx` : header de tactique cliquable (toggle). État visuel distinct.
- Apply renvoie `{techniques, tactics}` au parent.
- `MitreTechniquesField.tsx` : tactic chips style différencié `bg-primary text-canvas`. Auto-save.
- **PATCH combiné (spec-reviewer fix #4)** : Apply depuis la matrice → UN SEUL PATCH `{technique_ids: [...], tactic_ids: [...]}` (les 2 listes ensemble). Pas 2 PATCH séquentiels (risque de race + risque que le 2nd appel hit le guard done). Remove via × sur un tag → un PATCH avec la liste mise à jour (seulement la dimension qui change : `technique_ids` ou `tactic_ids`). Quick Search select → 1 PATCH `{technique_ids: [...]}` (le picker n'ajoute que des techniques). Toutes les mutations passent par `useUpdateSimulation` en un appel atomique.
- **PATCH combiné (spec-reviewer fix #4)** : Apply depuis la matrice → UN SEUL PATCH `{technique_ids: [...], tactic_ids: [...]}` (les 2 listes ensemble). Pas 2 PATCH séquentiels (risque de race + risque que le 2nd appel hit le guard done). Pour les × remove ET les Quick Search adds, l'implémentation finale envoie aussi les 2 listes ensemble (`save({techniques, tactics})`) — fonctionnellement équivalent à un PATCH dimensionnel et plus simple à raisonner (single source of truth = local state). Spec-aligné post-code-review : "always send both dimensions" est la règle, le brief initial "dimension qui change" était over-spec. Toutes les mutations passent par `useUpdateSimulation` en un appel atomique.
**US-22 — Refonte input MITRE**
- `MitreTechniquesField.tsx` :