Files
cleveragents-core/features/invariant_enforcement_strategize.feature
HAL9000 bc6677b3cd feat(subplans): Subplan System Specification and Invariant Enforcement v3.3.0 (#8725)
Add comprehensive Subplan System specification defining module boundaries,
data models (Subplan, SubplanResult, SubplanTree), PostgreSQL schema with
indexes, the 8-step spawning algorithm, concurrency control via per-plan
semaphores, and error handling.

Implement invariant loading and enforcement in Strategize phase:
- Add InvariantViolationError exception class
- Add load_active_invariants() and check_invariants() methods to InvariantService
- Add _is_violation heuristic for action/invariant matching
- Add BDD tests with @load_invariants, @check_invariants, etc. tags

ISSUES CLOSED: #8725
2026-06-02 19:50:41 -04:00

179 lines
8.7 KiB
Gherkin

Feature: Invariant Enforcement in Strategize Phase
As a plan strategizer
I want invariants to be loaded and enforced during the Strategize phase
So that plan actions that violate constraints are rejected with clear error messages
Background:
Given a fresh InvariantService for enforcement
And a fresh PlanLifecycleService for enforcement
# === Invariant Loading ===
@load_invariants
Scenario: Load all active global invariants
Given a global invariant "Never delete production data" from source "system"
And a global invariant "All APIs must maintain backward compatibility" from source "system"
When I load active invariants for plan "01JQAAAAAAAAAAAAAAAAAAAA01"
Then 2 invariants should be loaded
And the loaded set should contain "Never delete production data"
And the loaded set should contain "All APIs must maintain backward compatibility"
@load_invariants
Scenario: Load invariants with project scope
Given a global invariant "Never delete production data" from source "system"
And a project invariant "Use ORM for all queries" from source "local/api-service" for project "local/api-service"
When I load active invariants for plan "01JQAAAAAAAAAAAAAAAAAAAA01" with project "local/api-service"
Then 2 invariants should be loaded
And the loaded set should contain "Never delete production data"
And the loaded set should contain "Use ORM for all queries"
@load_invariants
Scenario: Load invariants respecting plan > project > global precedence
Given a global invariant "Use REST for all APIs" from source "system"
And a project invariant "Use REST for all APIs" from source "local/api-service" for project "local/api-service"
And a plan invariant "Use REST for all APIs" from source "01JQAAAAAAAAAAAAAAAAAAAA01"
When I load active invariants for plan "01JQAAAAAAAAAAAAAAAAAAAA01" with project "local/api-service"
Then 1 invariant should be loaded
And the winning invariant for "use rest for all apis" should be from "plan" scope
@load_invariants
Scenario: Inactive invariants are not loaded
Given a global invariant "Never delete production data" from source "system"
And a global invariant "All APIs must maintain backward compatibility" from source "system"
When I deactivate the invariant "All APIs must maintain backward compatibility"
And I load active invariants for plan "01JQAAAAAAAAAAAAAAAAAAAA01"
Then 1 invariant should be loaded
And the loaded set should contain "Never delete production data"
And the loaded set should not contain "All APIs must maintain backward compatibility"
# === Invariant Checking ===
@check_invariants
Scenario: Action that violates "do not delete" invariant is rejected
Given a global invariant "Never delete production data" from source "system"
When I check action "Delete all production database records" against loaded invariants
Then an InvariantViolationError should be raised
And the error should include invariant ID
And the error should include the violated text "Never delete production data"
And the error should include the action text "Delete all production database records"
@check_invariants
Scenario: Action that violates "must not" invariant is rejected
Given a global invariant "Must not use hardcoded credentials" from source "system"
When I check action "Add hardcoded API key to config file" against loaded invariants
Then an InvariantViolationError should be raised
And the error should include the violated text "Must not use hardcoded credentials"
@check_invariants
Scenario: Action that complies with invariant is accepted
Given a global invariant "Never delete production data" from source "system"
When I check action "Create backup of production database" against loaded invariants
Then no inv error should be raised
And the action should be accepted
@check_invariants
Scenario: Multiple invariants are all checked
Given a global invariant "Never delete production data" from source "system"
And a global invariant "All APIs must maintain backward compatibility" from source "system"
When I check action "Create backup of production database" against loaded invariants
Then no inv error should be raised
And the action should be accepted
@check_invariants
Scenario: First violating invariant raises error
Given a global invariant "Never delete production data" from source "system"
And a global invariant "Must not use hardcoded credentials" from source "system"
When I check action "Delete production data with hardcoded key" against loaded invariants
Then an InvariantViolationError should be raised
And the error should include one of the violated invariants
@check_invariants
Scenario: Empty action text raises ValidationError
Given a global invariant "Never delete production data" from source "system"
When I check action "" against loaded invariants
Then an inv ValidationError should be raised
And the error message should contain "Action text must not be empty"
# === Integration with Strategize Phase ===
@strategize_integration
Scenario: Strategize phase loads invariants at startup
Given a plan "01JQAAAAAAAAAAAAAAAAAAAA01" in Strategize phase
And a global invariant "Never delete production data" from source "system"
When I start the Strategize phase for the plan
Then invariants should be loaded
And 1 invariant should be active for the plan
@strategize_integration
Scenario: Strategize phase rejects violating strategy decision
Given a plan "01JQAAAAAAAAAAAAAAAAAAAA01" in Strategize phase
And a global invariant "Never delete production data" from source "system"
When I attempt to create a strategy decision "Delete all production database records"
Then an InvariantViolationError should be raised
And the plan should remain in Strategize phase
And the decision should not be recorded
@strategize_integration
Scenario: Strategize phase accepts compliant strategy decision
Given a plan "01JQAAAAAAAAAAAAAAAAAAAA01" in Strategize phase
And a global invariant "Never delete production data" from source "system"
When I create a strategy decision "Create backup of production database"
Then the decision should be recorded
And the plan should progress normally
@strategize_integration
Scenario: Invariants survive plan restarts
Given a plan "01JQAAAAAAAAAAAAAAAAAAAA01" in Strategize phase
And a global invariant "Never delete production data" from source "system"
When I start the Strategize phase for the plan
And I restart the plan
And I start the Strategize phase again
Then invariants should be loaded fresh from database
And 1 invariant should be active for the plan
# === Error Messages ===
@error_messages
Scenario: Violation error includes all required information
Given a global invariant "Never delete production data" from source "system"
When I check action "Delete all production database records" against loaded invariants
Then an InvariantViolationError should be raised
And the error message should include the invariant ID
And the error message should include the invariant text
And the error message should include the action text
And the error should have scope information
And the error should have source_name information
@error_messages
Scenario: Clear error message identifies violated invariant
Given a global invariant "Never delete production data" from source "system"
When I check action "Delete all production database records" against loaded invariants
Then an InvariantViolationError should be raised
And the error message should clearly identify which invariant was violated
And the error message should explain why the action violates the invariant
# === Edge Cases ===
@edge_cases
Scenario: No invariants loaded for empty context
When I load active invariants for plan "01JQAAAAAAAAAAAAAAAAAAAA01"
Then 0 invariants should be loaded
@edge_cases
Scenario: Checking action with no invariants succeeds
When I check action "Delete all production database records" against empty invariants
Then no inv error should be raised
And the action should be accepted
@edge_cases
Scenario: Case-insensitive invariant checking
Given a global invariant "Never delete production data" from source "system"
When I check action "DELETE ALL PRODUCTION DATABASE RECORDS" against loaded invariants
Then an InvariantViolationError should be raised
@edge_cases
Scenario: Whitespace-only action text raises ValidationError
Given a global invariant "Never delete production data" from source "system"
When I check action " " against loaded invariants
Then an inv ValidationError should be raised