feat: sprint 5 — simulation templates + instantiation + nav + dropdown #8

Merged
knacky merged 11 commits from sprint/5-templates into main 2026-06-07 16:08:39 +00:00
Owner

Summary

  • Simulation templates (CRUD complet sous /api/templates) — admin/redteam créent des templates pré-remplis (name + description + commands + prerequisites + MITRE techniques + tactics) qui survivent indépendamment des engagements.
  • Instanciation 1-clic depuis EngagementDetailPage : dropdown split-button "Blank | From template…" → modal picker → simu créée avec tous les champs RT pré-remplis.
  • Décorrélation totale : pas de FK template_id sur Simulation. Modifier l'instance ne touche pas le template ; modifier le template ne touche pas les instances ; supprimer le template ne supprime pas les instances.
  • RBAC SOC zero access : admin + redteam gèrent ; SOC ne voit pas le nav link, tous endpoints /api/templates* → 403, ProtectedRoute redirige les SOC qui tapent l'URL.

Test plan

  • Backend : 226/226 pytest (ruff + mypy clean) — 28 nouveaux + 5 post-code-review.
  • Frontend : 121/121 vitest (typecheck + lint clean) — 22 nouveaux + 3 post-code-review.
  • E2e Playwright : 201/201 verts — 44 nouveaux sprint 5 (us26, us27, us28) + adaptations sprint 2/3 pour le dropdown.

Comment tester en local

make build && make start                            # auto-podman
make create-admin USER=alice PASS=changeme8         # si premier setup
# Ouvrir http://127.0.0.1:5000 (IPv4 explicite si IPv6 par défaut)

Scénarios :

  1. Créer un template — Topbar → "Templates" → "+ New" → remplir name "Mimikatz LSASS Dump" + description + commands multiline + ≥ 2 techniques MITRE → Save. Vérifier /admin/templates liste le template.
  2. Instancier depuis un template — Engagement détail → dropdown sur "+ New" → "From template…" → click sur le template → la simu apparaît avec tous les champs RT pré-remplis. Status reste pending (auto-transition NE déclenche PAS sur instantiation).
  3. Décorrélation — Modifier la simu instanciée → vérifier que le template original est intact. Inverse aussi.
  4. SOC zero access — Se connecter en SOC → vérifier que "Templates" est absent de la topbar. Taper /admin/templates dans l'URL → redirect /engagements + toast "Accès refusé".
  5. Empty state — Sur un engagement vide, dropdown "+ New" doit toujours proposer "From template…" (UX fix sprint 5 post-code-review).
  6. Dropdown close-on-outside — Ouvrir le dropdown puis cliquer ailleurs OU presser Escape → ferme.

Notes

  • API path final : /api/templates (et NON /api/simulation-templates du brief original). Backend choice silently propagé en frontend via fix commit 2b70011.
  • Migration 0005 : CREATE TABLE simple, downgrade DROP TABLE.
  • Process wins sprint 5 : spec-reviewer 2-pass APPROVED AVANT dispatch backend = 0 addendum mid-implementation (vs sprint 3/4). Lesson captured.

🤖 Generated with Claude Code

## Summary - **Simulation templates** (CRUD complet sous `/api/templates`) — admin/redteam créent des templates pré-remplis (name + description + commands + prerequisites + MITRE techniques + tactics) qui survivent indépendamment des engagements. - **Instanciation 1-clic** depuis EngagementDetailPage : dropdown split-button "Blank | From template…" → modal picker → simu créée avec tous les champs RT pré-remplis. - **Décorrélation totale** : pas de FK `template_id` sur Simulation. Modifier l'instance ne touche pas le template ; modifier le template ne touche pas les instances ; supprimer le template ne supprime pas les instances. - **RBAC SOC zero access** : admin + redteam gèrent ; SOC ne voit pas le nav link, tous endpoints `/api/templates*` → 403, ProtectedRoute redirige les SOC qui tapent l'URL. ## Test plan - Backend : **226/226** pytest (`ruff` + `mypy` clean) — 28 nouveaux + 5 post-code-review. - Frontend : **121/121** vitest (`typecheck` + `lint` clean) — 22 nouveaux + 3 post-code-review. - E2e Playwright : **201/201** verts — 44 nouveaux sprint 5 (`us26`, `us27`, `us28`) + adaptations sprint 2/3 pour le dropdown. ## Comment tester en local ```bash make build && make start # auto-podman make create-admin USER=alice PASS=changeme8 # si premier setup # Ouvrir http://127.0.0.1:5000 (IPv4 explicite si IPv6 par défaut) ``` Scénarios : 1. **Créer un template** — Topbar → "Templates" → "+ New" → remplir name "Mimikatz LSASS Dump" + description + commands multiline + ≥ 2 techniques MITRE → Save. Vérifier `/admin/templates` liste le template. 2. **Instancier depuis un template** — Engagement détail → dropdown sur "+ New" → "From template…" → click sur le template → la simu apparaît avec tous les champs RT pré-remplis. Status reste `pending` (auto-transition NE déclenche PAS sur instantiation). 3. **Décorrélation** — Modifier la simu instanciée → vérifier que le template original est intact. Inverse aussi. 4. **SOC zero access** — Se connecter en SOC → vérifier que "Templates" est absent de la topbar. Taper `/admin/templates` dans l'URL → redirect `/engagements` + toast "Accès refusé". 5. **Empty state** — Sur un engagement vide, dropdown "+ New" doit toujours proposer "From template…" (UX fix sprint 5 post-code-review). 6. **Dropdown close-on-outside** — Ouvrir le dropdown puis cliquer ailleurs OU presser Escape → ferme. ## Notes - API path final : `/api/templates` (et NON `/api/simulation-templates` du brief original). Backend choice silently propagé en frontend via fix commit `2b70011`. - Migration 0005 : CREATE TABLE simple, downgrade DROP TABLE. - Process wins sprint 5 : spec-reviewer 2-pass APPROVED AVANT dispatch backend = 0 addendum mid-implementation (vs sprint 3/4). Lesson captured. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
knacky added 8 commits 2026-05-28 05:18:58 +00:00
- SimulationTemplate model + migration 0005 (CREATE TABLE + name index)
- 5 CRUD endpoints under /api/templates (admin|redteam only, SOC 403)
- POST /api/engagements/<eid>/simulations extended with optional template_id
- serialize_template() reusing _enrich_techniques/_enrich_tactics helpers
- IntegrityError → 409 for duplicate name on both POST and PATCH
- 28 new tests (CRUD, RBAC, dedup, instantiation, migration round-trip)
- 221 tests pass; ruff clean; mypy clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- types.ts: SimulationTemplate, SimulationTemplateCreateInput, SimulationTemplatePatchInput,
  extend SimulationCreateInput with template_id
