Files
cleveragents-core/features/correction_subtree_isolation.feature
Luis Mendes 1ffb0fddbe
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 21s
CI / lint (pull_request) Successful in 3m18s
CI / typecheck (pull_request) Successful in 4m11s
CI / security (pull_request) Successful in 4m13s
CI / integration_tests (pull_request) Successful in 5m56s
CI / coverage (pull_request) Successful in 10m2s
CI / quality (pull_request) Successful in 3m40s
CI / unit_tests (pull_request) Successful in 6m6s
CI / docker (pull_request) Successful in 1m2s
CI / status-check (pull_request) Successful in 1s
CI / e2e_tests (pull_request) Successful in 8m19s
CI / build (push) Successful in 14s
CI / lint (push) Successful in 3m35s
CI / typecheck (push) Successful in 3m53s
CI / benchmark-regression (push) Has been skipped
CI / integration_tests (push) Successful in 5m54s
CI / unit_tests (push) Successful in 7m17s
CI / e2e_tests (push) Successful in 9m39s
CI / coverage (push) Successful in 11m22s
CI / benchmark-regression (pull_request) Successful in 58m8s
CI / benchmark-publish (push) Successful in 32m8s
CI / quality (push) Successful in 3m29s
CI / security (push) Successful in 3m40s
CI / docker (push) Successful in 51s
CI / status-check (push) Successful in 1s
feat(plan): decision correction recomputes only affected subtree
Enhanced the CorrectionService to properly isolate correction scope:

1. analyze_impact() now populates excluded_decisions by computing
   the set difference between all plan decisions and the affected
   subtree, ensuring root and sibling decisions are tracked.

2. Added rollback_tier computation that counts parent hops from
   the target decision to the root, enabling depth-aware rollback
   strategies (tier 0 = root targeted, tier N = N levels deep).

3. Enhanced dry-run report with excluded decisions list, rollback
   tier, and tier-0 warning when entire tree is affected.

4. Added subtree isolation validation confirming root exclusion
   and sibling non-contamination for non-root corrections.

5. Fixed status state-machine regression in execute_revert() where
   analyze_impact() overwrote status back to ANALYZING from EXECUTING.
   Guard now only transitions to ANALYZING when status is PENDING.
   execute_revert() now transitions through ANALYZING before EXECUTING
   for correct lifecycle ordering.

6. Fixed validate_subtree_isolation() to use structural-only BFS for
   the sibling invariant check, so that influence-DAG-caused sibling
   reachability is not misreported as an isolation violation (per spec
   § Affected Subtree Computation).

7. Fixed false-positive cycle-detection warnings from convergent
   (diamond) topologies by replacing per-node seen_this_round with a
   global enqueued set in BFS, preventing duplicate queue insertions
   from different parents.

8. Added dry_run enforcement guard in _assert_executable() to prevent
   execution of dry-run-only corrections per spec (§ plan correct
   --dry-run: "Show impact without executing").

9. Fixed generate_dry_run_report() to preserve request status so that
   generating a preview does not advance the correction lifecycle
   (dry-run is non-mutating per spec).  Status restoration now uses
   try/finally to guarantee recovery even when analyze_impact() raises
   after transitioning the status.

10. Improved cycle-detection log message accuracy to cover both
    structural tree and influence DAG sources.

11. Extracted cost/time estimation constants (_COST_PER_DECISION,
    _RECOMPUTE_SECONDS_PER_DECISION) from magic numbers.

12. Added terminal-state guard in analyze_impact() to reject
    re-analysis after execution (APPLIED/FAILED/CANCELLED/REJECTED),
    preventing audit-data corruption.  Promoted terminal-status set
    to a module-level _TERMINAL_STATUSES frozenset constant.

13. Added mode validation in execute_revert()/execute_append() to
    prevent mode-mismatched execution (e.g. calling execute_revert
    on an APPEND correction).

14. Fixed _collect_all_decisions() to always include the target
    decision in the universe, preventing broken partition invariant
    for isolated single-node plans.

15. Fixed tier-0 dry-run warning to only trigger when the target
    is genuinely in the structural tree (avoiding false warnings for
    nodes not present in the tree).

16. Removed duplicate as_cli_dict regression scenarios in
    resource_type_deferred_physical.feature.

ISSUES CLOSED: #845
2026-03-23 18:41:53 +00:00

255 lines
12 KiB
Gherkin

