UAT: merge_invariants / InvariantSet.merge / get_effective_invariants drop ACTION-scope invariants — four-tier precedence chain plan > action > project > global not implemented #5100

Closed
opened 2026-04-09 01:01:30 +00:00 by HAL9000 · 1 comment
Owner

Bug Report

Feature Area: Validation and Invariants — Invariant Precedence Chain
Tested By: UAT worker (uat-pool-1), feature area: Validation and Invariants
Severity: Critical — action-scope invariants are silently discarded during effective invariant computation


What Was Tested

Code-level analysis of the invariant merge/effective-set computation in:

  • src/cleveragents/domain/models/core/invariant.py
  • src/cleveragents/application/services/invariant_service.py

Expected Behavior (from spec)

The spec defines a four-tier precedence chain (§Invariant System, line 19730):

The full precedence chain (highest to lowest): plan > action > project > global.

The spec glossary (line 92) also states:

A natural-language constraint on plan execution scoped to global, project, action, or plan level. The runtime precedence chain is four-tier: plan > action > project > global.

The InvariantSet.merge method and merge_invariants function should accept and process invariants from all four scopes.

Actual Behavior

merge_invariants function (invariant.py, line 166)

The function signature only accepts three parameters — plan, project, global — completely omitting the ACTION scope:

def merge_invariants(
    plan_invariants: list[Invariant],
    project_invariants: list[Invariant],
    global_invariants: list[Invariant],   # ← ACTION scope missing
) -> list[Invariant]:
    """Merge invariants implementing plan > project > global precedence."""
    # ...
    for inv_list in (plan_invariants, project_invariants, global_invariants):
        # ACTION invariants never processed

The module docstring (line 7) also incorrectly documents the precedence as:

plan > project > global

InvariantSet.merge class method (invariant.py, line 137)

Same issue — only three parameters:

@classmethod
def merge(
    cls,
    plan_invariants: list[Invariant],
    project_invariants: list[Invariant],
    global_invariants: list[Invariant],   # ← ACTION scope missing
) -> InvariantSet:

InvariantService.get_effective_invariants (invariant_service.py, line 167)

The method only collects plan, project, and global invariants — action-scope invariants are never collected:

def get_effective_invariants(
    self,
    plan_id: str | None = None,
    project_name: str | None = None,
) -> list[Invariant]:
    # ...
    plan_invs = [...]   # plan scope ✓
    project_invs = [...]  # project scope ✓
    global_invs = [...]   # global scope ✓
    # action_invs = [...]  ← MISSING

    return merge_invariants(plan_invs, project_invs, global_invs)
    # action_invs never passed

Note: The InvariantReconciliationActor in actor/reconciliation.py correctly handles all four scopes via ScopeInvariants and reconcile_invariants. However, the lower-level merge_invariants / InvariantSet.merge / get_effective_invariants functions used by the service layer are broken.

Impact

  1. agents invariant list --effective: When a user runs agents invariant list --plan <ID> --effective, action-scope invariants are silently dropped from the result. The user sees an incomplete effective invariant set.
  2. InvariantService.get_effective_invariants: Any code calling this method (e.g., for plan validation) will miss action-scope invariants.
  3. InvariantSet.merge: Any code using the InvariantSet.merge class method will silently discard action-scope invariants.
  4. Spec compliance: The spec explicitly requires the four-tier chain. The three-tier implementation is a spec deviation.

Code Locations

  • src/cleveragents/domain/models/core/invariant.py:
    • Line 7: Module docstring incorrectly states plan > project > global
    • Line 24: InvariantSet docstring incorrectly states plan > project > global
    • Lines 137–161: InvariantSet.merge — missing action_invariants parameter
    • Lines 166–197: merge_invariants — missing action_invariants parameter
  • src/cleveragents/application/services/invariant_service.py:
    • Lines 167–202: get_effective_invariants — missing action scope collection and merge

Steps to Reproduce

from cleveragents.domain.models.core.invariant import Invariant, InvariantScope, merge_invariants

action_inv = Invariant(
    text="Test files must not import production secrets",
    scope=InvariantScope.ACTION,
    source_name="local/code-coverage"
)

# merge_invariants only accepts 3 args — action invariants cannot be passed
result = merge_invariants([], [], [])  # action_inv is silently dropped
assert action_inv not in result  # True — action invariant is lost

Fix Required

  1. Add action_invariants: list[Invariant] parameter to merge_invariants and InvariantSet.merge, inserting it between plan_invariants and project_invariants (to respect plan > action > project > global order).
  2. Update InvariantService.get_effective_invariants to collect action-scope invariants and pass them to merge_invariants.
  3. Update module/class docstrings to reflect the correct four-tier chain.

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

