Files
cleveragents-core/features/invariant_models.feature
freemo 2d4b330f75
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Failing after 14s
CI / build (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 17s
CI / typecheck (pull_request) Successful in 30s
CI / coverage (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
CI / security (pull_request) Successful in 33s
CI / integration_tests (pull_request) Successful in 2m49s
CI / unit_tests (pull_request) Successful in 5m25s
CI / docker (pull_request) Has been skipped
feat(invariant): add invariant models and enforcement
2026-02-22 09:23:35 +00:00

243 lines
9.6 KiB
Gherkin

Feature: Invariant Models and Enforcement
As a developer
I want invariant models that enforce natural-language constraints on plans
So that plan execution respects configured constraints at every scope level
# === Invariant Creation with Different Scopes ===
Scenario: Create a global invariant
When I create an invariant with text "Never delete production data" scope "global" and source "system"
Then the invariant should be created
And the invariant scope should be "global"
And the invariant source name should be "system"
And the invariant should be active
Scenario: Create a project invariant
When I create an invariant with text "All API changes need tests" scope "project" and source "myapp"
Then the invariant should be created
And the invariant scope should be "project"
And the invariant source name should be "myapp"
Scenario: Create an action invariant
When I create an invariant with text "Minimum 80% coverage" scope "action" and source "local/code-coverage"
Then the invariant should be created
And the invariant scope should be "action"
Scenario: Create a plan invariant
When I create an invariant with text "Use Python 3.13 only" scope "plan" and source "PLAN123"
Then the invariant should be created
And the invariant scope should be "plan"
Scenario: Invariant gets a ULID id
When I create an invariant with text "Test constraint" scope "global" and source "system"
Then the invariant id should be a valid ULID
Scenario: Invariant text is stripped of whitespace
When I create an invariant with text " padded text " scope "global" and source "system"
Then the invariant text should be "padded text"
# === Empty Invariant Text Rejection ===
Scenario: Empty invariant text is rejected
When I try to create an invariant with empty text
Then a validation error should be raised
And the error should mention "at least 1 character"
Scenario: Whitespace-only invariant text is rejected
When I try to create an invariant with whitespace-only text
Then a validation error should be raised
# === InvariantScope Enum ===
Scenario: InvariantScope has four values
Then InvariantScope should have values "global, project, action, plan"
# === Merge Precedence (plan > project > global) ===
Scenario: Merge with no duplicates preserves all invariants
Given I have plan invariants
| text | source |
| Plan constraint | plan1 |
And I have project invariants
| text | source |
| Project constraint | proj1 |
And I have global invariants
| text | source |
| Global constraint | system |
When I merge the invariants
Then the merged set should have 3 invariants
And the merged invariant at index 0 should have text "Plan constraint"
And the merged invariant at index 1 should have text "Project constraint"
And the merged invariant at index 2 should have text "Global constraint"
Scenario: Plan invariant overrides project invariant with same text
Given I have plan invariants
| text | source |
| Log all changes | plan1 |
And I have project invariants
| text | source |
| Log all changes | proj1 |
And I have global invariants
| text | source |
When I merge the invariants
Then the merged set should have 1 invariants
And the merged invariant at index 0 should have scope "plan"
Scenario: Project invariant overrides global invariant with same text
Given I have plan invariants
| text | source |
And I have project invariants
| text | source |
| Log all changes | proj1 |
And I have global invariants
| text | source |
| Log all changes | system |
When I merge the invariants
Then the merged set should have 1 invariants
And the merged invariant at index 0 should have scope "project"
Scenario: Merge precedence is case-insensitive for de-duplication
Given I have plan invariants
| text | source |
| LOG ALL CHANGES | plan1 |
And I have global invariants
| text | source |
| log all changes | system |
And I have project invariants
| text | source |
When I merge the invariants
Then the merged set should have 1 invariants
And the merged invariant at index 0 should have text "LOG ALL CHANGES"
Scenario: Inactive invariants are excluded from merge
Given I have plan invariants with an inactive entry
And I have project invariants
| text | source |
And I have global invariants
| text | source |
When I merge the invariants
Then the merged set should have 0 invariants
# === InvariantSet merge class method ===
Scenario: InvariantSet.merge produces correct result
Given I have plan invariants
| text | source |
| Plan rule | plan1 |
And I have project invariants
| text | source |
| Project rule | proj1 |
And I have global invariants
| text | source |
| Global rule | system |
When I merge using InvariantSet
Then the invariant set should have 3 invariants
# === Service: Add/List/Remove ===
Scenario: Service add invariant
Given an invariant service
When I add an invariant via service with text "No secrets in logs" scope "global" source "system"
Then the service should return the created invariant
And the service invariant text should be "No secrets in logs"
Scenario: Service add invariant rejects empty text
Given an invariant service
When I try to add an invariant via service with empty text
Then a service validation error should be raised
Scenario: Service add invariant rejects empty source
Given an invariant service
When I try to add an invariant via service with empty source
Then a service validation error should be raised
Scenario: Service list all invariants
Given an invariant service with 3 invariants
When I list all invariants via service
Then the service should return 3 invariants
Scenario: Service list invariants filtered by scope
Given an invariant service with mixed scopes
When I list invariants via service with scope "global"
Then all returned invariants should have scope "global"
Scenario: Service list invariants filtered by source
Given an invariant service with mixed sources
When I list invariants via service with source "myapp"
Then all returned invariants should have source "myapp"
Scenario: Service remove invariant soft-deletes
Given an invariant service with 1 invariant
When I remove the invariant via service
Then the invariant should be inactive
And listing active invariants should return 0
Scenario: Service remove non-existent invariant raises not found
Given an invariant service
When I try to remove a non-existent invariant
Then a not found error should be raised
Scenario: Service remove with empty ID raises validation error
Given an invariant service
When I try to remove an invariant with empty ID
Then a service validation error should be raised
# === Effective Invariant Computation ===
Scenario: Effective invariants for a plan with project context
Given an invariant service with invariants at all scopes
When I get effective invariants for plan "plan1" and project "proj1"
Then the effective set should contain plan invariants first
And the effective set should contain project invariants second
And the effective set should contain global invariants last
Scenario: Effective invariants de-duplicate across scopes
Given an invariant service with duplicate text across scopes
When I get effective invariants for plan "plan1" and project "proj1"
Then the effective set should have no duplicates
# === Enforcement Record Creation ===
Scenario: Enforce invariants creates records
Given an invariant service with 2 invariants
When I enforce invariants for plan "PLAN_XYZ"
Then 2 enforcement records should be created
And each enforcement record should be marked as enforced
And each enforcement record should have a decision ID
Scenario: Enforce invariants with actor response
Given an invariant service with 1 invariant
When I enforce invariants with actor response "All constraints satisfied"
Then the enforcement record actor response should be "All constraints satisfied"
Scenario: Enforce invariants with empty plan ID raises error
Given an invariant service
When I try to enforce invariants with empty plan ID
Then a service validation error should be raised
# === InvariantViolation Model ===
Scenario: Create an invariant violation
When I create an invariant violation with severity "error"
Then the violation should be created
And the invariant violation severity should be "error"
Scenario: Violation severity is case-insensitive
When I create an invariant violation with severity "WARNING"
Then the invariant violation severity should be "warning"
Scenario: Invalid violation severity is rejected
When I try to create a violation with invalid severity "critical"
Then a validation error should be raised
# === InvariantEnforcementRecord Model ===
Scenario: Create an enforcement record
When I create an enforcement record with enforced true
Then the record should be created
And the record enforced flag should be true
Scenario: Create an enforcement record with enforced false
When I create an enforcement record with enforced false
Then the record enforced flag should be false