Feature: Decision correction recomputes only affected subtree
As a developer using CleverAgents correction flows
I want corrections to recompute only the affected subtree
So that sibling and ancestor decisions are preserved unchanged
Background:
Given a subtree isolation correction service
And a subtree isolation decision tree with root "root" and children
| parent | children |
| root | A,B |
| A | C,D |
| B | E |
# --- Leaf targeting ---
Scenario: Correction targets leaf node - only leaf affected
When I subtree isolate target decision "D" with mode "revert"
And I subtree isolate analyze the impact
Then the subtree isolate affected decisions should be "D"
And the subtree isolate excluded decisions should contain "root"
And the subtree isolate excluded decisions should contain "A"
And the subtree isolate excluded decisions should contain "B"
And the subtree isolate excluded decisions should contain "C"
And the subtree isolate excluded decisions should contain "E"
# --- Middle node targeting ---
Scenario: Correction targets middle node - middle and descendants affected
When I subtree isolate target decision "A" with mode "revert"
And I subtree isolate analyze the impact
Then the subtree isolate affected decisions should contain "A"
And the subtree isolate affected decisions should contain "C"
And the subtree isolate affected decisions should contain "D"
And the subtree isolate excluded decisions should contain "root"
And the subtree isolate excluded decisions should contain "B"
And the subtree isolate excluded decisions should contain "E"
# --- Root targeting ---
Scenario: Correction targets root - all decisions affected with tier 0 warning
When I subtree isolate target decision "root" with mode "revert"
And I subtree isolate generate a dry-run report
Then the subtree isolate affected decisions should contain "root"
And the subtree isolate affected decisions should contain "A"
And the subtree isolate affected decisions should contain "B"
And the subtree isolate affected decisions should contain "C"
And the subtree isolate affected decisions should contain "D"
And the subtree isolate affected decisions should contain "E"
And the subtree isolate excluded decisions should be empty
And the subtree isolate rollback tier depth should be 0
And the subtree isolate dry-run warnings should contain "Tier 0"
# --- Rollback tier computation ---
Scenario: Rollback tier is 0 for root
When I subtree isolate compute rollback tier for "root"
Then the subtree isolate rollback tier should be 0
Scenario: Rollback tier is 1 for direct child
When I subtree isolate compute rollback tier for "A"
Then the subtree isolate rollback tier should be 1
Scenario: Rollback tier is 1 for second direct child
When I subtree isolate compute rollback tier for "B"
Then the subtree isolate rollback tier should be 1
Scenario: Rollback tier is 2 for grandchild
When I subtree isolate compute rollback tier for "C"
Then the subtree isolate rollback tier should be 2
Scenario: Rollback tier depth 3 for deep tree
Given a subtree isolation deep tree with depth 4
When I subtree isolate compute rollback tier for "depth_3"
Then the subtree isolate rollback tier should be 3
# --- Dry-run report enhancements ---
Scenario: Dry-run report includes excluded decisions and rollback tier
When I subtree isolate target decision "A" with mode "revert"
And I subtree isolate generate a dry-run report
Then the subtree isolate dry-run report should include excluded decisions
And the subtree isolate dry-run report rollback tier depth should be 1
And the subtree isolate dry-run report excluded decisions should contain "root"
And the subtree isolate dry-run report excluded decisions should contain "B"
And the subtree isolate dry-run report excluded decisions should contain "E"
# --- Subtree isolation validation ---
Scenario: Subtree isolation is valid for non-root target
When I subtree isolate validate isolation for target "A"
Then the subtree isolate isolation should be valid
Scenario: Subtree isolation is valid for leaf target
When I subtree isolate validate isolation for target "D"
Then the subtree isolate isolation should be valid
# --- Edge cases for coverage ---
Scenario: Subtree isolation with empty tree returns valid
Given a subtree isolation empty tree
When I subtree isolate validate isolation for target "X"
Then the subtree isolate isolation should be valid
Scenario: Rollback tier for unknown node is 0
When I subtree isolate compute rollback tier for "UNKNOWN"
Then the subtree isolate rollback tier should be 0
Scenario: Find parent returns None for root node
When I subtree isolate check parent of "root"
Then the subtree isolate parent should be none
# --- Influence DAG traversal ---
Scenario: Influence edge adds extra decision to affected set
Given a subtree isolate influence edge from "D" to "E"
When I subtree isolate target decision "D" with mode "revert"
And I subtree isolate analyze the impact with influence edges
Then the subtree isolate affected decisions should contain "D"
And the subtree isolate affected decisions should contain "E"
And the subtree isolate excluded decisions should contain "root"
And the subtree isolate excluded decisions should contain "A"
And the subtree isolate excluded decisions should contain "B"
And the subtree isolate excluded decisions should contain "C"
Scenario: Influence DAG cycle does not cause infinite loop
Given a subtree isolate influence cycle from "C" to "D" back to "C"
When I subtree isolate target decision "C" with mode "revert"
And I subtree isolate analyze the impact with influence edges
Then the subtree isolate affected decisions should contain "C"
And the subtree isolate affected decisions should contain "D"
Scenario: Isolation validation passes when influence DAG reaches sibling
Given a subtree isolate influence edge from "D" to "E"
When I subtree isolate validate isolation for target "D" with influence edges
Then the subtree isolate isolation should be valid
# --- Append mode ---
Scenario: Append correction spawns child plan
When I subtree isolate target decision "A" with mode "append"
And I subtree isolate execute append correction
Then the subtree isolate append result should have a spawned child plan
And the subtree isolate append result status should be "applied"
# --- Dry-run enforcement ---
Scenario: Executing a dry-run revert correction raises ValidationError
When I subtree isolate target decision "A" with mode "revert" and dry_run
Then subtree isolate executing the correction should raise a dry-run error
Scenario: Executing a dry-run append correction raises ValidationError
When I subtree isolate target decision "A" with mode "append" and dry_run
Then subtree isolate executing the correction should raise a dry-run error
# --- Negative isolation validation ---
Scenario: Isolation validation detects structural root contamination
Given a subtree isolation tree where child structurally reaches root
| parent | children |
| root | A |
| A | root |
When I subtree isolate validate isolation for target "A"
Then the subtree isolate isolation should be invalid
# --- Execute revert (non-dry-run) ---
Scenario: Execute revert correction succeeds for middle node
When I subtree isolate target decision "A" with mode "revert"
And I subtree isolate execute revert correction
Then the subtree isolate revert result status should be "applied"
And the subtree isolate revert result should contain reverted decisions "A,C,D"
And the subtree isolate revert result should have archived artifacts
# --- Status guard enforcement ---
Scenario: Executing an already-applied correction raises ValidationError
When I subtree isolate target decision "A" with mode "revert"
And I subtree isolate execute revert correction
Then subtree isolate executing the correction again should raise a status error
Scenario: Executing a cancelled correction raises ValidationError
When I subtree isolate target decision "A" with mode "revert"
And I subtree isolate cancel the correction
Then subtree isolate executing the correction should raise a status error
# --- Mode mismatch enforcement ---
Scenario: Calling execute_revert on an append correction raises ValidationError
When I subtree isolate target decision "A" with mode "append"
Then subtree isolate calling execute_revert should raise a mode error
Scenario: Calling execute_append on a revert correction raises ValidationError
When I subtree isolate target decision "A" with mode "revert"
Then subtree isolate calling execute_append should raise a mode error
# --- Single-node tree edge case ---
Scenario: Single-node tree has no excluded decisions
Given a subtree isolation single node tree
When I subtree isolate target decision "root" with mode "revert"
And I subtree isolate analyze the impact
Then the subtree isolate affected decisions should be "root"
And the subtree isolate excluded decisions should be empty
And the subtree isolate rollback tier depth should be 0
# --- Terminal state guard ---
Scenario: Re-analyzing an applied correction raises ValidationError
When I subtree isolate target decision "A" with mode "revert"
And I subtree isolate execute revert correction
Then subtree isolate re-analyzing the impact should raise a terminal state error
# --- Exact-match affected set for middle node ---
Scenario: Middle node affected count is exactly 3
When I subtree isolate target decision "A" with mode "revert"
And I subtree isolate analyze the impact
Then the subtree isolate affected decisions count should be 3
# --- Convergent (diamond) DAG topology ---
Scenario: Convergent diamond topology does not duplicate affected nodes
Given a subtree isolate diamond influence where "C" and "D" both target "E"
When I subtree isolate target decision "A" with mode "revert"
And I subtree isolate analyze the impact with influence edges
Then the subtree isolate affected decisions should contain "C"
And the subtree isolate affected decisions should contain "D"
And the subtree isolate affected decisions should contain "E"
And the subtree isolate affected decision "E" should appear exactly once
# --- Dry-run exception recovery ---
Scenario: Dry-run report restores status when analyze_impact fails
When I subtree isolate target decision "A" with mode "revert"
And I subtree isolate generate a dry-run report that fails internally
Then the subtree isolate correction status should be "pending"
# --- Execute revert with influence edges ---
Scenario: Execute revert with influence edges includes DAG-reachable decisions
Given a subtree isolate influence edge from "D" to "E"
When I subtree isolate target decision "A" with mode "revert"
And I subtree isolate execute revert correction with influence edges
Then the subtree isolate revert result status should be "applied"
And the subtree isolate revert result should contain reverted decision "E"
# --- DAG-only nodes in excluded set ---
Scenario: Nodes only in influence DAG appear in excluded set
Given a subtree isolate influence edge from "X_EXT" to "Y_EXT" only in DAG
When I subtree isolate target decision "D" with mode "revert"
And I subtree isolate analyze the impact with influence edges
Then the subtree isolate excluded decisions should contain "X_EXT"
And the subtree isolate excluded decisions should contain "Y_EXT"