- TestTemplate/TemplateSuggestion creation and updates now reject any
mitre_technique_id that doesn't match a real, already-synced MITRE
ATT&CK technique. Frontend swaps the free-text ID input for a
technique picker.
- Test Catalog now shows pending template suggestions before the
catalog grid, with full field detail (platform, severity, tool,
atomic ID, source URL, remediation) instead of just name/procedure.
- A manager can edit and directly re-approve a campaign they previously
rejected, instead of needing the original lead to resubmit it.
Test Catalog's pagination showed only 'Page N' with no way to know
how many pages existed, and the campaign 'Add Test' modal's template
picker had no pagination at all — with 2800+ templates in the
catalog, its flat 100-row fetch made most templates unreachable
unless a search happened to match. Adds GET /test-templates/count
(open to any authenticated user, mirrors the list endpoint's filters)
and wires real 'Page N of M' + total-available counts into both.
- start_execution now blocks a test from starting before its campaign's
start_date, closing a gap where the field was persisted but never
enforced
- GET /tests gains a reviewer_id "my reviews" filter for the red_review/
blue_review lead-gate stage, distinct from pending_validation_side
(which only ever covered the later in_review stage and ignored
assignment entirely)
- get_coverage_by_tactic now returns all 14 MITRE tactics in canonical
kill-chain order instead of an alphabetical, partial list — the
regular Dashboard and Executive Dashboard both consume this endpoint
and previously disagreed on order/completeness
- test-template listing now includes existing_test_count per technique
so the catalog can warn before creating a likely-duplicate test
Task D — Google-style docstrings (Args/Returns) on every public function,
method, and class across all 158 Python files in the backend. Zero ruff D
violations (pydocstyle Google convention).
Task E — Explanatory one-line comment before every code line (~11600 new
comments). ruff check passes clean after isort re-sort.