fix(domain): align plan lifecycle model with spec (ERRORED terminality, phase-state validation) #918

Closed
opened 2026-03-14 00:04:58 +00:00 by freemo · 1 comment
Owner

Background

The plan lifecycle state machine is functionally correct for the happy path — all phase transitions, reversion paths, and automation controls match the specification. However, three model-level discrepancies exist:

1. ERRORED Not Treated as Terminal (plan.py:772-776)

The spec (lines 18253-18258) marks errored as "Terminal? Yes" in all processing phases. The code's is_terminal property excludes ERRORED:

@property
def is_terminal(self) -> bool:
    return self.processing_state in (
        ProcessingState.APPLIED,
        ProcessingState.CANCELLED,
        ProcessingState.CONSTRAINED,
    )

The code docstring (plan.py:32) says "Failed (may retry)", indicating this is intentional for retry workflows. If retry is desired, the spec should be updated; otherwise, the code should match spec.

2. No Model-Level Phase-State Validation (plan.py:738-748)

The spec defines states per-phase: applied/constrained are Apply-only; complete is Strategize/Execute only. The current model validator only handles the ACTION-phase default — it does not prevent invalid combinations like Plan(phase=STRATEGIZE, processing_state=APPLIED). The lifecycle service enforces this procedurally, but the model allows invalid states.

3. Docstring Labels COMPLETE as "Non-Terminal" (plan.py:33)

The spec labels complete as "Terminal? Yes" (per-phase). The code docstring says "Phase work finished (non-terminal)." This is a semantic interpretation difference (plan-level vs phase-level), but the docstring is misleading.

Acceptance Criteria

  • is_terminal behavior is explicitly decided: either include ERRORED (match spec) or document the retry-based deviation in an ADR
  • Model validator enforces per-phase state constraints (e.g., APPLIED/CONSTRAINED only valid in APPLY phase)
  • Docstring for COMPLETE clarifies phase-level terminality vs plan-level progression
  • Existing tests pass

Metadata

  • Suggested commit message: fix(domain): align plan lifecycle model validation with specification
  • Suggested branch name: fix/plan-lifecycle-model-validation

Definition of Done

Code merged to main, plan model enforces phase-state constraints, terminality semantics are clear.

## Background The plan lifecycle state machine is functionally correct for the happy path — all phase transitions, reversion paths, and automation controls match the specification. However, three model-level discrepancies exist: ### 1. ERRORED Not Treated as Terminal (plan.py:772-776) The spec (lines 18253-18258) marks `errored` as "Terminal? Yes" in all processing phases. The code's `is_terminal` property excludes `ERRORED`: ```python @property def is_terminal(self) -> bool: return self.processing_state in ( ProcessingState.APPLIED, ProcessingState.CANCELLED, ProcessingState.CONSTRAINED, ) ``` The code docstring (`plan.py:32`) says `"Failed (may retry)"`, indicating this is intentional for retry workflows. If retry is desired, the spec should be updated; otherwise, the code should match spec. ### 2. No Model-Level Phase-State Validation (plan.py:738-748) The spec defines states per-phase: `applied`/`constrained` are Apply-only; `complete` is Strategize/Execute only. The current model validator only handles the ACTION-phase default — it does not prevent invalid combinations like `Plan(phase=STRATEGIZE, processing_state=APPLIED)`. The lifecycle service enforces this procedurally, but the model allows invalid states. ### 3. Docstring Labels COMPLETE as "Non-Terminal" (plan.py:33) The spec labels `complete` as "Terminal? Yes" (per-phase). The code docstring says "Phase work finished (non-terminal)." This is a semantic interpretation difference (plan-level vs phase-level), but the docstring is misleading. ## Acceptance Criteria - [ ] `is_terminal` behavior is explicitly decided: either include ERRORED (match spec) or document the retry-based deviation in an ADR - [ ] Model validator enforces per-phase state constraints (e.g., APPLIED/CONSTRAINED only valid in APPLY phase) - [ ] Docstring for COMPLETE clarifies phase-level terminality vs plan-level progression - [ ] Existing tests pass ## Metadata - **Suggested commit message:** `fix(domain): align plan lifecycle model validation with specification` - **Suggested branch name:** `fix/plan-lifecycle-model-validation` ## Definition of Done Code merged to `main`, plan model enforces phase-state constraints, terminality semantics are clear.
freemo added the
Points
3
Priority
Medium
State
Verified
Type
Task
labels 2026-03-14 00:06:04 +00:00
freemo added this to the v3.4.0 milestone 2026-03-14 00:06:13 +00:00
CoreRasurae was assigned by freemo 2026-03-14 04:27:04 +00:00
CoreRasurae added
State
In Progress
and removed
State
Verified
labels 2026-03-19 17:08:30 +00:00
CoreRasurae added
State
In Review
and removed
State
In Progress
labels 2026-03-19 20:47:40 +00:00
Member

