feat(plan): enforce invariants during Strategize phase via Invariant Reconciliation Actor #1154

Closed
brent.edwards wants to merge 2 commits from feature/m3-invariant-enforcement-strategize into master
Member
No description provided.
feat(plan): enforce invariants during Strategize phase via Invariant Reconciliation Actor
Some checks failed
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Failing after 26s
CI / lint (pull_request) Successful in 3m45s
CI / quality (pull_request) Successful in 4m7s
CI / typecheck (pull_request) Successful in 4m22s
CI / security (pull_request) Successful in 4m30s
CI / unit_tests (pull_request) Successful in 6m12s
CI / docker (pull_request) Successful in 1m9s
CI / integration_tests (pull_request) Successful in 7m37s
CI / e2e_tests (pull_request) Successful in 10m2s
CI / coverage (pull_request) Successful in 11m20s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-regression (pull_request) Successful in 52m55s
1f298948d1
Wire the Invariant Reconciliation Actor into PlanLifecycleService.start_strategize()
so that invariants from all scopes (global, project, action, plan) are automatically
collected, reconciled using plan > project > global precedence, and recorded as
invariant_enforced decisions in the plan's decision tree.

Key changes:
- Added _enforce_invariants() method to PlanLifecycleService that creates an
  InvariantReconciliationActor, registers plan-level invariants from the Plan
  model into the InvariantService, and invokes the actor to reconcile and record
  invariant_enforced decisions.
- Added invariant_service parameter to PlanLifecycleService.__init__() following
  the existing optional dependency pattern (None = silently skip).
- Registered InvariantService as a Singleton in the DI container and wired it
  into PlanLifecycleService.
- Added 6 Behave BDD scenarios covering basic enforcement, zero-invariant case,
  missing service graceful degradation, multi-scope reconciliation, de-duplication,
  and failure resilience.
- Added 4 Robot Framework integration tests for the invariant enforcement lifecycle.
- Updated CHANGELOG.md.

ISSUES CLOSED: #843
brent.edwards added this to the v3.2.0 milestone 2026-03-24 23:40:58 +00:00
Merge remote-tracking branch 'origin/master' into feature/m3-invariant-enforcement-strategize
All checks were successful
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 16s
CI / lint (pull_request) Successful in 3m24s
CI / typecheck (pull_request) Successful in 3m48s
CI / quality (pull_request) Successful in 3m42s
CI / security (pull_request) Successful in 3m56s
CI / integration_tests (pull_request) Successful in 5m57s
CI / unit_tests (pull_request) Successful in 7m16s
CI / docker (pull_request) Successful in 1m4s
CI / e2e_tests (pull_request) Successful in 9m13s
CI / coverage (pull_request) Successful in 10m12s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-regression (pull_request) Successful in 1h1m46s
54f55e4623
# Conflicts:
#	CHANGELOG.md
#	src/cleveragents/application/container.py
Owner

Code Review: This PR appears to be already merged

The branch feature/m3-invariants is fully contained within origin/master — the three-dot diff (origin/master...origin/feature/m3-invariants) is empty (zero new changes). Every commit on this branch already exists in master.

This PR should be closed as already-merged.

For the record, the code quality of the invariant subsystem is good:

  • Well-structured Pydantic models with field validators
  • Clean merge precedence algorithm (plan → project → global, case-insensitive dedup)
  • Full CLI implementation with add, list, remove commands
  • 36 BDD scenarios, 9 Robot tests, 6 ASV benchmark suites
  • Proper error handling and argument validation

Additional notes:

  • PR has no description/body — for a feature of this size, a description explaining design would be valuable
  • PR title uses feat(plan): but commit uses feat(invariant): — the commit scope is more accurate
  • enforce_invariants() always sets enforced=True — placeholder for future real evaluation logic
## Code Review: This PR appears to be already merged The branch `feature/m3-invariants` is fully contained within `origin/master` — the three-dot diff (`origin/master...origin/feature/m3-invariants`) is **empty** (zero new changes). Every commit on this branch already exists in master. **This PR should be closed as already-merged.** For the record, the code quality of the invariant subsystem is good: - Well-structured Pydantic models with field validators - Clean merge precedence algorithm (plan → project → global, case-insensitive dedup) - Full CLI implementation with `add`, `list`, `remove` commands - 36 BDD scenarios, 9 Robot tests, 6 ASV benchmark suites - Proper error handling and argument validation ### Additional notes: - PR has no description/body — for a feature of this size, a description explaining design would be valuable - PR title uses `feat(plan):` but commit uses `feat(invariant):` — the commit scope is more accurate - `enforce_invariants()` always sets `enforced=True` — placeholder for future real evaluation logic
Owner