- api/templates.ts: listTemplates, getTemplate, createTemplate, updateTemplate, deleteTemplate
- hooks/useTemplates.ts: useTemplates, useTemplate, useCreateTemplate, useUpdateTemplate,
  useDeleteTemplate (TanStack Query, invalidates ["templates"])
- TemplatesListPage: /admin/templates — table (name, MITRE count, created by, updated),
  New/Edit/Delete actions, loading/error/empty states
- TemplateFormPage: /admin/templates/new + /admin/templates/:id/edit — controlled form
  with inline MITRE field (picker + matrix modal), ConfirmDialog for delete
- TemplatePickerModal: reusable modal listing templates with empty state (AC-27.6)
- SimulationList: replace "New simulation" link with split-button dropdown
  (Blank → /simulations/new | From template… → TemplatePickerModal + POST template_id)
- Layout: "Templates" nav link (admin | redteam, before "Users")
- App.tsx: /admin/templates routes gated roles=["admin","redteam"]
- 26 new Vitest tests (118 total, 92 original preserved)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- shadow-floating dark:shadow-floating-dark on dropdown menu (Fix 1)
- hover:bg-cloud dark:hover:bg-fog on dropdown items (Fix 2)
- Plus icon + "New" label on split-button primary half (Fix 3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- useEffect pointerdown + Escape listeners when dropdown open (NIT 1)
- empty state now renders NewSimulationDropdown instead of plain Link (NIT 2)
- 3 new Vitest: close-on-outside, close-on-Escape, empty-state has dropdown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- create_simulation: name falls back to template.name when template_id provided
  and name is absent/empty (AC-27.1)
- templates POST/PATCH: isinstance(list) check on technique_ids/tactic_ids
  before resolving, returns 400 with clear message
- 5 new tests: unknown technique_id → 400 (POST+PATCH), unknown tactic_id → 400
  (POST+PATCH), name fallback to template.name
- mypy: merged template branch into if/else to eliminate union-attr false positives

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add three new spec files:
- us26-templates-crud: API CRUD (AC-26.3–26.7) + UI list/form/delete/redirect (AC-26.8)
- us27-instantiate-from-template: template_id copy + name override + 404 + decoupling
  (AC-27.1–27.3) + no auto-transition/engagement-activate (AC-27.4–27.5) + dropdown
  UI + picker modal + empty state + SOC gate (AC-27.6–27.7)
- us28-templates-nav: Templates link admin+redteam only, SOC redirect, form editable (AC-28.1–28.3)

Adapt sprint 2/3 e2e for sprint 5 dropdown:
- us4-engagements: getByRole link "New simulation" → getByTestId "new-simulation-btn"
- us7-simulation-create: same — split-button dropdown replaced the link

Suite: 201 passed (1 pre-existing flaky in us3 re DB state, unrelated to sprint 5).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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) <noreply@anthropic.com>
knacky added 1 commit 2026-05-28 05:24:18 +00:00
- us26: add AC-26.4 isinstance guard (technique_ids string→400) + AC-26.7 cascade test (DELETE template does not affect instantiated sim)
- us27: add NIT-1 dropdown Escape/click-outside close, NIT-2 empty-engagement dropdown visibility
- 49 sprint 5 tests passing, 206/207 full suite passing (us1 pre-existing isolation issue)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
knacky added 1 commit 2026-05-28 05:24:45 +00:00
Sprint 5 plan §0 added a new ## Templates de simulations section to SPEC.md
(between § Fonctionnement and § Authentification & rôles). The edit sat in
the sprint 5 worktree but was never committed across the 9 sprint commits,
so PR #8 currently does not include the corresponding spec text.

This is the THIRD sprint running this happens (sprint 3 → fixed at sprint 4
start; sprint 4 → fixed at sprint 5 start; sprint 5 → fixed here mid-PR
because I caught the M SPEC.md before merge).

Lesson updated in tasks/lessons.md to make the "git status pre-sprint-close"
discipline harder to forget.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
knacky added 1 commit 2026-05-28 05:25:10 +00:00
knacky merged commit 678ee8fbfb into main 2026-06-07 16:08:39 +00:00
knacky deleted branch sprint/5-templates 2026-06-07 16:08:39 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: knacky/mimic#8