Implementation Notes

Changes (commit 7ebb687c)

  1. ERRORED is now terminal: Added ProcessingState.ERRORED to the is_terminal tuple in Plan.is_terminal, matching the spec's "Terminal? Yes" designation for all processing phases.

  2. Per-phase state validation: Added validate_phase_state_constraints model validator:

    • APPLIED and CONSTRAINED only valid in APPLY phase
    • COMPLETE only valid in STRATEGIZE or EXECUTE phases
    • Other states (QUEUED, PROCESSING, ERRORED, CANCELLED) valid in any phase
    • Invalid combinations raise ValueError at Plan construction time
  3. COMPLETE docstring updated: Changed from "Phase work finished (non-terminal)" to "Phase work finished (terminal within phase; plan advances to next phase)" to clarify phase-level vs plan-level terminality.

  4. Service-layer fix: Fixed field assignment order in plan_lifecycle_service.pyapply_plan() and _perform_reversion() now set processing_state before phase to avoid triggering the new validator during transitions.

Test Impact

  • Updated 8 existing test files that created invalid phase-state combinations (e.g., APPLY/COMPLETE, STRATEGIZE/APPLIED, ACTION/COMPLETE)
  • Changed 1 assertion in edge_case_plan_scenarios.feature from "ERRORED is not terminal" to "ERRORED is terminal"
  • Added 13 new Behave scenarios, 5 Robot tests

Quality Gates

Session Result
lint PASS
typecheck PASS (0 errors)
unit_tests PASS (11,507 scenarios)
integration_tests PASS (1,609 tests)
coverage 97% (plan.py at 99.8%)

PR

PR #1077Closes #918

## Implementation Notes ### Changes (commit `7ebb687c`) 1. **ERRORED is now terminal**: Added `ProcessingState.ERRORED` to the `is_terminal` tuple in `Plan.is_terminal`, matching the spec's "Terminal? Yes" designation for all processing phases. 2. **Per-phase state validation**: Added `validate_phase_state_constraints` model validator: - `APPLIED` and `CONSTRAINED` only valid in `APPLY` phase - `COMPLETE` only valid in `STRATEGIZE` or `EXECUTE` phases - Other states (`QUEUED`, `PROCESSING`, `ERRORED`, `CANCELLED`) valid in any phase - Invalid combinations raise `ValueError` at Plan construction time 3. **COMPLETE docstring updated**: Changed from "Phase work finished (non-terminal)" to "Phase work finished (terminal within phase; plan advances to next phase)" to clarify phase-level vs plan-level terminality. 4. **Service-layer fix**: Fixed field assignment order in `plan_lifecycle_service.py` — `apply_plan()` and `_perform_reversion()` now set `processing_state` before `phase` to avoid triggering the new validator during transitions. ### Test Impact - Updated 8 existing test files that created invalid phase-state combinations (e.g., APPLY/COMPLETE, STRATEGIZE/APPLIED, ACTION/COMPLETE) - Changed 1 assertion in `edge_case_plan_scenarios.feature` from "ERRORED is not terminal" to "ERRORED is terminal" - Added 13 new Behave scenarios, 5 Robot tests ### Quality Gates | Session | Result | |---|---| | lint | PASS | | typecheck | PASS (0 errors) | | unit_tests | PASS (11,507 scenarios) | | integration_tests | PASS (1,609 tests) | | coverage | 97% (plan.py at 99.8%) | ### PR PR #1077 — `Closes #918`
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cleveragents/cleveragents-core#918