feat(m7-amend2): implicit lifecycle — writes drive state, no workflow UI
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>
This commit is contained in:
@@ -591,15 +591,9 @@ def update_mission_test_fields(
|
||||
|
||||
if "executed_at_overridden" in touched or "executed_at" in touched:
|
||||
# Editing executed_at is a red-only privilege (gated above via
|
||||
# _RED_FIELDS). We no longer reject the write based on the
|
||||
# current state — the spec amendement 2026-05-15 lets the red
|
||||
# team record an execution timestamp inline, which would be
|
||||
# circular if they had to transition the state machine first.
|
||||
# Instead, stamping a non-null timestamp implicitly bumps the
|
||||
# state forward from any non-executed source so the persisted
|
||||
# record stays internally consistent. The same `mission.
|
||||
# write_red_fields` perm covers both moves, so this isn't a
|
||||
# privilege escalation.
|
||||
# _RED_FIELDS). State auto-promotion is handled by the general
|
||||
# block below; this branch just validates and applies the
|
||||
# timestamp + override flag.
|
||||
new_overridden = (
|
||||
bool(executed_at_overridden)
|
||||
if "executed_at_overridden" in touched
|
||||
@@ -612,11 +606,25 @@ def update_mission_test_fields(
|
||||
)
|
||||
if "executed_at" in touched and new_at is not None and not isinstance(new_at, datetime):
|
||||
raise InvalidTestPayload("executed_at must be an ISO datetime")
|
||||
if new_at is not None and test.state in {"pending", "skipped", "blocked"}:
|
||||
test.state = "executed"
|
||||
test.executed_at = new_at
|
||||
test.executed_at_overridden = new_overridden
|
||||
|
||||
# Implicit lifecycle (post-amendement 2026-05-15 bis): the explicit
|
||||
# workflow is gone from the UI. A write to ANY red field implies the
|
||||
# test was executed (auto-stamp executed_at if the operator didn't
|
||||
# supply one); a write to ANY blue field on an executed test implies
|
||||
# the blue team reviewed it. The /transition endpoint stays for
|
||||
# back-fill but is no longer the primary path.
|
||||
red_touched = bool(touched & _RED_FIELDS)
|
||||
blue_touched = bool(touched & _BLUE_FIELDS)
|
||||
if red_touched and test.state in {"pending", "skipped", "blocked"}:
|
||||
if test.executed_at is None:
|
||||
test.executed_at = datetime.now(tz=timezone.utc)
|
||||
test.executed_at_overridden = False
|
||||
test.state = "executed"
|
||||
if blue_touched and test.state == "executed":
|
||||
test.state = "reviewed_by_blue"
|
||||
|
||||
_touch(test, viewer_id)
|
||||
s.flush()
|
||||
s.refresh(test)
|
||||
|
||||
Reference in New Issue
Block a user