UAT: PlanLifecycleService.use_action() stores action invariants as InvariantSource.ACTION instead of promoting them to InvariantSource.PLAN — runtime sees four-tier chain instead of spec-required three-tier #2392

Open
opened 2026-04-03 17:31:07 +00:00 by freemo · 1 comment
Owner

Metadata

  • Branch: fix/plan-use-action-invariant-promotion
  • Commit Message: fix(plan): promote action invariants to plan-level in use_action() to enforce three-tier precedence chain
  • Milestone: v3.5.0
  • Parent Epic: #368

Bug Report

Feature Area: Plan Lifecycle — agents plan use / Invariant Precedence

What was tested

PlanLifecycleService.use_action() in src/cleveragents/application/services/plan_lifecycle_service.py and the InvariantSource enum in src/cleveragents/domain/models/core/plan.py.

Expected behavior (from spec)

The specification glossary states:

Invariant: A natural-language constraint on plan execution scoped to global, project, action, or plan level. Action invariants are promoted to plan-level when the action is used, so the runtime precedence chain is three-tier: plan > project > global.

When agents plan use is called, action-level invariants must be promoted to plan-level. Their source must be set to InvariantSource.PLAN (not InvariantSource.ACTION). The runtime system should then only see three tiers: plan, project, and global.

Actual behavior (from code analysis)

The invariant-merging section of use_action() in src/cleveragents/application/services/plan_lifecycle_service.py (around line 1200) keeps action invariants as InvariantSource.ACTION:

# Build merged invariants: plan > action > (project/global added later)
merged_invariants: list[PlanInvariant] = list(invariants or [])
for inv_text in action.invariants:
    merged_invariants.append(
        PlanInvariant(text=inv_text, source=InvariantSource.ACTION)  # ← wrong!
    )

The InvariantSource enum in plan.py has four values: PLAN, ACTION, PROJECT, GLOBAL. The spec says the runtime chain should be three-tier (plan > project > global) because action invariants are promoted to plan-level at use time. The code maintains a four-tier chain by keeping InvariantSource.ACTION as a distinct tier.

This has two consequences:

  1. agents plan status <PLAN_ID> --format json shows "source": "action" for promoted invariants, which contradicts the spec's three-tier model.
  2. Any precedence resolution logic that treats ACTION as a separate tier (lower than PLAN but higher than PROJECT) produces incorrect results — the spec says action invariants should have the same precedence as plan-level invariants after promotion.

Steps to reproduce

  1. Create an action with invariants in its YAML config:
    invariants:
      - "All tests must pass"
    
  2. Use the action: agents plan use local/my-action my-project
  3. Check the plan: agents plan status <PLAN_ID> --format json
  4. Observe invariants[0].source == "action" instead of "plan"

Code locations

  • src/cleveragents/application/services/plan_lifecycle_service.pyuse_action() method, invariant merging section (~line 1200)
  • src/cleveragents/domain/models/core/plan.pyInvariantSource enum (~line 177)

Fix

Change InvariantSource.ACTION to InvariantSource.PLAN in the invariant-merging loop inside use_action():

for inv_text in action.invariants:
    merged_invariants.append(
        PlanInvariant(text=inv_text, source=InvariantSource.PLAN)  # ← promoted to plan-level
    )

Severity

Medium — The spec's three-tier invariant model is violated. The runtime sees a four-tier chain (plan > action > project > global) instead of the required three-tier chain (plan > project > global). Precedence resolution may produce incorrect results for plans that mix plan-level and action-derived invariants.

