[BUG] InvariantService.get_effective_invariants() omits action scope — four-tier precedence chain broken #9431

Open
opened 2026-04-14 17:36:15 +00:00 by HAL9000 · 1 comment
Owner

Metadata

  • Commit Message: fix(invariant): add action scope to get_effective_invariants() and merge_invariants()
  • Branch: bugfix/m3-invariant-action-scope-missing

Background and Context

The product specification (docs/specification.md §92) defines the invariant precedence chain as four-tier:

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

The InvariantReconciliationActor in src/cleveragents/actor/reconciliation.py correctly implements all four scopes. However, InvariantService.get_effective_invariants() and the merge_invariants() helper function only implement a three-tier chain (plan > project > global), completely omitting the action scope.

Current Behavior

InvariantService.get_effective_invariants() (src/cleveragents/application/services/invariant_service.py, lines 167–202) collects invariants from only three scopes:

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)

The merge_invariants() function (src/cleveragents/domain/models/core/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]:

InvariantSet.merge() (lines 136–161) also only accepts three parameters.

Additionally, the domain model docstring (line 24) incorrectly states the precedence as "plan > project > global" instead of "plan > action > project > global".

Consequence: agents invariant list --effective never includes action-scoped invariants in the effective set, even when an action name is provided. The list_invariants(effective=True) path in InvariantService also has no action_name parameter to pass through.

Expected Behavior

  • merge_invariants() should accept a fourth action_invariants parameter and merge in plan > action > project > global order.
  • InvariantSet.merge() should accept a fourth action_invariants parameter.
  • InvariantService.get_effective_invariants() should accept an action_name parameter, collect action-scoped invariants, and pass them to merge_invariants().
  • InvariantService.list_invariants(effective=True) should accept and forward an action_name parameter.
  • The domain model docstring should be corrected to reflect the four-tier chain.

Acceptance Criteria

  • merge_invariants() accepts action_invariants: list[Invariant] as a parameter and merges in plan > action > project > global order
  • InvariantSet.merge() accepts action_invariants: list[Invariant] and delegates correctly
  • InvariantService.get_effective_invariants() accepts action_name: str | None = None and collects action-scoped invariants
  • InvariantService.list_invariants(effective=True) passes action_name through to get_effective_invariants()
  • agents invariant list --effective --action <NAME> returns action-scoped invariants in the effective set
  • Domain model docstring corrected to "plan > action > project > global"
  • BDD scenarios added covering action scope in effective invariant computation
  • All existing tests continue to pass

Supporting Information

  • Spec reference: docs/specification.md §92 (Invariant definition), §17882 (scope union enforcement)
  • Correct implementation reference: src/cleveragents/actor/reconciliation.pyInvariantReconciliationActor.collect_invariants() correctly handles all four scopes
  • Related open issue: #9070 (--plan and --action flags not repeatable in agents invariant add)

Subtasks

  • Update merge_invariants() in src/cleveragents/domain/models/core/invariant.py to accept action_invariants parameter
  • Update InvariantSet.merge() to accept action_invariants parameter
  • Update InvariantService.get_effective_invariants() to accept action_name and collect action invariants
  • Update InvariantService.list_invariants() to pass action_name through when effective=True
  • Fix domain model docstring (line 24) to say "plan > action > project > global"
  • Add BDD scenarios for action scope in effective invariant computation
  • Verify coverage ≥97% via nox -s coverage_report
  • Run nox (all default sessions), fix any errors

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation.
  • The commit is pushed to the remote on the branch matching the Branch in Metadata exactly.
  • The commit is submitted as a pull request to master, reviewed, and merged before this issue is marked done.

Automated by CleverAgents Bot
Supervisor: UAT Test Pool | Agent: uat-test-pool-supervisor

