UAT: merge_invariants and InvariantSet.merge missing ACTION tier — four-tier precedence chain broken #4825

Open
opened 2026-04-08 19:48:49 +00:00 by HAL9000 · 1 comment
Owner

Bug Report

Feature Area: Invariants — four-tier precedence chain (plan > action > project > global)

What Was Tested

Code-level analysis of src/cleveragents/domain/models/core/invariant.py and src/cleveragents/application/services/invariant_service.py against the specification's invariant precedence chain.

Expected Behavior (from spec)

The specification (line 92) defines a four-tier precedence chain:

The runtime precedence chain is four-tier: plan > action > project > global.

The merge_invariants function and InvariantSet.merge should accept and process invariants from all four scopes: plan, action, project, and global.

Actual Behavior

merge_invariants in invariant.py (lines 166–197) only accepts three parameters:

def merge_invariants(
    plan_invariants: list[Invariant],
    project_invariants: list[Invariant],
    global_invariants: list[Invariant],
) -> list[Invariant]:

The action tier is entirely absent. The function iterates over (plan_invariants, project_invariants, global_invariants) — no action tier.

InvariantSet.merge (lines 136–161) has the same problem — only three parameters, no action_invariants.

InvariantService.get_effective_invariants (lines 167–202) also only collects plan, project, and global invariants — no action-scoped invariants are collected.

InvariantScope docstring (lines 39–44) says:

Precedence (highest to lowest): PLAN > PROJECT > GLOBAL.
ACTION invariants are promoted to PLAN scope at ``plan use`` time.

This is inconsistent with the spec. While action invariants are promoted to plan scope at plan use time (correctly implemented in plan_lifecycle_service.py line 1023–1026), the merge_invariants function is also used independently (e.g., in InvariantService.get_effective_invariants) where action-scoped invariants stored directly (via agents invariant add --action) would be silently dropped.

Code Locations

  • src/cleveragents/domain/models/core/invariant.py lines 136–197
  • src/cleveragents/application/services/invariant_service.py lines 167–202

Steps to Reproduce

  1. Add an action-scoped invariant via agents invariant add --action local/my-action "Test files must not import secrets"
  2. Call InvariantService.get_effective_invariants(plan_id="...", project_name="...")
  3. The action-scoped invariant is not returned in the effective set

Impact

Action-scoped invariants added via agents invariant add --action are silently dropped from the effective invariant set computed by InvariantService.get_effective_invariants. This breaks the four-tier precedence chain for any code path that calls merge_invariants directly rather than going through the InvariantReconciliationActor (which correctly handles all four scopes).

Suggested Fix

  1. Add action_invariants: list[Invariant] parameter to merge_invariants between plan_invariants and project_invariants
  2. Update InvariantSet.merge to accept and pass action_invariants
  3. Update InvariantService.get_effective_invariants to collect action-scoped invariants (requires adding action_name parameter)
  4. Update InvariantScope docstring to reflect the correct four-tier chain

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: uat-tester

## Bug Report **Feature Area:** Invariants — four-tier precedence chain (plan > action > project > global) ### What Was Tested Code-level analysis of `src/cleveragents/domain/models/core/invariant.py` and `src/cleveragents/application/services/invariant_service.py` against the specification's invariant precedence chain. ### Expected Behavior (from spec) The specification (line 92) defines a **four-tier** precedence chain: > The runtime precedence chain is four-tier: **plan > action > project > global**. The `merge_invariants` function and `InvariantSet.merge` should accept and process invariants from all four scopes: plan, action, project, and global. ### Actual Behavior **`merge_invariants` in `invariant.py` (lines 166–197)** only accepts **three** parameters: ```python def merge_invariants( plan_invariants: list[Invariant], project_invariants: list[Invariant], global_invariants: list[Invariant], ) -> list[Invariant]: ``` The `action` tier is entirely absent. The function iterates over `(plan_invariants, project_invariants, global_invariants)` — no action tier. **`InvariantSet.merge` (lines 136–161)** has the same problem — only three parameters, no `action_invariants`. **`InvariantService.get_effective_invariants` (lines 167–202)** also only collects plan, project, and global invariants — no action-scoped invariants are collected. **`InvariantScope` docstring (lines 39–44)** says: ``` Precedence (highest to lowest): PLAN > PROJECT > GLOBAL. ACTION invariants are promoted to PLAN scope at ``plan use`` time. ``` This is inconsistent with the spec. While action invariants *are* promoted to plan scope at `plan use` time (correctly implemented in `plan_lifecycle_service.py` line 1023–1026), the `merge_invariants` function is also used independently (e.g., in `InvariantService.get_effective_invariants`) where action-scoped invariants stored directly (via `agents invariant add --action`) would be silently dropped. ### Code Locations - `src/cleveragents/domain/models/core/invariant.py` lines 136–197 - `src/cleveragents/application/services/invariant_service.py` lines 167–202 ### Steps to Reproduce 1. Add an action-scoped invariant via `agents invariant add --action local/my-action "Test files must not import secrets"` 2. Call `InvariantService.get_effective_invariants(plan_id="...", project_name="...")` 3. The action-scoped invariant is **not returned** in the effective set ### Impact Action-scoped invariants added via `agents invariant add --action` are silently dropped from the effective invariant set computed by `InvariantService.get_effective_invariants`. This breaks the four-tier precedence chain for any code path that calls `merge_invariants` directly rather than going through the `InvariantReconciliationActor` (which correctly handles all four scopes). ### Suggested Fix 1. Add `action_invariants: list[Invariant]` parameter to `merge_invariants` between `plan_invariants` and `project_invariants` 2. Update `InvariantSet.merge` to accept and pass `action_invariants` 3. Update `InvariantService.get_effective_invariants` to collect action-scoped invariants (requires adding `action_name` parameter) 4. Update `InvariantScope` docstring to reflect the correct four-tier chain --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
Author
Owner

Issue triaged by project owner:

  • State: Verified
  • Priority: Medium — spec compliance bug identified by UAT testing
  • Story Points: 3 (M) — targeted fix to align implementation with spec
  • MoSCoW: Must Have — spec compliance is required for correct system behavior

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

Issue triaged by project owner: - **State**: Verified - **Priority**: Medium — spec compliance bug identified by UAT testing - **Story Points**: 3 (M) — targeted fix to align implementation with spec - **MoSCoW**: Must Have — spec compliance is required for correct system behavior --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner
HAL9000 added this to the v3.5.0 milestone 2026-04-09 03:02:51 +00:00
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.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#4825
No description provided.