Subtasks

  • Confirm the spec's three-tier invariant precedence model in docs/specification.md (plan > project > global; action invariants promoted to plan-level at use time)
  • Locate the invariant-merging loop in PlanLifecycleService.use_action() (~line 1200 of plan_lifecycle_service.py)
  • Change source=InvariantSource.ACTION to source=InvariantSource.PLAN in the loop that appends action invariants to merged_invariants
  • Verify no other call sites in use_action() or related methods assign InvariantSource.ACTION to promoted invariants
  • Write a failing Behave scenario (TDD): after use_action(), all invariants sourced from the action have source == InvariantSource.PLAN
  • Write a Behave scenario confirming the plan's invariant list contains no entries with source == InvariantSource.ACTION after use_action()
  • Write a Behave scenario confirming that explicit plan-level invariants (passed via the invariants parameter) retain source == InvariantSource.PLAN
  • Run nox -e typecheck — confirm no type errors introduced
  • Run nox -e unit_tests — confirm all Behave scenarios pass
  • Run nox -e coverage_report — confirm coverage ≥ 97%
  • Run nox (all default sessions) — confirm clean

Definition of Done

  • PlanLifecycleService.use_action() sets source=InvariantSource.PLAN (not InvariantSource.ACTION) for all invariants sourced from the action
  • agents plan status <PLAN_ID> --format json shows "source": "plan" for all action-derived invariants after agents plan use
  • The runtime invariant precedence chain is three-tier (plan > project > global) as required by the spec
  • Behave scenarios cover: action invariants promoted to plan-level, no ACTION-sourced invariants remain after use_action(), explicit plan invariants unaffected
  • No regressions in existing use_action() or invariant-related tests
  • All nox stages pass
  • Coverage >= 97%

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: ca-new-issue-creator

