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
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
99 lines
4.6 KiB
Gherkin
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
|