feat: sprint 3 — multi-technique simulations + MITRE matrix modal #6

Merged
knacky merged 8 commits from sprint/3-mitre-matrix into main 2026-05-27 17:11:22 +00:00
Owner

Summary

  • Multi-techniques par simulation : autocomplete + matrice cliquable + tags avec auto-save
  • GET /api/mitre/matrix exposant le tree tactique → technique → sub-technique
  • Breaking : mitre_technique_id / mitre_technique_name scalaires → techniques: [{id, name, tactics}] array. Migration Alembic 0003 backfille les simulations existantes.
  • Workflow sprint 2 préservé (RBAC field-level, state machine, auto-transition, SOC restrictions)

Tests

  • Backend : 164/164 pytest (ruff + mypy clean)
  • Frontend : 86/86 vitest (typecheck + lint clean)
  • E2e Playwright : 105/106 (1 échec pré-existant = sprint 1 DB isolation, non-régression)

Test plan

  • make build && make start
  • make create-admin USER=alice PASS=changeme8 (si premier setup)
  • Ouvrir http://127.0.0.1:5000 (IPv4 explicite si IPv6 par défaut)
  • Quick Search : taper T1059 Enter → tag apparaît + toast, re-essayer même ID → no-op silencieux
  • Matrice : click "ADD TECHNIQUE", expand sub-techniques via chevron, sélection toggle, search auto-expand parent
  • Apply 0 vs Clear all : vider sélection → bouton devient "Clear all" si liste existante, disabled sinon
  • SimulationList colonne MITRE : T1059 +2 ou
  • SOC blocked sur simu review_required : tags read-only, boutons Add/Quick Search masqués
  • Auto-transition : ajouter technique → pending flip à in_progress

Notes

  • Bundle MITRE déjà committé en sprint 2, make update-mitre pour refresh
  • 7 retours QA utilisateur restant à traiter en sprint 4 (cf. mémoire projet)

🤖 Generated with Claude Code

## Summary - Multi-techniques par simulation : autocomplete + matrice cliquable + tags avec auto-save - `GET /api/mitre/matrix` exposant le tree tactique → technique → sub-technique - **Breaking** : `mitre_technique_id` / `mitre_technique_name` scalaires → `techniques: [{id, name, tactics}]` array. Migration Alembic 0003 backfille les simulations existantes. - Workflow sprint 2 préservé (RBAC field-level, state machine, auto-transition, SOC restrictions) ## Tests - Backend : **164/164** pytest (ruff + mypy clean) - Frontend : **86/86** vitest (typecheck + lint clean) - E2e Playwright : **105/106** (1 échec pré-existant = sprint 1 DB isolation, non-régression) ## Test plan - [ ] `make build && make start` - [ ] `make create-admin USER=alice PASS=changeme8` (si premier setup) - [ ] Ouvrir http://127.0.0.1:5000 (IPv4 explicite si IPv6 par défaut) - [ ] Quick Search : taper `T1059` Enter → tag apparaît + toast, re-essayer même ID → no-op silencieux - [ ] Matrice : click "ADD TECHNIQUE", expand sub-techniques via chevron, sélection toggle, search auto-expand parent - [ ] Apply 0 vs Clear all : vider sélection → bouton devient "Clear all" si liste existante, disabled sinon - [ ] SimulationList colonne MITRE : `T1059 +2` ou `—` - [ ] SOC blocked sur simu `review_required` : tags read-only, boutons Add/Quick Search masqués - [ ] Auto-transition : ajouter technique → `pending` flip à `in_progress` ## Notes - Bundle MITRE déjà committé en sprint 2, `make update-mitre` pour refresh - 7 retours QA utilisateur restant à traiter en sprint 4 (cf. mémoire projet) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
knacky added 8 commits 2026-05-27 17:08:42 +00:00
- Simulation model: replace mitre_technique_id/name scalars with techniques JSON column [{id, name}]
- Alembic migration 0003: add techniques, backfill from scalars, drop old columns (reversible)
- MITRE service: add get_tactics(), lookup_name(), get_matrix() with canonical tactic order and sub-technique nesting
- serializer: enrich techniques with tactics from service at serialize time (graceful empty tactics if bundle outdated)
- simulation_workflow: PATCH now accepts technique_ids list, validates against bundle, deduplicates preserving order, auto-transitions on non-empty list
- simulations API: add GET /api/mitre/matrix endpoint (503 if bundle absent)
- test_mitre.py: updated _reset_mitre fixture, added T1059.006 sub-technique, 14 new tests for get_tactics/lookup_name/get_matrix/matrix endpoint
- test_simulations_techniques.py: 20 new tests covering AC-13.1 to AC-13.5 (create, PATCH, dedup, auto-transition, SOC blocked, migration backfill logic)

