fix(m7): stamping executed_at no longer requires a prior state transition
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>
This commit is contained in:
@@ -590,13 +590,16 @@ def update_mission_test_fields(
|
||||
)
|
||||
|
||||
if "executed_at_overridden" in touched or "executed_at" in touched:
|
||||
# Editing executed_at is a red-only privilege and only valid when
|
||||
# the test is past the `executed` milestone. Spec M7: override is
|
||||
# behind a deliberate toggle so the auto-stamp default is sticky.
|
||||
if test.state not in {"executed", "reviewed_by_blue"}:
|
||||
raise InvalidTestPayload(
|
||||
"executed_at can only be set when state is executed/reviewed_by_blue"
|
||||
)
|
||||
# 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.
|
||||
new_overridden = (
|
||||
bool(executed_at_overridden)
|
||||
if "executed_at_overridden" in touched
|
||||
@@ -609,6 +612,8 @@ 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user