UAT: InvariantService.get_effective_invariants() silently drops action-scoped invariants — missing action_name parameter #3128

Open
opened 2026-04-05 06:37:51 +00:00 by freemo · 2 comments
Owner

Metadata

  • Branch: fix/invariant-service-action-scope-effective
  • Commit Message: fix(invariant): add action_name param to get_effective_invariants() — include action-scoped invariants in 4-tier merge
  • Milestone: v3.2.0
  • Parent Epic: #394

Bug Description

Code location: src/cleveragents/application/services/invariant_service.py, get_effective_invariants() method (approximately lines 120–145)

What was tested: Code-level analysis of src/cleveragents/application/services/invariant_service.py

Expected Behavior (from spec/ADR-016)

The get_effective_invariants() method should collect invariants from all four scopes (global, project, action, plan) and merge them using the 4-tier precedence chain: plan > action > project > global. Action-scoped invariants must be included in the effective set.

Actual Behavior

The get_effective_invariants() method only accepts plan_id and project_name parameters. It has no action_name parameter. Action-scoped invariants are completely excluded from the effective invariant set:

def get_effective_invariants(
    self,
    plan_id: str | None = None,
    project_name: str | None = None,  # ← no action_name parameter!
) -> list[Invariant]:
    active = [inv for inv in self._invariants.values() if inv.active]

    plan_invs = [...]  # plan-scoped
    project_invs = [...]  # project-scoped
    global_invs = [...]  # global-scoped
    # ← action-scoped invariants are NEVER collected!

    return merge_invariants(plan_invs, project_invs, global_invs)  # 3-tier only

Impact

  • Any code calling get_effective_invariants() (including the agents invariant list --effective CLI command) will silently miss action-scoped invariants
  • The list_invariants(effective=True) path in the service also calls get_effective_invariants(), so the --effective flag in the CLI is broken for action-scoped invariants
  • This is a silent data loss bug — no error is raised, action invariants are simply not returned

Steps to Reproduce

  1. Add an action-scoped invariant: service.add_invariant(text="Do not modify public API", scope=InvariantScope.ACTION, source_name="deploy-action")
  2. Call service.get_effective_invariants(plan_id="some-plan")
  3. Observe that the action-scoped invariant is NOT in the returned list
  4. Expected: action-scoped invariant should be included

Relationship to #3066

This is related to but distinct from issue #3066 (InvariantSet.merge() missing action parameter). Even if merge_invariants() were fixed to accept 4 parameters, get_effective_invariants() would still silently drop action-scoped invariants because it never collects them. Both issues must be fixed together for the 4-tier precedence chain to work end-to-end.

Spec Reference

ADR-016 (Invariant System), Constraints section: "Invariant precedence (plan > action > project > global) is fixed and cannot be overridden." The effective set must include all four scopes.

