fix(p0): SessionStatus enum — add INACTIVE + REDIRECTED states#23
Conversation
…BR-1, §BS-7) Closes the P0 #1 issue from the 2026-05-08 CTO audit. The PRD requires six classification states (Inactive, Active, WaitingHuman, Completed, Redirected, Suspended); we previously had four. Without REDIRECTED as a state, the audit log can't carry the redirect signal cleanly. Without INACTIVE distinct from SUSPENDED, BS-7's "game open, no work" vs "game closed" distinction wasn't representable. bridge/session_state.py: - Adds INACTIVE and REDIRECTED to SessionStatus - Docstring documents the full state semantics + the BS-7 distinction - get_active_sessions now includes REDIRECTED (no clawback rule) bridge/scoring.py: - SimpleScoringStrategy treats REDIRECTED like ACTIVE for base reward. Culture falls to 0 because momentum is 0, but production / gold / science still flow — that's the PRD's "no negative balance" rule in action. bridge/hook_listener.py: - _handle_permission_denied now marks the session REDIRECTED in addition to zeroing momentum; emits 1 or 2 StateChange records depending on whether momentum was already zero. - _handle_task_completed flips REDIRECTED → ACTIVE (productive event ends the redirect period). _handle_user_prompt_submit already covers WAITING/REDIRECTED/INACTIVE → ACTIVE via the existing generic transition. bridge/tests/test_economy.py: - Updates test_redirect_via_hook_listener_permission_denied to expect the new 2-change emission. bridge/tests/test_hook_listener.py: - Updates test_permission_denied_with_zero_momentum to assert the new "still marks REDIRECTED" behavior (single status change, no momentum change since already zero). bridge/tests/test_redirected_state_machine.py — 10 new tests: - Enum has all six PRD-mandated states - INACTIVE + REDIRECTED serialize through SaveExchange round-trip - PermissionDenied → REDIRECTED + momentum=0 - REDIRECTED → ACTIVE on UserPromptSubmit - REDIRECTED → ACTIVE on TaskCompleted - Redirected sessions keep earning base reward (no clawback) - get_active_sessions includes REDIRECTED - get_active_sessions excludes INACTIVE + SUSPENDED - INACTIVE and SUSPENDED are distinct (BS-7) Total: 159 passing (was 149; adds 10). Refs: kanban t_26404be3 (P0 audit issues followup), PR #22 (audit doc). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request implements the INACTIVE and REDIRECTED session states, updating the state machine to handle transitions during permission denials and task completions. It also ensures REDIRECTED sessions earn base rewards in the scoring logic. Feedback recommends broadening the transition to ACTIVE in _handle_task_completed for consistency and updating the SessionStatus docstring to correctly reflect that both ACTIVE and REDIRECTED states contribute to rewards.
| if session.status == SessionStatus.REDIRECTED: | ||
| changes.append(listener._set_status( | ||
| session, SessionStatus.ACTIVE, note="task_completed_resumes_active" | ||
| )) |
There was a problem hiding this comment.
The current logic only transitions a session from REDIRECTED to ACTIVE upon task completion. However, for consistency with _handle_user_prompt_submit (line 210), any productive event should resume activity if the session is in a non-active state (like WAITING or INACTIVE).
| if session.status == SessionStatus.REDIRECTED: | |
| changes.append(listener._set_status( | |
| session, SessionStatus.ACTIVE, note="task_completed_resumes_active" | |
| )) | |
| # A productive event ends a non-active period — flip back to ACTIVE. | |
| if session.status != SessionStatus.ACTIVE: | |
| changes.append(listener._set_status( | |
| session, SessionStatus.ACTIVE, note="task_completed_resumes_active" | |
| )) |
| """Status of a Claude session. | ||
|
|
||
| Per PRD §BR-1, sessions are classified as one of these states. | ||
| All states except ACTIVE contribute zero base value per turn (BR-4). |
There was a problem hiding this comment.
This docstring is now inaccurate. With the introduction of the REDIRECTED state, ACTIVE is no longer the only state that contributes base value per turn. As seen in bridge/scoring.py and get_active_sessions, REDIRECTED sessions also earn rewards.
| All states except ACTIVE contribute zero base value per turn (BR-4). | |
| All states except ACTIVE and REDIRECTED contribute zero base value per turn (BR-4). |
Summary
Closes P0 #1 from the 2026-05-08 CTO audit. Adds the two missing PRD-mandated SessionStatus values (INACTIVE, REDIRECTED) plus the surrounding state-machine behavior.
PermissionDenied→REDIRECTED(transient state) + momentum=0REDIRECTED→ACTIVEon next productive event (TaskCompleted, UserPromptSubmit)REDIRECTEDsessions keep earning base reward (PRD "no negative balance")INACTIVEdistinct fromSUSPENDED(BS-7)Closes
PRD §BR-1, §BS-7, §TU-4. References: PR #22 (audit), kanban t_26404be3.
Test plan
pytest -q→ 159 passed (was 149; adds 10)