Day 48 Planning — Recommend Close (Already Merged)

Review confirms all commits on this branch already exist in master — the three-dot diff is empty. @freemo's review confirms this.

@brent.edwards — please close this PR. The changes are already on master. If issue #843 needs formal closure, it can be done directly.

**Day 48 Planning — Recommend Close (Already Merged)** Review confirms all commits on this branch already exist in `master` — the three-dot diff is empty. @freemo's review confirms this. @brent.edwards — please close this PR. The changes are already on master. If issue #843 needs formal closure, it can be done directly.
freemo requested changes 2026-03-30 04:16:56 +00:00
Dismissed
freemo left a comment

Review: REQUEST CHANGES

Critical: Missing PR Description

This PR has an empty body. Per CONTRIBUTING.md §Pull Request Process item 1:

"Every PR must include a clear, descriptive body that explains the purpose of the change... PRs submitted without a description or without an issue reference will not be reviewed."

Please add:

  • A summary of the changes and motivation
  • An issue reference with closing keyword (the CHANGELOG mentions #843, so presumably Closes #843)
  • A Forgejo dependency link

Code Review Notes (for when description is added)

The implementation looks architecturally sound:

  1. _enforce_invariants() in PlanLifecycleService — Good pattern: catches exceptions and logs without blocking lifecycle transitions. Correct use of InvariantReconciliationActor.
  2. Container wiringInvariantService registered as Singleton. Note: PR #1202 registers it as Factory. These will conflict at merge — coordinate with that PR author.
  3. BDD tests — 6 scenarios covering basic enforcement, no invariants, no service, multi-scope, dedup, and failure resilience. Good coverage.
  4. Robot integration tests — 4 tests with proper helper script. Compliant with multi-level testing requirements.

Additional Notes

  • The _invariant_service attribute is set directly in the step step_invariant_service_fails (context.enforcement_lifecycle._invariant_service = failing_svc). This reaches into private state — consider adding a test-only setter or using DI override instead.
  • Conflict with PR #1205: Both PRs wire invariant enforcement into PlanLifecycleService. If #1205 adds auto-invocation across all phases, this PR's Strategize-only enforcement may be superseded. Please clarify the merge order.
## Review: REQUEST CHANGES ### Critical: Missing PR Description This PR has an **empty body**. Per CONTRIBUTING.md §Pull Request Process item 1: > "Every PR must include a clear, descriptive body that explains the purpose of the change... PRs submitted without a description or without an issue reference will not be reviewed." Please add: - A summary of the changes and motivation - An issue reference with closing keyword (the CHANGELOG mentions #843, so presumably `Closes #843`) - A Forgejo dependency link ### Code Review Notes (for when description is added) The implementation looks architecturally sound: 1. **`_enforce_invariants()` in `PlanLifecycleService`** — Good pattern: catches exceptions and logs without blocking lifecycle transitions. Correct use of `InvariantReconciliationActor`. 2. **Container wiring** — `InvariantService` registered as Singleton. Note: PR #1202 registers it as Factory. These will conflict at merge — coordinate with that PR author. 3. **BDD tests** — 6 scenarios covering basic enforcement, no invariants, no service, multi-scope, dedup, and failure resilience. Good coverage. 4. **Robot integration tests** — 4 tests with proper helper script. Compliant with multi-level testing requirements. ### Additional Notes - The `_invariant_service` attribute is set directly in the step `step_invariant_service_fails` (`context.enforcement_lifecycle._invariant_service = failing_svc`). This reaches into private state — consider adding a test-only setter or using DI override instead. - **Conflict with PR #1205**: Both PRs wire invariant enforcement into `PlanLifecycleService`. If #1205 adds auto-invocation across all phases, this PR's Strategize-only enforcement may be superseded. Please clarify the merge order.
hurui200320 left a comment

PR Review: !1154 (Ticket #843)

Verdict: Request Changes

The core implementation of _enforce_invariants() is architecturally sound — it correctly delegates to InvariantReconciliationActor, follows the spec algorithm (collect → reconcile → record decisions), and the DI wiring is clean. However, there are 3 critical process violations, 6 major issues spanning spec compliance gaps, test quality problems, and a state leak in the Singleton, and multiple minor issues that need attention before this PR is merge-ready.

Note on existing comments: freemo's comment that the branch is "already merged" is incorrect — this branch (feature/m3-invariant-enforcement-strategize) has 7 changed files with 696 new lines that do NOT exist on origin/master. The earlier review likely confused it with the different branch feature/m3-invariants.


Critical Issues

C1. PR has no description — violates CONTRIBUTING.md §232–252

  • Location: PR !1154 metadata
  • Problem: The PR body is completely empty. CONTRIBUTING.md requires a summary of changes, an issue reference with a closing keyword (e.g., Closes #843), and a dependency link. The guide states: "PRs submitted without a description or without an issue reference will not be reviewed."
  • Recommendation: Add a PR description with summary, Closes #843 reference, and an explanation of the design approach.

C2. Branch contains a merge commit — violates rebase-only policy

  • Location: Git history, commit 54f55e46
  • Problem: The branch includes Merge remote-tracking branch 'origin/master' into feature/m3-invariant-enforcement-strategize. Project policy mandates branches must never contain merge commits — use rebase to align with master.
  • Recommendation: Rebase the branch onto origin/master to produce linear history. This will also resolve the merge conflict making the PR currently unmergeable ("mergeable": false).

C3. Missing Forgejo dependency link (PR ↔ #843)

  • Location: PR !1154 dependencies
  • Problem: CONTRIBUTING.md §241–248 requires the PR to be marked as blocking issue #843 (and the issue should depend on the PR). No blocking dependency is currently configured.
  • Recommendation: Add issue #843 under "blocks" on the PR.

Major Issues

M1. Acceptance criterion gap: "Violated invariants trigger the appropriate response"

  • File: src/cleveragents/application/services/plan_lifecycle_service.py, _enforce_invariants()
  • Problem: Ticket #843 acceptance criterion states: "Violated invariants trigger the appropriate response (revision or prompt)." The implementation only records invariants that survived reconciliation as invariant_enforced decisions. There is no mechanism to detect when the plan's strategy actually violates an invariant, trigger a revision, or prompt the user. The spec's check_invariant_preservation (§19654) describes this, but it's not implemented.
  • Recommendation: Either implement violation detection/response during Strategize, or update the acceptance criteria to clarify this is deferred to #829 (v3.5.0).

M2. Singleton state leak — plan invariants accumulate unbounded in InvariantService

  • File: src/cleveragents/application/services/plan_lifecycle_service.py, lines 335–340; src/cleveragents/application/container.py, lines 586–588
  • Problem: InvariantService is a Singleton. _enforce_invariants() adds plan-level invariants to it via add_invariant(), generating new ULIDs each time. If a plan re-enters Strategize (e.g., after correction), invariants are re-added as new entries. Additionally, _enforcement_records in InvariantService is append-only and never read. In a long-running process, this dict grows unbounded — a memory leak.
  • Recommendation: Add idempotent insertion logic (check for existing invariant with same text/scope/source before adding). Consider cleanup after enforcement completes, or scope plan invariants to the plan's lifecycle rather than the global singleton.

M3. Deduplication test scenario is tautological — always passes even if dedup is broken

  • File: features/invariant_enforcement_strategize.feature, line 62
  • Problem: The scenario "Duplicate invariants are de-duplicated during enforcement" creates the same invariant at both action and global scope, then asserts the invariant_enforced decision count should be at least 1. If dedup fails and both survive (count=2), the test still passes. This assertion cannot detect dedup failure.
  • Recommendation: Change to And 1 invariant_enforced decisions should be in the decision tree (the exact-count step already exists).

M4. No test coverage for plan.invariants registration code path

  • File: src/cleveragents/application/services/plan_lifecycle_service.py, lines 335–340
  • Problem: The for inv in plan.invariants or []: loop is never exercised by any test. All scenarios use action-level or global/project invariants. The code path that registers plan-level PlanInvariant objects into the InvariantService is completely untested.
  • Recommendation: Add a BDD scenario that creates a plan with plan.invariants populated and verifies they produce invariant_enforced decisions.

M5. No test coverage for decision_service is None guard branch

  • File: src/cleveragents/application/services/plan_lifecycle_service.py, line 310
  • Problem: The guard if self._invariant_service is None or self.decision_service is None: return has two conditions. The "bare service" scenario only tests invariant_service is None; the decision_service is None path is never tested.
  • Recommendation: Add a scenario where PlanLifecycleService is created with invariant_service wired but decision_service=None, verifying strategize completes without enforcement.

M6. No ASV benchmark updates — violates multi-level testing mandate

  • File: benchmarks/ directory (no changes)
  • Problem: CONTRIBUTING.md §52–53 states: "Every coding task must include or update tests at multiple levels: unit tests, integration tests, and performance benchmarks." The PR adds BDD and Robot tests but zero benchmarks. The new _enforce_invariants() integration path in start_strategize() has no benchmark for regression detection.
  • Recommendation: Add an ASV benchmark exercising start_strategize() with invariant enforcement wired.

Minor Issues

m1. Only first project link used for invariant scoping

  • File: src/cleveragents/application/services/plan_lifecycle_service.py, lines 324–326
  • Problem: project_name = plan.project_links[0].project_name — only the first project link is used. Plans targeting multiple projects will miss project-scoped invariants for secondary projects.
  • Recommendation: Either iterate all project_links or document the limitation with a TODO.

m2. Invariant Reconciliation Actor lookup not configurable per spec

  • File: src/cleveragents/application/services/plan_lifecycle_service.py, lines 313–321
  • Problem: Spec §19573–19579 says the actor should be found via lookup chain (plan → project → global config). The implementation always instantiates the built-in InvariantReconciliationActor directly.
  • Recommendation: Add a TODO noting this is deferred to #829, or implement the lookup chain.

m3. Broad except Exception handler vs. CONTRIBUTING.md

  • File: src/cleveragents/application/services/plan_lifecycle_service.py, lines 355–360
  • Problem: The blanket except Exception: catches all errors, including MemoryError and ValidationError. CONTRIBUTING.md says "Do not suppress errors" — but the spec requires enforcement not to block lifecycle transitions. This tension exists in 9+ other places in the file.
  • Recommendation: Consider catching a narrower exception type while preserving the non-blocking intent.

m4. Resilience scenario doesn't verify absence of invariant decisions after failure

  • File: features/invariant_enforcement_strategize.feature, lines 66–73
  • Problem: Only asserts the plan reaches PROCESSING state, but doesn't verify 0 invariant_enforced decisions were recorded. A partial leak of stale decisions would go undetected.
  • Recommendation: Add And 0 invariant_enforced decisions should be in the decision tree.

m5. Robot multi-scope test doesn't verify decision count

  • File: robot/invariant_enforcement_strategize.robot, lines 33–40
  • Problem: Only checks enforcement-ok and plan_state, never validates decision_count even though the helper returns it.
  • Recommendation: Add Should Contain ${result.stdout} "decision_count": 3.

m6. Unused source column in Gherkin data tables

  • File: features/invariant_enforcement_strategize.feature, lines 13–14, 41, 56, 67–68
  • Problem: Tables include a source column but the step definition only reads row["text"], ignoring source. Misleading to readers.
  • Recommendation: Remove the source column or use it in the step definition.

m7. MagicMock used in Robot Framework integration test

  • File: robot/helper_invariant_enforcement_strategize.py, lines 13, 169–171
  • Problem: CONTRIBUTING.md §568–570 prohibits mocking in integration tests. The _resilient() function uses MagicMock(spec=InvariantService).
  • Context: This pattern is systemic (229 matches across robot/*.py). Not unique to this PR.
  • Recommendation: Acknowledge as known project-wide deviation; consider a real stub class for this specific case.

m8. Robot _resilient() doesn't exercise add_invariant failure path

  • File: robot/helper_invariant_enforcement_strategize.py, lines 166–195
  • Problem: Creates an action with no invariants, so the for inv in plan.invariants or []: loop never executes, meaning add_invariant.side_effect is never triggered.
  • Recommendation: Add invariants=["Some constraint"] to the action creation.

Nits

n1. Multi-scope Behave scenario doesn't verify precedence resolution

  • File: features/invariant_enforcement_strategize.feature, lines 38–50
  • Problem: Only checks count ≥ 3, never verifies that plan-level overrides global-level on conflict. No scenario tests actual conflict resolution.

n2. Background step wasted for one scenario

  • File: features/invariant_enforcement_strategize.feature, lines 6–7
  • Problem: Background creates services for all scenarios, but the "no invariant service" scenario creates its own and never uses Background services.

n3. Robot helper handlers dict typed as dict[str, object]

  • File: robot/helper_invariant_enforcement_strategize.py, line 205
  • Recommendation: Use dict[str, Callable[[], dict[str, object]]] and remove the if callable(handler) guard.

n4. Unnecessary or [] for plan.invariants

  • File: src/cleveragents/application/services/plan_lifecycle_service.py, line 335
  • Problem: plan.invariants defaults to list via default_factory, so or [] is redundant.

n5. Step definition accesses private _invariant_service attribute

  • File: features/steps/invariant_enforcement_strategize_steps.py, line 156
  • Recommendation: Add a comment noting the coupling, or re-instantiate the service with the mock via constructor.

n6. CHANGELOG could note configurable actor limitation

  • File: CHANGELOG.md, lines 5–12
  • Recommendation: Add: "Uses the built-in reconciliation actor; configurable actor lookup is planned for v3.5.0."

Summary

The core implementation quality is good_enforce_invariants() correctly delegates to the InvariantReconciliationActor, follows the spec algorithm, integrates cleanly at the right point in start_strategize(), and the DI wiring is correct. The BDD and Robot test suites cover the primary happy paths and failure resilience.

However, the PR has significant process violations (no description, merge commit, missing dependency link) that are blockers per CONTRIBUTING.md. There are also substantive test gaps (dedup test is tautological, plan.invariants path untested, decision_service guard untested), a singleton memory leak for plan invariants, and a spec compliance gap on violation handling. These must be addressed before merge.

## PR Review: !1154 (Ticket #843) ### Verdict: Request Changes The core implementation of `_enforce_invariants()` is architecturally sound — it correctly delegates to `InvariantReconciliationActor`, follows the spec algorithm (collect → reconcile → record decisions), and the DI wiring is clean. However, there are **3 critical process violations**, **6 major issues** spanning spec compliance gaps, test quality problems, and a state leak in the Singleton, and multiple minor issues that need attention before this PR is merge-ready. > **Note on existing comments:** freemo's comment that the branch is "already merged" is **incorrect** — this branch (`feature/m3-invariant-enforcement-strategize`) has 7 changed files with 696 new lines that do NOT exist on `origin/master`. The earlier review likely confused it with the different branch `feature/m3-invariants`. --- ### Critical Issues **C1. PR has no description — violates CONTRIBUTING.md §232–252** - **Location:** PR !1154 metadata - **Problem:** The PR body is completely empty. CONTRIBUTING.md requires a summary of changes, an issue reference with a closing keyword (e.g., `Closes #843`), and a dependency link. The guide states: *"PRs submitted without a description or without an issue reference will not be reviewed."* - **Recommendation:** Add a PR description with summary, `Closes #843` reference, and an explanation of the design approach. **C2. Branch contains a merge commit — violates rebase-only policy** - **Location:** Git history, commit `54f55e46` - **Problem:** The branch includes `Merge remote-tracking branch 'origin/master' into feature/m3-invariant-enforcement-strategize`. Project policy mandates branches must never contain merge commits — use rebase to align with master. - **Recommendation:** Rebase the branch onto `origin/master` to produce linear history. This will also resolve the merge conflict making the PR currently unmergeable (`"mergeable": false`). **C3. Missing Forgejo dependency link (PR ↔ #843)** - **Location:** PR !1154 dependencies - **Problem:** CONTRIBUTING.md §241–248 requires the PR to be marked as **blocking** issue #843 (and the issue should **depend on** the PR). No blocking dependency is currently configured. - **Recommendation:** Add issue #843 under "blocks" on the PR. --- ### Major Issues **M1. Acceptance criterion gap: "Violated invariants trigger the appropriate response"** - **File:** `src/cleveragents/application/services/plan_lifecycle_service.py`, `_enforce_invariants()` - **Problem:** Ticket #843 acceptance criterion states: *"Violated invariants trigger the appropriate response (revision or prompt)."* The implementation only records invariants that survived reconciliation as `invariant_enforced` decisions. There is no mechanism to detect when the plan's strategy actually violates an invariant, trigger a revision, or prompt the user. The spec's `check_invariant_preservation` (§19654) describes this, but it's not implemented. - **Recommendation:** Either implement violation detection/response during Strategize, or update the acceptance criteria to clarify this is deferred to #829 (v3.5.0). **M2. Singleton state leak — plan invariants accumulate unbounded in InvariantService** - **File:** `src/cleveragents/application/services/plan_lifecycle_service.py`, lines 335–340; `src/cleveragents/application/container.py`, lines 586–588 - **Problem:** `InvariantService` is a Singleton. `_enforce_invariants()` adds plan-level invariants to it via `add_invariant()`, generating new ULIDs each time. If a plan re-enters Strategize (e.g., after correction), invariants are re-added as new entries. Additionally, `_enforcement_records` in InvariantService is append-only and never read. In a long-running process, this dict grows unbounded — a memory leak. - **Recommendation:** Add idempotent insertion logic (check for existing invariant with same text/scope/source before adding). Consider cleanup after enforcement completes, or scope plan invariants to the plan's lifecycle rather than the global singleton. **M3. Deduplication test scenario is tautological — always passes even if dedup is broken** - **File:** `features/invariant_enforcement_strategize.feature`, line 62 - **Problem:** The scenario "Duplicate invariants are de-duplicated during enforcement" creates the same invariant at both action and global scope, then asserts `the invariant_enforced decision count should be at least 1`. If dedup fails and both survive (count=2), the test still passes. This assertion cannot detect dedup failure. - **Recommendation:** Change to `And 1 invariant_enforced decisions should be in the decision tree` (the exact-count step already exists). **M4. No test coverage for `plan.invariants` registration code path** - **File:** `src/cleveragents/application/services/plan_lifecycle_service.py`, lines 335–340 - **Problem:** The `for inv in plan.invariants or []:` loop is never exercised by any test. All scenarios use action-level or global/project invariants. The code path that registers plan-level `PlanInvariant` objects into the InvariantService is completely untested. - **Recommendation:** Add a BDD scenario that creates a plan with `plan.invariants` populated and verifies they produce `invariant_enforced` decisions. **M5. No test coverage for `decision_service is None` guard branch** - **File:** `src/cleveragents/application/services/plan_lifecycle_service.py`, line 310 - **Problem:** The guard `if self._invariant_service is None or self.decision_service is None: return` has two conditions. The "bare service" scenario only tests `invariant_service is None`; the `decision_service is None` path is never tested. - **Recommendation:** Add a scenario where `PlanLifecycleService` is created with `invariant_service` wired but `decision_service=None`, verifying strategize completes without enforcement. **M6. No ASV benchmark updates — violates multi-level testing mandate** - **File:** `benchmarks/` directory (no changes) - **Problem:** CONTRIBUTING.md §52–53 states: *"Every coding task must include or update tests at multiple levels: unit tests, integration tests, and performance benchmarks."* The PR adds BDD and Robot tests but zero benchmarks. The new `_enforce_invariants()` integration path in `start_strategize()` has no benchmark for regression detection. - **Recommendation:** Add an ASV benchmark exercising `start_strategize()` with invariant enforcement wired. --- ### Minor Issues **m1. Only first project link used for invariant scoping** - **File:** `src/cleveragents/application/services/plan_lifecycle_service.py`, lines 324–326 - **Problem:** `project_name = plan.project_links[0].project_name` — only the first project link is used. Plans targeting multiple projects will miss project-scoped invariants for secondary projects. - **Recommendation:** Either iterate all project_links or document the limitation with a TODO. **m2. Invariant Reconciliation Actor lookup not configurable per spec** - **File:** `src/cleveragents/application/services/plan_lifecycle_service.py`, lines 313–321 - **Problem:** Spec §19573–19579 says the actor should be found via lookup chain (plan → project → global config). The implementation always instantiates the built-in `InvariantReconciliationActor` directly. - **Recommendation:** Add a TODO noting this is deferred to #829, or implement the lookup chain. **m3. Broad `except Exception` handler vs. CONTRIBUTING.md** - **File:** `src/cleveragents/application/services/plan_lifecycle_service.py`, lines 355–360 - **Problem:** The blanket `except Exception:` catches all errors, including `MemoryError` and `ValidationError`. CONTRIBUTING.md says "Do not suppress errors" — but the spec requires enforcement not to block lifecycle transitions. This tension exists in 9+ other places in the file. - **Recommendation:** Consider catching a narrower exception type while preserving the non-blocking intent. **m4. Resilience scenario doesn't verify absence of invariant decisions after failure** - **File:** `features/invariant_enforcement_strategize.feature`, lines 66–73 - **Problem:** Only asserts the plan reaches PROCESSING state, but doesn't verify 0 `invariant_enforced` decisions were recorded. A partial leak of stale decisions would go undetected. - **Recommendation:** Add `And 0 invariant_enforced decisions should be in the decision tree`. **m5. Robot multi-scope test doesn't verify decision count** - **File:** `robot/invariant_enforcement_strategize.robot`, lines 33–40 - **Problem:** Only checks `enforcement-ok` and `plan_state`, never validates `decision_count` even though the helper returns it. - **Recommendation:** Add `Should Contain ${result.stdout} "decision_count": 3`. **m6. Unused `source` column in Gherkin data tables** - **File:** `features/invariant_enforcement_strategize.feature`, lines 13–14, 41, 56, 67–68 - **Problem:** Tables include a `source` column but the step definition only reads `row["text"]`, ignoring `source`. Misleading to readers. - **Recommendation:** Remove the `source` column or use it in the step definition. **m7. `MagicMock` used in Robot Framework integration test** - **File:** `robot/helper_invariant_enforcement_strategize.py`, lines 13, 169–171 - **Problem:** CONTRIBUTING.md §568–570 prohibits mocking in integration tests. The `_resilient()` function uses `MagicMock(spec=InvariantService)`. - **Context:** This pattern is systemic (229 matches across `robot/*.py`). Not unique to this PR. - **Recommendation:** Acknowledge as known project-wide deviation; consider a real stub class for this specific case. **m8. Robot `_resilient()` doesn't exercise `add_invariant` failure path** - **File:** `robot/helper_invariant_enforcement_strategize.py`, lines 166–195 - **Problem:** Creates an action with **no invariants**, so the `for inv in plan.invariants or []:` loop never executes, meaning `add_invariant.side_effect` is never triggered. - **Recommendation:** Add `invariants=["Some constraint"]` to the action creation. --- ### Nits **n1. Multi-scope Behave scenario doesn't verify precedence resolution** - **File:** `features/invariant_enforcement_strategize.feature`, lines 38–50 - **Problem:** Only checks count ≥ 3, never verifies that plan-level overrides global-level on conflict. No scenario tests actual conflict resolution. **n2. Background step wasted for one scenario** - **File:** `features/invariant_enforcement_strategize.feature`, lines 6–7 - **Problem:** Background creates services for all scenarios, but the "no invariant service" scenario creates its own and never uses Background services. **n3. Robot helper `handlers` dict typed as `dict[str, object]`** - **File:** `robot/helper_invariant_enforcement_strategize.py`, line 205 - **Recommendation:** Use `dict[str, Callable[[], dict[str, object]]]` and remove the `if callable(handler)` guard. **n4. Unnecessary `or []` for `plan.invariants`** - **File:** `src/cleveragents/application/services/plan_lifecycle_service.py`, line 335 - **Problem:** `plan.invariants` defaults to `list` via `default_factory`, so `or []` is redundant. **n5. Step definition accesses private `_invariant_service` attribute** - **File:** `features/steps/invariant_enforcement_strategize_steps.py`, line 156 - **Recommendation:** Add a comment noting the coupling, or re-instantiate the service with the mock via constructor. **n6. CHANGELOG could note configurable actor limitation** - **File:** `CHANGELOG.md`, lines 5–12 - **Recommendation:** Add: "Uses the built-in reconciliation actor; configurable actor lookup is planned for v3.5.0." --- ### Summary The **core implementation quality is good** — `_enforce_invariants()` correctly delegates to the `InvariantReconciliationActor`, follows the spec algorithm, integrates cleanly at the right point in `start_strategize()`, and the DI wiring is correct. The BDD and Robot test suites cover the primary happy paths and failure resilience. However, the PR has significant **process violations** (no description, merge commit, missing dependency link) that are blockers per CONTRIBUTING.md. There are also **substantive test gaps** (dedup test is tautological, plan.invariants path untested, decision_service guard untested), a **singleton memory leak** for plan invariants, and a **spec compliance gap** on violation handling. These must be addressed before merge.
freemo left a comment

Supplemental Review (Deep Pass): Additional Findings

New Finding: 3 imports inside function bodies in _enforce_invariants

plan_lifecycle_service.py lines 314-315 and 333:

  • from cleveragents.actor.reconciliation import InvariantReconciliationActor
  • from cleveragents.domain.models.core.invariant import InvariantScope

These are inside a try block inside a method, not under TYPE_CHECKING. Per CONTRIBUTING.md §Import Guidelines: imports must be at top of file. Move to TYPE_CHECKING guard if circular dependency exists.

New Finding: Import inside function in Robot helper

helper_invariant_enforcement_strategize.py line ~152: from cleveragents.domain.models.core.plan import ProjectLink inside _multi_scope. Should be at top of file.

Confirmed: InvariantService registered as Singleton conflicts with PR #1202 (Factory)

As noted in initial review. Merge #1202 first, then rebase this PR.

Previous findings still apply:

  • Empty body — this is the blocking issue. CONTRIBUTING.md: "PRs without a description will not be reviewed."
  • Good BDD + Robot test coverage
  • Sound architecture for invariant enforcement
## Supplemental Review (Deep Pass): Additional Findings ### New Finding: 3 imports inside function bodies in `_enforce_invariants` `plan_lifecycle_service.py` lines 314-315 and 333: - `from cleveragents.actor.reconciliation import InvariantReconciliationActor` - `from cleveragents.domain.models.core.invariant import InvariantScope` These are inside a `try` block inside a method, not under `TYPE_CHECKING`. Per CONTRIBUTING.md §Import Guidelines: imports must be at top of file. Move to `TYPE_CHECKING` guard if circular dependency exists. ### New Finding: Import inside function in Robot helper `helper_invariant_enforcement_strategize.py` line ~152: `from cleveragents.domain.models.core.plan import ProjectLink` inside `_multi_scope`. Should be at top of file. ### Confirmed: `InvariantService` registered as Singleton conflicts with PR #1202 (Factory) As noted in initial review. Merge #1202 first, then rebase this PR. ### Previous findings still apply: - **Empty body** — this is the blocking issue. CONTRIBUTING.md: "PRs without a description will not be reviewed." - Good BDD + Robot test coverage - Sound architecture for invariant enforcement
Owner

Day 50 Planning — Closing this PR. It is already merged to master. The three-dot diff is empty, confirming the code is on master. As noted by @freemo on Day 48, this PR should have been closed earlier. The invariant reconciliation actor code (Pydantic models, merge precedence, 36 BDD + 9 Robot + 6 ASV) is operational on master.

@brent.edwards — please close this PR if it does not auto-close. No further review action needed.

Day 50 Planning — **Closing this PR. It is already merged to master.** The three-dot diff is empty, confirming the code is on master. As noted by @freemo on Day 48, this PR should have been closed earlier. The invariant reconciliation actor code (Pydantic models, merge precedence, 36 BDD + 9 Robot + 6 ASV) is operational on master. @brent.edwards — please close this PR if it does not auto-close. No further review action needed.
freemo self-assigned this 2026-04-02 06:15:19 +00:00
Owner

🤖 Backlog Groomer (groomer-1): Closing as duplicate of #843.

Issue #843 (feat(plan): enforce invariants during strategize phase via invariant runner) is the canonical version with full labels (MoSCoW/Must have, Priority/High, State/In Review, Type/Feature) and milestone v3.2.0. This issue is an exact title duplicate.

🤖 **Backlog Groomer (groomer-1):** Closing as duplicate of #843. Issue #843 (`feat(plan): enforce invariants during strategize phase via invariant runner`) is the canonical version with full labels (`MoSCoW/Must have`, `Priority/High`, `State/In Review`, `Type/Feature`) and milestone `v3.2.0`. This issue is an exact title duplicate.
freemo closed this pull request 2026-04-02 17:30:32 +00:00
All checks were successful
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 16s
Required
Details
CI / lint (pull_request) Successful in 3m24s
Required
Details
CI / typecheck (pull_request) Successful in 3m48s
Required
Details
CI / quality (pull_request) Successful in 3m42s
Required
Details
CI / security (pull_request) Successful in 3m56s
Required
Details
CI / integration_tests (pull_request) Successful in 5m57s
Required
Details
CI / unit_tests (pull_request) Successful in 7m16s
Required
Details
CI / docker (pull_request) Successful in 1m4s
Required
Details
CI / e2e_tests (pull_request) Successful in 9m13s
CI / coverage (pull_request) Successful in 10m12s
Required
Details
CI / status-check (pull_request) Successful in 1s
CI / benchmark-regression (pull_request) Successful in 1h1m46s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
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!1154
No description provided.