fix(domain): align plan lifecycle model validation with specification #1077
Merged
CoreRasurae
merged 1 commits from 2026-03-23 23:56:34 +00:00
fix/plan-lifecycle-model-validation into master
Dismiss Review
Are you sure you want to dismiss this review?
Labels
Clear labels
auto/needs-reevaluation
controller-managed
overdue
auto/blocked-by-deps
auto/ci-timeout
auto/claimed-implementer
auto/claimed-merge
auto/claimed-reviewer
auto/driver-down
auto/invariant-violation
auto/last-attempt-tier-0
auto/last-attempt-tier-1
auto/last-attempt-tier-2
auto/last-attempt-tier-min
Automation Tracking
auto/needs-conflict-resolution
auto/needs-implementer
auto/postmortem
auto/ready-to-merge
auto/restart-throttled
auto/revert
auto/sentinel
auto/stale-inactivity
auto/unstable
Blocked
Needs Feedback
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
Controller deferred this PR; awaiting Phase 6+ scope-evaluator or operator re-enablement.
Auto-agents controller manages this PR/issue (see tools/controller/deploy/RUNBOOK.md). Remove this label to abandon controller management.
PR blocked by an open issue dependency. Operator must close the dep (or remove the dependency link) before the merge driver can act. Auto-cleared by merge_drive when no open deps remain.
Most recent merge cycle hit CI timeout. Driver excludes this PR while last merge_cycle row is < 30 min old; label persists thereafter as visible history.
Currently being processed by an implementer worker.
Currently being processed by the merge driver.
Currently being processed by a reviewer worker.
Merge driver heartbeat stale; pipeline halted. Closed automatically on next clean tick.
Detected master commit violating the strict merge invariant. Tracked as an issue (not a PR label); kept here for label completeness.
In-cycle escalation: most recent attempt ran at the Tier 0 slot (`tier-0`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 1 slot (`tier-1`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 2 slot (`tier-2`). Slot's model defined in .opencode/models/tiers.yaml. Gated behind IMPLEMENTER_ESCALATION_TIER2_ENABLED.
In-cycle escalation: most recent attempt ran at the Tier -1 slot (`tier-min`). Slot's model defined in .opencode/models/tiers.yaml. Suffix is ``-min`` (not ``--1``) so the Forgejo UI reads naturally.
Tracking issues used by the AI Automation system for agents to communicate and report.
Rebase conflict needs LLM conflict-resolver.
Failing CI needs implementer attention.
Documenting a driver incident or rollback.
Reviewer has APPROVED this PR and no later REQUEST_CHANGES is outstanding. The merge driver requires this label to even consider a PR for merging. Set by the reviewer worker on APPROVE; cleared on REQUEST_CHANGES.
Train repeatedly lost master-tempo races. Driver excludes via merge_cycle until cooldown elapses; label persists as visible history.
Revert PR backing out an invariant violation. Fast-tracked through the merge driver.
Sentinel PR duplicated from upstream into a personal fork by tools/duplicate_prs_to_fork.py for pipeline testing. Lives only in the fork; the canonical pipeline never sees it.
No implementer activity for N days. Flagged for human review. Auto-cleared on next push to head branch.
Repeatedly fails on current master (>= 3 ci-fail-on-rebased-sha releases in 12 h). Excluded from driver until human triage.
A ticket in a blocked state and unable to complete until some other task is completed first.
Bounty
$100
A bounty of $100 for any open-source contributor who provides a MR that solves this issue
Bounty
$1000
A bounty of $1000 for any open-source contributor who provides a MR that solves this issue
Bounty
$10000
A bounty of $10000 for any open-source contributor who provides a MR that solves this issue
Bounty
$20
A bounty of $20 for any open-source contributor who provides a MR that solves this issue
Bounty
$2000
A bounty of $2000 for any open-source contributor who provides a MR that solves this issue
Bounty
$250
A bounty of $250 for any open-source contributor who provides a MR that solves this issue
Bounty
$50
A bounty of $50 for any open-source contributor who provides a MR that solves this issue
Bounty
$500
A bounty of $500 for any open-source contributor who provides a MR that solves this issue
Bounty
$5000
A bounty of $5000 for any open-source contributor who provides a MR that solves this issue
Bounty
$750
A bounty of $750 for any open-source contributor who provides a MR that solves this issue
MoSCoW
Could have
Could have feature in order to satisfy the epic/legendary.
MoSCoW
Must have
Must have feature in order to satisfy the epic/legendary.
MoSCoW
Should have
Should have feature in order to satisfy the epic/legendary.
There are questions in the ticket that can not be completed until the project owner provides clarity.
Points
1
1 man-hours worth of work for an expert with no learning curve.
Points
13
13 man-hours worth of work for an expert with no learning curve.
Points
2
2 man-hours worth of work for an expert with no learning curve.
Points
21
21 man-hours worth of work for an expert with no learning curve.
Points
3
3 man-hours worth of work for an expert with no learning curve.
Points
34
34 man-hours worth of work for an expert with no learning curve.
Points
5
5 man-hours worth of work for an expert with no learning curve.
Points
55
55 man-hours worth of work for an expert with no learning curve.
Points
8
8 man-hours worth of work for an expert with no learning curve.
Points
88
88 man-hours worth of work for an expert with no learning curve.
Priority
Backlog
This ticket has backlogged priority and is not to be worked on yet
Priority
CI Blocker
Critical priority issue that blocks CI/CD pipeline and prevents PR merges
Priority
Critical
The priority is critical
Priority
High
The priority is high
Priority
Low
The priority is low
Priority
Medium
The priority is medium
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
A ticket for learning a tool or technology that is needed to be able to do future planning and design.
State
Completed
The ticket has been fully implemented, completed, and merged with the source code. This label should only be applied once a ticket is closed.
State
Duplicate
A ticket that represents the same content as an existing ticket.
State
In Progress
A ticket that is actively being developed.
State
In Review
A ticket that has had some code completed to implement but is waiting to pass peer review and is not yet merged in.
State
Paused
This ticket's work started but wasn't finished. It's on hold (likely in a feature branch) and will be resumed later, either due to a blocker or a delay.
State
Unverified
All new tickets start in this state. A developer may set it to show the ticket is unverified. This means we haven't agreed to work on it. It will either move to a verified state or be closed as wontdo.
State
Verified
The issue has been verified by a developer as legitimate. It will be worked on and verified tickets are now considered part of the backlog.
State
Wont Do
This ticket has been decided it wont be done. This may mean the bug has been determined to not be real (cant verify) or the feature is one we have decided we dont want to adopt.
Type
Automation
Any edits or discussion about the AI automated coding system.
Type
Bug
Something that doesnt work as intended.
Type
Discussion
Anytime a ticket represents a discussion about a subject and doesnt fall into one of the other categories.
Type
Documentation
An error or improvement needed in the documentation.
Type
Epic
Any first tier epic. That is, an epic which contains only issues as children and will not have sub-epics.
Type
Feature
Some new functionality not present.
Type
Legendary
A type of Epic which will contain other Epics.
Type
Refactor
A code change that restructures existing code without changing its external behavior.
Type
Support
Someone needs help using the project.
Type
Task
A generic task that doesnt fit into the other type categories.
Type
Testing
Work exclusively focusing on fixing or expanding testing.
Projects
Clear projects
No project
Assignees
aditya (Aditya Chhabra)
aleenaumair (Aleena Umair)
brent.edwards (Brent Edwards)
CoreRasurae (Luis Mendes)
drew (Drew Morris)
eugen.thaci (Eugen Thaci)
freemo (Jeffrey Phillips Freeman)
HAL9000 (HAL 9000)
HAL9001 (HAL9001)
hamza.khyari (Hamza Khyari)
hurui200320 (Rui Hu)
justin.morris
khird (Kyle Hird)
org.cleveragents
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cleveragents/cleveragents-core#1077
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "fix/plan-lifecycle-model-validation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Aligns the plan lifecycle model with the specification by fixing three discrepancies:
ERRORED is now terminal — Added
ProcessingState.ERROREDto theis_terminalproperty tuple, matching the spec table where errored is marked "Terminal? Yes" for all processing phases.Per-phase state validation — Added
validate_phase_state_constraintsmodel validator enforcing:APPLIEDandCONSTRAINEDare only valid in theAPPLYphaseCOMPLETEis only valid inSTRATEGIZEorEXECUTEphasesQUEUED,PROCESSING,ERRORED,CANCELLEDare valid in any phaseValueErrorat construction timeDocstring update — Updated
ProcessingState.COMPLETEdocstring from "non-terminal" to "terminal within phase" to clarify phase-level terminality semantics.Files Modified/Created
Core changes
src/cleveragents/domain/models/core/plan.py— is_terminal, model validator, docstringsrc/cleveragents/application/services/plan_lifecycle_service.py— Fixed field assignment order inapply_planand_perform_reversionto setprocessing_statebeforephase(avoids validator rejection during phase transitions)New test files
features/plan_lifecycle_model_validation.feature— 13 Behave scenariosfeatures/steps/plan_lifecycle_model_validation_steps.py— Step definitionsrobot/plan_lifecycle_model_validation.robot— 5 Robot Framework integration testsrobot/helper_plan_lifecycle_model_validation.py— Robot helperUpdated existing tests
features/edge_case_plan_scenarios.feature— ERRORED is now terminalfeatures/steps/database_models_lifecycle_coverage_steps.py— APPLY/COMPLETE → APPLY/APPLIEDfeatures/steps/m1_sourcecode_smoke_steps.py— APPLY/COMPLETE → APPLY/QUEUEDfeatures/steps/plan_cli_legacy_r2_steps.py— Added APPLY phase for APPLIED statefeatures/steps/plan_lifecycle_cli_steps.py— Fixed phase-state combofeatures/steps/plan_persistence_steps.py— Fixed assignment orderrobot/helper_m6_e2e_verification.py— ACTION/COMPLETE → ACTION/QUEUEDrobot/helper_phase_reversion.py— Fixed assignment orderQuality Gates
ISSUES CLOSED: #918
Review: APPROVED (with minor comments)
This is a well-crafted, specification-aligned fix. The implementation correctly makes ERRORED terminal, adds per-phase state validators, and carefully handles the Pydantic field-assignment ordering subtlety.
Minor items (non-blocking):
Closing keyword format — The PR body uses
ISSUES CLOSED: #918which is the conventional-commit footer format, not a Forgejo auto-close keyword. AddCloses #918orFixes #918to the PR body so the issue auto-closes on merge.ERRORED + ACTION phase test gap — The BDD scenario "ERRORED state is terminal" only tests STRATEGIZE phase. The Robot helper tests STRATEGIZE/EXECUTE/APPLY but omits ACTION. Consider adding
PlanPhase.ACTIONto the Robot helper's_errored_is_terminal()loop for completeness.Module docstring inconsistency — The Processing States table at the top of
plan.pystill readsERRORED | Failed (may retry). Now that ERRORED is terminal, this should be updated toFailed (terminal)orFailed; includes error metadatato match the inline enum comment.What's done well:
validate_phase_state_constraintsvalidator is clean and well-documented with spec rationale.plan_lifecycle_service.py(settingprocessing_statebeforephase) is a subtle but important correctness fix, and the explanatory comments are helpful.plan_lifecycle_model_validation_steps.py↔plan_lifecycle_model_validation.feature) follows guidelines correctly.Day 43 Review — PR #1077
fix(domain): plan lifecycle model validationMilestone: v3.4.0
Status: Mergeable (no conflicts)
Review Notes
This PR has been reviewed for compliance with
CONTRIBUTING.mdstandards. Key checks:Action Items
Closes #NNN)Please ensure all subtasks in the linked issue are complete before merging.
Code Review Report — PR #1077
PR:
fix(domain): align plan lifecycle model validation with specificationBranch:
fix/plan-lifecycle-model-validationIssue: #918
Reviewer scope: Code changes in this branch + immediate surrounding code interactions.
Summary
The PR correctly addresses the three issues in #918: ERRORED terminality, per-phase state validation via model validator, and COMPLETE docstring clarification. The test coverage for the new validator itself is solid (13 Behave scenarios + 5 Robot tests). However, the review uncovered behavioral regressions in surrounding service-layer code caused by adding ERRORED to
is_terminal, a database deserialization crash risk, an inconsistent assignment-ordering pattern, and test coverage gaps in the new test files.Findings by Severity
MEDIUM Severity
M1 — Behavioral Regression:
cancel_plan()now rejects ERRORED plansplan_lifecycle_service.py:1401cancel_plan()guards withif plan.is_terminal: raise PlanError(...). Since ERRORED is now terminal, users can no longer explicitly cancel an errored plan. Previously, a user could cancel an errored plan to signal intent (e.g., "stop retrying, mark as cancelled"). This is a user-facing behavioral change not addressed by the PR or its tests.cancel_planto check a narrower set (APPLIEDonly, sinceCANCELLEDis already the target state), or (b) document this as an intentional consequence and add a test proving it.M2 — Database Deserialization Crash Risk
infrastructure/database/models.py:749-815(to_domain())phase='apply'+processing_state='complete'(no cross-column constraint exists). However,to_domain()callsPlan(phase=..., processing_state=...)which now triggersvalidate_phase_state_constraintsand raisesValueErrorfor this combination. If any such row exists in the database (from legacy code, manual edits, or pre-validator writes), all code paths loading that plan will crash — includingplan list,plan status, etc. No migration or guard was added.try/exceptinto_domain()that logs a warning and falls back to a safe state (e.g., coercecomplete → appliedwhenphase=apply), or add a data migration that fixes any invalid rows. At minimum, a one-time migration script should be provided.M3 — Inconsistent Resume Paths for ERRORED Plans
plan_lifecycle_service.py:1632vsplan_resume_service.py:255PlanLifecycleService.resume_plan()usesis_terminaland now blocks ERRORED plans. ButPlanResumeService.resume_plan()bypassesis_terminalentirely and directly transitions ERRORED → PROCESSING. Two resume paths with contradictory behavior for the same state.errored → processing), the lifecycle service'sresume_planshould either not checkis_terminalor use the same narrower set ascan_revert_to(APPLIED + CANCELLED only).M4 — Spec Tension: ERRORED Terminal vs Recovery Paths
plan.py:841-858errored → processingtransitions (error recovery, plan prompt). Making ERRORED terminal inis_terminalblockscancel_plan,pause_plan, andresume_plan(lifecycle service), whilerevert_planandPlanResumeService.resume_planstill allow recovery. The PR doesn't document which recovery paths remain valid or explain the "terminal but recoverable" semantics. Issue #918 itself notes this tension and says "If retry is desired, the spec should be updated; otherwise, the code should match spec" — but the PR doesn't resolve this ambiguity.is_terminal(preventing auto-progression, applying, etc.) but still revertable and resumable through explicit recovery paths. Alternatively, introduce anis_permanently_terminalproperty for guards that should truly block all recovery.LOW Severity
L1 — Inconsistent Assignment Ordering in
execute_plan()plan_lifecycle_service.py:1095-1096execute_plan()uses phase-first ordering (plan.phase = PlanPhase.EXECUTEthenplan.processing_state = ProcessingState.QUEUED). This is the opposite of the state-first pattern established by the PR's own fixes at lines 1230-1231 and 1842-1843. It currently works by coincidence (COMPLETE is valid in EXECUTE), but the pattern is inconsistent and fragile.plan.processing_state = ProcessingState.QUEUEDfirst, thenplan.phase = PlanPhase.EXECUTE.L2 — Stale Module Docstring: ERRORED Description
plan.py:32ERRORED | Failed (may retry). The "(may retry)" wording contradicts the new terminal semantics. The PR updated line 33 (COMPLETE) but not line 32 (ERRORED).ERRORED | Failed (terminal; recovery via revert)or similar.L3 — Stale Module Docstring: Terminal Outcomes Location
plan.py:13-15erroredandcancelledare terminal in any phase, whileappliedandconstrainedare Apply-phase-specific terminals.L4 —
can_revert_toDocstring Incompleteplan.py:960-963is_terminal, just not permanently so.is_terminal) but still revertable."L5 —
pause_plan()Rejects ERRORED Plansplan_lifecycle_service.py:1588L6 —
PlanResumeService.validate_eligibility()Docstring Omits ERROREDplan_resume_service.py:104-108is_terminalincluding ERRORED.TEST COVERAGE Gaps
T1 — Missing PROCESSING State Validation Tests
T2 — Missing ERRORED in ACTION Phase Test
T3 — Missing Negative Test: APPLIED in ACTION Phase
T4 — Missing Negative Test: CONSTRAINED in ACTION Phase
T5 — Missing Negative Test: CONSTRAINED in STRATEGIZE Phase
T6 — No Assignment-Ordering Regression Test
validate_assignment=Truefires validators on each individual field set.T7 — No Database Deserialization Edge-Case Test
Performance
No performance concerns. The new validator performs simple tuple-membership checks — negligible overhead even with
validate_assignment=True.Security
No security issues identified. Changes are purely domain-model validation.
Recommendation
The Medium-severity items (M1-M4) should be addressed before merge, particularly M2 (DB deserialization crash risk) and M3 (inconsistent resume paths). L1 (assignment ordering) is a quick fix that should be included for consistency with the PR's own pattern. The test coverage gaps (T1-T7) would strengthen the PR significantly.
7ebb687c91to464e6281a3New commits pushed, approval review dismissed automatically according to repository settings
Code Review Report — PR #1077
fix(domain): align plan lifecycle model validation with specificationReviewer: Automated Code Review (2 full cycles across all categories)
Branch:
fix/plan-lifecycle-model-validationCommit:
464e628Issue: #918
Overall Assessment
The PR correctly addresses the core issue: aligning ERRORED terminality and adding per-phase state validation. The implementation is well-structured, the commit message is thorough, and the new tests are comprehensive. However, the review identified several issues across bug detection, test coverage, data integrity, and spec compliance that warrant attention before merge.
Findings by Severity
HIGH — Behavioral Regression / Bug Risk
H1.
cancel_plan()now rejects ERRORED plansplan_lifecycle_service.py:1403is_terminalguard incancel_plan()now blocks cancellation of ERRORED plans. Before this change, a user could explicitly cancel an errored plan (e.g., "I don't want to retry, just mark it done"). Now the user gets"Plan {id} is already in terminal state and cannot be cancelled". While the spec says ERRORED is terminal, the UX impact should be considered: a user seeing a plan stuck in ERRORED may attempt to cancel it for cleanup. The error message doesn't guide them toward the alternative (revert_plan).cancel_planto accept ERRORED plans as a convenience (making it idempotent for terminal states), or (b) improve the error message to suggest usingrevert_planfor ERRORED plans, or (c) add a note in theis_terminaldocstring acknowledging this trade-off.H2. Silent defensive coercion without logging
infrastructure/database/models.py:752-765to_domain()coercion silently rewrites legacy DB values (e.g., APPLY/COMPLETE → APPLY/APPLIED, STRATEGIZE/APPLIED → STRATEGIZE/ERRORED) without any logging or warning. In production, this makes it impossible to detect when legacy data is being coerced and to differentiate between genuine ERRORED plans and coerced ones. This could mask data corruption.logger.warning("Coercing legacy phase/state combo ...", phase=..., old_state=..., new_state=...)to each coercion branch.H3. Incomplete test coverage for coercion branches
robot/helper_plan_lifecycle_model_validation.py:159-193_deserialization_coercion()function only tests one of the three coercion branches (APPLY/COMPLETE → APPLIED). The remaining two branches are untested:APPLIEDorCONSTRAINEDin non-APPLY phases →ERROREDCOMPLETEin ACTION phase →QUEUEDH4. No BDD scenario for deserialization coercion
features/plan_lifecycle_model_validation.featureMEDIUM — Correctness Concern / Spec Compliance
M1.
PlanResumeService.validate_eligibility()docstring is staleplan_resume_service.py:107-108is_terminal, this docstring is misleading — ERRORED is terminal but IS eligible for resume (the method checks individual states, notis_terminal). The asymmetry betweenPlanLifecycleService.resume_plan()(blocks ERRORED viais_terminal) andPlanResumeService.resume_plan()(allows ERRORED) is subtle and could confuse maintainers.M2. Phase-first assignment ordering in test code not updated
plan.phase = Xbeforeplan.processing_state = Y) which is fragile with the newvalidate_phase_state_constraintsvalidator:features/steps/plan_repository_steps.py:251-252features/steps/phase_reversion_steps.py:109(fallback path)robot/helper_plan_persistence_e2e.py:130,142,153robot/helper_persistence_lifecycle.py:145,156,167These currently pass because the intermediate states happen to be universally valid (QUEUED, PROCESSING), but future test changes could unknowingly trigger the validator with incompatible intermediate states.
M3. Coercion maps APPLIED/CONSTRAINED to ERRORED for non-APPLY phases
infrastructure/database/models.py:759-763STRATEGIZE/APPLIEDto the DB, it's a data corruption bug that should be investigated, not silently "fixed" toSTRATEGIZE/ERRORED. The coercion makes the corruption invisible. The ERRORED state also has semantic meaning (processing failed), which doesn't accurately describe a data integrity issue.M4. Inconsistent "Terminal" interpretation between ERRORED and COMPLETE
domain/models/core/plan.py:841-869is_terminal→ True) but COMPLETE as phase-terminal only (is_terminal→ False). While this is a deliberate and reasonable design choice (COMPLETE means "advance to next phase," not "stop"), the asymmetric interpretation of the same spec column isn't formally documented.M5. No test for
cancel_plan()behavioral change with ERROREDM6. No negative test for phase-first assignment order
features/plan_lifecycle_model_validation.featurevalidate_assignment=Trueis accidentally removed.LOW — Minor / Informational
L1. No test for
pause_plan()andresume_plan()(lifecycle) on ERROREDpause_plan()andPlanLifecycleService.resume_plan()now reject ERRORED plans are untested.L2. CLI active plan resolution excludes ERRORED plans
cli/commands/plan.py:3067active = [p for p in plans if not p.is_terminal]now excludes ERRORED plans. Previously, an ERRORED plan in STRATEGIZE would still be auto-resolved as the "active" plan. This is correct behavior but is an undocumented behavioral change.L3. Multiple model validators on every field assignment
domain/models/core/plan.py(Plan model_config)validate_assignment=True, every individual field assignment triggers all model validators (including the newvalidate_phase_state_constraints). In service methods that mutate multiple Plan fields sequentially, this means multiple full validation passes. The overhead is O(1) per validator but cumulative across the ~5 validators on Plan. This is unlikely to be a bottleneck but worth being aware of.Summary Table
cancel_plan()rejects ERRORED plansPlanResumeServicedocstring staleRecommendation: Address H1-H4 and M1 before merge. The remaining medium and low items can be tracked as follow-up tasks if desired.
464e6281a3toa4fd59bd27a4fd59bd27to9e316b1a3eCode Review Report — PR #1077 (
fix/plan-lifecycle-model-validation)Reviewed commit:
9e316b1by Luis MendesIssue: #918 — Align plan lifecycle model with spec (ERRORED terminality, phase-state validation)
Reviewer method: 3 full review cycles (bugs, security, performance, test coverage, test flaws, code quality) until convergence (no new findings on cycles 2 and 3).
Summary
The commit correctly addresses all three acceptance criteria from #918: ERRORED is now terminal in
is_terminal, model-level phase-state validation is enforced, and the COMPLETE docstring clarifies phase-level terminality. The state-first assignment ordering fix and defensive deserialization coercion are well-designed. Test coverage is comprehensive (28 Behave scenarios + 9 Robot tests). The findings below are refinement opportunities, not blockers.Findings by Severity and Category
MEDIUM — Documentation Bug
B1.
is_terminaldocstring incorrectly claims CONSTRAINED is resumable viaPlanResumeServicesrc/cleveragents/domain/models/core/plan.py:870-872PlanResumeService.validate_eligibility()explicitly blocks CONSTRAINED plans (returnseligible=FalsewithTERMINAL_CONSTRAINEDreason atplan_resume_service.py:148-156). Only ERRORED is eligible for resume viaPlanResumeService; CONSTRAINED can only be reverted.MEDIUM — Test Coverage Gap
T1. "QUEUED valid in any phase" scenario omits ACTION phase
features/plan_lifecycle_model_validation.feature:41-44And a plmv plan in ACTION phase with QUEUED stateto the Given steps:MEDIUM — Test Flaw
T2. "Not cancellable/pausable/resumable" test assertions are indirect proxies
features/steps/plan_lifecycle_model_validation_steps.py:186-210step_plmv_not_cancellable,step_plmv_not_pausable, andstep_plmv_not_resumable_lifecycleall assertis_terminalonly. They don't verify the actual service-layer guards (cancel_plan(),pause_plan(),resume_plan()). The feature file scenario names ("cannot be cancelled", "cannot be paused") imply behavioral testing, but the assertions only test the model property.is_terminalin the future, these tests would pass erroneously.is_terminalproperty, or adding service-level integration tests in a separate file.LOW — Test Coverage Gap
T3. Missing Behave deserialization scenario for EXECUTE/CONSTRAINED coercion
EXECUTE/CONSTRAINED -> EXECUTE/ERRORED(branch 2b in_deserialization_coercion_all_branches), but there is no matching Behave scenario. This creates asymmetric coverage between the two test layers.LOW — Code Robustness
C1.
step_update_plan_phase_statestate-first pattern is fragile for phase-restricted target statesfeatures/steps/plan_persistence_steps.py:272-273processing_state = X; phase = Y) can fail if the target state is phase-restricted (APPLIED, CONSTRAINED, COMPLETE) and the current phase doesn't allow it. For example, transitioning fromEXECUTE/PROCESSINGtoAPPLY/APPLIEDin one step would create the invalid intermediateEXECUTE/APPLIED.LOW — Documentation
C2. Coercion semantic choice lacks justification comment
src/cleveragents/infrastructure/database/models.py:770-782APPLIED(success semantics) toERRORED(failure semantics) for legacy rows outside APPLY phase is a lossy transformation. The code comment explains what happens but not why ERRORED was chosen over alternatives like QUEUED (neutral reset). A brief rationale would help future maintainers.Areas Reviewed with No Issues Found
validate_phase_state_consistency(ACTION/None→QUEUED) runs beforevalidate_phase_state_constraints; correctprocessing_stateassignments inplan_lifecycle_service.pyverified safe (phase-guarded or universally valid states)can_revert_to/revert_planis_terminalchangeto_domain()coercion completenessReview performed using 3 global review cycles across all categories (bugs, security, performance, test coverage, test flaws, code quality). Cycles 2 and 3 found no new issues, confirming convergence.