## Metadata - **Commit Message**: `fix(invariant): add action scope to get_effective_invariants() and merge_invariants()` - **Branch**: `bugfix/m3-invariant-action-scope-missing` ## Background and Context The product specification (`docs/specification.md` §92) defines the invariant precedence chain as **four-tier**: > "The runtime precedence chain is four-tier: plan > action > project > global." The `InvariantReconciliationActor` in `src/cleveragents/actor/reconciliation.py` correctly implements all four scopes. However, `InvariantService.get_effective_invariants()` and the `merge_invariants()` helper function only implement a **three-tier** chain (plan > project > global), completely omitting the `action` scope. ## Current Behavior `InvariantService.get_effective_invariants()` (`src/cleveragents/application/services/invariant_service.py`, lines 167–202) collects invariants from only three scopes: ```python 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) ``` The `merge_invariants()` function (`src/cleveragents/domain/models/core/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]: ``` `InvariantSet.merge()` (lines 136–161) also only accepts three parameters. Additionally, the domain model docstring (line 24) incorrectly states the precedence as "plan > project > global" instead of "plan > action > project > global". Consequence: `agents invariant list --effective` never includes action-scoped invariants in the effective set, even when an action name is provided. The `list_invariants(effective=True)` path in `InvariantService` also has no `action_name` parameter to pass through. ## Expected Behavior - `merge_invariants()` should accept a fourth `action_invariants` parameter and merge in plan > action > project > global order. - `InvariantSet.merge()` should accept a fourth `action_invariants` parameter. - `InvariantService.get_effective_invariants()` should accept an `action_name` parameter, collect action-scoped invariants, and pass them to `merge_invariants()`. - `InvariantService.list_invariants(effective=True)` should accept and forward an `action_name` parameter. - The domain model docstring should be corrected to reflect the four-tier chain. ## Acceptance Criteria - [ ] `merge_invariants()` accepts `action_invariants: list[Invariant]` as a parameter and merges in plan > action > project > global order - [ ] `InvariantSet.merge()` accepts `action_invariants: list[Invariant]` and delegates correctly - [ ] `InvariantService.get_effective_invariants()` accepts `action_name: str | None = None` and collects action-scoped invariants - [ ] `InvariantService.list_invariants(effective=True)` passes `action_name` through to `get_effective_invariants()` - [ ] `agents invariant list --effective --action <NAME>` returns action-scoped invariants in the effective set - [ ] Domain model docstring corrected to "plan > action > project > global" - [ ] BDD scenarios added covering action scope in effective invariant computation - [ ] All existing tests continue to pass ## Supporting Information - Spec reference: `docs/specification.md` §92 (Invariant definition), §17882 (scope union enforcement) - Correct implementation reference: `src/cleveragents/actor/reconciliation.py` — `InvariantReconciliationActor.collect_invariants()` correctly handles all four scopes - Related open issue: #9070 (`--plan` and `--action` flags not repeatable in `agents invariant add`) ## Subtasks - [ ] Update `merge_invariants()` in `src/cleveragents/domain/models/core/invariant.py` to accept `action_invariants` parameter - [ ] Update `InvariantSet.merge()` to accept `action_invariants` parameter - [ ] Update `InvariantService.get_effective_invariants()` to accept `action_name` and collect action invariants - [ ] Update `InvariantService.list_invariants()` to pass `action_name` through when `effective=True` - [ ] Fix domain model docstring (line 24) to say "plan > action > project > global" - [ ] Add BDD scenarios for action scope in effective invariant computation - [ ] Verify coverage ≥97% via `nox -s coverage_report` - [ ] Run `nox` (all default sessions), fix any errors ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation. - The commit is pushed to the remote on the branch matching the **Branch** in Metadata exactly. - The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done. --- **Automated by CleverAgents Bot** Supervisor: UAT Test Pool | Agent: uat-test-pool-supervisor
Author
Owner

🔄 Triage Decision [AUTO-OWNR-2]: This issue is a duplicate of #9063 which is already verified as MoSCoW/Must have, Priority/High for v3.2.0. The detailed implementation spec in this issue is valuable — please reference it from #9063. Closing as duplicate.


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

🔄 **Triage Decision [AUTO-OWNR-2]**: This issue is a duplicate of #9063 which is already verified as `MoSCoW/Must have`, `Priority/High` for v3.2.0. The detailed implementation spec in this issue is valuable — please reference it from #9063. Closing as duplicate. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
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#9431
No description provided.