Subtasks

  • Add action_name: str | None = None parameter to get_effective_invariants() signature
  • Implement collection of action-scoped invariants filtered by action_name within get_effective_invariants()
  • Update the call to merge_invariants() to pass all 4 tiers (depends on #3066 being fixed first)
  • Update list_invariants(effective=True) path to accept and pass action_name context through to get_effective_invariants()
  • Update any CLI command handlers that call list_invariants(effective=True) to pass action_name where available
  • Add BDD scenarios (Behave) for action-scoped effective invariant computation
  • Run nox -e unit_tests — all tests pass
  • Run nox -e typecheck — type checking passes
  • Run nox -e coverage_report — coverage >= 97%
  • Run nox (all default sessions) — no errors

Definition of Done

This issue is complete when:

  • get_effective_invariants() signature updated to accept action_name: str | None = None
  • Method collects action-scoped invariants filtered by action_name
  • Method calls merge_invariants() with all 4 tiers (after #3066 is fixed)
  • list_invariants(effective=True) path updated to pass action context
  • BDD scenarios added for action-scoped effective invariant computation
  • 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
  • All nox stages pass
  • Coverage >= 97%

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

## Metadata - **Branch**: `fix/invariant-service-action-scope-effective` - **Commit Message**: `fix(invariant): add action_name param to get_effective_invariants() — include action-scoped invariants in 4-tier merge` - **Milestone**: v3.2.0 - **Parent Epic**: #394 ## Bug Description **Code location**: `src/cleveragents/application/services/invariant_service.py`, `get_effective_invariants()` method (approximately lines 120–145) **What was tested**: Code-level analysis of `src/cleveragents/application/services/invariant_service.py` ### Expected Behavior (from spec/ADR-016) The `get_effective_invariants()` method should collect invariants from all four scopes (global, project, action, plan) and merge them using the 4-tier precedence chain: `plan > action > project > global`. Action-scoped invariants must be included in the effective set. ### Actual Behavior The `get_effective_invariants()` method only accepts `plan_id` and `project_name` parameters. It has no `action_name` parameter. Action-scoped invariants are completely excluded from the effective invariant set: ```python def get_effective_invariants( self, plan_id: str | None = None, project_name: str | None = None, # ← no action_name parameter! ) -> list[Invariant]: active = [inv for inv in self._invariants.values() if inv.active] plan_invs = [...] # plan-scoped project_invs = [...] # project-scoped global_invs = [...] # global-scoped # ← action-scoped invariants are NEVER collected! return merge_invariants(plan_invs, project_invs, global_invs) # 3-tier only ``` ### Impact - Any code calling `get_effective_invariants()` (including the `agents invariant list --effective` CLI command) will silently miss action-scoped invariants - The `list_invariants(effective=True)` path in the service also calls `get_effective_invariants()`, so the `--effective` flag in the CLI is broken for action-scoped invariants - This is a **silent data loss bug** — no error is raised, action invariants are simply not returned ### Steps to Reproduce 1. Add an action-scoped invariant: `service.add_invariant(text="Do not modify public API", scope=InvariantScope.ACTION, source_name="deploy-action")` 2. Call `service.get_effective_invariants(plan_id="some-plan")` 3. Observe that the action-scoped invariant is **NOT** in the returned list 4. Expected: action-scoped invariant should be included ### Relationship to #3066 This is related to but distinct from issue #3066 (`InvariantSet.merge()` missing action parameter). Even if `merge_invariants()` were fixed to accept 4 parameters, `get_effective_invariants()` would still silently drop action-scoped invariants because it never collects them. Both issues must be fixed together for the 4-tier precedence chain to work end-to-end. ### Spec Reference ADR-016 (Invariant System), Constraints section: *"Invariant precedence (plan > action > project > global) is fixed and cannot be overridden."* The effective set must include all four scopes. ## Subtasks - [ ] Add `action_name: str | None = None` parameter to `get_effective_invariants()` signature - [ ] Implement collection of action-scoped invariants filtered by `action_name` within `get_effective_invariants()` - [ ] Update the call to `merge_invariants()` to pass all 4 tiers (depends on #3066 being fixed first) - [ ] Update `list_invariants(effective=True)` path to accept and pass `action_name` context through to `get_effective_invariants()` - [ ] Update any CLI command handlers that call `list_invariants(effective=True)` to pass `action_name` where available - [ ] Add BDD scenarios (Behave) for action-scoped effective invariant computation - [ ] Run `nox -e unit_tests` — all tests pass - [ ] Run `nox -e typecheck` — type checking passes - [ ] Run `nox -e coverage_report` — coverage >= 97% - [ ] Run `nox` (all default sessions) — no errors ## Definition of Done This issue is complete when: - [ ] `get_effective_invariants()` signature updated to accept `action_name: str | None = None` - [ ] Method collects action-scoped invariants filtered by `action_name` - [ ] Method calls `merge_invariants()` with all 4 tiers (after #3066 is fixed) - [ ] `list_invariants(effective=True)` path updated to pass action context - [ ] BDD scenarios added for action-scoped effective invariant computation - [ ] 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 - All nox stages pass - Coverage >= 97% --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-uat-tester
freemo added this to the v3.2.0 milestone 2026-04-05 06:39:37 +00:00
Author
Owner

Label compliance fix applied:

  • Removed conflicting label: State/Verified (was accidentally added)
  • Kept: State/In Progress
  • Reason: Issue already had State/In Progress and Priority/Critical. The groomer accidentally added State/Verified — this has been corrected.

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

Label compliance fix applied: - Removed conflicting label: `State/Verified` (was accidentally added) - Kept: `State/In Progress` - Reason: Issue already had `State/In Progress` and `Priority/Critical`. The groomer accidentally added `State/Verified` — this has been corrected. --- **Automated by CleverAgents Bot** Supervisor: Backlog Grooming | Agent: ca-backlog-groomer
Author
Owner

PR #3329 created on branch fix/invariant-service-action-scope-effective. PR review and merge handled by continuous review stream.

Implementation summary:

  • Added action_name: str | None = None parameter to get_effective_invariants()
  • Implemented action-scoped invariant collection filtered by action_name
  • Updated merge_invariants() and InvariantSet.merge() to accept all 4 tiers (plan > action > project > global)
  • Updated list_invariants(effective=True) to pass action_name context through
  • Added 14 BDD scenarios in features/invariant_action_scope_effective.feature
  • Updated benchmarks, robot helper, and existing BDD step definitions for 4-parameter merge signature
  • Pyright: 0 errors, 0 warnings on all modified src/ files

Automated by CleverAgents Bot
Supervisor: Implementation | Agent: ca-issue-worker

PR #3329 created on branch `fix/invariant-service-action-scope-effective`. PR review and merge handled by continuous review stream. **Implementation summary:** - Added `action_name: str | None = None` parameter to `get_effective_invariants()` - Implemented action-scoped invariant collection filtered by `action_name` - Updated `merge_invariants()` and `InvariantSet.merge()` to accept all 4 tiers (plan > action > project > global) - Updated `list_invariants(effective=True)` to pass `action_name` context through - Added 14 BDD scenarios in `features/invariant_action_scope_effective.feature` - Updated benchmarks, robot helper, and existing BDD step definitions for 4-parameter merge signature - Pyright: 0 errors, 0 warnings on all modified `src/` files --- **Automated by CleverAgents Bot** Supervisor: Implementation | Agent: ca-issue-worker
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
#394 Epic: Decision Framework
cleveragents/cleveragents-core
Reference
cleveragents/cleveragents-core#3128
No description provided.