Files
cleveragents-core/features/invariant_reconciliation_autowire.feature
T
Luis Mendes a0c7f5188e
CI / lint (pull_request) Successful in 19s
CI / quality (pull_request) Successful in 32s
CI / build (pull_request) Successful in 17s
CI / typecheck (pull_request) Successful in 55s
CI / security (pull_request) Successful in 54s
CI / helm (pull_request) Successful in 47s
CI / unit_tests (pull_request) Successful in 6m37s
CI / docker (pull_request) Successful in 1m19s
CI / coverage (pull_request) Successful in 10m3s
CI / e2e_tests (pull_request) Successful in 16m47s
CI / integration_tests (pull_request) Successful in 22m14s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 56m42s
feat(plan): wire invariant reconciliation actor auto-invocation
Wire the InvariantReconciliationActor into PlanLifecycleService phase
transitions so that invariant reconciliation runs automatically at each
lifecycle boundary. This ensures plan invariants are verified before
processing can proceed.

Changes:
- Add InvariantService as a new optional dependency on PlanLifecycleService
  (injected via the DI container as a Singleton provider)
- Add _run_invariant_reconciliation() method that creates and invokes the
  reconciliation actor, emits INVARIANT_RECONCILED events on success, and
  raises ReconciliationBlockedError (with INVARIANT_VIOLATED event) on
  failure to block the phase transition
- Invoke reconciliation at three phase transition points:
  1. start_strategize() - after preflight guardrails, before PROCESSING
  2. execute_plan() - after estimation/error patterns, before Execute
  3. apply_plan() - after state validation, before Apply transition
- Subscribe to CORRECTION_APPLIED events for post-correction reconciliation
  (best-effort, failures logged but not re-raised)
- Per-plan disable: reconciliation is skipped when plan.invariant_actor is
  None or "__optional__", following the established estimation actor pattern
- Decision recording: the reconciliation actor already records
  invariant_enforced decisions via DecisionService

Tests:
- Behave: 10 scenarios in invariant_reconciliation_autowire.feature
  covering auto-invocation, skip-when-disabled, transition blocking,
  decision recording, and post-correction reconciliation
- Robot: 5 integration tests in invariant_reconciliation_autowire.robot
- All nox sessions pass (lint, typecheck, unit_tests, integration_tests,
  coverage_report at 97%)

ISSUES CLOSED: #829
2026-04-05 05:46:05 +00:00

99 lines
4.6 KiB
Gherkin

Feature: Invariant Reconciliation Actor Auto-Invocation
As a plan lifecycle manager
I want invariant reconciliation to run automatically at phase transitions
So that invariant violations are detected before they propagate
Background:
Given a fresh PlanLifecycleService with invariant reconciliation wired
# === Auto-invocation at Strategize start ===
@autowire @strategize
Scenario: Reconciliation runs at start_strategize when invariant_actor is set
Given a plan in Strategize/QUEUED with invariant_actor "builtin/invariant-reconciliation"
And a global invariant "All tests must pass" is registered
When I call start_strategize on the plan
Then the plan should transition to Strategize/PROCESSING
And invariant reconciliation should have been invoked
And an INVARIANT_RECONCILED event should have been emitted
@autowire @strategize
Scenario: Reconciliation is skipped at start_strategize when invariant_actor is None
Given a plan in Strategize/QUEUED with invariant_actor unset
When I call start_strategize on the plan
Then the plan should transition to Strategize/PROCESSING
And invariant reconciliation should not have been invoked
@autowire @strategize
Scenario: Reconciliation is skipped when invariant_actor is "__optional__"
Given a plan in Strategize/QUEUED with invariant_actor "__optional__"
When I call start_strategize on the plan
Then the plan should transition to Strategize/PROCESSING
And invariant reconciliation should not have been invoked
# === Auto-invocation at Execute transition ===
@autowire @execute
Scenario: Reconciliation runs at execute_plan transition
Given a plan in Strategize/COMPLETE with invariant_actor "builtin/invariant-reconciliation"
And a global invariant "No unsafe operations" is registered
When I call execute_plan on the plan
Then the plan should transition to Execute/QUEUED
And invariant reconciliation should have been invoked
# === Auto-invocation at Apply transition ===
@autowire @apply
Scenario: Reconciliation runs at apply_plan transition
Given a plan in Execute/COMPLETE with invariant_actor "builtin/invariant-reconciliation"
And a global invariant "Review before merge" is registered
When I call apply_plan on the plan
Then the plan should transition to Apply/QUEUED
And invariant reconciliation should have been invoked
# === Transition blocking on failure ===
@autowire @blocking
Scenario: Reconciliation failure blocks start_strategize
Given a plan in Strategize/QUEUED with invariant_actor "builtin/invariant-reconciliation"
And the invariant service is configured to raise an error
When I attempt to call start_strategize on the plan
Then a ReconciliationBlockedError should be raised
And the plan should remain in Strategize/QUEUED
And an INVARIANT_VIOLATED event should have been emitted
@autowire @blocking
Scenario: Reconciliation failure blocks execute_plan
Given a plan in Strategize/COMPLETE with invariant_actor "builtin/invariant-reconciliation"
And the invariant service is configured to raise an error
When I attempt to call execute_plan on the plan
Then a ReconciliationBlockedError should be raised
And the plan should remain in Strategize/COMPLETE
@autowire @blocking
Scenario: Reconciliation failure blocks apply_plan
Given a plan in Execute/COMPLETE with invariant_actor "builtin/invariant-reconciliation"
And the invariant service is configured to raise an error
When I attempt to call apply_plan on the plan
Then a ReconciliationBlockedError should be raised
And the plan should remain in Execute/COMPLETE
# === Decision recording ===
@autowire @decisions
Scenario: Reconciliation records invariant_enforced decisions in decision tree
Given a plan in Strategize/QUEUED with invariant_actor "builtin/invariant-reconciliation"
And a global invariant "Maintain backward compatibility" is registered
And a plan invariant "Use Python 3.12 only" is registered for the plan
When I call start_strategize on the plan without mocking
Then 2 invariant_enforced decisions should be recorded for the plan
# === Post-correction reconciliation ===
@autowire @correction
Scenario: Reconciliation runs after correction is applied via event subscription
Given a plan in Strategize/PROCESSING with invariant_actor "builtin/invariant-reconciliation"
And a global invariant "All tests must pass" is registered
When a CORRECTION_APPLIED event is emitted for the plan
Then invariant reconciliation should have been invoked for the plan