Total: 161 tests passing. ruff clean. mypy: no new errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added bundle-loaded guard in _resolve_technique_ids() before attempting any
lookup; matches behavior of GET /api/mitre/matrix and GET /api/mitre/techniques.
Added corresponding test case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
  on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
  SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
  input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
  search filter (auto-expands parent on sub match), selection state, focus trap
  (Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
  every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
  technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
  MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MitreTechniquesField test: rewrite dedup test to actually exercise picker
  selection path — types query, waits for option, fires pointerDown,
  asserts no PATCH sent (dedup guard in handleSelect now truly covered)
- MitreMatrixModal: Apply button disabled only when totalSelected === 0
  AND initialSelection.length === 0 (no-op case); when totalSelected === 0
  but initialSelection was non-empty, shows "Clear all" and stays enabled
  so user can explicitly wipe the list
- MitreMatrixModal tests: update disabled test to match "Clear all" label,
  add "Clear all" enabled + onApply([]) path test
- SimulationList: stopPropagation on Name <Link> to prevent double-navigate
  with row onClick handler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration 0003: enforce techniques NOT NULL via batch_alter_table
- Migration 0003: remove unused _sims table proxy and dead column/table imports
- mitre.py: add _TACTIC_NAMES dict to fix 'Command And Control' → 'Command and Control'
- Migration 0003: enforce techniques NOT NULL via batch_alter_table (AC-13.1 DDL spec)
- Migration 0003: remove unused _sims table proxy and orphaned column/table imports
- mitre.py: rename _TACTIC_NAMES → TACTIC_NAMES (public); add all 12 correct display names
- mitre.py: use TACTIC_NAMES dict in _build_matrix() to fix "Command And Control" → "Command and Control"
- test_mitre.py: add T1071 fixture entry under command-and-control; assert tactic_name lowercase "and"
- test_simulations_techniques.py: real Alembic round-trip test asserting techniques NOT NULL after upgrade
Four new spec files covering the MITRE multi-technique feature:
- us13: API contract (techniques array, dedup, unknown ID → 400, SOC 403, auto-transition)
- us14: tag UI (empty state, add/remove auto-save, SimulationList column, order, styling)
- us15: matrix modal (tactic tree, layout, select/expand/search, Apply/Cancel/Escape/backdrop, a11y)
- us16: sprint 2 regression (workflow, badge, SOC RBAC, picker still works)

Updated sprint 2 specs (us8, us10) to use technique_ids array and Quick search button
instead of deprecated scalar mitre_technique_id/name fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- README: status bump to sprint 3, test counts refreshed (164/86/105), IPv6 note for the e2e runner
- CHANGELOG: sprint 3 entry under [Unreleased] (multi-tech model + matrix endpoint + auto-save UI); sprint 2 moved to its own [Sprint 2] section (merged 2026-05-27)
- tasks/lessons.md: 6 lessons captured (2-pass spec-review, inline summary scoping, "test in brief means test in commit" discipline, SQLite batch_alter_table, real migration round-trip, modal Apply 0 disambiguation)
- tasks/todo.md: status flipped to 🟢 SPRINT COMPLET, execution sequence ticks updated with commit hashes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
knacky merged commit 27573f5228 into main 2026-05-27 17:11:22 +00:00
knacky deleted branch sprint/3-mitre-matrix 2026-05-27 17:11:22 +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#6