UAT: InvariantService.get_effective_invariants() omits ACTION-scoped invariants — inconsistent with InvariantReconciliationActor 4-tier collection #6099

Open
opened 2026-04-09 14:37:15 +00:00 by HAL9000 · 0 comments
Owner

Bug Report

Feature Area: Invariant Management
Component: InvariantService.get_effective_invariants()
Severity: Non-critical (Priority/Backlog)


What Was Tested

Code analysis comparing src/cleveragents/application/services/invariant_service.py get_effective_invariants() against src/cleveragents/actor/reconciliation.py collect_invariants().


Expected Behavior (from spec ADR-016 and docs/modules/invariant-reconciliation.md)

The reconciliation algorithm uses a 4-tier precedence chain: plan > action > project > global.

InvariantService.get_effective_invariants() is used by agents invariant list --effective to show the merged effective set. It should be consistent with what the InvariantReconciliationActor actually uses during strategize.


Actual Behavior (from code analysis)

InvariantService.get_effective_invariants() (3-tier, missing ACTION):

# src/cleveragents/application/services/invariant_service.py
plan_invs = [inv for inv in active if inv.scope == InvariantScope.PLAN ...]
project_invs = [inv for inv in active if inv.scope == InvariantScope.PROJECT ...]
global_invs = [inv for inv in active if inv.scope == InvariantScope.GLOBAL]
return merge_invariants(plan_invs, project_invs, global_invs)  # ← only 3 tiers

InvariantReconciliationActor.collect_invariants() (4-tier, includes ACTION):

# src/cleveragents/actor/reconciliation.py
return ScopeInvariants(
    global_invariants=svc.list_invariants(scope=InvariantScope.GLOBAL),
    project_invariants=svc.list_invariants(scope=InvariantScope.PROJECT, source_name=project_name) if project_name else [],
    action_invariants=svc.list_invariants(scope=InvariantScope.ACTION, source_name=action_name) if action_name else [],  # ← ACTION included
    plan_invariants=svc.list_invariants(scope=InvariantScope.PLAN, source_name=plan_id) if plan_id else [],
)

Consequence: agents invariant list --effective --project myapp will NOT show action-scoped invariants that ARE present in the InvariantService, even though the InvariantReconciliationActor WILL include them during strategize. The --effective output is therefore an inaccurate preview of what will actually be enforced.


Code Locations

  • src/cleveragents/application/services/invariant_service.py, get_effective_invariants() — missing ACTION tier
  • src/cleveragents/actor/reconciliation.py, collect_invariants() — correctly uses 4 tiers
  • src/cleveragents/domain/models/core/invariant.py, merge_invariants() — only accepts 3 tiers (plan, project, global)

Note on Design Intent

The spec says "Action-level invariants are promoted to plan scope when plan use is called." This suggests action-scoped invariants in InvariantService are a transitional state. However:

  1. The InvariantReconciliationActor still collects them directly (not just promoted copies)
  2. agents invariant add --action my-action "..." creates ACTION-scoped invariants that persist in the service
  3. agents invariant list --effective should accurately reflect what will be enforced

Fix Required

Either:

  1. Add ACTION tier to get_effective_invariants() and merge_invariants() to match the reconciliation actor's 4-tier collection, OR
  2. Document clearly that --effective shows the 3-tier view (without action scope) and update the spec accordingly

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

## Bug Report **Feature Area:** Invariant Management **Component:** `InvariantService.get_effective_invariants()` **Severity:** Non-critical (Priority/Backlog) --- ## What Was Tested Code analysis comparing `src/cleveragents/application/services/invariant_service.py` `get_effective_invariants()` against `src/cleveragents/actor/reconciliation.py` `collect_invariants()`. --- ## Expected Behavior (from spec ADR-016 and `docs/modules/invariant-reconciliation.md`) The reconciliation algorithm uses a **4-tier precedence chain**: `plan > action > project > global`. `InvariantService.get_effective_invariants()` is used by `agents invariant list --effective` to show the merged effective set. It should be consistent with what the `InvariantReconciliationActor` actually uses during strategize. --- ## Actual Behavior (from code analysis) **`InvariantService.get_effective_invariants()`** (3-tier, missing ACTION): ```python # src/cleveragents/application/services/invariant_service.py plan_invs = [inv for inv in active if inv.scope == InvariantScope.PLAN ...] project_invs = [inv for inv in active if inv.scope == InvariantScope.PROJECT ...] global_invs = [inv for inv in active if inv.scope == InvariantScope.GLOBAL] return merge_invariants(plan_invs, project_invs, global_invs) # ← only 3 tiers ``` **`InvariantReconciliationActor.collect_invariants()`** (4-tier, includes ACTION): ```python # src/cleveragents/actor/reconciliation.py return ScopeInvariants( global_invariants=svc.list_invariants(scope=InvariantScope.GLOBAL), project_invariants=svc.list_invariants(scope=InvariantScope.PROJECT, source_name=project_name) if project_name else [], action_invariants=svc.list_invariants(scope=InvariantScope.ACTION, source_name=action_name) if action_name else [], # ← ACTION included plan_invariants=svc.list_invariants(scope=InvariantScope.PLAN, source_name=plan_id) if plan_id else [], ) ``` **Consequence**: `agents invariant list --effective --project myapp` will NOT show action-scoped invariants that ARE present in the `InvariantService`, even though the `InvariantReconciliationActor` WILL include them during strategize. The `--effective` output is therefore an inaccurate preview of what will actually be enforced. --- ## Code Locations - `src/cleveragents/application/services/invariant_service.py`, `get_effective_invariants()` — missing ACTION tier - `src/cleveragents/actor/reconciliation.py`, `collect_invariants()` — correctly uses 4 tiers - `src/cleveragents/domain/models/core/invariant.py`, `merge_invariants()` — only accepts 3 tiers (plan, project, global) --- ## Note on Design Intent The spec says "Action-level invariants are promoted to plan scope when `plan use` is called." This suggests action-scoped invariants in `InvariantService` are a transitional state. However: 1. The `InvariantReconciliationActor` still collects them directly (not just promoted copies) 2. `agents invariant add --action my-action "..."` creates ACTION-scoped invariants that persist in the service 3. `agents invariant list --effective` should accurately reflect what will be enforced --- ## Fix Required Either: 1. Add ACTION tier to `get_effective_invariants()` and `merge_invariants()` to match the reconciliation actor's 4-tier collection, OR 2. Document clearly that `--effective` shows the 3-tier view (without action scope) and update the spec accordingly --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
HAL9000 added this to the v3.2.0 milestone 2026-04-09 21:19:18 +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#6099
No description provided.