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

8 Commits

Author SHA1 Message Date
Knacky
b001f57774 docs: sprint 3 wrap-up — README + CHANGELOG + lessons + plan final
- 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>
2026-05-27 04:55:12 +02:00
Knacky
df8a6b605b test(e2e): sprint 3 acceptance tests — US-13 to US-16
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>
2026-05-27 04:51:34 +02:00
Knacky
393b6ed416 fix(backend): sprint 3 post-review — migration nullable + dead code + tactic names + tests
- 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
2026-05-27 04:31:10 +02:00
Knacky
4596f09e71 fix(backend): sprint 3 post-review — nullable migration + dead code + tactic names
- 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'
2026-05-27 04:25:20 +02:00
Knacky
39f4076a81 fix(frontend): sprint 3 post-review — real dedup test + Apply 0 guard + Link stopPropagation
- 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>
2026-05-27 04:23:46 +02:00
Knacky
771483f3b0 feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- 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>
2026-05-27 04:04:23 +02:00
Knacky
673b25e0b0 fix(backend): PATCH technique_ids returns 503 when MITRE bundle not loaded
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>
2026-05-27 03:58:30 +02:00
Knacky
b5ea2929de feat(backend): sprint 3 — multi-technique simulations + MITRE matrix
- 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>
2026-05-27 03:56:02 +02:00