## Bug Report **Feature Area**: Validation and Invariants — Invariant Precedence Chain **Tested By**: UAT worker (uat-pool-1), feature area: Validation and Invariants **Severity**: Critical — action-scope invariants are silently discarded during effective invariant computation --- ## What Was Tested Code-level analysis of the invariant merge/effective-set computation in: - `src/cleveragents/domain/models/core/invariant.py` - `src/cleveragents/application/services/invariant_service.py` ## Expected Behavior (from spec) The spec defines a **four-tier** precedence chain (§Invariant System, line 19730): > The full precedence chain (highest to lowest): `plan > action > project > global`. The spec glossary (line 92) also states: > A natural-language constraint on plan execution scoped to global, project, action, or plan level. The runtime precedence chain is four-tier: **plan > action > project > global**. The `InvariantSet.merge` method and `merge_invariants` function should accept and process invariants from all four scopes. ## Actual Behavior ### `merge_invariants` function (`invariant.py`, line 166) The function signature only accepts **three** parameters — plan, project, global — completely omitting the ACTION scope: ```python def merge_invariants( plan_invariants: list[Invariant], project_invariants: list[Invariant], global_invariants: list[Invariant], # ← ACTION scope missing ) -> list[Invariant]: """Merge invariants implementing plan > project > global precedence.""" # ... for inv_list in (plan_invariants, project_invariants, global_invariants): # ACTION invariants never processed ``` The module docstring (line 7) also incorrectly documents the precedence as: ``` plan > project > global ``` ### `InvariantSet.merge` class method (`invariant.py`, line 137) Same issue — only three parameters: ```python @classmethod def merge( cls, plan_invariants: list[Invariant], project_invariants: list[Invariant], global_invariants: list[Invariant], # ← ACTION scope missing ) -> InvariantSet: ``` ### `InvariantService.get_effective_invariants` (`invariant_service.py`, line 167) The method only collects plan, project, and global invariants — action-scope invariants are never collected: ```python def get_effective_invariants( self, plan_id: str | None = None, project_name: str | None = None, ) -> list[Invariant]: # ... plan_invs = [...] # plan scope ✓ project_invs = [...] # project scope ✓ global_invs = [...] # global scope ✓ # action_invs = [...] ← MISSING return merge_invariants(plan_invs, project_invs, global_invs) # action_invs never passed ``` Note: The `InvariantReconciliationActor` in `actor/reconciliation.py` correctly handles all four scopes via `ScopeInvariants` and `reconcile_invariants`. However, the lower-level `merge_invariants` / `InvariantSet.merge` / `get_effective_invariants` functions used by the service layer are broken. ## Impact 1. **`agents invariant list --effective`**: When a user runs `agents invariant list --plan <ID> --effective`, action-scope invariants are silently dropped from the result. The user sees an incomplete effective invariant set. 2. **`InvariantService.get_effective_invariants`**: Any code calling this method (e.g., for plan validation) will miss action-scope invariants. 3. **`InvariantSet.merge`**: Any code using the `InvariantSet.merge` class method will silently discard action-scope invariants. 4. **Spec compliance**: The spec explicitly requires the four-tier chain. The three-tier implementation is a spec deviation. ## Code Locations - `src/cleveragents/domain/models/core/invariant.py`: - Line 7: Module docstring incorrectly states `plan > project > global` - Line 24: `InvariantSet` docstring incorrectly states `plan > project > global` - Lines 137–161: `InvariantSet.merge` — missing `action_invariants` parameter - Lines 166–197: `merge_invariants` — missing `action_invariants` parameter - `src/cleveragents/application/services/invariant_service.py`: - Lines 167–202: `get_effective_invariants` — missing action scope collection and merge ## Steps to Reproduce ```python from cleveragents.domain.models.core.invariant import Invariant, InvariantScope, merge_invariants action_inv = Invariant( text="Test files must not import production secrets", scope=InvariantScope.ACTION, source_name="local/code-coverage" ) # merge_invariants only accepts 3 args — action invariants cannot be passed result = merge_invariants([], [], []) # action_inv is silently dropped assert action_inv not in result # True — action invariant is lost ``` ## Fix Required 1. Add `action_invariants: list[Invariant]` parameter to `merge_invariants` and `InvariantSet.merge`, inserting it between `plan_invariants` and `project_invariants` (to respect `plan > action > project > global` order). 2. Update `InvariantService.get_effective_invariants` to collect action-scope invariants and pass them to `merge_invariants`. 3. Update module/class docstrings to reflect the correct four-tier chain. --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
Author
Owner

Closing as duplicate of #4825 — both issues report the same problem: merge_invariants and InvariantSet.merge missing ACTION tier in the four-tier precedence chain.


Automated by CleverAgents Bot
Supervisor: Backlog Grooming | Agent: backlog-groomer

Closing as duplicate of #4825 — both issues report the same problem: `merge_invariants` and `InvariantSet.merge` missing ACTION tier in the four-tier precedence chain. --- **Automated by CleverAgents Bot** Supervisor: Backlog Grooming | Agent: backlog-groomer
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.

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