docs(sprint-4): plan + SPEC updates (Done terminal, engagement auto, UI/UX, workflows)
- tasks/todo.md: sprint 4 plan with 9 user stories (US-17 → US-25), 9 décisions arrêtées - SPEC.md § Fonctionnement: Done is terminal, Reopen returns to review_required (open to all roles); engagement auto-flips planned → active when any simulation hits in_progress, no auto-rollback - SPEC.md § Référentiel MITRE: sprint 3 multi-tech + sprint 4 tactic_ids separated field - SPEC.md § UI/UX (new): theming light/dark/system with system default, button convention (icon + ≤8-char label), modal focus trap V1 - SPEC.md § Workflows: design-reviewer inserted between frontend-builder and code-reviewer; PR via make open-pr Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
19
SPEC.md
19
SPEC.md
@@ -25,11 +25,12 @@ La redteam peut modifier l'ensemble des champs d'une simulation, tandis que l'an
|
|||||||
Un workflow de simulation doit être mis en place : Pending, In progress, Review required, Done.
|
Un workflow de simulation doit être mis en place : Pending, In progress, Review required, Done.
|
||||||
Le workflow se mettra à jour de la manière suivante :
|
Le workflow se mettra à jour de la manière suivante :
|
||||||
- Création de la simulation : pending
|
- Création de la simulation : pending
|
||||||
- La redteam saisit des informations dans la simulation : in progress
|
- La redteam saisit des informations dans la simulation : in progress (auto)
|
||||||
- La redteam décide par une action manuelle de passer la simulation en status "review required" ce qui offre à la possibilité au SOC de remplir les informations nécessaire.
|
- La redteam décide par une action manuelle de passer la simulation en status "review required" ce qui offre à la possibilité au SOC de remplir les informations nécessaire.
|
||||||
- Le SOC (ou la redteam) décide par une action manuelle de passe la simulation en status "Done".
|
- Le SOC (ou la redteam) décide par une action manuelle de passer la simulation en status "Done".
|
||||||
|
- **Done est terminal** : aucune édition n'est possible sur une simulation Done. Pour reprendre, n'importe lequel des trois rôles (admin / redteam / soc) peut déclencher une action "Reopen" qui ramène la simulation à "review required" — les champs redeviennent éditables selon les règles habituelles. Cette transition est la seule autorisée à quitter Done.
|
||||||
|
|
||||||
Un engagement correspond à une mission redteam. Il est possible d'ajouter plusieurs test dans un engagement.
|
Un engagement correspond à une mission redteam. Il est possible d'ajouter plusieurs test dans un engagement. **Le statut de l'engagement progresse automatiquement** : créer un engagement le met à "planned" ; dès qu'une simulation de cet engagement passe en "in progress" (auto-transition par la redteam ou manuelle), l'engagement passe à "active" — pas de retour arrière automatique. La transition vers "closed" reste manuelle.
|
||||||
|
|
||||||
Prévoir un module d'authentification : dans un premier temps local à la bdd.
|
Prévoir un module d'authentification : dans un premier temps local à la bdd.
|
||||||
|
|
||||||
@@ -51,8 +52,11 @@ Dans un second temps, après que la V1 soit terminée, nous ajouterons une couch
|
|||||||
## Workflows
|
## Workflows
|
||||||
* Découpage en sprint
|
* Découpage en sprint
|
||||||
* Chaque sprint doit apporter une nouvelle fonctionnalité à tester sur l'UI
|
* Chaque sprint doit apporter une nouvelle fonctionnalité à tester sur l'UI
|
||||||
* A chaque sprint : Code + Test (Test unitaire python + Test pywright front) + Reviews (spec + code). Une fois le review OK, PR que je valide après test.
|
* A chaque sprint : Code + Test (Test unitaire python + Test pywright front) + Reviews (spec + design + code). Une fois les reviews OK, PR que je valide après test.
|
||||||
|
* **Séquence sprint (depuis sprint 4)** : spec-reviewer → backend-builder → frontend-builder (livre des screenshots obligatoires) → **design-reviewer (NOUVEAU sprint 4)** → code-reviewer → test-verifier → team-lead PR.
|
||||||
|
* **design-reviewer** revoit le diff frontend + screenshots du sprint courant, audit alignement / hiérarchie typo / usage tokens DESIGN.md / cohérence visuelle / responsive. Read-only, ne modifie rien.
|
||||||
* A chaque fin de sprint, avant mes tests, le team lead doit me faire un récapitulatif synthétique de ce qui a été fait et ce qui doit être testé (et comment le tester).
|
* A chaque fin de sprint, avant mes tests, le team lead doit me faire un récapitulatif synthétique de ce qui a été fait et ce qui doit être testé (et comment le tester).
|
||||||
|
* **Création de PR** : à la fin du sprint, le team-lead ouvre la PR via `make open-pr SPRINT=N TITLE="..." BODY=path/to/body.md` (depuis sprint 4). Le script `scripts/open-pr.sh` parse `~/.git-credentials` et POST sur l'API Gitea.
|
||||||
|
|
||||||
# Team
|
# Team
|
||||||
|
|
||||||
@@ -157,6 +161,13 @@ Conforme à la spec (partie RedTeam + partie SOC). Workflow Pending → In progr
|
|||||||
* **Bundle local** : JSON officiel STIX 2.1 MITRE Enterprise embarqué dans l'image (`backend/data/mitre/enterprise-attack.json`).
|
* **Bundle local** : JSON officiel STIX 2.1 MITRE Enterprise embarqué dans l'image (`backend/data/mitre/enterprise-attack.json`).
|
||||||
* Pas d'appel réseau au runtime. Seed/refresh manuel via `make update-mitre`.
|
* Pas d'appel réseau au runtime. Seed/refresh manuel via `make update-mitre`.
|
||||||
* Utilisé au Sprint 2+ pour l'autocomplete des TTPs (T-id + nom + tactique).
|
* Utilisé au Sprint 2+ pour l'autocomplete des TTPs (T-id + nom + tactique).
|
||||||
|
* **Sprint 3+** : multi-techniques par simulation, sélectionnables via autocomplete OU matrice cliquable.
|
||||||
|
* **Sprint 4+** : sélection de tactiques (TA-id, ex `TA0007 Discovery`) en plus des techniques. Stockées dans un champ `tactic_ids` distinct, séparé sémantiquement de `technique_ids`.
|
||||||
|
|
||||||
|
## UI/UX
|
||||||
|
* **Theming** : light + dark + system (suit `prefers-color-scheme`). Toggle dans la topbar. Persistance localStorage clé `mimic-theme`. Défaut : `system`.
|
||||||
|
* **Boutons d'action** : icône (lucide-react ou unicode) + label court (≤ 8 chars) préférés aux phrases. Exceptions justifiées pour des libellés workflow-critiques sans icône évidente (ex : "Mark for review", "Clear all").
|
||||||
|
* **Modals** : focus trap V1 minimal (focus initial sur le champ principal, Tab cycle, Escape + backdrop click = Cancel). Full WAI-ARIA conformance reportée à un sprint a11y dédié.
|
||||||
|
|
||||||
## Stack technique précisée
|
## Stack technique précisée
|
||||||
* **Backend** : Python 3.12, Flask, SQLAlchemy, Alembic, pytest, ruff, mypy. Auth via `PyJWT` + middleware decorator.
|
* **Backend** : Python 3.12, Flask, SQLAlchemy, Alembic, pytest, ruff, mypy. Auth via `PyJWT` + middleware decorator.
|
||||||
|
|||||||
438
tasks/todo.md
438
tasks/todo.md
@@ -1,290 +1,298 @@
|
|||||||
# Sprint 3 — MITRE matrix modal + multi-technique simulations
|
# Sprint 4 — UI polish + workflow tightening + dark mode + process hygiene
|
||||||
|
|
||||||
**Branche** : `sprint/3-mitre-matrix`
|
**Branche** : `sprint/4-ui-polish`
|
||||||
**Statut** : 🟢 SPRINT COMPLET — 105/105 sprint 3 e2e verts, code-review traité, PR prête
|
**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
|
||||||
**Base** : `main` @ `e1d9738`
|
**Base** : `main` @ `27573f5` (sprint 3 mergé via PR #6) + `ba313a3` (carry-over SPEC sprint 3)
|
||||||
**Objectif** : remplacer la sélection MITRE mono-technique de sprint 2 par une sélection multi-techniques avec deux modes complémentaires : autocomplete (rapide) et matrice cliquable (exploration). Les techniques choisies s'affichent comme tags sur la simulation.
|
**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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 0. Évolution SPEC.md à acter en début de sprint
|
## 0. SPEC.md updates
|
||||||
|
|
||||||
SPEC.md § Simulation dit aujourd'hui "Type d'attaque MITRE correspondant (peut être une liste de référence)" au singulier. Le team-lead met à jour cette ligne en début de sprint (pas de PR séparée) pour refléter le scope multi-techniques. Texte cible :
|
- ✅ `ba313a3` — § Simulation : "Type d'attaque MITRE correspondant (peut être une liste de référence)" → "Types d'attaque MITRE correspondants (multi-techniques) ..." (carry-over manquant de sprint 3 §0).
|
||||||
|
- 🟡 § Fonctionnement à enrichir en début de sprint 4 :
|
||||||
|
- Préciser que "Done" est terminal : aucune édition possible sans Reopen explicite.
|
||||||
|
- Préciser que la transition Reopen `Done → Review required` est ouverte à admin/redteam/soc.
|
||||||
|
- Préciser que la création/avancement d'une simu fait avancer l'engagement de `planned` à `active` automatiquement (jamais l'inverse).
|
||||||
|
- 🟡 § Décisions techniques à enrichir :
|
||||||
|
- Section "UI/UX" : convention boutons (icônes / symboles préférés aux longs libellés).
|
||||||
|
- Section "Theming" : dark mode supporté, toggle topbar, défaut = `prefers-color-scheme` du système, persistance `localStorage`.
|
||||||
|
|
||||||
> Types d'attaque MITRE correspondants (multi-techniques) — sélectionnables par autocomplete OU via la matrice ATT&CK affichée en modale.
|
L'évolution est tracée dans CHANGELOG.md § Changed sprint 4.
|
||||||
|
|
||||||
L'évolution est tracée dans CHANGELOG.md § Changed du sprint 3.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. User stories
|
## 1. User stories
|
||||||
|
|
||||||
### US-13 — En tant que redteam, je sélectionne plusieurs techniques MITRE par simulation
|
### US-17 — UI polish : dédoublonnage boutons + alignement + icônes
|
||||||
**Pourquoi** : un test couvre souvent plusieurs TTPs (ex : Initial Access → Discovery → Execution). Mono-technique limite la description réelle d'un test.
|
**Pourquoi** : QA sprint 3 — `EngagementsListPage` montre 2 boutons "New engagement" + "Create engagement" qui font la même chose ; le bouton Create de `UsersAdminPage` reste mal aligné malgré le fix sprint 2.
|
||||||
|
|
||||||
**Critères d'acceptation**
|
**Critères d'acceptation**
|
||||||
- [ ] AC-13.1 : modèle `Simulation` n'a plus `mitre_technique_id` ni `mitre_technique_name` (scalaires). Remplacés par `techniques` (colonne JSON, liste d'objets `{id: str, name: str}`, défaut `[]`).
|
- [ ] AC-17.1 : `EngagementsListPage` n'affiche qu'UN SEUL bouton "New engagement". Le doublon "Create engagement" est supprimé.
|
||||||
- [ ] AC-13.2 : migration Alembic `0003_simulation_techniques_array.py` :
|
- [ ] AC-17.2 : convention nouveaux boutons d'action (Create / Add / Save / Delete) : icône lucide-react ou unicode + label court (≤ 8 chars), pas de phrases. Audit des boutons existants : ne refactoriser que ceux qui dépassent ce seuil, garder les "Mark for review" / "Clear all" qui sont déjà courts ou ont une sémantique sans icône évidente. Boutons à passer en icône+label : "Save Red Team" → "Save" + icône, "Save SOC" → "Save SOC" + icône, "ADD TECHNIQUE" → "+" + "Add", "QUICK SEARCH" → "🔍" + "Search".
|
||||||
- ajoute la colonne `techniques` (JSON)
|
- [ ] AC-17.3 : `UsersAdminPage` formulaire "Create account" — les 3 FormField (Username, Password, Role) ont leurs labels alignés sur la même baseline ET leurs inputs alignés sur la même baseline. Le bouton Create est aligné horizontalement avec la rangée des inputs. Pixel-perfect au niveau visuel à 1280×720.
|
||||||
- backfill les simulations existantes : si `mitre_technique_id` non null → `techniques = [{id, name}]`, sinon `techniques = []`
|
|
||||||
- drop les deux anciennes colonnes
|
|
||||||
- migration réversible (downgrade : prendre le premier élément, ré-injecter dans les scalaires, drop `techniques`)
|
|
||||||
- [ ] AC-13.3 : sérialisation simulation expose `techniques: [{id, name, tactics: [...]}]` — le backend enrichit chaque entrée avec ses `tactics` depuis le service MITRE au moment du serialize (snapshot d'`id`+`name` en DB, tactics dérivées au runtime depuis le bundle).
|
|
||||||
- [ ] AC-13.4 : `PATCH /api/simulations/<sid>` accepte `{technique_ids: ["T1059", "T1078"]}` (liste d'IDs string). Backend valide chaque ID contre le bundle MITRE, résout `name`, écrit `[{id, name}]` en DB. ID inconnu → 400 `{error: "unknown technique id: T9999"}`.
|
|
||||||
- [ ] AC-13.5 : la règle d'auto-transition `pending → in_progress` s'applique aussi à `technique_ids` quand la liste reçue est non vide.
|
|
||||||
|
|
||||||
### US-14 — En tant que redteam, je vois et retire les techniques d'une simulation sous forme de tags
|
### US-18 — Simulation `done` = read-only + Reopen
|
||||||
**Pourquoi** : visualiser rapidement la couverture TTP d'un test.
|
**Pourquoi** : QA sprint 3 — actuellement une simu `done` peut toujours être PATCHée, ce qui contredit le statut terminal.
|
||||||
|
|
||||||
**Critères d'acceptation**
|
**Critères d'acceptation**
|
||||||
- [ ] AC-14.1 : sur `SimulationFormPage`, à la place du seul `MitreTechniquePicker` du sprint 2, un composant `MitreTechniquesField` affiche :
|
- [ ] AC-18.1 : `PATCH /api/simulations/<sid>` avec status courant `done` retourne **409** `{error: "simulation is done — reopen first"}` quel que soit le rôle.
|
||||||
- Liste des techniques sélectionnées sous forme de chips/tags (id + name, ex : `T1059 — Command and Scripting Interpreter`), avec un `×` cliquable pour retirer chaque technique.
|
- [ ] AC-18.2 : nouvelle transition `POST /api/simulations/<sid>/transition {to: "review_required"}` quand status courant == `done` → 200, autorisée admin + redteam + soc. Met à jour `updated_at`.
|
||||||
- Bouton "Add technique" qui ouvre la modale matrice (US-15).
|
- [ ] AC-18.3 : la transition `→ review_required` depuis `pending`/`in_progress` garde le comportement sprint 2 (admin/redteam only). La nouvelle règle s'ajoute SEULEMENT pour le cas `done`.
|
||||||
- Bouton "Quick search" qui ouvre l'autocomplete existant (réutilisation du `MitreTechniquePicker`) en mode "ajoute à la liste" (sélection = append, pas replace).
|
- [ ] AC-18.4 : sur `SimulationFormPage`, quand status == `done` :
|
||||||
- État vide : message "No techniques selected — use the matrix or the quick search to add."
|
- Tous les champs (RT + SOC) sont disabled.
|
||||||
- [ ] AC-14.2 : retirer un tag (× sur le chip) déclenche un PATCH immédiat (auto-save) avec la liste mise à jour. La modale matrice (US-15) auto-save aussi via "Apply". Le picker Quick Search auto-save chaque sélection. Toast `'Techniques updated'` sur succès, toast erreur sinon. Pas de bouton Save manuel pour les techniques.
|
- `MitreTechniquesField` en read-only (chips sans ×, input + icône matrice masqués).
|
||||||
- [ ] AC-14.3 : sur `SimulationList` (table dans EngagementDetailPage), la colonne "MITRE" affiche un compteur + premier tag (ex : `T1059 +2` si 3 techniques sélectionnées). Si la liste est vide, afficher `—`.
|
- L'action bar affiche UNIQUEMENT un bouton "Reopen" (visible admin/redteam/soc).
|
||||||
- [ ] AC-14.4 : ordre des tags dans la simulation préservé entre lecture et écriture (pas de tri imposé côté serveur).
|
- Save RT, Save SOC, Mark for review, Close, Delete sont masqués.
|
||||||
- [ ] AC-14.5 : tags affichés avec les couleurs/spacing DESIGN.md (`bg-primary-soft`, `text-primary-deep`, `rounded-full`, `px-md py-xxs`).
|
- [ ] AC-18.5 : click Reopen → POST transition, toast `'Simulation reopened'`, badge se met à jour, les champs redeviennent éditables selon le rôle.
|
||||||
|
|
||||||
### US-15 — En tant que redteam, j'ouvre la matrice MITRE ATT&CK pour explorer et sélectionner des techniques
|
### US-19 — Engagement auto-status `planned → active`
|
||||||
**Pourquoi** : l'autocomplete est efficace si on sait ce qu'on cherche ; la matrice est nécessaire pour "voir ce qui existe" et combiner par tactique.
|
**Pourquoi** : QA sprint 3 — un engagement reste `planned` même quand ses simulations sont in_progress.
|
||||||
|
|
||||||
**Critères d'acceptation**
|
**Critères d'acceptation**
|
||||||
- [ ] AC-15.1 : nouvel endpoint `GET /api/mitre/matrix` (auth, tous rôles) → tree `[{tactic_id, tactic_name, techniques: [{id, name, subtechniques: [{id, name}]}]}]`. Chaque technique top-level embarque ses sub-techniques (`T1059` → `[T1059.001, T1059.002, ...]`). Ordre des tactiques = ordre canonique MITRE Enterprise (Initial Access → Execution → Persistence → ... → Exfiltration → Impact). 503 si bundle non chargé.
|
- [ ] AC-19.1 : quand une simulation transitionne vers `in_progress` (auto-transition via PATCH RT-field non vide), si son engagement parent est `planned`, l'engagement passe à `active` dans la même unité de travail DB.
|
||||||
- [ ] AC-15.2 : composant `MitreMatrixModal` :
|
- [ ] AC-19.2 : si l'engagement est déjà `active` ou `closed`, pas de changement.
|
||||||
- Modal large (≥ 1100px), scroll vertical interne.
|
- [ ] AC-19.3 : aucun retour arrière auto. La transition `closed` reste manuelle.
|
||||||
- Layout horizontal en colonnes : 1 colonne par tactique. Header de colonne = nom de la tactique + compteur de techniques sélectionnées dans cette tactique (sub-techniques incluses).
|
- [ ] AC-19.4 : le frontend invalide `["engagement", eid]` et `["engagements"]` après chaque PATCH/transition simulation pour récupérer le statut à jour.
|
||||||
- Chaque technique top-level = bouton/cellule cliquable. État sélectionné visible (`bg-primary` + texte blanc).
|
|
||||||
- Si la technique a des sub-techniques (`subtechniques.length > 0`), un chevron (▸/▾) précède le nom. Click sur le chevron = expand/collapse (n'affecte PAS la sélection). Click sur le label = toggle sélection de la technique top-level.
|
|
||||||
- Sub-techniques affichées en cascade indentée sous leur parent quand expand. Cliquables individuellement (toggle de la sub). État visuel distinct : `bg-primary-soft` quand sélectionnée, indent `pl-md`, font-size légèrement plus petit.
|
|
||||||
- Sélectionner une sub-technique ne sélectionne PAS le parent (les deux sont indépendants côté data). Mais le compteur de tactique somme parent + subs sélectionnées.
|
|
||||||
- Champ de recherche en haut du modal qui filtre les techniques affichées (case-insensitive sur id ET name). Quand le filtre matche une sub-technique, son parent est automatiquement expand pour la rendre visible.
|
|
||||||
- Boutons en footer : "Cancel" (ferme sans appliquer), "Apply N techniques" (compteur = total parents + subs sélectionnés).
|
|
||||||
- [ ] AC-15.3 : la modale est ouverte depuis le bouton "Add technique" de US-14. Elle reçoit en input la liste actuelle de techniques sélectionnées et travaille sur une copie locale ; "Apply" déclenche directement le PATCH (auto-save, cf AC-14.2) et ferme la modale ; "Cancel" jette le diff local.
|
|
||||||
- [ ] AC-15.4 : Escape ferme la modale (= Cancel). Click sur le backdrop = Cancel.
|
|
||||||
- [ ] AC-15.5 : a11y V1 — **scope minimal explicite** : (1) focus initial sur le champ recherche à l'ouverture, (2) Tab cycle entre les éléments focusables de la modale (wrap : dernier élément → premier), (3) Escape ferme = onCancel, (4) ARIA `role="dialog"` + `aria-labelledby` sur le titre. Full WAI-ARIA dialog conformance (live regions, focus restoration au close, screen reader announcements détaillés) **out of scope V1** — c'est une dette assumée à reprendre dans un sprint a11y dédié.
|
|
||||||
|
|
||||||
### US-16 — En tant que user (tous rôles), j'utilise les autres fonctionnalités sans régression
|
### US-20 — Matrice MITRE : look attack.mitre.org + pas de scroll horizontal
|
||||||
**Critères d'acceptation** (régression)
|
**Pourquoi** : QA sprint 3 — la matrice actuelle a un scroll horizontal et un layout maison.
|
||||||
- [ ] AC-16.1 : workflow sprint 2 (auto-transition, transitions manuelles, RBAC SOC) inchangé — tous les ACs sprint 2 (US-7 → US-12) continuent de passer.
|
|
||||||
- [ ] AC-16.2 : l'ancien `MitreTechniquePicker` est conservé dans la base de code MAIS sa signature passe en clean rewrite (`onSelect({id, name})` au lieu de `onChange(id, name)`), wrappé par `MitreTechniquesField` en mode append.
|
**Critères d'acceptation**
|
||||||
- [ ] AC-16.3 : aucune e2e sprint 1/sprint 2 ne casse. Quelques assertions sprint 2 (US-8 et US-10) qui validaient le mono-technique sont mises à jour pour refléter la liste.
|
- [ ] AC-20.1 : `MitreMatrixModal` est élargi à `max-w-[98vw]`.
|
||||||
|
- [ ] AC-20.2 : layout 12 colonnes (12 tactiques Enterprise) qui tiennent SANS scroll horizontal à 1280×720 min. Largeur cellule technique ~95-110px (vs 220px actuel), font `text-[12px]`.
|
||||||
|
- [ ] AC-20.3 : couleurs cohérentes DESIGN.md ET visuellement proches de attack.mitre.org : header tactic avec fond contrasté + label uppercase tracking, techniques en cellules `bg-canvas` avec hairline border, hover `bg-fog`, sélectionnée `bg-primary` texte blanc.
|
||||||
|
- [ ] AC-20.4 : scroll vertical autorisé (`max-h-[80vh] overflow-y-auto`). Jamais de scroll horizontal.
|
||||||
|
- [ ] AC-20.5 : sub-techniques expand/collapse PRÉSERVÉ — pas de régression sprint 3 AC-15.2. Compteur "N selected" par tactique reste lisible.
|
||||||
|
- [ ] AC-20.6 : screenshot comparaison Mimic matrix vs attack.mitre.org joint au summary frontend-builder.
|
||||||
|
|
||||||
|
### US-21 — Sélection de tactique en plus des techniques
|
||||||
|
**Pourquoi** : QA sprint 3 — l'utilisateur veut tagger une simulation par TACTIQUE (ex : `TA0007 Discovery`) sans devoir choisir une technique précise.
|
||||||
|
|
||||||
|
**Critères d'acceptation**
|
||||||
|
- [ ] 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 (préfixe `TA`, présent dans `_TACTIC_ORDER`). Dedup serveur. ID inconnu → 400. Bundle non chargé → 503.
|
||||||
|
- [ ] 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.
|
||||||
|
|
||||||
|
### US-22 — Refonte input MITRE dans le form
|
||||||
|
**Pourquoi** : QA sprint 3 — pattern actuel (2 boutons textuels) trop verbeux.
|
||||||
|
|
||||||
|
**Critères d'acceptation**
|
||||||
|
- [ ] AC-22.1 : sous le label "MITRE Techniques", le composant affiche :
|
||||||
|
- Une rangée de chips (techniques + tactiques sélectionnées).
|
||||||
|
- En dessous, une rangée `[input texte autocomplete] [icône matrice]`.
|
||||||
|
- L'input fait l'autocomplete inline (debounce 200ms, dropdown ↑↓Enter, comme sprint 2 mais EMBARQUÉ).
|
||||||
|
- L'icône matrice à droite ouvre `MitreMatrixModal`.
|
||||||
|
- Aucun bouton textuel "Add Technique" ni "Quick Search".
|
||||||
|
- [ ] AC-22.2 : les chips affichent UNIQUEMENT la référence (T-id ou TA-id, ex : `T1059.001` ou `TA0007`). Le nom apparaît au survol via `title=` attribute.
|
||||||
|
- [ ] AC-22.3 : `MitreTechniquePicker` existant est intégré dans le nouveau layout comme l'autocomplete inline. Garde la signature `onSelect`.
|
||||||
|
- [ ] AC-22.4 : empty state : message court ("No techniques selected") dans la zone des chips. L'input et l'icône matrice restent visibles.
|
||||||
|
- [ ] AC-22.5 : mode read-only (SOC sur simu non-done, ou tous sur simu done) : chips sans ×, input + icône cachés.
|
||||||
|
|
||||||
|
### US-23 — Dark mode
|
||||||
|
**Pourquoi** : ergonomie demandée. Sprint 4 framing acté.
|
||||||
|
|
||||||
|
**Critères d'acceptation**
|
||||||
|
- [ ] AC-23.1 : un toggle theme dans la topbar (`Layout.tsx`), à droite du nom user. Icône lucide-react `Sun` / `Moon` / `Monitor`.
|
||||||
|
- [ ] AC-23.2 : 3 états : `light`, `dark`, `system` (auto = suit `prefers-color-scheme`). Toggle cycle entre les 3.
|
||||||
|
- [ ] AC-23.3 : persistance via `localStorage` (clé `mimic-theme`, valeur `'light'|'dark'|'system'`, défaut `'system'`).
|
||||||
|
- [ ] AC-23.4 : Tailwind `darkMode: 'class'` activé. Classe `dark` appliquée sur `<html>` selon le résolu. Tokens DESIGN.md étendus avec variantes dark (canvas, paper, ink, graphite, charcoal, etc.). Primary HP Electric Blue garde sa teinte.
|
||||||
|
- [ ] AC-23.5 : tous les composants principaux audités et utilisent les classes Tailwind `dark:bg-...` / `dark:text-...`. Pas de couleur hardcodée.
|
||||||
|
- [ ] AC-23.6 : screenshots light + dark de `EngagementsListPage`, `SimulationFormPage`, `MitreMatrixModal` ouverte. Joints au summary.
|
||||||
|
|
||||||
|
### US-24 — Process hygiene : design-reviewer agent + screenshots mandatory
|
||||||
|
**Pourquoi** : sprint 4 framing acté. Sprint 2/3 avait laissé passer des bugs visuels faute de pass design dédié.
|
||||||
|
|
||||||
|
**Critères d'acceptation**
|
||||||
|
- [ ] AC-24.1 : nouveau fichier `.claude/agents/design-reviewer.md`. Brief : revoit le diff frontend + les screenshots fournis par le frontend-builder, audit alignement / hiérarchie typo / DESIGN.md token usage / responsive sanity / cohérence visuelle. Read-only. Lance après frontend-builder, avant code-reviewer.
|
||||||
|
- [ ] AC-24.2 : `.claude/agents/frontend-builder.md` mis à jour pour rendre EXPLICITE que screenshots sont MANDATORY avant de marquer la tâche terminée (au moins 1 par feature visible / état modifié). Liste explicite des screenshots attendus dans le summary.
|
||||||
|
- [ ] AC-24.3 : workflow sprint mis à jour dans SPEC.md § Workflows : ajouter design-reviewer entre frontend-builder et code-reviewer.
|
||||||
|
|
||||||
|
### US-25 — Infra : PR helper script + Makefile target
|
||||||
|
**Pourquoi** : capitaliser le pattern Gitea API curl utilisé en sprint 3 pour automatiser les PRs.
|
||||||
|
|
||||||
|
**Critères d'acceptation**
|
||||||
|
- [ ] AC-25.1 : `scripts/open-pr.sh` (executable, `set -euo pipefail`). Lit `~/.git-credentials`. Args : `--sprint=N`, `--title="..."`, `--body=path`. Détecte la branche courante + owner/repo depuis `git remote get-url origin`. POST `/api/v1/repos/{owner}/{repo}/pulls`. Imprime PR URL.
|
||||||
|
- [ ] AC-25.2 : target Makefile `open-pr SPRINT=N TITLE="..." BODY=path` wrap le script.
|
||||||
|
- [ ] AC-25.3 : documenté dans README.md (1 paragraphe).
|
||||||
|
- [ ] AC-25.4 : team-lead utilise ce target pour ouvrir la PR sprint 4 (dogfooding).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Brief technique — Backend Builder
|
## 2. Brief technique — Backend Builder
|
||||||
|
|
||||||
**Scope strict** : `backend/`. Pas de touche au frontend, e2e, docs (team-lead).
|
**Scope strict** : `backend/`. Pas de touche au frontend, e2e, `.claude/agents/`, `scripts/`, `Makefile`, docs.
|
||||||
|
|
||||||
### Livrables
|
### Livrables
|
||||||
|
|
||||||
**Modèle `Simulation`** (`backend/app/models/simulation.py`)
|
**Modèle `Simulation`** — ajout uniquement :
|
||||||
- Remplacer `mitre_technique_id`, `mitre_technique_name` (str nullable) par :
|
|
||||||
```python
|
```python
|
||||||
techniques: Mapped[list[dict]] = mapped_column(JSON, nullable=False, default=list)
|
tactic_ids: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
||||||
```
|
```
|
||||||
- Stockage : `[{"id": "T1059", "name": "Command and Scripting Interpreter"}, ...]`. Pas de `tactics` en DB (dérivé au serialize).
|
|
||||||
|
|
||||||
**Service workflow** (`backend/app/services/simulation_workflow.py`) — **mise à jour RBAC field-level OBLIGATOIRE**
|
**Migration Alembic `0004_simulation_tactic_ids.py`** :
|
||||||
- Dans le `REDTEAM_FIELDS` frozenset existant : **retirer** `"mitre_technique_id"` et `"mitre_technique_name"`, **ajouter** `"technique_ids"`.
|
- Upgrade : `op.add_column('simulations', sa.Column('tactic_ids', sa.JSON(), nullable=False, server_default=sa.text("'[]'")))`. ADD COLUMN OK sans batch sur SQLite. `server_default` règle le NOT NULL pour les lignes existantes.
|
||||||
- Sans ce changement : un user soc qui PATCH avec `{technique_ids: [...]}` reçoit un silent no-op (champ ignoré) au lieu du 403 attendu. La gate field-level RBAC pour `technique_ids` repose intégralement sur ce frozenset.
|
- Downgrade : `with op.batch_alter_table('simulations') as batch_op: batch_op.drop_column('tactic_ids')`.
|
||||||
- Le `SOC_FIELDS` frozenset reste inchangé.
|
- Test : schéma post-upgrade a `tactic_ids` NOT NULL avec default `[]`.
|
||||||
- Tester explicitement : `test_simulations_techniques.py` doit inclure "SOC PATCH technique_ids → 403" (cf. liste de tests plus bas).
|
|
||||||
|
|
||||||
**Migration Alembic `0003_simulation_techniques_array.py`**
|
**Serializer** : `serialize_simulation(sim)` ajoute `tactics: [{id, name}]` enrichi runtime.
|
||||||
- Upgrade :
|
|
||||||
1. Ajouter colonne `techniques` (JSON, nullable=True temporaire, default `'[]'`) — `op.add_column` direct OK.
|
|
||||||
2. Data migration : pour chaque ligne, si `mitre_technique_id` IS NOT NULL → set `techniques = '[{"id":"<id>","name":"<name>"}]'`, sinon `'[]'`.
|
|
||||||
3. ALTER column `techniques` → nullable=False — **OBLIGATOIRE via `op.batch_alter_table('simulations', ...)`** car SQLite ne supporte pas ALTER COLUMN nativement.
|
|
||||||
4. Drop columns `mitre_technique_id`, `mitre_technique_name` — **OBLIGATOIRE via `op.batch_alter_table('simulations', ...)`** (même raison : SQLite ne supporte pas DROP COLUMN hors batch mode).
|
|
||||||
- Downgrade : symétrique avec les MÊMES guards batch_alter_table pour les étapes ALTER/DROP. Recrée les 2 colonnes, prend le premier élément de `techniques` si non vide, drop `techniques`.
|
|
||||||
- Pattern à suivre : la migration `0002_add_simulations.py` (sprint 2) — vérifier le style batch_alter_table déjà en place.
|
|
||||||
|
|
||||||
**Serializer** (`backend/app/serializers.py`)
|
**Service MITRE** :
|
||||||
- `serialize_simulation(sim)` :
|
- Nouvelle fonction `lookup_tactic(tactic_id)` → `{id, name}` ou None.
|
||||||
- Avant retour, enrichir chaque tag avec `tactics` depuis `mitre_svc.get_tactics(id)`. Si la technique a été retirée du bundle MITRE entre-temps, `tactics = []` (gracieux).
|
- Nouvelle fonction `get_tactic_name(tactic_id)` → name string ou fallback id.
|
||||||
- `commands` reste tel quel (text brut, inchangé sprint 2).
|
- `_TACTIC_ORDER` / `_TACTIC_NAMES` réutilisés.
|
||||||
|
|
||||||
**Service MITRE** (`backend/app/services/mitre.py`)
|
**Service workflow `simulation_workflow.py`** — modifications :
|
||||||
- Étendre l'index avec un dict `tactics_by_technique: dict[str, list[str]]` pour lookup O(1) au serialize.
|
1. **Guard `done` (AC-18.1)** : tout en haut de `apply_patch`, AVANT le check RBAC, si `simulation.status == "done"` → 409 `{error: "simulation is done — reopen first"}`.
|
||||||
- Nouvelle fonction `get_tactics(technique_id: str) -> list[str]`.
|
2. **SOC gate étendu** : `(REDTEAM_FIELDS | {"technique_ids", "tactic_ids"}) & payload.keys()`.
|
||||||
- Nouvelle fonction `lookup_name(technique_id: str) -> str | None` — utilisée par l'endpoint PATCH pour résoudre le name côté serveur (le client n'envoie que les IDs).
|
3. **Validation `tactic_ids`** upfront (similaire à `technique_ids`) : tous les IDs validés contre le bundle, dedup `dict.fromkeys`. Bundle non chargé → 503.
|
||||||
- Nouvelle fonction `get_matrix() -> list[dict]` :
|
4. **Auto-transition** : ajouter le check `len(payload["tactic_ids"]) > 0` au calcul `auto_trigger`.
|
||||||
```json
|
5. **Transition `done → review_required` (AC-18.2)** : ajouter ce cas au state machine. Autorisé admin + redteam + soc. Update `updated_at`. Autres transitions depuis `done` → 409.
|
||||||
[
|
6. **Hook engagement auto-status (AC-19.1)** : après une transition de simu vers `in_progress` (auto OU manual), appeler une fonction `_maybe_activate_engagement(simulation)` qui, si `simulation.engagement.status == "planned"`, set `engagement.status = "active"` et add à la session (commit en même temps que la simu).
|
||||||
{"tactic_id": "TA0001", "tactic_name": "Initial Access",
|
|
||||||
"techniques": [
|
|
||||||
{"id": "T1078", "name": "Valid Accounts",
|
|
||||||
"subtechniques": [{"id": "T1078.001", "name": "Default Accounts"}, ...]},
|
|
||||||
...
|
|
||||||
]},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
```
|
|
||||||
Sub-techniques embarquées sous chaque parent (relation STIX `subtechnique-of` dans le bundle). Si la technique n'en a pas, `subtechniques: []`.
|
|
||||||
Ordre des tactiques : canonical MITRE Enterprise order (12 tactics). Lecture depuis les objets STIX `x-mitre-tactic` ordonnés par `x_mitre_shortname` natif OU constante module-level hardcodée si plus simple.
|
|
||||||
Ordre des techniques au sein d'une tactique : alphabétique par `name` (déterministe, lisible).
|
|
||||||
|
|
||||||
**API** (`backend/app/api/simulations.py`)
|
**API `simulations.py`** :
|
||||||
- `GET /api/mitre/matrix` — nouvel endpoint, 200 + tree, 503 si bundle absent.
|
- PATCH : le check status==done est fait dans `apply_patch` (voir au-dessus).
|
||||||
- `PATCH /api/simulations/<sid>` : le payload accepte maintenant `technique_ids: list[str]` à la place de `mitre_technique_id` + `mitre_technique_name`. Validation : tous les IDs doivent exister dans le bundle (400 sinon), `name` snapshot servi par `lookup_name`. Pas de rétrocompat avec les anciens champs scalaires (clean break — pas d'utilisateur externe).
|
- Transition : accepter le nouveau cas done → review_required pour admin/redteam/soc.
|
||||||
- **Dedup serveur** : avant écriture en DB, dédupliquer la liste `technique_ids` en préservant l'ordre (`list(dict.fromkeys(technique_ids))`). Le client peut envoyer accidentellement des doublons (race UI ou bug), le serveur ne doit jamais persister deux fois la même technique.
|
|
||||||
- Auto-transition (AC-13.5) : un `technique_ids` non vide (≥1 élément) compte comme redteam-side filled, déclenche `pending → in_progress`. Liste vide = pas de trigger.
|
|
||||||
|
|
||||||
**Tests pytest**
|
**Tests pytest**
|
||||||
- `test_simulations_techniques.py` (nouveau) :
|
- `test_simulations_tactics.py` (nouveau) : PATCH valide, ID inconnu → 400, bundle absent → 503, dedup, auto-transition, SOC → 403.
|
||||||
- Création + PATCH `technique_ids` → simulation a la bonne liste, sérialisation expose `techniques` avec `tactics`.
|
- `test_simulations_done_readonly.py` (nouveau) : PATCH simu done → 409 (admin/redteam/soc). Reopen via transition → 200. Autres transitions depuis done → 409. Après reopen, PATCH OK.
|
||||||
- PATCH avec ID inconnu → 400.
|
- `test_engagement_lifecycle.py` (nouveau) : création simu → engagement reste `planned`. PATCH simu → simu in_progress + engagement active. Engagement déjà active → pas de changement. Engagement closed → pas de changement.
|
||||||
- Auto-transition sur `technique_ids` non vide.
|
- Migration test : `tactic_ids` column NOT NULL après upgrade 0004 (similaire au pattern Alembic round-trip sprint 3).
|
||||||
- Retirer toutes les techniques (`technique_ids: []`) → pas de trigger d'auto-transition (cohérent avec règle "valeur vide").
|
- Adapter `test_simulations_crud.py`, `test_simulations_patch.py`, `test_simulations_workflow.py` si nécessaire pour les assertions sur `tactics` et la garde done.
|
||||||
- **Dedup** : PATCH avec `technique_ids: ["T1059", "T1078", "T1059"]` → DB ne stocke que 2 entrées, ordre préservé (`T1059` en premier).
|
|
||||||
- `test_mitre.py` (existant) — ajouter :
|
|
||||||
- `get_matrix()` renvoie les bonnes tactiques dans le bon ordre.
|
|
||||||
- `lookup_name(unknown)` → None.
|
|
||||||
- `get_tactics(known)` → liste correcte (≥1 tactique).
|
|
||||||
- `test_simulations_crud.py` + `test_simulations_patch.py` + `test_simulations_workflow.py` (existants) — adapter toute assertion qui touchait `mitre_technique_id` / `mitre_technique_name`.
|
|
||||||
- Migration : test que les anciennes simulations en `pending` avec un id mono-tech sont upgradées en `techniques: [{id, name}]` (fixture inline ou test direct sur Alembic).
|
|
||||||
|
|
||||||
**Quality bar** : ruff + mypy clean, tous les tests existants + nouveaux verts.
|
**Quality bar** : ruff + mypy clean, tous les tests existants + nouveaux verts.
|
||||||
|
|
||||||
|
### Règles
|
||||||
|
- Pas de touche au frontend, `.claude/agents/`, `scripts/`, `Makefile`.
|
||||||
|
- Renvoyer le summary attendu (cf `.claude/agents/backend-builder.md`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Brief technique — Frontend Builder
|
## 3. Brief technique — Frontend Builder
|
||||||
|
|
||||||
**Scope strict** : `frontend/` uniquement.
|
**Scope strict** : `frontend/` UNIQUEMENT.
|
||||||
|
|
||||||
**Note process (lesson learned sprint 2)** : avant de marquer la tâche terminée, lance le dev server et screenshot (a) la matrice modale ouverte avec ≥3 techniques sélectionnées et (b) la simulation form avec ≥2 tags affichés. Joins-les à ton summary final.
|
**SCREENSHOTS MANDATORY** (lesson sprint 2/3) : à la fin de ton travail, lance le dev server et fournis ≥ 5 screenshots :
|
||||||
|
1. `EngagementsListPage` light + dark
|
||||||
|
2. `SimulationFormPage` avec ≥ 2 chips technique + ≥ 1 chip tactique light + dark
|
||||||
|
3. `MitreMatrixModal` ouverte avec sélections light + dark
|
||||||
|
4. `UsersAdminPage` form "Create account" (alignement vérifié) light + dark
|
||||||
|
5. `SimulationFormPage` status `done` (read-only + Reopen visible) light
|
||||||
|
|
||||||
|
Paths absolus dans le summary final. Si le dev server n'a pas pu tourner, dis-le EXPLICITEMENT avec les raisons techniques précises.
|
||||||
|
|
||||||
### Livrables
|
### Livrables
|
||||||
|
|
||||||
**Types** (`frontend/src/api/types.ts`)
|
**US-17 — UI polish**
|
||||||
- `MitreTechnique`: `{id: string, name: string, tactics: string[]}` (déjà existant pour le picker — réutiliser, ajouter `tactics` si manquant).
|
- `EngagementsListPage.tsx` : supprimer le doublon "Create engagement". Garder un seul CTA "New" + icône `+` (selon convention AC-17.2).
|
||||||
- Ajouter `MitreTactic`: `{tactic_id: string, tactic_name: string, techniques: MitreMatrixTechnique[]}` avec `MitreMatrixTechnique = {id: string, name: string, subtechniques: {id: string, name: string}[]}`.
|
- `UsersAdminPage.tsx` : retravailler la grille pour pixel-perfect alignment. Choix laissé au builder (align-items: stretch + align-self, ou restructurer en 2 rangées).
|
||||||
- `Simulation.techniques: MitreTechnique[]` à la place de `mitre_technique_id` + `mitre_technique_name`. PATCH payload : `{technique_ids: string[]}`.
|
- Audit boutons : refactoriser ceux qui dépassent ≤ 8 chars. Garder "Mark for review" / "Clear all" / "Reopen" sans icône si pas d'icône évidente. Boutons à passer en icône+label : "Save Red Team" → icône + "Save", "Save SOC" → icône + "Save SOC", "ADD TECHNIQUE" → "+" + "Add" (rendu obsolète par US-22), "QUICK SEARCH" → "🔍" + "Search" (rendu obsolète par US-22).
|
||||||
|
|
||||||
**API client** (`frontend/src/api/mitre.ts`)
|
**US-18 — Done read-only + Reopen**
|
||||||
- `searchMitreTechniques(q)` — existant, garder.
|
- `SimulationFormPage.tsx` :
|
||||||
- `getMitreMatrix()` — nouveau, GET `/api/mitre/matrix`.
|
- Quand `simulation.status === 'done'` : tous champs disabled, `MitreTechniquesField disabled`, action bar montre UNIQUEMENT "Reopen" + icône (`↻`).
|
||||||
|
- Bouton Reopen : visible admin/redteam/soc, click → `useTransitionSimulation` to `review_required`, toast.
|
||||||
|
|
||||||
**Hooks** (`frontend/src/hooks/useMitre.ts`)
|
**US-19 — Engagement auto-status (côté UI)**
|
||||||
- `useMitreSearch(q, enabled)` — existant, garder.
|
- `useUpdateSimulation` et `useTransitionSimulation` : ajouter `["engagement", eid]` et `["engagements"]` aux invalidations après mutation réussie. Pas d'autre changement visuel.
|
||||||
- `useMitreMatrix(enabled)` — nouveau hook TanStack Query, `staleTime: Infinity` (la matrice ne change qu'avec `make update-mitre` + redémarrage).
|
|
||||||
|
|
||||||
**Composants**
|
**US-20 — Matrice MITRE attack.mitre.org look**
|
||||||
|
- `MitreMatrixModal.tsx` overhaul :
|
||||||
|
- `max-w-[98vw]`, `max-h-[80vh] overflow-y-auto`, JAMAIS de scroll horizontal.
|
||||||
|
- `display: grid; grid-template-columns: repeat(12, minmax(0, 1fr))` pour répartir équitablement.
|
||||||
|
- Cellule technique : `text-[12px]`, padding minimal, hairline border.
|
||||||
|
- Header tactique : sticky top, fond contrasté, uppercase tracking, badge compteur à droite.
|
||||||
|
- Sub-techniques indent `pl-[8px]`, fond `bg-cloud`.
|
||||||
|
- Search input top inchangé.
|
||||||
|
|
||||||
- **`MitreTechniqueTag.tsx`** (nouveau) : chip affichant `{id} — {name}` avec un bouton `×`. Props : `technique: MitreTechnique`, `onRemove: () => void`, `disabled?: boolean`.
|
**US-21 — Tactic selection**
|
||||||
|
- `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 tactic_ids.
|
||||||
|
|
||||||
- **`MitreTechniquesField.tsx`** (nouveau, dans `frontend/src/components/`) : conteneur qui orchestre la sélection multi-tech avec **auto-save** (PATCH déclenché par chaque add/remove/Apply).
|
**US-22 — Refonte input MITRE**
|
||||||
- Props : `value: MitreTechnique[]`, `simulationId: number`, `disabled?: boolean`. (Pas de `onChange` du parent — le composant fait son propre PATCH via `useUpdateSimulation`.)
|
- `MitreTechniquesField.tsx` :
|
||||||
- UI : liste de `<MitreTechniqueTag>` + 2 boutons "Add technique" (ouvre matrix) et "Quick search" (ouvre/toggle picker autocomplete inline).
|
- Layout : chips area | input autocomplete inline + icône matrice button.
|
||||||
- Dédup : si l'utilisateur essaye d'ajouter une technique déjà présente, no-op silencieux (pas de PATCH non plus).
|
- Plus de boutons textuels "Add Technique" / "Quick Search".
|
||||||
- Auto-save : chaque mutation (× sur tag, Apply matrice, sélection Quick Search) déclenche `useUpdateSimulation` avec `{technique_ids: [...]}`. Toast succès `'Techniques updated'`, toast erreur sinon. Pendant le PATCH : disable l'interaction (les × deviennent grisés, les boutons disabled).
|
- Chips compacts (T-id ou TA-id seul, name en `title=`).
|
||||||
|
- Empty state minimal.
|
||||||
|
|
||||||
- **`MitreMatrixModal.tsx`** (nouveau) : modale matrice avec sub-techniques expand/collapse.
|
**US-23 — Dark mode**
|
||||||
- Props : `isOpen: boolean`, `initialSelection: MitreTechnique[]`, `onApply: (selection: MitreTechnique[]) => void`, `onCancel: () => void`.
|
- `Layout.tsx` : toggle theme dans la topbar. Hook `useTheme()` (localStorage + media query). 3 états avec cycle.
|
||||||
- État local : (a) copie de `initialSelection` mutée par les toggles, (b) `expandedTechniques: Set<string>` pour les IDs parents dépliés.
|
- `tailwind.config.ts` : `darkMode: 'class'`. Tokens étendus avec variantes dark (recommandé via CSS variables sous `.dark { ... }` dans `index.css`, comme ça les composants n'ont pas à dupliquer leurs classes).
|
||||||
- Layout : flex horizontal scrollable, 1 colonne par tactique. Largeur fixe 220px par colonne pour cohérence visuelle.
|
- Audit tous les composants : aucune couleur hardcodée (pas de `bg-white`, `text-black`, `#xxxxxx` inline). Tous passent un check visuel light + dark.
|
||||||
- Chevron `▸/▾` à gauche du nom des techniques qui ont des sub-techniques (`subtechniques.length > 0`). Click chevron = toggle expand (mute le set `expandedTechniques`), ne modifie PAS la sélection.
|
|
||||||
- Click sur le label d'une technique top-level = toggle sa sélection (le chevron ne se déclenche pas dans ce cas — séparer les zones cliquables).
|
|
||||||
- Sub-techniques rendues en cascade indentée sous leur parent quand expand : `pl-md text-[12px] bg-cloud rounded` (vs parent `text-[14px]`). Cliquables individuellement, sélection indépendante du parent.
|
|
||||||
- Compteur header de tactique = nombre de techniques **parents + subs** sélectionnées dans cette tactique.
|
|
||||||
- Champ recherche en haut : filtre case-insensitive sur id ET name. Une sub-technique matchée force l'expand de son parent (modifie automatiquement `expandedTechniques`).
|
|
||||||
- Modale : `position: fixed`, backdrop `bg-ink/60`, container `bg-canvas rounded-xl shadow-elevated max-w-[95vw] max-h-[85vh] overflow-hidden`.
|
|
||||||
- Footer : "Cancel" (jette les changements locaux + ferme), "Apply N techniques" (compteur total ; click → onApply renvoie la liste complète, parent fait le PATCH auto-save US-14.2).
|
|
||||||
- Focus trap (scope minimal V1, cf AC-15.5) :
|
|
||||||
- `useEffect` au mount → `searchInputRef.current?.focus()`.
|
|
||||||
- `onKeyDown` au niveau du container modale :
|
|
||||||
- `Tab` sans shift sur le dernier élément focusable → `preventDefault()` + focus le premier.
|
|
||||||
- `Shift+Tab` sur le premier → `preventDefault()` + focus le dernier.
|
|
||||||
- Récupérer la liste des focusables via `container.querySelectorAll('a, button, input, [tabindex]:not([tabindex="-1"])')`, ignorer ceux `disabled` ou `hidden`.
|
|
||||||
- Pas de focus restoration ni de live region — out of scope V1.
|
|
||||||
- Pas de dépendance npm.
|
|
||||||
- Escape → onCancel. Click backdrop → onCancel.
|
|
||||||
|
|
||||||
- **`MitreTechniquePicker.tsx`** (existant, sprint 2) : clean rewrite de la signature.
|
|
||||||
- Avant : `onChange(id: string | null, name: string | null)` qui remplaçait la valeur.
|
|
||||||
- Après : `onSelect({id, name})` — un seul match sélectionné, le parent (MitreTechniquesField) gère l'append + le dédup.
|
|
||||||
- Plus de prop `techniqueId`/`techniqueName` en entrée (le picker est désormais un sélecteur "one-shot" qui se réinitialise après chaque sélection).
|
|
||||||
|
|
||||||
**Pages**
|
|
||||||
|
|
||||||
- **`SimulationFormPage.tsx`** : remplacer le `<MitreTechniquePicker>` standalone par un `<MitreTechniquesField simulationId={sim.id}>`. Le state `rt.techniques` disparait du form (les techniques ont leur propre cycle de save via le champ lui-même — auto-save). Le bouton "Save Red Team" continue de batcher tous les AUTRES champs (name, description, commands, etc.) mais ne touche pas aux techniques. Affichage read-only (rôle SOC) : afficher les tags sans `×`, boutons Add/Quick Search masqués (`disabled` prop).
|
|
||||||
|
|
||||||
- **`SimulationList.tsx`** : colonne MITRE — afficher `techniques[0]?.id + (techniques.length > 1 ? ` +${techniques.length - 1}` : '')`. Si `techniques` est vide, afficher `—`.
|
|
||||||
|
|
||||||
**Tests Vitest**
|
|
||||||
- `MitreTechniqueTag.test.tsx` — render id+name, click × appelle onRemove.
|
|
||||||
- `MitreTechniquesField.test.tsx` — affiche tags, "Add technique" ouvre le modal matrix, "Quick search" ouvre le picker, dédup silencieuse, remove via × appelle onChange avec liste mise à jour.
|
|
||||||
- `MitreMatrixModal.test.tsx` — render colonnes par tactique, click toggle sélection, Apply renvoie liste, Cancel jette, Escape ferme, search filtre.
|
|
||||||
- Adapter `MitreTechniquePicker.test.tsx` (sprint 2) à la nouvelle signature `onSelect`.
|
|
||||||
- Adapter `SimulationFormPage.test.tsx` (sprint 2) — assertions sur `techniques` array au lieu de scalaire.
|
|
||||||
|
|
||||||
**Quality bar** : typecheck + lint + vitest clean.
|
|
||||||
|
|
||||||
### Règles
|
### Règles
|
||||||
- Lit le summary backend EN PREMIER.
|
- Lit le summary backend EN PREMIER.
|
||||||
- Pas d'invention d'endpoints — `GET /api/mitre/matrix` est le seul nouveau, déjà spec'd.
|
- Pas d'invention d'endpoints.
|
||||||
- Réutiliser `LoadingState`, `ErrorState`, `ConfirmDialog`, `useToast`, action bar pattern (sprint 2) existants.
|
- Réutiliser les patterns sprint 1/2/3.
|
||||||
- Respect DESIGN.md tokens (palette + spacing). Tags = `bg-primary-soft text-primary-deep rounded-full px-md py-xxs gap-xxs text-[14px]`.
|
- Respect DESIGN.md tokens.
|
||||||
- Pas de nouvelle dépendance npm sans escalade au team-lead.
|
- Pas de dépendance npm sans escalade (sauf `lucide-react` autorisé).
|
||||||
|
- **Interdiction absolue de toucher `e2e/`, `backend/`, `.claude/agents/`, `scripts/`, `Makefile`.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Brief — Test verifier
|
## 4. Brief — Team-lead infra (US-24 + US-25, en parallèle des builders)
|
||||||
|
|
||||||
E2e Playwright. Un fichier par US :
|
**US-24 — Process hygiene**
|
||||||
- `us13-multi-techniques.spec.ts` — AC-13.1 → AC-13.5 (focus API + données)
|
- Créer `.claude/agents/design-reviewer.md` avec frontmatter agent (model `opus`, tools : `Read`, `Glob`, `Grep`, `Bash` lecture seule). Brief : revoit diff frontend + screenshots, audit alignement / DESIGN.md tokens / cohérence visuelle / responsive.
|
||||||
- `us14-techniques-tags.spec.ts` — AC-14.1 → AC-14.5 (UI tags + remove)
|
- Mettre à jour `.claude/agents/frontend-builder.md` : DoD strict sur les screenshots.
|
||||||
- `us15-mitre-matrix-modal.spec.ts` — AC-15.1 → AC-15.5 (modal interaction + a11y)
|
- Mettre à jour SPEC.md § Workflows : insérer design-reviewer entre frontend-builder et code-reviewer.
|
||||||
- `us16-regression-sprint2.spec.ts` — re-exécuter les ACs critiques sprint 2 (auto-transition US-8, workflow US-11, SOC restrictions US-9) avec le nouveau modèle.
|
|
||||||
|
|
||||||
Mettre à jour les e2e sprint 2 qui assertaient `mitre_technique_id` / `mitre_technique_name` scalaires (US-8, US-10 selon le grep).
|
**US-25 — PR helper**
|
||||||
|
- Écrire `scripts/open-pr.sh` (cf AC-25.1).
|
||||||
|
- Target Makefile `open-pr`.
|
||||||
|
- Documenter README.md.
|
||||||
|
- Dogfood en fin de sprint.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Definition of Done — Sprint 3
|
## 5. Brief — Test verifier
|
||||||
|
|
||||||
- [ ] Tous les AC US-13 → US-16 passent.
|
E2e Playwright :
|
||||||
- [ ] Backend tests verts (`pytest -q`). Ruff + mypy clean.
|
- `us17-ui-polish.spec.ts` — AC-17.1 (single button), AC-17.3 (alignment via locator boundingBox).
|
||||||
- [ ] Frontend tests verts (`npm run test -- --run`). Typecheck + lint clean.
|
- `us18-done-readonly-reopen.spec.ts` — AC-18.1 → AC-18.5.
|
||||||
- [ ] E2e Playwright suite verte (sprint 1 + 2 + 3).
|
- `us19-engagement-auto-status.spec.ts` — AC-19.1 → AC-19.4.
|
||||||
- [ ] Migration Alembic testée upgrade + downgrade.
|
- `us20-matrix-fits-modal.spec.ts` — AC-20.1, AC-20.4 (no horizontal scroll via `boundingBox`).
|
||||||
- [ ] SPEC.md mis à jour (multi-techniques acté).
|
- `us21-tactic-selection.spec.ts` — AC-21.4 → AC-21.7.
|
||||||
- [ ] README.md mis à jour (mention matrice + multi-tech dans la description workflow).
|
- `us22-mitre-input-redesign.spec.ts` — AC-22.1 → AC-22.5.
|
||||||
- [ ] CHANGELOG.md sprint 3 entry sous [Unreleased].
|
- `us23-dark-mode.spec.ts` — AC-23.1 → AC-23.3.
|
||||||
- [ ] Code-reviewer sans BLOCKER.
|
|
||||||
- [ ] **Frontend-builder a screenshot la matrice modale + la simulation form avec tags AVANT de marquer la tâche terminée (lesson learned sprint 2).**
|
US-24/25 non e2e (process / repo files). Couverture par dogfood (la PR sprint 4 elle-même est ouverte via `make open-pr`).
|
||||||
- [ ] PR ouverte + récap synthétique team-lead.
|
|
||||||
|
Adapter les sprint 2/3 e2e si l'audit boutons (AC-17.2) renomme certains labels.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Décisions arrêtées (utilisateur 2026-05-27)
|
## 6. Décisions arrêtées
|
||||||
|
|
||||||
1. **Storage multi-tech** : colonne JSON `[{id, name}]` (KISS, pattern `commands` sprint 2).
|
1. **Tactic storage** : colonne JSON `tactic_ids` séparée. ✓ 2026-05-27
|
||||||
2. **Sub-techniques dans la matrice** : OUI, affichées avec expand/collapse par technique parent. Sub-techniques sont aussi accessibles via Quick Search en plus.
|
2. **Dark mode default** : `system` (suit `prefers-color-scheme`, fallback `light` si non détecté). ✓ 2026-05-27
|
||||||
3. **API shape** : `PATCH` reçoit `{technique_ids: ["T1059", "T1059.001", ...]}` — IDs uniquement (parents et subs au même niveau dans la liste). Backend résout names depuis le bundle.
|
3. **Matrix CSS fidelity** : look similaire qualitatif (frontend-builder itère, pas pixel-perfect). ✓ 2026-05-27
|
||||||
4. **Rétrocompat** : migration backfill `[{id, name}]` depuis les scalaires. Pas de rétrocompat API.
|
4. **Reopen target** : `done → review_required`. ✓ mémoire (sprint 4 scope)
|
||||||
5. **MitreTechniquePicker** : clean rewrite de la signature (`onSelect({id, name})`).
|
5. **Reopen RBAC** : admin + redteam + soc. ✓ mémoire
|
||||||
6. **Matrix layout** : colonnes par tactique, 220px fixe, scroll horizontal global.
|
6. **Engagement auto trigger** : `planned → active` sur 1ère simu in_progress (auto-transition ou manual). Pas de retour arrière auto. ✓ mémoire
|
||||||
7. **Apply de la modale matrice** : auto-save immédiat (PATCH déclenché par `MitreTechniquesField` quand le modal renvoie sa liste via `onApply`). Add/remove via tag × ou Quick Search aussi auto-save.
|
7. **PR helper token source** : `~/.git-credentials` (parse user + token via sed, cf [[reference-gitea-pr-api]]). ✓ 2026-05-27
|
||||||
8. **Sprint 4 framing** (anticipation, NE PAS implémenter dans sprint 3) : Dark mode (toggle + tokens dark + persistence) + Hygiène process UI (`design-reviewer` agent + screenshot mandatory dans brief frontend-builder). Connecteur C2 reporté au-delà. Les builders sprint 3 N'ajoutent PAS de tokens dark, N'invoquent PAS le design-reviewer (qui n'existe pas encore). Seule la lesson `screenshots mandatory` est déjà appliquée en sprint 3 dans le brief frontend (§3).
|
8. **Workflow design-reviewer** : insérée entre frontend-builder et code-reviewer, read-only. Diff frontend + screenshots. Format rapport à la code-reviewer mais focus visuel/design. ✓ mémoire
|
||||||
|
9. **Screenshots frontend-builder** : MANDATORY au sprint 4, en sortie du frontend-builder, paths absolus dans summary, refus de marquer la tâche done sans. ✓ mémoire
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Plan d'exécution
|
## 7. Plan d'exécution
|
||||||
|
|
||||||
1. ✅ User a validé les 8 décisions §6 (2026-05-27).
|
1. ✅ Team-lead a re-appliqué le SPEC sprint 3 oublié (`ba313a3`).
|
||||||
2. ✅ Team-lead a mis à jour SPEC.md (§0).
|
2. ✅ User a validé les 4 décisions ouvertes (tactic separated, theme system, matrix qualitative, token from ~/.git-credentials). Avec les 5 acquises en mémoire (sprint 4 scope), ça fait 9 décisions arrêtées.
|
||||||
3. ✅ Spec-reviewer : APPROVED WITH NOTES après 2 passes (5 items au total, tous traités).
|
3. 🟡 Team-lead met à jour SPEC.md § Workflows + § Décisions techniques (§0).
|
||||||
4. ✅ Backend-builder : commits `b5ea292` + `673b25e` (model + migration + matrix endpoint + 503 unloaded, 162 passing).
|
4. 🟡 Spec-reviewer valide le plan vs SPEC.md (anti-trous comme à sprint 3 — RBAC field-level, batch SQLite, scope ambigu).
|
||||||
5. ✅ Frontend-builder : commit `771483f` (MitreTechniquesField + MitreMatrixModal + tags + auto-save + screenshots, 84 passing).
|
5. 🔵 Backend-builder : modèle + migration 0004 + workflow done-readonly/reopen + engagement auto-lifecycle + tactic_ids + tests.
|
||||||
6. ✅ Code-reviewer : APPROVED WITH NITS (2 MINORs + 4 NITs).
|
6. 🔵 Frontend-builder : UI polish + done read-only + matrix overhaul + tactic selection + input redesign + dark mode + screenshots.
|
||||||
7. ✅ Post-review fixes : `4596f09` + `393b6ed` backend (164 passing) + `39f4076` frontend (86 passing).
|
7. 🔵 Team-lead (US-24 + US-25 en parallèle de frontend) : design-reviewer agent + frontend-builder.md update + scripts/open-pr.sh + Makefile target.
|
||||||
8. ✅ Test-verifier : commit `df8a6b6` (105/106 sprint 3 e2e verts, 1 pré-existant sprint 1 — DB pollution, non-régression).
|
8. 🔵 Design-reviewer (NEW STEP) : revoit diff frontend + screenshots.
|
||||||
9. 🟡 Team-lead : récap + PR en cours.
|
9. 🔵 Code-reviewer : revoit le diff complet (LSP-first).
|
||||||
4. 🔵 Backend-builder : modèle + migration + endpoints + tests.
|
10. 🔵 Test-verifier : e2e US-17 → US-23.
|
||||||
5. 🔵 Frontend-builder : composants + page update + tests Vitest. Screenshots obligatoires avant "done".
|
11. 🟢 Team-lead : PR via `make open-pr` (dogfood AC-25.4) + récap.
|
||||||
6. 🔵 Code-reviewer : LSP-first.
|
|
||||||
7. 🔵 Test-verifier : e2e US-13 → US-16 + adaptation sprint 2.
|
|
||||||
8. 🟢 Team-lead : PR + récap.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user