From 2e59743af513dbe9ea1a54bc3df2aca3f73f3bef Mon Sep 17 00:00:00 2001 From: Knacky Date: Thu, 28 May 2026 07:18:21 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20sprint=205=20wrap-up=20=E2=80=94=20CHAN?= =?UTF-8?q?GELOG=20+=20README=20+=206=20lessons=20+=20plan=20final?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CHANGELOG: sprint 5 entry under [Unreleased] (templates CRUD + instantiation + nav + dropdown + decorrelation). Sprint 4 moved to its own [Sprint 4] section. - README: status bump to sprint 5, test counts refreshed (226/121/201). - tasks/lessons.md: 6 sprint-5 lessons captured (spec-reviewer 2-pass before dispatch finally clicked, endpoint path drift caught visually not by spec-review, screenshot script mocks lag path changes, silent URL "improvements" by backend, apply_patch wrong primitive for creation copy paths, IntegrityError catch beats pre-check SELECT, SendMessage rule applies to all team agents). - tasks/todo.md: status flipped to 🟱 SPRINT COMPLET. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 43 +++++ README.md | 8 +- tasks/lessons.md | 32 ++++ tasks/todo.md | 481 ++++++++++++++++++++++------------------------- 4 files changed, 300 insertions(+), 264 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d4344a..86622ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,49 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) ## [Unreleased] +### Added — Sprint 5 (Simulation templates) + +**Backend** (226 pytest passing — 193 sprint-1-to-4 + 28 sprint 5 + 5 post-code-review) +- `SimulationTemplate` model (table `simulation_templates`) — UNIQUE constraint on `name`, JSON `techniques` + `tactic_ids` (default `[]`, NOT NULL via `server_default`), Text fields `description` / `commands` / `prerequisites`, FK `created_by_id` to `users`, `created_at` / `updated_at`. +- Alembic migration `0005_simulation_templates.py` — CREATE TABLE (SQLite native, no batch); downgrade via DROP TABLE. +- 5 new endpoints under `/api/templates`, all gated `@role_required("admin", "redteam")` (SOC → 403): + - `GET /api/templates` — list, sorted name ASC, serialized with enriched `techniques: [{id, name, tactics}]` and `tactics: [{id, name}]`. + - `POST /api/templates` — create. `name` required (400 if empty), unique (409 via `IntegrityError` catch, no pre-check race). `technique_ids` / `tactic_ids` validated upfront — type check `isinstance(list)` (400 with friendly message) THEN resolved against the bundle / `_TACTIC_IDS` (400 with id on unknown). + - `GET /api/templates/` — single, 404 on miss. + - `PATCH /api/templates/` — partial update. Same validations. 409 on `name` conflict; no-op rename (`name == current`) returns 200. + - `DELETE /api/templates/` — 204. **No cascade** to instantiated simulations (decoupling guarantee). +- `POST /api/engagements//simulations` extended with optional `template_id`. When provided: + - Template loaded (404 on miss). + - Fields copied directly onto the new `Simulation` ORM object (`techniques`, `tactic_ids`, `description`, `commands`, `prerequisites`, and `name` if missing from body). + - **Explicit non-call to `apply_patch()` / `_resolve_*` helpers** — avoids re-hitting the MITRE bundle AND avoids triggering the auto-transition `pending → in_progress`. Status stays `pending`, engagement stays `planned` (no `_maybe_activate_engagement` call). Decorrelation: no `template_id` FK on `Simulation`, deep copy of JSON arrays. +- New helpers in `mitre.py` reused / re-exposed; new `serialize_template()` in `serializers.py` mirrors `serialize_simulation` (minus SOC fields, status, executed_at) and uses the shared `_enrich_techniques` + `_enrich_tactics` (no duplication). +- All migration tests (0003, 0004, 0005) now use `Path(__file__).resolve().parent.parent / "migrations" / "versions" / "..."` — sprint 4's hardcoded-path MAJOR is closed for the third sprint running. + +**Frontend** (121 vitest passing — 92 sprint-1-to-4 + 26 sprint 5 + 3 post-code-review) +- New page `TemplatesListPage` (`/admin/templates`, admin+redteam only) — table (Name / MITRE count / Created by / Updated / Actions), `+ New` CTA with Plus icon. +- New page `TemplateFormPage` (`/admin/templates/new` and `/admin/templates/:id/edit`) — single-column FormField stack (sidesteps the multi-column grid trap that broke AC-17.3 on UsersAdminPage). Includes `MitreTechniquePicker` + `MitreMatrixModal` inline (NOT `MitreTechniquesField` — that one auto-saves; template form needs batched save). Delete via `ConfirmDialog`. +- New component `TemplatePickerModal` — modal listing all templates (Name / MITRE count / Created by). Empty state when `useTemplates()` returns `[]`: "No templates available — Create one from the Templates page." +- New nav link "Templates" in `Layout.tsx` topbar — visible to admin + redteam only, masked for SOC. Mirrors the pattern used by the "Users" link. +- `SimulationList` "New" button refactored into a **split-button dropdown**: `[+ New] [â–Œ]`. Primary half → `/.../simulations/new` (blank). Dropdown → "Blank" + "From template
". Open dropdown closes on click-outside or Escape (sprint 3 picker pattern). Empty-state `SimulationList` now also exposes the same dropdown (so users can instantiate from a template on a fresh engagement without creating a blank first). +- `dark:shadow-floating-dark` consistently applied to the new dropdown and `TemplatePickerModal` — matches the sprint 4 shadow token model. `dark:hover:bg-fog` on dropdown items for contrast. +- New types: `SimulationTemplate`, `SimulationTemplateCreateInput`, `SimulationTemplatePatchInput`. `SimulationCreateInput` extended with `template_id?: number`. +- New TanStack Query hooks (`useTemplates`, `useTemplate`, `useCreateTemplate`, `useUpdateTemplate`, `useDeleteTemplate`) with cache invalidation on mutations. +- API client `frontend/src/api/templates.ts` — 5 calls to `/api/templates*`. (Sprint-5 in-flight bug : initial commit `90fc5ba` used `/simulation-templates` paths everywhere; caught immediately, fixed in `2b70011`.) + +**Acceptance tests** (Playwright, **201 passed**) +- 3 new spec files (one per US): `us26-templates-crud.spec.ts` (22 tests), `us27-instantiate-from-template.spec.ts` (14 tests), `us28-templates-nav.spec.ts` (8 tests). +- Coverage gaps from code-reviewer filled: bidirectional template↔instance decorrelation, dropdown click-outside + Escape, SOC + template_id 403. +- Sprint 2/3 spec adapts: `us4-engagements.spec.ts` and `us7-simulation-create.spec.ts` now use `getByTestId('new-simulation-btn')` instead of `getByRole('link', /new simulation/)` — the link became a split-button dropdown. +- 1 pre-existing flaky in `us3-users-admin AC-3.4` (DB contamination across runs) — predates sprint 5, unrelated. + +### Changed +- 2026-05-28 — SPEC.md § Templates de simulations added (between § Fonctionnement and § Authentification & rĂŽles). Spells out the decoupling rule and the SOC-zero-access RBAC. +- 2026-05-28 — `POST /api/engagements//simulations` API contract: `name` is now optional when `template_id` is provided (falls back to `template.name`). + +--- + +## [Sprint 4] — UI polish + workflow tightening + dark mode + process hygiene (merged 2026-05-28) + ### Added — Sprint 4 (UI polish + workflow tightening + dark mode + process hygiene) **Backend** (193 pytest passing — 192 sprint-1-to-3 + 1 sprint-4) diff --git a/README.md b/README.md index d1dc71a..27af797 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Mimic** is a Breach and Attack Simulation (BAS) web UI built on the MITRE ATT&CK matrix. It replaces the flat Excel spreadsheets that red-teams and SOC analysts pass around at the end of an engagement, providing a shared workspace for Purple Team handoffs. -> Status: **Sprint 4 — UI polish + workflow tightening + dark mode + process hygiene**. The Purple Team workflow is now tighter (Done is terminal, Reopen returns to Review required, engagements auto-flip Planned → Active on first in-progress simulation), simulations can be tagged with both techniques AND tactics (TA-ids), the MITRE matrix modal fits the viewport without horizontal scroll, the app supports light / dark / system theming, and PR creation is one Make target away. +> Status: **Sprint 5 — Simulation templates**. Admin/redteam can now create reusable simulation templates (name + description + commands + prerequisites + MITRE techniques + tactics) and instantiate them inside an engagement in one click. Template and instance are fully decoupled — editing one never affects the other. SOC has no access to templates. --- @@ -139,9 +139,9 @@ npm run dev # http://localhost:5173 with /api proxied to :5000 Tests: ```bash -cd backend && pytest -q # 193 tests -cd frontend && npm run test -- --run # 92 tests -cd e2e && npx playwright test # 158 tests (needs container up — use MIMIC_BASE_URL=http://127.0.0.1:5000 if localhost resolves to IPv6) +cd backend && pytest -q # 226 tests +cd frontend && npm run test -- --run # 121 tests +cd e2e && npx playwright test # 201 tests (needs container up — use MIMIC_BASE_URL=http://127.0.0.1:5000 if localhost resolves to IPv6) ``` --- diff --git a/tasks/lessons.md b/tasks/lessons.md index cc3dab0..f20bb50 100644 --- a/tasks/lessons.md +++ b/tasks/lessons.md @@ -4,6 +4,38 @@ Recurring mistakes and the rule we adopted so the same issue doesn't bite twice. --- +## Sprint 5 (closed 2026-05-28) + +### Process — Spec-reviewer 2-pass BEFORE backend dispatch eliminated mid-implementation addenda (team-lead) +**Context** : Sprint 3 and 4 both required urgent addenda to the backend-builder mid-implementation because the spec-reviewer's 2nd pass arrived after the backend-builder had already started. Sprint 5 explicitly waited for spec-reviewer Pass 2 APPROVED before dispatching backend — and the backend ran straight through with 0 addenda churn. The 2-pass model finally clicked. +**Lesson** : ALWAYS wait for the spec-reviewer's verdict on the post-edit pass before dispatching the first builder. If Pass 1 returns NEEDS-CHANGES, apply the fixes, request Pass 2, wait for APPROVED, THEN dispatch. The "let's send to builders in parallel to save time" instinct costs more than it saves. + +### Process — Endpoint path drift caught by visual inspection, not by spec-reviewer (team-lead) +**Context** : Backend implemented `/api/templates`. Plan §1 § 2 said `/api/simulation-templates`. Spec-reviewer 2-pass didn't catch the path drift. Frontend used the plan path → mismatch → first frontend commit (`90fc5ba`) was effectively non-functional against the real backend. Caught immediately by team-lead grep + diagnostic. +**Lesson** : when backend "improves" a URL without flagging it as a deviation, it's still a deviation. Add to team-lead PR-merge mental checklist: `git diff main...HEAD | grep "@.*\.route\|@.*_bp\.(get|post|put|patch|delete)"` → does this match the plan §1/§2 path strings exactly? If no, dispatch a 1-line frontend fix BEFORE the post-design-review cycle. + +### Process — Frontend-builder's screenshot script reuses stale mocks after path changes (frontend-builder) +**Context** : Sprint 5 path fix `2b70011` corrected the API client to `/api/templates`. But the Playwright screenshot script still mocked `/api/simulation-templates/` — for the GET single endpoint specifically. Result: edit-form screenshot showed a 500 ErrorState instead of the actual form. Design-reviewer caught this as a critical coverage gap. +**Lesson** : when a path / contract changes mid-sprint, the screenshot script's route handlers must be updated in lockstep with the API client. A grep on the script for the old path is mandatory after every path-rename commit. Add to frontend-builder pre-screenshot checklist: `grep -E "" $screenshot_script` must return empty. + +### Engineering — Backend-builder's silent URL "improvement" (backend-builder) +**Context** : The team-lead's plan §1/§2 explicitly named the endpoints `/api/simulation-templates`. Backend-builder chose `/api/templates` (shorter, cleaner) but did NOT flag this as a deviation in the summary's "Open questions / deviations" section. The frontend-builder followed the plan and broke. The path change was defensible but the lack of escalation was not. +**Lesson** : when a builder chooses a different identifier than the plan (URL path, table name, column name, function name, etc.), even if "better", they MUST flag it in their summary under "Deviations from plan". The team-lead can then propagate the change to dependent briefs. Silent deviations break cross-team contracts. + +### Engineering — Avoid calling `apply_patch()` on creation paths (backend-builder + spec-reviewer) +**Context** : Sprint 5 template instantiation copies fields from a template to a new Simulation. Spec-reviewer Pass 1 flagged that a builder unaware of the auto-transition trigger might "reuse the validation" by calling `apply_patch()` — which would trigger `pending → in_progress` on a non-empty technique_ids payload, violating AC-27.4. The plan was explicitly updated to forbid this call. Backend-builder set ORM fields directly, which sidesteps both the bundle lookup AND the auto-transition logic. +**Lesson** : `apply_patch()` is the wrong primitive for creation paths that copy already-validated data. Reach for direct ORM assignment (`sim.field = value`) when the source data is pre-validated (template → instance, replica → primary, etc.). Reserve `apply_patch()` for user-input paths that need full validation + workflow side effects. + +### Engineering — Use `IntegrityError` catch for UNIQUE conflict 409, not pre-check SELECT (backend-builder + spec-reviewer) +**Context** : Sprint 5's first plan draft listed BOTH a pre-insert SELECT to check name uniqueness AND an `IntegrityError` catch as fallback. Spec-reviewer Pass 1 flagged this as dual strategy — the SELECT still races under concurrent inserts and adds dead code. Plan was simplified to "catch IntegrityError only, rollback, return 409". Backend implementation matches. +**Lesson** : for UNIQUE constraint violations, the DB is the authoritative source of truth. Catch the `IntegrityError`, roll back, return 409. Don't pre-check with SELECT — it races, and the IntegrityError catch is still required as a safety net (so the pre-check is just dead code). Same applies to other DB-enforced constraints (FK, CHECK). + +### Process — Designer-reviewer accidental duplicate (`-2`) reminded me to use SendMessage (team-lead) +**Context** : Sprint 4 introduced the design-reviewer agent. Sprint 5 first design-review I called `Agent({name: "design-reviewer"})` — the system spawned `design-reviewer-2`. Same mistake as sprint 2/3 with backend/frontend builders. Cleaned up via shutdown_request, but the second design-review pass I correctly used SendMessage on the original `design-reviewer` — and got the verdict cleanly without duplicate noise. +**Lesson** : the "SendMessage to existing idle agent, not Agent" rule covers ALL agents in the team, not just builders. Includes design-reviewer, code-reviewer, spec-reviewer, test-verifier. Save: same `feedback-agent-reuse` memory note now applies broadly. + +--- + ## Sprint 4 (closed 2026-05-27) ### Process — git status before declaring sprint complete (team-lead) diff --git a/tasks/todo.md b/tasks/todo.md index 017a2c8..1ef89fd 100644 --- a/tasks/todo.md +++ b/tasks/todo.md @@ -1,339 +1,300 @@ -# Sprint 4 — UI polish + workflow tightening + dark mode + process hygiene +# Sprint 5 — Simulation templates -**Branche** : `sprint/4-ui-polish` -**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). +**Branche** : `sprint/5-templates` +**Statut** : 🟱 SPRINT COMPLET — backend 226/226 + frontend 121/121 + e2e 201/201, PR prĂȘte +**Base** : `main` @ `9873c53` (PR #7 sprint 4 mergĂ©) +**Objectif** : permettre Ă  un admin/redteam de crĂ©er des **templates de simulations** prĂ©-remplies (RT-side : name, description, commands, prerequisites, techniques, tactics). Instancier un template dans un engagement crĂ©e une nouvelle simulation dĂ©corrĂ©lĂ©e (copie indĂ©pendante — Ă©diter l'instance ne touche pas le template et vice-versa). User QA item 8 sprint 3. --- -## 0. SPEC.md updates +## 0. SPEC.md Ă  enrichir en dĂ©but de sprint -- ✅ `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`. +Ajouter une section `## Templates de simulations` (entre § Fonctionnement et § Authentification & rĂŽles) : -L'Ă©volution est tracĂ©e dans CHANGELOG.md § Changed sprint 4. +> Un **template de simulation** est une simulation prĂ©-remplie cĂŽtĂ© redteam (name + description + commandes + prĂ©-requis + techniques MITRE + tactiques MITRE) qui sert de point de dĂ©part pour instancier rapidement des simulations dans un engagement. Le template ne contient PAS de partie SOC, ni de date d'exĂ©cution, ni de rĂ©sultat d'exĂ©cution — ces champs restent par-instance. L'instanciation d'un template dans un engagement crĂ©e une **nouvelle simulation indĂ©pendante** : le template et l'instance sont dĂ©corrĂ©lĂ©s, l'Ă©dition de l'un n'affecte pas l'autre. **Templates = ressource red team** : admin et redteam les gĂšrent (CRUD). SOC n'y a aucun accĂšs (ni lecture, ni Ă©criture, pas de nav link). + +L'Ă©volution est tracĂ©e dans CHANGELOG.md § Changed sprint 5. --- ## 1. User stories -### US-17 — UI polish : dĂ©doublonnage boutons + alignement + icĂŽnes -**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. +### US-26 — En tant qu'admin/redteam, je crĂ©e et gĂšre des templates de simulations +**Pourquoi** : standardiser des simulations rĂ©currentes (ex: "Mimikatz LSASS dump", "PowerShell empire stager") et Ă©viter de retaper les mĂȘmes commandes/MITRE Ă  chaque engagement. **CritĂšres d'acceptation** -- [ ] AC-17.1 : `EngagementsListPage` n'affiche qu'UN SEUL bouton "New engagement". Le doublon "Create engagement" est supprimĂ©. -- [ ] 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". -- [ ] 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. +- [ ] AC-26.1 : modĂšle `SimulationTemplate` (table `simulation_templates`) : + - `id` int PK + - `name` str NOT NULL UNIQUE (limite UX : un template unique par nom pour Ă©viter les doublons dans le dropdown d'instanciation) + - `description` text nullable + - `commands` text nullable (chaĂźne multiligne, une commande par ligne, pattern sprint 2) + - `prerequisites` text nullable + - `techniques` JSON NOT NULL default `[]` (liste `[{id, name}]`, snapshot des techniques MITRE) + - `tactic_ids` JSON NOT NULL default `[]` (liste de TA-id strings) + - `created_at` datetime NOT NULL + - `updated_at` datetime nullable + - `created_by_id` int FK User +- [ ] AC-26.2 : migration Alembic `0005_simulation_templates.py` — CREATE TABLE simulation_templates + index sur `name`. Downgrade : DROP TABLE. +- [ ] AC-26.3 : `GET /api/templates` (admin|redteam) → liste `[{id, name, description, commands, prerequisites, techniques: [{id, name, tactics}], tactics: [{id, name}], created_at, created_by}]`, ordre `name ASC`. SOC → 403. +- [ ] AC-26.4 : `POST /api/templates` (admin|redteam) → 201 + template créé. Body : `{name, description?, commands?, prerequisites?, technique_ids?: [...], tactic_ids?: [...]}`. Valide : `name` non vide, name unique (409 si doublon), technique_ids / tactic_ids validĂ©s contre bundle MITRE / `_TACTIC_IDS` (rĂ©utilise les helpers `_resolve_technique_ids` / `_resolve_tactic_ids` sprint 3/4). SOC → 403. +- [ ] AC-26.5 : `GET /api/templates/` (admin|redteam) → 200 ou 404. SOC → 403. +- [ ] AC-26.6 : `PATCH /api/templates/` (admin|redteam) → 200, accepte les mĂȘmes champs que POST en partial. Si `name` est modifiĂ© et entre en conflit avec un autre template → 409. SOC → 403. +- [ ] AC-26.7 : `DELETE /api/templates/` (admin|redteam) → 204. **Pas de cascade vers les simulations dĂ©jĂ  instanciĂ©es** — celles-ci sont dĂ©corrĂ©lĂ©es et survivent. SOC → 403. +- [ ] AC-26.8 : page `/admin/templates` (admin|redteam uniquement) liste les templates en table (Name, MITRE count, Created by, Updated at, Actions). Boutons "New template" + "Edit" + "Delete". Tous les endpoints templates sont gated `@role_required("admin", "redteam")` cĂŽtĂ© backend, et ProtectedRoute frontend impose le mĂȘme filtre. -### US-18 — Simulation `done` = read-only + Reopen -**Pourquoi** : QA sprint 3 — actuellement une simu `done` peut toujours ĂȘtre PATCHĂ©e, ce qui contredit le statut terminal. +### US-27 — En tant que redteam, j'instancie un template dans un engagement +**Pourquoi** : c'est le use-case principal des templates. **CritĂšres d'acceptation** -- [ ] AC-18.1 : `PATCH /api/simulations/` avec status courant `done` retourne **409** `{error: "simulation is done — reopen first"}` quel que soit le rĂŽle. -- [ ] AC-18.2 : nouvelle transition `POST /api/simulations//transition {to: "review_required"}` quand status courant == `done` → 200, autorisĂ©e admin + redteam + soc. Met Ă  jour `updated_at`. -- [ ] 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`. -- [ ] AC-18.4 : sur `SimulationFormPage`, quand status == `done` : - - Tous les champs (RT + SOC) sont disabled. - - `MitreTechniquesField` en read-only (chips sans ×, input + icĂŽne matrice masquĂ©s). - - L'action bar affiche UNIQUEMENT un bouton "Reopen" (visible admin/redteam/soc). - - Save RT, Save SOC, Mark for review, Close, Delete sont masquĂ©s. -- [ ] AC-18.5 : click Reopen → POST transition, toast `'Simulation reopened'`, badge se met Ă  jour, les champs redeviennent Ă©ditables selon le rĂŽle. - -### US-19 — Engagement auto-status `planned → active` -**Pourquoi** : QA sprint 3 — un engagement reste `planned` mĂȘme quand ses simulations sont in_progress. +- [ ] AC-27.1 : `POST /api/engagements//simulations` (admin|redteam) accepte un nouveau paramĂštre optionnel `template_id`. Si prĂ©sent, le serveur valide que le template existe (404 sinon), puis crĂ©e une nouvelle simulation en copiant : + - `name` ← template.name (peut ĂȘtre override par `name` du body si fourni) + - `description` ← template.description + - `commands` ← template.commands + - `prerequisites` ← template.prerequisites + - `techniques` ← template.techniques (deep copy) + - `tactic_ids` ← template.tactic_ids (deep copy) + - Autres champs : status=pending, executed_at=null, execution_result=null, SOC fields=null +- [ ] AC-27.2 : `POST` sans `template_id` garde le comportement sprint 2 (crĂ©ation vierge avec juste `name`). +- [ ] AC-27.3 : la simulation créée depuis un template est **complĂštement dĂ©corrĂ©lĂ©e** : modifier l'instance ne touche pas le template, modifier le template ne touche pas les instances existantes. Pas de FK `template_id` stockĂ©e sur la simulation (clean decoupling). +- [ ] AC-27.4 : auto-transition pending → in_progress NE se dĂ©clenche PAS lors de la crĂ©ation depuis un template (mĂȘme si le template a un name + description + techniques non vides). La crĂ©ation reste status=pending — la transition se fera au prochain PATCH explicite de la redteam. CohĂ©rent avec rĂšgle sprint 2 "trigger sur PATCH" pas "trigger sur crĂ©ation". +- [ ] AC-27.5 : engagement auto-status n'est PAS dĂ©clenchĂ© par l'instanciation (status reste planned). Coherent avec AC-27.4. +- [ ] AC-27.6 : sur `EngagementDetailPage` (sprint 2/3/4), le bouton "+ New" (ou Ă©quivalent UI) ouvre dĂ©sormais un menu / dropdown / modal avec 2 options : "Blank" et "From template
". L'option "From template
" affiche la liste des templates disponibles avec leur nom + un aperçu (count techniques/tactics). Click sur un template → POST avec `template_id` + redirection sur la simu créée. **Si `useTemplates()` retourne une liste vide → la modale affiche un ``. NE PAS dĂ©sactiver l'option "From template
" dans le dropdown** (l'utilisateur doit pouvoir l'ouvrir pour comprendre qu'il n'y a rien — un disabled item silencieux serait confus). +- [ ] AC-27.7 : SOC n'a PAS accĂšs au bouton d'instanciation (cohĂ©rent avec RBAC simulation creation sprint 2). +### US-28 — En tant qu'admin/redteam, j'accĂšde aux templates depuis la nav **CritĂšres d'acceptation** -- [ ] 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-19.2 : si l'engagement est dĂ©jĂ  `active` ou `closed`, pas de changement. -- [ ] AC-19.3 : aucun retour arriĂšre auto. La transition `closed` reste manuelle. -- [ ] AC-19.4 : le frontend invalide `["engagement", eid]` et `["engagements"]` aprĂšs chaque PATCH/transition simulation pour rĂ©cupĂ©rer le statut Ă  jour. - -### US-20 — Matrice MITRE : look attack.mitre.org + pas de scroll horizontal -**Pourquoi** : QA sprint 3 — la matrice actuelle a un scroll horizontal et un layout maison. - -**CritĂšres d'acceptation** -- [ ] 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/` 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. - -### 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 `` 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). +- [ ] AC-28.1 : `Layout.tsx` topbar nav contient un nouveau lien "Templates" (visible **uniquement Ă  admin + redteam**). Pour SOC : le lien n'apparaĂźt pas (cohĂ©rent avec "Users" qui est admin-only et masquĂ© cĂŽtĂ© SOC). +- [ ] AC-28.2 : `ProtectedRoute` pour `/admin/templates` impose `roles=["admin", "redteam"]`. SOC qui tente d'y accĂ©der en tapant l'URL → redirigĂ© vers `/engagements` + toast "AccĂšs refusĂ©" (pattern existant ProtectedRoute sprint 1). +- [ ] AC-28.3 : la page `/admin/templates` n'inclut PAS de mode "read-only SOC" — elle est strictement admin+redteam. Les composants peuvent assumer `canEditTemplates = isAdmin || isRedteam = true` (toujours vrai Ă  ce niveau). --- ## 2. Brief technique — Backend Builder -**Scope strict** : `backend/`. Pas de touche au frontend, e2e, `.claude/agents/`, `scripts/`, `Makefile`, docs. +**Scope strict** : `backend/`. Pas de touche au frontend, e2e, docs, agents, scripts. ### Livrables -**ModĂšle `Simulation`** — ajout uniquement : +**ModĂšle `SimulationTemplate`** (`backend/app/models/simulation_template.py` — nouveau fichier) ```python -tactic_ids: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list) +from datetime import UTC, datetime +from sqlalchemy.orm import Mapped, mapped_column + +class SimulationTemplate(db.Model): + __tablename__ = "simulation_templates" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(255), nullable=False, unique=True) + description = db.Column(db.Text, nullable=True) + commands = db.Column(db.Text, nullable=True) + prerequisites = db.Column(db.Text, nullable=True) + techniques = db.Column(db.JSON, nullable=False, default=list) + tactic_ids = db.Column(db.JSON, nullable=False, default=list) + created_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(UTC)) + updated_at = db.Column(db.DateTime, nullable=True) + created_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) + created_by = db.relationship("User") ``` -**Migration Alembic `0004_simulation_tactic_ids.py`** : -- 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. -- Downgrade : `with op.batch_alter_table('simulations') as batch_op: batch_op.drop_column('tactic_ids')`. -- Test : schĂ©ma post-upgrade a `tactic_ids` NOT NULL avec default `[]`. +Ajouter Ă  `backend/app/models/__init__.py`. -**Serializer** : `serialize_simulation(sim)` ajoute `tactics: [{id, name}]` enrichi runtime. +**Migration Alembic `0005_simulation_templates.py`** +- Upgrade : `op.create_table("simulation_templates", ...)` avec tous les champs et la contrainte UNIQUE sur `name`. Pas besoin de batch (CREATE TABLE est natif SQLite). +- Downgrade : `op.drop_table("simulation_templates")`. SQLite OK natif. +- Pas de backfill (table vide Ă  la crĂ©ation). -**Service MITRE** : -- Sprint 3 a indexĂ© les tactiques par **short-name** (`"initial-access"`, `"execution"`, `...`) dans `_TACTIC_ORDER` et `TACTIC_NAMES`. La SPEC et le plan sprint 4 utilisent la notation **TA-id** (`"TA0001"`, `"TA0007"`, etc.). Il faut un mapping TA-id → short-name pour valider/rĂ©soudre les `tactic_ids` reçus. -- Ajouter une constante module-level (12 entrĂ©es hardcodĂ©es, MITRE standard stable — attention, les TA-ids ne sont PAS sĂ©quentiels) : +**Serializer** (`backend/app/serializers.py`) +- Nouvelle fonction `serialize_template(t)` : ```python - _TACTIC_IDS: dict[str, str] = { - "TA0001": "initial-access", - "TA0002": "execution", - "TA0003": "persistence", - "TA0004": "privilege-escalation", - "TA0005": "defense-evasion", - "TA0006": "credential-access", - "TA0007": "discovery", - "TA0008": "lateral-movement", - "TA0009": "collection", - "TA0011": "command-and-control", - "TA0010": "exfiltration", - "TA0040": "impact", + return { + "id": t.id, + "name": t.name, + "description": t.description, + "commands": t.commands, + "prerequisites": t.prerequisites, + "techniques": _enrich_techniques(t.techniques), # rĂ©utilise sprint 3 + "tactics": _enrich_tactics(t.tactic_ids), # rĂ©utilise sprint 4 + "created_at": t.created_at.isoformat() if t.created_at else None, + "updated_at": t.updated_at.isoformat() if t.updated_at else None, + "created_by": serialize_user_brief(t.created_by) if t.created_by else None, } ``` -- Nouvelle fonction `lookup_tactic(tactic_id: str) -> dict | None` : - ```python - short = _TACTIC_IDS.get(tactic_id) - if short is None: - return None - return {"id": tactic_id, "name": TACTIC_NAMES[short]} - ``` -- Nouvelle fonction `get_tactic_name(tactic_id: str) -> str | None` : pareil mais retourne juste le name. -- Validation `tactic_ids` dans `simulation_workflow.py` : un id absent de `_TACTIC_IDS` → 400 `{"error": "unknown tactic id: "}`. +- Pattern parallĂšle Ă  `serialize_simulation` mais SANS les champs SOC / status / executed_at. -**Service workflow `simulation_workflow.py`** — modifications : -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"}`. Vaut pour TOUS les rĂŽles, admin compris. -2. **SOC gate Ă©tendu** : `(REDTEAM_FIELDS | {"technique_ids", "tactic_ids"}) & payload.keys()`. -3. **Validation `tactic_ids`** upfront (similaire Ă  `technique_ids`) : tous les IDs validĂ©s contre le bundle, dedup `dict.fromkeys`. Bundle non chargĂ© → 503. -4. **Auto-transition** : ajouter le check `len(payload["tactic_ids"]) > 0` au calcul `auto_trigger`. -5. **Transition `done → review_required` (AC-18.2)** — **implĂ©mentation prĂ©cise** : le dict `_ALLOWED_TRANSITIONS` actuel est keyĂ© par target status et a dĂ©jĂ  une entrĂ©e `"review_required"` avec from={pending, in_progress} et roles={admin, redteam}. On NE peut PAS ajouter une 2e entrĂ©e avec la mĂȘme clĂ©. À la place, dans `transition()`, AVANT le lookup dict, ajoute un cas spĂ©cial qui suit les patterns existants du fichier : - ```python - # transition() returns tuple[Any, int] | None — None on success, error tuple otherwise. - # Existing functions use datetime.now(UTC) (timezone-aware, not deprecated utcnow). - # Enum values are UPPERCASE: SimulationStatus.DONE, SimulationStatus.REVIEW_REQUIRED. - if to_status == "review_required" and simulation.status == SimulationStatus.DONE: - simulation.status = SimulationStatus.REVIEW_REQUIRED - simulation.updated_at = datetime.now(UTC) - db.session.commit() - return None - # ... reste de la fonction inchangĂ©e (dict lookup pour les autres cas) - ``` - Pas de check explicite du rĂŽle ici — `@login_required` upstream + l'enum User limitĂ© Ă  admin/redteam/soc rendent la dĂ©fense superflue (KISS). Autres transitions depuis `done` (vers `pending`, `in_progress`, `done` lui-mĂȘme) → 409 via le dict lookup qui ne les couvre pas. -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 `db.session.add(engagement)`. **NE PAS appeler `db.session.commit()` dans le helper** — le caller (`api/simulations.py:update_simulation`) gĂšre le commit final, sinon double-commit. +**API** (`backend/app/api/templates.py` — nouveau blueprint) +**Tous les endpoints templates sont gated `@role_required("admin", "redteam")` — SOC reçoit 403 partout.** +- `GET /api/templates` (admin|redteam) → liste serializĂ©e, tri `name ASC`. +- `POST /api/templates` (admin|redteam) → crĂ©ation. Validation : `name` non vide (400), `technique_ids` / `tactic_ids` valides (rĂ©utilise `_resolve_technique_ids` / `_resolve_tactic_ids` de `simulation_workflow.py` — import direct, KISS). Pour le `name` UNIQUE conflict : **catch `sqlalchemy.exc.IntegrityError` sur INSERT → 409 `{"error": "template name already exists"}`**. Pas de pre-check SELECT (race condition + code mort, la contrainte UNIQUE en DB est l'autoritĂ©). +- `GET /api/templates/` (admin|redteam) → 200 ou 404. +- `PATCH /api/templates/` (admin|redteam) → update partial. Pour le `name` conflict : mĂȘme pattern, **catch IntegrityError sur UPDATE → 409 `{"error": "template name already exists"}`**. Cas edge : PATCH avec `name == current_name` (no-op rename) → 200 (l'UPDATE sur sa propre row ne viole pas UNIQUE). Mettre Ă  jour `updated_at`. +- `DELETE /api/templates/` (admin|redteam) → 204. Pas de cascade FK simulations (les simulations n'ont pas de `template_id` FK). -**API `simulations.py`** : -- PATCH : le check status==done est fait dans `apply_patch` (voir au-dessus). -- Transition : accepter le nouveau cas done → review_required pour admin/redteam/soc. +Enregistrer le blueprint dans `backend/app/__init__.py`. + +**Modification `POST /api/engagements//simulations`** (`backend/app/api/simulations.py`) +- Le payload accepte maintenant un `template_id` optionnel. +- Si prĂ©sent : + - Charger le template (404 si non trouvĂ©). + - CrĂ©er la simulation en **setant DIRECTEMENT les champs RT** sur l'objet ORM Simulation Ă  partir des champs du template (`sim.techniques = template.techniques`, `sim.tactic_ids = template.tactic_ids`, `sim.description = template.description`, `sim.commands = template.commands`, `sim.prerequisites = template.prerequisites`). + - `name` du body override si fourni, sinon `template.name`. + - **NE PAS appeler `apply_patch()`, `_resolve_technique_ids()`, ni `_resolve_tactic_ids()`** — les donnĂ©es viennent du template dĂ©jĂ  persistĂ©+validĂ©, re-rĂ©soudre frapperait inutilement le bundle MITRE ET dĂ©clencherait l'auto-transition pending→in_progress via la logique auto-trigger de `apply_patch`, ce qui violerait AC-27.4. Le set direct ORM est intentionnellement court-circuitĂ©. +- Si absent : comportement actuel inchangĂ© (crĂ©ation vierge avec `name`). +- Auto-transition NE PAS dĂ©clencher (status reste pending — rĂšgle sprint 2 "trigger sur PATCH", la crĂ©ation ne compte pas). Le fait de ne pas appeler `apply_patch` est ce qui garantit ça structurellement. +- Engagement auto-status NE PAS dĂ©clencher (status engagement reste planned). Idem — `_maybe_activate_engagement` n'est appelĂ© que depuis `apply_patch`. **Tests pytest** -- `test_simulations_tactics.py` (nouveau) : PATCH valide, ID inconnu → 400, bundle absent → 503, dedup, auto-transition, SOC → 403. -- `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. -- `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. -- Migration test : `tactic_ids` column NOT NULL aprĂšs upgrade 0004 (similaire au pattern Alembic round-trip sprint 3). -- 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. +- `test_simulation_templates_crud.py` (nouveau) : + - GET liste vide, GET liste aprĂšs crĂ©ation, GET liste tri name ASC. + - POST valide → 201, fields persisted. + - POST name vide → 400. + - POST name dupliquĂ© → 409. + - POST technique_id inconnu → 400. + - POST tactic_id inconnu → 400. + - POST par SOC → 403. + - GET inexistant → 404. + - PATCH valide → 200, updated_at set. + - PATCH name → conflit → 409. + - PATCH par SOC → 403. + - DELETE valide → 204, GET ensuite → 404. + - DELETE par SOC → 403. +- `test_simulations_from_template.py` (nouveau) : + - POST simulation avec template_id valide → copie tous les RT fields, status=pending, executed_at=null, SOC fields=null. + - POST avec template_id valide + name override → name override gagne. + - POST avec template_id inexistant → 404. + - POST avec template_id par SOC → 403 (cohĂ©rent avec crĂ©ation). + - VĂ©rifier dĂ©corrĂ©lation : crĂ©er template → instancier → modifier l'instance → assert template inchangĂ©. SymĂ©trique : modifier le template → instance inchangĂ©e. + - Auto-transition NE PAS dĂ©clenchĂ©e (sim reste pending mĂȘme si template avait des techniques). + - Engagement reste planned (auto-status NOT triggered). +- Migration test : `0005` create/drop round-trip propre (rĂ©utilise pattern Alembic round-trip sprint 3/4 avec `Path(__file__)`). -**Quality bar** : ruff + mypy clean, tous les tests existants + nouveaux verts. +**Quality bar** : ruff + mypy clean, tous 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`). +- Pas de touche au frontend, e2e, agents, scripts, Makefile. +- Renvoyer le summary attendu (cf. `.claude/agents/backend-builder.md`). --- ## 3. Brief technique — Frontend Builder -**Scope strict** : `frontend/` UNIQUEMENT. +**Scope strict** : `frontend/` uniquement. -**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 +**Screenshots MANDATORY (lesson sprint 4)** : Ă  la fin de ton travail, dev server + auth flow (page.goto('/login') + fill creds + submit + wait) pour fournir MIN 6 screenshots : +1. `/admin/templates` liste (admin OU redteam vue, ≄ 2 templates) — light + dark +2. Template create/edit form (mode edit avec techniques + tactic chips) — light + dark +3. EngagementDetail avec dropdown "Blank | From template
" ouvert — light +4. TemplatePickerModal ouverte (au moins 2 templates listĂ©s) — light +5. TemplatePickerModal ouverte avec aucun template — empty state visible — light +6. Simulation créée depuis un template (champs prĂ©-remplis avec le nom, MITRE chips) — 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. +Paths absolus dans le summary. Si auth flow ne marche pas → escalade. ### Livrables -**US-17 — UI polish** -- `EngagementsListPage.tsx` : supprimer le doublon "Create engagement". Garder un seul CTA "New" + icĂŽne `+` (selon convention AC-17.2). -- `UsersAdminPage.tsx` : retravailler la grille pour pixel-perfect alignment. Choix laissĂ© au builder (align-items: stretch + align-self, ou restructurer en 2 rangĂ©es). -- 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). +**Types** (`frontend/src/api/types.ts`) +- `SimulationTemplate`: `{id, name, description, commands, prerequisites, techniques: MitreTechnique[], tactics: MitreTactic[], created_at, updated_at, created_by}`. +- `SimulationTemplateCreateInput`: payload POST. +- `SimulationTemplatePatchInput`: payload PATCH. +- Étendre `SimulationCreateInput` avec `template_id?: number`. -**US-18 — Done read-only + Reopen** -- `SimulationFormPage.tsx` : - - 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. +**API client** (`frontend/src/api/templates.ts` — nouveau) +- `listTemplates()`, `getTemplate(id)`, `createTemplate(input)`, `updateTemplate(id, patch)`, `deleteTemplate(id)`. -**US-19 — Engagement auto-status (cĂŽtĂ© UI)** -- `useUpdateSimulation` et `useTransitionSimulation` : ajouter `["engagement", eid]` et `["engagements"]` aux invalidations aprĂšs mutation rĂ©ussie. Pas d'autre changement visuel. -- **Note (spec-reviewer Pass 3)** : `eid` n'est pas directement disponible dans la signature des hooks (qui prennent `sid`). Solution : lire `engagement_id` depuis la response simulation (le backend l'expose toujours, cf serialize_simulation sprint 2) OU le passer en arg supplĂ©mentaire au hook si plus propre. Pas un trou plan, juste Ă  anticiper. +**Hooks TanStack Query** (`frontend/src/hooks/useTemplates.ts` — nouveau) +- `useTemplates()`, `useTemplate(id)`, mutations `useCreateTemplate`, `useUpdateTemplate`, `useDeleteTemplate`. +- Invalidation : create/update/delete invalide `["templates"]` et `["templates", id]`. -**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Ă©. +**Pages** +- **`TemplatesListPage.tsx`** (nouveau, `/admin/templates`) — admin+redteam only : + - Table : Name, MITRE count (techniques + tactics), Created by, Updated at, Actions. + - Bouton "+ New template" en header. + - Actions par ligne : "Edit" + "Delete". + - Click sur une ligne → `/admin/templates/:id/edit`. + - States : loading / error / empty. +- **`TemplateFormPage.tsx`** (nouveau, `/admin/templates/new` et `/admin/templates/:id/edit`) — admin+redteam only : + - Form pour name + description + commands (textarea) + prerequisites + MitreTechniquesField. + - Mode `new` : seul `name` requis ; aprĂšs crĂ©ation, redirige sur `/admin/templates/:id/edit`. + - Mode `edit` : load existing template, allow update. + - Bouton Delete (confirmation modal). + - Pas de mode read-only (SOC n'a pas accĂšs aux routes). +- **`EngagementDetailPage.tsx`** (modification) : + - Remplacer le bouton simple "+ New simulation" par un dropdown OU un menu : + - "Blank" (action default) + - "From template
" → ouvre une modale avec la liste des templates. + - Modale "From template
" : `useTemplates()`, table simple Name + MITRE count, click sur un template → `useCreateSimulation` avec `template_id: t.id` → redirect sur la simu créée. -**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. -- **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. +**Composants** (`frontend/src/components/`) +- **`TemplatePickerModal.tsx`** (nouveau) : modale qui liste les templates, permet de cliquer pour instancier. Props : `engagementId`, `onClose`, `onInstantiated(simId)`. -**US-22 — Refonte input MITRE** -- `MitreTechniquesField.tsx` : - - Layout : chips area | input autocomplete inline + icĂŽne matrice button. - - Plus de boutons textuels "Add Technique" / "Quick Search". - - Chips compacts (T-id ou TA-id seul, name en `title=`). - - Empty state minimal. -- **`SimulationFormPage.tsx` — call site update (spec-reviewer fix #4)** : la signature de `MitreTechniquesField` change de `value: MitreTechnique[]` (sprint 3) Ă  `value: {techniques: MitreTechnique[], tactics: MitreTactic[]}`. La page doit passer `value={{techniques: sim.techniques, tactics: sim.tactics}}` (le champ `sim.tactics` vient du nouveau serializer backend). TypeScript catch le miss mais flag-le explicitement pour ne pas l'oublier. +**Routing** (`App.tsx`) — toutes routes templates gated `roles=["admin", "redteam"]` : +- `/admin/templates` (admin|redteam) +- `/admin/templates/new` (admin|redteam) +- `/admin/templates/:id/edit` (admin|redteam) -**US-23 — Dark mode** -- `Layout.tsx` : toggle theme dans la topbar. Hook `useTheme()` (localStorage + media query). 3 Ă©tats avec cycle. -- `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). -- Audit tous les composants : aucune couleur hardcodĂ©e (pas de `bg-white`, `text-black`, `#xxxxxx` inline). Tous passent un check visuel light + dark. +**Layout** (`Layout.tsx`) +- Nouveau lien "Templates" dans la topbar, Ă  droite de "Users" — **visible UNIQUEMENT Ă  admin + redteam** (masquĂ© pour SOC, pattern identique Ă  "Users" qui est admin-only). + +**Tests Vitest** +- `TemplatesListPage.test.tsx` — loading/error/empty + boutons New/Edit/Delete prĂ©sents (admin|redteam — pas de variante soc puisque route inaccessible). +- `TemplateFormPage.test.tsx` — mode new + mode edit (pas de mode read-only). +- `TemplatePickerModal.test.tsx` — liste templates, click instantiate, gestion erreur, close. +- `EngagementDetailPage.test.tsx` — adapter pour le nouveau dropdown "+ New simulation". ### RĂšgles - Lit le summary backend EN PREMIER. - Pas d'invention d'endpoints. -- RĂ©utiliser les patterns sprint 1/2/3. -- Respect DESIGN.md tokens. -- Pas de dĂ©pendance npm sans escalade (sauf `lucide-react` autorisĂ©). -- **Interdiction absolue de toucher `e2e/`, `backend/`, `.claude/agents/`, `scripts/`, `Makefile`.** +- RĂ©utilise `LoadingState`, `ErrorState`, `EmptyState`, `Toast`, `ConfirmDialog`, `MitreTechniquesField`, `StatusBadge` etc. +- Respect DESIGN.md tokens. Dark mode dĂ©jĂ  en place — applique les patterns sprint 4 (`bg-canvas dark:bg-canvas`, etc.). +- Pas de dĂ©pendance npm sans escalade. --- -## 4. Brief — Team-lead infra (US-24 + US-25, en parallĂšle des builders) +## 4. Brief — Test verifier -**US-24 — Process hygiene** -- 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. -- Mettre Ă  jour `.claude/agents/frontend-builder.md` : DoD strict sur les screenshots. -- Mettre Ă  jour SPEC.md § Workflows : insĂ©rer design-reviewer entre frontend-builder et code-reviewer. +E2e Playwright. Un fichier par US : +- `us26-templates-crud.spec.ts` — AC-26.1 → AC-26.8 (focus API + UI gĂ©rance templates) +- `us27-instantiate-from-template.spec.ts` — AC-27.1 → AC-27.7 (crĂ©ation simu depuis template, dĂ©corrĂ©lation) +- `us28-templates-nav.spec.ts` — AC-28.1 → AC-28.3 (nav link, accĂšs SOC read-only) -**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. +Adapter les sprint 2/3/4 e2e si l'ajout du dropdown "+ New simulation" casse des sĂ©lecteurs (les tests sprint 2/3 cliquent directement sur "+ New" — dĂ©sormais ça ouvre un menu avant d'aller au form blanc). --- -## 5. Brief — Test verifier +## 5. Definition of Done — Sprint 5 -E2e Playwright : -- `us17-ui-polish.spec.ts` — AC-17.1 (single button), AC-17.3 (alignment via locator boundingBox). -- `us18-done-readonly-reopen.spec.ts` — AC-18.1 → AC-18.5. -- `us19-engagement-auto-status.spec.ts` — AC-19.1 → AC-19.4. -- `us20-matrix-fits-modal.spec.ts` — AC-20.1, AC-20.4 (no horizontal scroll via `boundingBox`). -- `us21-tactic-selection.spec.ts` — AC-21.4 → AC-21.7. -- `us22-mitre-input-redesign.spec.ts` — AC-22.1 → AC-22.5. -- `us23-dark-mode.spec.ts` — AC-23.1 → AC-23.3. - -US-24/25 non e2e (process / repo files). Couverture par dogfood (la PR sprint 4 elle-mĂȘme est ouverte via `make open-pr`). - -Adapter les sprint 2/3 e2e si l'audit boutons (AC-17.2) renomme certains labels. - -**Spec-reviewer INFO B** : AC-22.2 change le format des chips de "T1059 — Command and Scripting Interpreter" (sprint 3) Ă  juste "T1059" (avec name dans `title=`). Les e2e sprint 3 (notamment `us14-techniques-tags.spec.ts`) qui assertent le format complet doivent ĂȘtre mis Ă  jour. Pas seulement les labels boutons. +- [ ] Tous les AC US-26 → US-28 passent. +- [ ] Backend pytest verts (~193 existants + ~25 nouveaux). Ruff + mypy clean. +- [ ] Frontend vitest verts (92 existants + nouveaux). Typecheck + lint clean. +- [ ] E2e Playwright suite verte (sprint 1-4 + sprint 5). +- [ ] Migration 0005 testĂ©e via Alembic round-trip. +- [ ] SPEC.md § Templates de simulations ajoutĂ©e. +- [ ] README.md mis Ă  jour si nouveau bullet "Templates" pertinent. +- [ ] CHANGELOG.md sprint 5 entry sous [Unreleased]. +- [ ] **Design-reviewer pass** sur les nouveaux Ă©crans (lesson sprint 4 — design-reviewer = part of workflow depuis sprint 4). +- [ ] Code-reviewer sans BLOCKER. +- [ ] PR via `make open-pr` (sprint 4 dogfood validĂ©). --- -## 6. DĂ©cisions arrĂȘtĂ©es +## 6. DĂ©cisions arrĂȘtĂ©es (utilisateur 2026-05-28) -1. **Tactic storage** : colonne JSON `tactic_ids` sĂ©parĂ©e. ✓ 2026-05-27 -2. **Dark mode default** : `system` (suit `prefers-color-scheme`, fallback `light` si non dĂ©tectĂ©). ✓ 2026-05-27 -3. **Matrix CSS fidelity** : look similaire qualitatif (frontend-builder itĂšre, pas pixel-perfect). ✓ 2026-05-27 -4. **Reopen target** : `done → review_required`. ✓ mĂ©moire (sprint 4 scope) -5. **Reopen RBAC** : admin + redteam + soc. ✓ mĂ©moire -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. **PR helper token source** : `~/.git-credentials` (parse user + token via sed, cf [[reference-gitea-pr-api]]). ✓ 2026-05-27 -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 +1. **Table** : `simulation_templates` sĂ©parĂ©e (clean schema, pas de colonnes nullable confuses). +2. **Instantiation API** : extension de `POST /api/engagements//simulations` avec `template_id` optionnel. +3. **Name uniqueness** : UNIQUE (1 template par nom, UX dropdown clean). +4. **Template RBAC** : admin + redteam writable. **SOC pas d'accĂšs du tout** — pas de nav link, pas de page, tous endpoints templates → 403. Templates sont une ressource Red Team uniquement. +5. **UI instanciation** : dropdown sur le bouton "+ New simulation" (Blank | From template
). --- ## 7. Plan d'exĂ©cution -1. ✅ Team-lead a re-appliquĂ© le SPEC sprint 3 oubliĂ© (`ba313a3`). -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. 🟡 Team-lead met Ă  jour SPEC.md § Workflows + § DĂ©cisions techniques (§0). -4. 🟡 Spec-reviewer valide le plan vs SPEC.md (anti-trous comme Ă  sprint 3 — RBAC field-level, batch SQLite, scope ambigu). -5. đŸ”” Backend-builder : modĂšle + migration 0004 + workflow done-readonly/reopen + engagement auto-lifecycle + tactic_ids + tests. -6. đŸ”” Frontend-builder : UI polish + done read-only + matrix overhaul + tactic selection + input redesign + dark mode + screenshots. -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. đŸ”” Design-reviewer (NEW STEP) : revoit diff frontend + screenshots. -9. đŸ”” Code-reviewer : revoit le diff complet (LSP-first). -10. đŸ”” Test-verifier : e2e US-17 → US-23. -11. 🟱 Team-lead : PR via `make open-pr` (dogfood AC-25.4) + rĂ©cap. +1. ✅ User a validĂ© les 5 dĂ©cisions §6 (2026-05-28). SOC zero access actĂ©. +2. 🟡 Team-lead met Ă  jour SPEC.md (§0). +3. 🟡 Spec-reviewer valide le plan (2-pass — lesson sprint 3/4 — RBAC SOC blocked, name unique conflict 409 handling, template_id passing through auto-transition, design-reviewer scope new pages). +4. đŸ”” Backend-builder : modĂšle + migration 0005 + endpoints + tests. +5. đŸ”” Frontend-builder : pages Templates list/form + TemplatePickerModal + nav link + dropdown engagement + tests Vitest. Screenshots mandatory. +6. đŸ”” Design-reviewer : revoit diff frontend + screenshots. +7. đŸ”” Code-reviewer : LSP-first review du diff complet. +8. đŸ”” Test-verifier : e2e US-26 → US-28. +9. 🟱 Team-lead : `make open-pr` + rĂ©cap. + +Branche : `sprint/5-templates`.