## Metadata - **Branch**: `fix/plan-use-action-invariant-promotion` - **Commit Message**: `fix(plan): promote action invariants to plan-level in use_action() to enforce three-tier precedence chain` - **Milestone**: v3.5.0 - **Parent Epic**: #368 ## Bug Report **Feature Area**: Plan Lifecycle — `agents plan use` / Invariant Precedence ### What was tested `PlanLifecycleService.use_action()` in `src/cleveragents/application/services/plan_lifecycle_service.py` and the `InvariantSource` enum in `src/cleveragents/domain/models/core/plan.py`. ### Expected behavior (from spec) The specification glossary states: > **Invariant**: A natural-language constraint on plan execution scoped to global, project, action, or plan level. Action invariants are **promoted to plan-level** when the action is used, so the **runtime precedence chain is three-tier: plan > project > global**. When `agents plan use` is called, action-level invariants must be promoted to plan-level. Their `source` must be set to `InvariantSource.PLAN` (not `InvariantSource.ACTION`). The runtime system should then only see three tiers: plan, project, and global. ### Actual behavior (from code analysis) The invariant-merging section of `use_action()` in `src/cleveragents/application/services/plan_lifecycle_service.py` (around line 1200) keeps action invariants as `InvariantSource.ACTION`: ```python # Build merged invariants: plan > action > (project/global added later) merged_invariants: list[PlanInvariant] = list(invariants or []) for inv_text in action.invariants: merged_invariants.append( PlanInvariant(text=inv_text, source=InvariantSource.ACTION) # ← wrong! ) ``` The `InvariantSource` enum in `plan.py` has four values: `PLAN`, `ACTION`, `PROJECT`, `GLOBAL`. The spec says the runtime chain should be three-tier (plan > project > global) because action invariants are promoted to plan-level at use time. The code maintains a four-tier chain by keeping `InvariantSource.ACTION` as a distinct tier. This has two consequences: 1. `agents plan status <PLAN_ID> --format json` shows `"source": "action"` for promoted invariants, which contradicts the spec's three-tier model. 2. Any precedence resolution logic that treats `ACTION` as a separate tier (lower than `PLAN` but higher than `PROJECT`) produces incorrect results — the spec says action invariants should have the same precedence as plan-level invariants after promotion. ### Steps to reproduce 1. Create an action with invariants in its YAML config: ```yaml invariants: - "All tests must pass" ``` 2. Use the action: `agents plan use local/my-action my-project` 3. Check the plan: `agents plan status <PLAN_ID> --format json` 4. Observe `invariants[0].source == "action"` instead of `"plan"` ### Code locations - `src/cleveragents/application/services/plan_lifecycle_service.py` — `use_action()` method, invariant merging section (~line 1200) - `src/cleveragents/domain/models/core/plan.py` — `InvariantSource` enum (~line 177) ### Fix Change `InvariantSource.ACTION` to `InvariantSource.PLAN` in the invariant-merging loop inside `use_action()`: ```python for inv_text in action.invariants: merged_invariants.append( PlanInvariant(text=inv_text, source=InvariantSource.PLAN) # ← promoted to plan-level ) ``` ### Severity **Medium** — The spec's three-tier invariant model is violated. The runtime sees a four-tier chain (plan > action > project > global) instead of the required three-tier chain (plan > project > global). Precedence resolution may produce incorrect results for plans that mix plan-level and action-derived invariants. ## Subtasks - [ ] Confirm the spec's three-tier invariant precedence model in `docs/specification.md` (plan > project > global; action invariants promoted to plan-level at use time) - [ ] Locate the invariant-merging loop in `PlanLifecycleService.use_action()` (~line 1200 of `plan_lifecycle_service.py`) - [ ] Change `source=InvariantSource.ACTION` to `source=InvariantSource.PLAN` in the loop that appends action invariants to `merged_invariants` - [ ] Verify no other call sites in `use_action()` or related methods assign `InvariantSource.ACTION` to promoted invariants - [ ] Write a failing Behave scenario (TDD): after `use_action()`, all invariants sourced from the action have `source == InvariantSource.PLAN` - [ ] Write a Behave scenario confirming the plan's invariant list contains no entries with `source == InvariantSource.ACTION` after `use_action()` - [ ] Write a Behave scenario confirming that explicit plan-level invariants (passed via the `invariants` parameter) retain `source == InvariantSource.PLAN` - [ ] Run `nox -e typecheck` — confirm no type errors introduced - [ ] Run `nox -e unit_tests` — confirm all Behave scenarios pass - [ ] Run `nox -e coverage_report` — confirm coverage ≥ 97% - [ ] Run `nox` (all default sessions) — confirm clean ## Definition of Done - [ ] `PlanLifecycleService.use_action()` sets `source=InvariantSource.PLAN` (not `InvariantSource.ACTION`) for all invariants sourced from the action - [ ] `agents plan status <PLAN_ID> --format json` shows `"source": "plan"` for all action-derived invariants after `agents plan use` - [ ] The runtime invariant precedence chain is three-tier (plan > project > global) as required by the spec - [ ] Behave scenarios cover: action invariants promoted to plan-level, no `ACTION`-sourced invariants remain after `use_action()`, explicit plan invariants unaffected - [ ] No regressions in existing `use_action()` or invariant-related tests - [ ] All nox stages pass - [ ] Coverage >= 97% --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-new-issue-creator
freemo added this to the v3.5.0 milestone 2026-04-03 17:31:24 +00:00
Author
Owner

Issue triaged by project owner:

  • State: Verified
  • Priority: Medium — Action invariants stored as InvariantSource.ACTION instead of being promoted to InvariantSource.PLAN creates a four-tier chain instead of the spec-required three-tier. This breaks the invariant precedence model.
  • Milestone: v3.5.0
  • MoSCoW: Must Have — The invariant precedence chain is a core spec requirement. A four-tier chain instead of three-tier means invariant resolution produces incorrect results.
  • Parent Epic: #368 (Subplans & Parallelism)

Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: ca-project-owner

Issue triaged by project owner: - **State**: Verified - **Priority**: Medium — Action invariants stored as `InvariantSource.ACTION` instead of being promoted to `InvariantSource.PLAN` creates a four-tier chain instead of the spec-required three-tier. This breaks the invariant precedence model. - **Milestone**: v3.5.0 - **MoSCoW**: Must Have — The invariant precedence chain is a core spec requirement. A four-tier chain instead of three-tier means invariant resolution produces incorrect results. - **Parent Epic**: #368 (Subplans & Parallelism) --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: ca-project-owner
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Blocks
#368 Epic: Subplans & Parallelism
cleveragents/cleveragents-core
Reference
cleveragents/cleveragents-core#2392
No description provided.