User reported two issues on the scenario table:
1. After filling only red_command + executed_at, the pill read
"Reviewed" instead of "Awaiting review". Cause: the auto-promotion
was gated on `state in {pending, skipped, blocked}`, so a test that
blue had already reviewed stayed in `reviewed_by_blue` after a red
edit. That contradicts the implicit lifecycle (red modifying a
reviewed test invalidates the review).
2. The state pill lived inside the Commentaires column, polluting a
cell meant for blue-team comments.
Fixes
- backend/app/services/mission_tests.py: any red-side write now lands
the state on `executed` unconditionally (including from
`reviewed_by_blue`). Same-PUT red+blue still flows
executed → reviewed_by_blue.
- frontend/src/pages/MissionScenarioTable.tsx: state pill moves from
CommentairesCell to the Test column (top of the row, above the
snapshot name + MITRE chips). Commentaires now holds only the
detection_level pill + blue_comment_md.
Tests
- New pytest: `test_red_write_on_reviewed_reverts_to_executed` —
reviewed_by_blue + red edit ⇒ executed. 143 pytest green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User: «Enlève également le workflow d'un test, quand on saisit des
informations côtés redteam cela signifie qu'il a été exécuté et donc
en attente d'une review blueteam.»
Backend (update_mission_test_fields)
- At the end of every PUT, inspect the touched-field set:
- any red write on state in {pending, skipped, blocked} → state=executed
+ auto-stamp executed_at=now() if absent
- any blue write on state=executed → state=reviewed_by_blue
- /transition endpoint kept for back-fill/admin use, not called from UI.
Frontend MissionTestPage
- Removed the transition-buttons header block and the `transition`
mutation. State pill stays as a passive indicator.
- New labels: "Not started" / "Awaiting review" / "Reviewed" describe
the implicit lifecycle, no longer exposing the state-machine concept.
E2E
- The SPA test that clicked `transition-executed` now verifies the
implicit promotion: typing red fields and saving flips the pill from
"Not started" → "Awaiting review", no button click required.
Spec
- §4 reword: "Cycle de vie implicite, piloté par les écritures" replaces
the old "Workflow par test instance" bullet.
Tests
- 3 new pytest: red_command-alone implicit execute + auto-stamp,
blue write promotes executed→reviewed, blue write on pending no-op.
- 142 pytest + 49 Playwright green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User reported `HTTP 400 — executed_at can only be set when state is
executed/reviewed_by_blue` when typing the timestamp inline in the new
scenario table. The state-gate predates the simplified UX — it made
sense back when the workflow was "Mark executed button + override
toggle", but the user has since asked for a single freely-typeable
datetime input.
- update_mission_test_fields drops the state check. Stamping a non-null
executed_at while state ∈ {pending, skipped, blocked} now auto-promotes
the state to `executed` in the same write. The promotion is gated by
the same mission.write_red_fields perm that executed_at already
required — no privilege escalation.
- MissionTestPage.tsx drops the state-based UI gate on canEditExecutedAt;
red perm alone now unlocks the input regardless of state.
- Replaced the old "rejection while pending" test with two new tests:
pending→executed via inline stamp + blue 403, and skipped→executed via
inline stamp.
- 139 pytest green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback after the M7 ship: blue team's Excel workflow had 5 extra
fields we didn't capture. Per-test page also doesn't match their
workflow — they need a tabular view, one table per scenario.
Spec
- tasks/spec.md amended (`revised: 2026-05-15`): §4 in-scope, §F6, §8
model bullet. §F6 now pins the column matrix, single-row-edit
semantics, Esc-cancel, blur-confirm, and reconciles detection_level
as a pill inside the Commentaires cell (no 8th column).
- tasks/todo.md M7 section grew an "Amendement 2026-05-15" sub-block
tracking backend ☑ and frontend ☐.
Backend
- Migration c2a8f4b1d6e9: 5 nullable columns on mission_tests
(blue_log_source, blue_siem_logs, blue_incident_at,
blue_incident_number, blue_incident_recipient_email).
- _BLUE_FIELDS extended; update_mission_test_fields propagates each
field; MissionTestDetailView + MissionTestView (the nested view in
GET /missions/{id}) surface every annotation field, plus
last_actor_*, updated_at, detection_level_key — O(1) batch lookup
for detection-level keys and last-actor users keeps it scalable.
- UpdateMissionTestPayload accepts each field with length caps
(120/200_000/120/255).
Reviewer follow-ups applied
- blue_incident_at + executed_at now reject naïve datetimes
(_ensure_aware_datetime) — Postgres would otherwise interpret
them in the session TZ, defeating the M7 verbatim-time contract.
- blue_incident_recipient_email goes through a permissive RFC-shape
regex (_validate_email_shape) so internal/lab TLDs like .local
/ .corp / .test pass — Pydantic EmailStr is too strict (lessons.md
M2 trap).
- Project-wide: switched `e.errors()` to
`e.errors(include_context=False, include_url=False)` because the
AfterValidator-raised ValueError lands in ctx and Flask can't
serialize it.
Tests
- 5 new pytest cases: blue user writes the 5 new fields, red user is
individually 403'd on each, round-trip via GET, naïve datetime
rejected, email shape validated (.local accepted, bad shape 400).
- 138 pytest green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>