Files
cleveragents-core/features/consolidated_domain_models.feature
T
freemo bf52a9c648 fix(invariant): restore ACTION scope in merge_invariants and InvariantSet.merge precedence chain
The 4-tier invariant precedence chain (plan > action > project > global) was
broken at the domain layer — merge_invariants() and InvariantSet.merge() only
accepted 3 parameters (plan, project, global), silently dropping all action-
scoped invariants. Added action_invariants as a fourth parameter with proper
backward compatibility (default to empty list). Updated module docstrings,
InvariantScope docstring, and InvariantService.get_effective_invariants() to
reflect the correct precedence chain. Added comprehensive BDD test scenarios
covering four-tier merge precedence, action-before-project ordering, and effective
invariant computation with all four scopes.

ISSUES CLOSED: #9126
2026-05-16 04:14:17 +00:00

1712 lines
62 KiB
Gherkin

Feature: Consolidated Domain Models
Combined scenarios from: domain_models, enums_coverage, invariant_models, namespaced_project_model, session_model, subplan_model, orguserconfig_coverage
# ============================================================
# Originally from: domain_models.feature
# Feature: Domain Models Validation
# ============================================================
Scenario: Create valid Project model
Given I have valid project data
When I create a Project model
Then the project model should be created successfully
And the project name should be validated
And the project path should be absolute
Scenario: Project name validation
Given I have project data with invalid name "123!@#"
When I try to create a Project model
Then a validation error should be raised
And the error should mention "Name must be alphanumeric"
Scenario: Create valid Plan model
Given I have valid plan data
When I create a Plan model
Then the plan model should be created successfully
And the plan status should be "pending"
And the plan should have timestamps
Scenario: Plan status transitions
Given I have a plan with status "pending"
When I update the plan status to "building"
Then the updated plan status should be "building"
And the updated_at timestamp should be updated
Scenario: Create valid Context model
Given I have valid context data
When I create a Context model
Then the context model should be created successfully
And the context type should be "file"
And the context should have a path
Scenario: Create valid Change model
Given I have valid change data
When I create a Change model
Then the change model should be created successfully
And the change operation should be valid
And the change should not be applied by default
Scenario: ChangeSet statistics
Given I have a changeset with multiple changes
When I get the changeset statistics
Then the stats should show correct counts
And the stats should count creates, modifies, and deletes
Scenario: Import auto-generated models
When I import the auto-generated models
Then all models should load without errors
And the models should have proper Pydantic configuration
# TODO: Uncomment when step definitions are implemented
# Scenario: Create an AIModel with default values
# Given I create an "AIModel" with the following data:
# | name | value |
# | provider_id | test |
# | model_id | test |
# Then the "AIModel" object should have the following attributes:
# | name | value |
# | provider_id | test |
# | model_id | test |
# | is_chat | False |
# | is_image_to_text | False |
# | is_multi_modal | False |
# | is_fine_tunable | False |
# | is_function_calling | False |
#
# TODO: Uncomment when step definitions are implemented
# Scenario: Create a Provider with default values
# Given I create a "Provider" with the following data:
# | name | value |
# | provider_id | test |
# | name | test |
# Then the "Provider" object should have the following attributes:
# | name | value |
# | provider_id | test |
# | name | test |
#
# TODO: Uncomment when step definitions are implemented
# Scenario: Create a CustomAIModel with default values
# Given I create a "CustomAIModel" with the following data:
# | name | value |
# | provider_id | test |
# | model_id | test |
# Then the "CustomAIModel" object should have the following attributes:
# | name | value |
# | provider_id | test |
# | model_id | test |
# | is_chat | False |
# | is_image_to_text | False |
# | is_multi_modal | False |
# | is_fine_tunable | False |
# | is_function_calling | False |
Scenario: Context update result aggregates statistics
Given I have context update statistics
When I create a ContextUpdateResult summary
Then the context update summary should report correct totals
And the token diffs should remain non-negative
Scenario: Context update summary parameters accept camelCase payloads
Given I have summary counts for context updates
When I create a SummaryForUpdateContextParams model
Then the summary counts should match the source data
Scenario: Create cloud billing fields from camelCase payload
Given I have sample cloud billing data
When I create a CloudBillingFields model
Then the billing fields should expose decimal values
Scenario: Create organization with billing fields
Given I have organization data with billing
When I create an Org domain model
Then the org should include billing configuration
Scenario: Create a user with default plan config
Given I have user data referencing a default plan config
When I create a User model
Then the user should include the default plan config
Scenario: Create an org role definition
Given I have an org role definition
When I create an OrgRole model
Then the org role should retain its label
Scenario: Create an org user association
Given I have an org user association
When I create an OrgUser model
Then the org user config should be attached
Scenario: Create an invite record with timestamps
Given I have invite metadata
When I create an Invite model
Then the invite should track acceptance timestamps
Scenario: Create a credits transaction entry
Given I have credits transaction data
When I create a CreditsTransaction model
Then the credits transaction should track balances
# ============================================================
# Originally from: enums_coverage.feature
# Feature: Domain Enums Coverage
# ============================================================
@unit @enums
Scenario: ModelPublisher enum has all expected values
Given I import the ModelPublisher enum
When I check all ModelPublisher values
Then ModelPublisher.OPENAI should equal "ModelPublisherOpenAI"
And ModelPublisher.ANTHROPIC should equal "ModelPublisherAnthropic"
And ModelPublisher.GOOGLE should equal "ModelPublisherGoogle"
And ModelPublisher.DEEPSEEK should equal "ModelPublisherDeepSeek"
And ModelPublisher.PERPLEXITY should equal "ModelPublisherPerplexity"
And ModelPublisher.QWEN should equal "ModelPublisherQwen"
And ModelPublisher.MISTRAL should equal "ModelPublisherMistral"
@unit @enums
Scenario: ModelPublisher enum is string-based
Given I have a ModelPublisher enum value
When I convert ModelPublisher.OPENAI to string
Then it should be a string type
And it should equal "ModelPublisherOpenAI"
@unit @enums
Scenario: ModelPublisher enum membership check
Given I import the ModelPublisher enum
When I check enum membership
Then "ModelPublisherOpenAI" should be a valid ModelPublisher value
And "InvalidPublisher" should not be a valid ModelPublisher value
@unit @enums
Scenario: ModelPublisher enum iteration
Given I import the ModelPublisher enum
When I iterate over all ModelPublisher values
Then I should get 7 enum members
And each member should be a ModelPublisher instance
@unit @enums
Scenario: ModelErrKind enum has all expected values
Given I import the ModelErrKind enum
When I check all ModelErrKind values
Then ModelErrKind.OVERLOADED should equal "ErrOverloaded"
And ModelErrKind.CONTEXT_TOO_LONG should equal "ErrContextTooLong"
And ModelErrKind.RATE_LIMITED should equal "ErrRateLimited"
And ModelErrKind.SUBSCRIPTION_QUOTA_EXHAUSTED should equal "ErrSubscriptionQuotaExhausted"
And ModelErrKind.OTHER should equal "ErrOther"
And ModelErrKind.CACHE_SUPPORT should equal "ErrCacheSupport"
@unit @enums
Scenario: ModelErrKind enum is string-based
Given I have a ModelErrKind enum value
When I convert ModelErrKind.RATE_LIMITED to string
Then it should be a string type
And it should equal "ErrRateLimited"
@unit @enums
Scenario: ModelErrKind enum membership check
Given I import the ModelErrKind enum
When I check enum membership
Then "ErrOverloaded" should be a valid ModelErrKind value
And "InvalidError" should not be a valid ModelErrKind value
@unit @enums
Scenario: ModelErrKind enum iteration
Given I import the ModelErrKind enum
When I iterate over all ModelErrKind values
Then I should get 6 enum members
And each member should be a ModelErrKind instance
@unit @enums
Scenario: FallbackType enum has all expected values
Given I import the FallbackType enum
When I check all FallbackType values
Then FallbackType.ERROR should equal "FallbackTypeError"
And FallbackType.CONTEXT should equal "FallbackTypeContext"
And FallbackType.PROVIDER should equal "FallbackTypeProvider"
@unit @enums
Scenario: FallbackType enum is string-based
Given I have a FallbackType enum value
When I convert FallbackType.ERROR to string
Then it should be a string type
And it should equal "FallbackTypeError"
@unit @enums
Scenario: FallbackType enum membership check
Given I import the FallbackType enum
When I check enum membership
Then "FallbackTypeError" should be a valid FallbackType value
And "InvalidFallback" should not be a valid FallbackType value
@unit @enums
Scenario: FallbackType enum iteration
Given I import the FallbackType enum
When I iterate over all FallbackType values
Then I should get 3 enum members
And each member should be a FallbackType instance
@unit @enums
Scenario: ModelProvider enum has all expected values
Given I import the ModelProvider enum
When I check all ModelProvider values
Then ModelProvider.OPENROUTER should equal "ModelProviderOpenRouter"
And ModelProvider.OPENAI should equal "ModelProviderOpenAI"
And ModelProvider.ANTHROPIC should equal "ModelProviderAnthropic"
And ModelProvider.ANTHROPIC_CLAUDE_MAX should equal "ModelProviderAnthropicClaudeMax"
And ModelProvider.GOOGLE_AI_STUDIO should equal "ModelProviderGoogleAIStudio"
And ModelProvider.GOOGLE_VERTEX should equal "ModelProviderGoogleVertex"
And ModelProvider.AZURE_OPENAI should equal "ModelProviderAzureOpenAI"
And ModelProvider.DEEPSEEK should equal "ModelProviderDeepSeek"
And ModelProvider.PERPLEXITY should equal "ModelProviderPerplexity"
And ModelProvider.AMAZON_BEDROCK should equal "ModelProviderAmazonBedrock"
@unit @enums
Scenario: ModelProvider enum is string-based
Given I have a ModelProvider enum value
When I convert ModelProvider.OPENAI to string
Then it should be a string type
And it should equal "ModelProviderOpenAI"
@unit @enums
Scenario: ModelProvider enum membership check
Given I import the ModelProvider enum
When I check enum membership
Then "ModelProviderOpenAI" should be a valid ModelProvider value
And "InvalidProvider" should not be a valid ModelProvider value
@unit @enums
Scenario: ModelProvider enum iteration
Given I import the ModelProvider enum
When I iterate over all ModelProvider values
Then I should get 10 enum members
And each member should be a ModelProvider instance
@unit @enums
Scenario: Enum value comparison
Given I have ModelPublisher enum values
When I compare enum values
Then ModelPublisher.OPENAI should equal ModelPublisher.OPENAI
And ModelPublisher.OPENAI should not equal ModelPublisher.ANTHROPIC
@unit @enums
Scenario: Enum name attribute access
Given I import the ModelPublisher enum
When I access the name attribute of ModelPublisher.OPENAI
Then the name should be "OPENAI"
@unit @enums
Scenario: Enum value attribute access
Given I import the ModelPublisher enum
When I access the value attribute of ModelPublisher.OPENAI
Then the value should be "ModelPublisherOpenAI"
@unit @enums
Scenario: Creating enum from string value
Given I import the ModelProvider enum
When I create ModelProvider from value "ModelProviderOpenAI"
Then I should get ModelProvider.OPENAI
@unit @enums
Scenario: Enum hash consistency
Given I have ModelErrKind enum values
When I use enum values as dictionary keys
Then the enum values should work as dictionary keys
And the hash should be consistent
@unit @enums
Scenario: Enum string representation
Given I have a FallbackType enum value
When I get the string representation
Then repr should contain "FallbackType.ERROR"
And str should equal "FallbackTypeError"
# ============================================================
# Originally from: invariant_models.feature
# Feature: Invariant Models and Enforcement
# ============================================================
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 > action > 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: Merge with all four scopes preserves action tier — Issue #9126
Given I have plan invariants
| text | source |
| Plan rule | plan1 |
And I have action invariants
| text | source |
| Action rule | action1 |
And I have project invariants
| text | source |
| Project rule | proj1 |
And I have global invariants
| text | source |
| Global rule | system |
When I merge the invariants
Then the merged set should have 4 invariants
And plan invariants appear before action invariants in merge
And action invariants appear before project invariants in merge
And the merged invariant at index 0 should have text "Plan rule"
And the merged invariant at index 1 should have text "Action rule"
And the merged invariant at index 2 should have text "Project rule"
And the merged invariant at index 3 should have text "Global rule"
Scenario: Action invariant overrides project with same text
Given I have plan invariants
| text | source |
And I have action invariants
| text | source |
| Log all changes | action1 |
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 "action"
Scenario: Action invariant is included in de-duplication with plan override
Given I have plan invariants
| text | source |
| Log all changes | plan1 |
And I have action invariants
| text | source |
| Log all changes | action1 |
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 1 invariants
And the merged invariant at index 0 should have scope "plan"
Scenario: Inactive action invariants are excluded from merge
Given I have plan invariants with an inactive entry
And I have action invariants
| text | source |
| Active | action1 |
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 1 invariants
And the merged invariant at index 0 should have scope "action"
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
Scenario: InvariantSet.merge with four-tier precedence — Issue #9126
Given I have plan invariants
| text | source |
| Plan rule | plan1 |
And I have action invariants
| text | source |
| Action rule | action1 |
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 4 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 include action scope — Issue #9126
Given an invariant service with invariants at all four scopes
When I get effective invariants for plan "plan1", action "action1", and project "proj1"
Then the effective set should contain plan invariants first
And the effective set should contain action invariants second
And the effective set should contain project invariants after action
And the effective set should contain global invariants last
And the effective set should have 4 invariants
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
# ============================================================
# Immutability contract (frozen=True) — Issue #3116
# ============================================================
Scenario: Invariant model is immutable after creation
When I create an invariant with text "Never delete production data" scope "global" and source "system"
Then mutating the invariant active field should raise an error
Scenario: InvariantViolation model is immutable after creation
When I create an invariant violation with severity "error"
Then mutating the violation severity field should raise an error
Scenario: InvariantEnforcementRecord model is immutable after creation
When I create an enforcement record with enforced true
Then mutating the record enforced field should raise an error
Scenario: Invariant model is hashable
When I create an invariant with text "Hashable constraint" scope "global" and source "system"
Then the invariant should be hashable
Scenario: InvariantViolation model is hashable
When I create an invariant violation with severity "warning"
Then the violation should be hashable
Scenario: InvariantEnforcementRecord model is hashable
When I create an enforcement record with enforced true
Then the record should be hashable
Scenario: InvariantSet model is immutable after creation
Given I have plan invariants
| text | source |
| Plan rule | plan1 |
When I merge using InvariantSet
Then mutating the invariant set invariants field should raise an error
Scenario: InvariantSet model is hashable
Given I have plan invariants
| text | source |
| Hashable rule | plan1 |
When I merge using InvariantSet
Then the invariant set should be hashable
Scenario: Service soft-delete creates new instance with active=False
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
And the removed invariant is a different object than the original
And the original invariant should still be active
# ============================================================
# Originally from: namespaced_project_model.feature
# Feature: Namespaced Project Domain Model
# ============================================================
Scenario: Parse bare name defaults to local namespace
When I parse the project namespaced name "my-project"
Then the nsproject parsed namespace should be "local"
And the nsproject parsed name should be "my-project"
And the nsproject parsed server should be empty
Scenario: Parse name with explicit namespace
When I parse the project namespaced name "freemo/code-coverage"
Then the nsproject parsed namespace should be "freemo"
And the nsproject parsed name should be "code-coverage"
And the nsproject parsed server should be empty
Scenario: Parse name with server qualifier
When I parse the project namespaced name "dev:freemo/code-coverage"
Then the nsproject parsed server should be "dev"
And the nsproject parsed namespace should be "freemo"
And the nsproject parsed name should be "code-coverage"
Scenario: Parse local namespace
When I parse the project namespaced name "local/my-project"
Then the nsproject parsed namespace should be "local"
And the nsproject parsed name should be "my-project"
Scenario: ParsedName qualified_name with server
When I parse the project namespaced name "prod:myorg/api"
Then the nsproject qualified name should be "prod:myorg/api"
Scenario: ParsedName qualified_name without server
When I parse the project namespaced name "myorg/api"
Then the nsproject qualified name should be "myorg/api"
Scenario: ParsedName namespaced_name
When I parse the project namespaced name "dev:myorg/api"
Then the nsproject namespaced name should be "myorg/api"
Scenario: ParsedName is_local for bare name
When I parse the project namespaced name "my-project"
Then the nsproject parsed name should be local
Scenario: ParsedName is_remote with server
When I parse the project namespaced name "dev:myorg/api"
Then the nsproject parsed name should be remote
Scenario: ParsedName is_remote with non-local namespace
When I parse the project namespaced name "freemo/api"
Then the nsproject parsed name should be remote
Scenario: Reject empty name
When I try to parse an empty project namespaced name
Then a nsproject validation error should be raised
Scenario: Reject reserved namespace system
When I try to parse the project namespaced name "system/my-project"
Then a nsproject validation error should be raised
Scenario: Reject reserved namespace admin
When I try to parse the project namespaced name "admin/my-project"
Then a nsproject validation error should be raised
Scenario: Reject provider namespace openai
When I try to parse the project namespaced name "openai/my-project"
Then a nsproject validation error should be raised
Scenario: Reject provider namespace anthropic
When I try to parse the project namespaced name "anthropic/my-model"
Then a nsproject validation error should be raised
Scenario: Reject invalid characters in name
When I try to parse the project namespaced name "my@project"
Then a nsproject validation error should be raised
# TemporalScope enum
Scenario: TemporalScope has three values
Then the nsproject TemporalScope enum should have values "current, recent, all"
# ContextConfig model
Scenario: ContextConfig defaults
Given a default ContextConfig
Then the nsproject ctx max_file_size should be 1000000
And the nsproject ctx max_total_size should be 52428800
And the nsproject ctx indexing_strategy should be "full_text"
And the nsproject ctx chunking_policy should be "smart"
And the nsproject ctx chunk_size should be 1000
And the nsproject ctx summarize should be true
And the nsproject ctx auto_refresh should be true
And the nsproject ctx temporal_scope should be "current"
And the nsproject ctx retention_policy should be empty
Scenario: ContextConfig merges default ignore patterns
Given a ContextConfig with custom ignore patterns
Then the nsproject ctx ignore patterns should include defaults
And the nsproject ctx ignore patterns should include the custom pattern
Scenario: ContextConfig with memory tier settings
Given a ContextConfig with hot_max_tokens 4096 and warm_max_decisions 50
Then the nsproject ctx hot_max_tokens should be 4096
And the nsproject ctx warm_max_decisions should be 50
Scenario: ContextConfig is frozen
Given a default ContextConfig
When I try to mutate a ContextConfig field
Then a nsproject validation error should be raised
Scenario: ContextConfig rejects zero max_file_size
When I try to create a ContextConfig with max_file_size 0
Then a nsproject validation error should be raised
Scenario: ContextConfig rejects negative chunk_size
When I try to create a ContextConfig with chunk_size -1
Then a nsproject validation error should be raised
# LinkedResource model
Scenario: Create a LinkedResource with defaults
Given a LinkedResource with resource_id "01HZQX7K8M3N4P5R6S7T8V9W0X"
Then the nsproject linked rid should be "01HZQX7K8M3N4P5R6S7T8V9W0X"
And the nsproject linked read_only should be false
And the nsproject linked alias should be empty
And the nsproject linked_at should be set
Scenario: Create a LinkedResource with alias
Given a LinkedResource with id "01HZQX7K8M3N4P5R6S7T8V9W0X" and alias "main-repo"
Then the nsproject linked alias should be "main-repo"
Scenario: LinkedResource rejects invalid ULID
When I try to create a LinkedResource with resource_id "invalid"
Then a nsproject validation error should be raised
Scenario: LinkedResource rejects empty alias
When I try to create a LinkedResource with empty alias
Then a nsproject validation error should be raised
Scenario: LinkedResource is frozen
Given a LinkedResource with resource_id "01HZQX7K8M3N4P5R6S7T8V9W0X"
When I try to mutate a LinkedResource field
Then a nsproject validation error should be raised
# NamespacedProject model
Scenario: Create a minimal NamespacedProject
Given a NamespacedProject with name "my-project"
Then the nsproject name should be "my-project"
And the nsproject namespace should be "local"
And the nsproject server should be empty
And the nsproject namespaced name should be "local/my-project"
And the nsproject qualified name should be "local/my-project"
And the nsproject linked resources should be empty
And the nsproject created_at should be set
And the nsproject updated_at should be set
Scenario: Create a NamespacedProject with namespace
Given a project "api" under namespace "freemo"
Then the nsproject namespace should be "freemo"
And the nsproject namespaced name should be "freemo/api"
Scenario: Create a NamespacedProject with server
Given a project "api" under namespace "freemo" on server "dev"
Then the nsproject server should be "dev"
And the nsproject qualified name should be "dev:freemo/api"
Scenario: NamespacedProject is_local
Given a NamespacedProject with name "my-project"
Then the nsproject should be local
Scenario: NamespacedProject is_remote with namespace
Given a project "api" under namespace "freemo"
Then the nsproject should be remote
Scenario: NamespacedProject is_remote with server
Given a project "api" under namespace "freemo" on server "dev"
Then the nsproject should be remote
# NamespacedProject - validation
Scenario: NamespacedProject rejects invalid name
When I try to create a NamespacedProject with name "123invalid"
Then a nsproject validation error should be raised
Scenario: NamespacedProject rejects reserved namespace
When I try to create a NamespacedProject with namespace "system"
Then a nsproject validation error should be raised
Scenario: NamespacedProject rejects provider namespace
When I try to create a NamespacedProject with namespace "openai"
Then a nsproject validation error should be raised
Scenario: NamespacedProject rejects duplicate linked resources
When I try to create a NamespacedProject with duplicate linked resources
Then a nsproject validation error should be raised
# NamespacedProject - domain methods
Scenario: Link a resource to a project
Given a NamespacedProject with name "my-project"
When I link resource "01HZQX7K8M3N4P5R6S7T8V9W0X" to the project
Then the nsproject should have 1 linked resource
And the nsproject first linked resource_id should be "01HZQX7K8M3N4P5R6S7T8V9W0X"
Scenario: Link a resource with read_only and alias
Given a NamespacedProject with name "my-project"
When I link resource "01HZQX7K8M3N4P5R6S7T8V9W0X" with read_only true and alias "docs"
Then the nsproject should have 1 linked resource
And the nsproject first linked resource should be read_only
And the nsproject first linked resource alias should be "docs"
Scenario: Unlink a resource from a project
Given a NamespacedProject with name "my-project"
And I link resource "01HZQX7K8M3N4P5R6S7T8V9W0X" to the project
When I unlink resource "01HZQX7K8M3N4P5R6S7T8V9W0X" from the project
Then the nsproject linked resources should be empty
Scenario: Link duplicate resource fails
Given a NamespacedProject with name "my-project"
And I link resource "01HZQX7K8M3N4P5R6S7T8V9W0X" to the project
When I try to link resource "01HZQX7K8M3N4P5R6S7T8V9W0X" again
Then a nsproject validation error should be raised
Scenario: Unlink non-existent resource fails
Given a NamespacedProject with name "my-project"
When I try to unlink resource "01HZQX7K8M3N4P5R6S7T8V9W0X" from the project
Then a nsproject validation error should be raised
Scenario: Get linked resource by resource_id
Given a NamespacedProject with name "my-project"
And I link resource "01HZQX7K8M3N4P5R6S7T8V9W0X" to the project
When I get linked resource "01HZQX7K8M3N4P5R6S7T8V9W0X" from the project
Then the nsproject retrieved link should not be empty
Scenario: Get linked resource by alias
Given a NamespacedProject with name "my-project"
And I link resource "01HZQX7K8M3N4P5R6S7T8V9W0X" with read_only false and alias "main"
When I get linked resource by alias "main" from the project
Then the nsproject retrieved link should not be empty
And the nsproject retrieved link alias should be "main"
Scenario: Get non-existent linked resource returns None
Given a NamespacedProject with name "my-project"
When I get linked resource "01HZQX7K8M3N4P5R6S7T8V9W0X" from the project
Then the nsproject retrieved link should be empty
Scenario: Link resource with empty resource_id fails
Given a NamespacedProject with name "my-project"
When I try to link an empty resource_id to the project
Then a nsproject validation error should be raised
Scenario: Unlink resource with empty resource_id fails
Given a NamespacedProject with name "my-project"
When I try to unlink an empty resource_id from the project
Then a nsproject validation error should be raised
# ============================================================
# Originally from: session_model.feature
# Feature: Session Domain Model
# ============================================================
Scenario: Create a session with valid ULID
When I create a session with a valid ULID
Then the session model should be created
And the session model session_id should be a valid ULID
Scenario: Create a session with actor name
When I create a session with actor name "local/orchestrator"
Then the session model should be created
And the session model actor_name should be "local/orchestrator"
Scenario: Create a session without actor name
When I create a session without actor name
Then the session model should be created
And the session model actor_name should be empty
Scenario: Session has default namespace local
When I create a session with a valid ULID
Then the session model namespace should be "local"
# ---- Session ID Validation ----
Scenario: Session rejects invalid ULID format
When I try to create a session with invalid ID "not-a-ulid"
Then a session model validation error should be raised
# ---- Actor Name Validation ----
Scenario: Session rejects invalid actor name format
When I try to create a session with invalid actor name "badformat"
Then a session model validation error should be raised
Scenario: Session accepts None actor name
When I create a session without actor name
Then the session model should be created
# ---- Message Append with Auto-sequencing ----
Scenario: Append a user message to session
Given a session with no messages
When I append a user message "Hello"
Then the session model should have 1 message
And the session last message content should be "Hello"
And the session last message role should be "user"
And the session last message sequence should be 0
Scenario: Append multiple messages with auto-sequencing
Given a session with no messages
When I append a user message "Hello"
And I append an assistant message "Hi there"
Then the session model should have 2 messages
And the session last message sequence should be 1
# ---- Message Role Validation ----
Scenario: Tool message requires tool_call_id
When I try to create a tool message without tool_call_id
Then a session model validation error should be raised
And the session model error should mention "tool_call_id"
Scenario: Tool message with tool_call_id is valid
When I create a tool message with tool_call_id "call_123"
Then the session message should be created
And the session message tool_call_id should be "call_123"
Scenario: User message does not require tool_call_id
When I create a user message "Hello"
Then the session message should be created
# ---- Content Validation ----
Scenario: Message rejects whitespace-only content
When I try to create a message with whitespace-only content
Then a session model validation error should be raised
And the session model error should mention "whitespace"
Scenario: Message rejects empty content
When I try to create a message with empty content
Then a session model validation error should be raised
# ---- Message Ordering ----
Scenario: Messages must be ordered by sequence
When I try to create a session with out-of-order messages
Then a session model validation error should be raised
And the session model error should mention "ordered by sequence"
# ---- Plan Linking ----
Scenario: Link a plan to session
Given a session with no messages
When I link plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the session
Then the session should have 1 linked plan
Scenario: Plan linking deduplicates
Given a session with no messages
When I link plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the session
And I link plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the session
Then the session should have 1 linked plan
Scenario: Link multiple plans
Given a session with no messages
When I link plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the session
And I link plan "01BX5ZZKBKACTAV9WEVGEMMVRY" to the session
Then the session should have 2 linked plans
# ---- Token Usage Tracking ----
Scenario: Default token usage is zero
When I create a session with a valid ULID
Then the session token usage input_tokens should be 0
And the session token usage output_tokens should be 0
And the session token usage estimated_cost should be 0.0
Scenario: Token usage with values
When I create a session with token usage 100 input 50 output 0.05 cost
Then the session token usage input_tokens should be 100
And the session token usage output_tokens should be 50
And the session token usage estimated_cost should be 0.05
Scenario: Token usage rejects negative values
When I try to create token usage with negative input tokens
Then a session model validation error should be raised
# ---- Export Dict ----
Scenario: Export dict contains checksum
Given a session with some messages
When I export the session
Then the export dict should have key "checksum"
And the export dict should have key "schema_version"
And the export dict should have key "messages"
And the export dict should have key "session_id"
Scenario: Export dict round-trip preserves data
Given a session with some messages
When I export the session
Then the export dict messages count should match session message count
# ---- CLI Dict ----
Scenario: CLI dict has required fields
Given a session with some messages
When I get the session model CLI dict
Then the session cli dict should have key "session_summary"
And the session cli dict should have key "token_usage"
And the session cli dict session_summary should have key "id"
And the session cli dict session_summary should have key "messages"
And the session cli dict session_summary should have key "created"
And the session cli dict session_summary should have key "updated"
Scenario: CLI dict includes recent messages
Given a session with some messages
When I get the session model CLI dict
Then the session cli dict should have key "recent_messages"
And the session cli dict recent_messages text key should be "text"
Scenario: CLI dict includes actor when set
When I create a session with actor name "local/orchestrator"
And I get the session model CLI dict
Then the session cli dict session_summary should have key "actor"
Scenario: CLI dict includes automation when set
When I create a session with automation "review"
And I get the session model CLI dict
Then the session cli dict session_summary should have key "automation"
And the session cli dict session_summary automation should be "review"
Scenario: CLI dict linked_plans uses spec-compliant objects
Given a session with linked plans
When I get the session model CLI dict
Then the session cli dict should have key "linked_plans"
And the session cli dict linked_plans should contain plan_id field
Scenario: CLI dict token_usage estimated_cost is formatted string
Given a session with token usage cost 0.0184
When I get the session model CLI dict
Then the session cli dict token_usage estimated_cost should be a string starting with "$"
# ---- Empty Session Properties ----
Scenario: Empty session has zero message count
Given a session with no messages
Then the session message count should be 0
And the session is_empty should be true
And the session last_message should be none
# ---- get_messages with limit/offset ----
Scenario: get_messages with no limit returns all
Given a session with 5 messages
When I get messages with no limit
Then I should get 5 messages
Scenario: get_messages with limit
Given a session with 5 messages
When I get messages with limit 3
Then I should get 3 messages
Scenario: get_messages with offset
Given a session with 5 messages
When I get messages with offset 2
Then I should get 3 messages
Scenario: get_messages with limit and offset
Given a session with 5 messages
When I get messages with limit 2 and offset 1
Then I should get 2 messages
# ---- Session Error Types ----
Scenario: SessionServiceError is base error
Then SessionServiceError should be an Exception subclass
Scenario: SessionNotFoundError inherits from SessionServiceError
Then SessionNotFoundError should be a SessionServiceError subclass
Scenario: SessionExportError inherits from SessionServiceError
Then SessionExportError should be a SessionServiceError subclass
Scenario: SessionImportError inherits from SessionServiceError
Then SessionImportError should be a SessionServiceError subclass
# ---- MessageRole enum ----
Scenario: MessageRole has expected values
Then the MessageRole enum should have values "user,assistant,system,tool"
# ---- Session metadata ----
Scenario: Session supports metadata
When I create a session with metadata
Then the session metadata should contain key "env"
# ---- Export dict linked plans ----
Scenario: Export dict includes linked plan IDs
Given a session with no messages
When I link plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the session
And I export the session
Then the export dict should have key "linked_plan_ids"
# ---- Robot Smoke Alignment: Serialization Order ----
Scenario: Export dict keys are in expected serialization order
Given a session with some messages
When I export the session
Then the export dict keys should be in expected serialization order
Scenario: Export dict messages are ordered by sequence
Given a session with some messages
When I export the session
Then the export dict messages should be ordered by sequence
# ============================================================
# Originally from: subplan_model.feature
# Feature: Subplan Model Hierarchy and Configuration
# ============================================================
Scenario: Root plan has no parent and root equals self
Given I create a subplan-test root plan
Then the subplan-test plan should not be a subplan
And the subplan-test plan should be a root plan
And the subplan-test plan depth should be 0
Scenario: Root plan with explicit root_plan_id equal to plan_id
Given I create a subplan-test plan with root_plan_id equal to plan_id
Then the subplan-test plan should be a root plan
And the subplan-test plan should not be a subplan
# Child plan identity
Scenario: Child plan with parent_plan_id set is a subplan
Given I create a subplan-test child plan with a parent
Then the subplan-test plan should be a subplan
And the subplan-test plan should not be a root plan
And the subplan-test plan attempt should be 1
Scenario: Root plan id propagates to nested children
Given I create a subplan-test three-level hierarchy
Then the subplan-test grandchild root_plan_id should match the root plan_id
# Default values for new plans
Scenario: New plan defaults to strategize phase and queued state
Given I create a subplan-test default plan
Then the subplan-test plan phase should be "strategize"
And the subplan-test plan processing state should be "queued"
And the subplan-test plan should not be terminal
And the subplan-test plan should not be errored
Scenario: New plan defaults to no automation profile
Given I create a subplan-test default plan
Then the subplan-test plan automation profile should be none
Scenario: New plan has no subplan config by default
Given I create a subplan-test default plan
Then the subplan-test plan subplan_config should be none
Scenario: New plan has empty subplan statuses by default
Given I create a subplan-test default plan
Then the subplan-test plan should have 0 subplan statuses
# Subplan config storage and retrieval
Scenario: Plan stores sequential subplan config
Given I create a subplan-test plan with sequential subplan config
Then the subplan-test plan subplan_config execution_mode should be "sequential"
And the subplan-test plan subplan_config merge_strategy should be "git_three_way"
And the subplan-test plan subplan_config fail_fast should be false
Scenario: Plan stores parallel subplan config with custom settings
Given I create a subplan-test plan with parallel subplan config
Then the subplan-test plan subplan_config execution_mode should be "parallel"
And the subplan-test plan subplan_config max_parallel should be 10
And the subplan-test plan subplan_config retry_failed should be true
And the subplan-test plan subplan_config max_retries should be 3
Scenario: SubplanConfig defaults are sensible
Given I create a subplan-test default subplan config
Then the subplan-test subplan config execution_mode should be "sequential"
And the subplan-test subplan config merge_strategy should be "git_three_way"
And the subplan-test subplan config max_parallel should be 5
And the subplan-test subplan config fail_fast should be false
And the subplan-test subplan config retry_failed should be true
And the subplan-test subplan config max_retries should be 2
And the subplan-test subplan config timeout should be none
# Plan hierarchy helpers
Scenario: Plan with subplan statuses reports has_subplans true
Given I create a subplan-test plan with one subplan status
Then the subplan-test plan should have subplans
Scenario: Plan without subplan statuses reports has_subplans false
Given I create a subplan-test default plan
Then the subplan-test plan should not have subplans
Scenario: Depth returns negative one for non-root plans
Given I create a subplan-test child plan with a parent
Then the subplan-test plan depth should be -1
# Child plans follow same lifecycle as root plans
Scenario: Child plan transitions follow standard lifecycle
Then subplan-test transition from "action" to "strategize" should be valid
And subplan-test transition from "strategize" to "execute" should be valid
And subplan-test transition from "execute" to "apply" should be valid
Scenario: Child plan invalid transitions are rejected
Then subplan-test transition from "strategize" to "apply" should be invalid
And subplan-test transition from "action" to "execute" should be invalid
And subplan-test transition from "action" to "apply" should be invalid
# Dependency guardrails
Scenario: SubplanFailureHandler stops others when fail_fast is true
Given I create a subplan-test fail-fast config with errored status
Then the subplan-test failure handler should stop others
Scenario: SubplanFailureHandler does not stop others for parallel without fail_fast
Given I create a subplan-test parallel-no-fail-fast config with errored status
Then the subplan-test failure handler should not stop others
Scenario: SubplanFailureHandler retries retriable failure
Given I create a subplan-test config with retriable timeout error
Then the subplan-test failure handler should retry
Scenario: SubplanFailureHandler does not retry non-retriable error
Given I create a subplan-test config with non-retriable configuration error
Then the subplan-test failure handler should not retry
Scenario: SubplanFailureHandler does not retry when retries disabled
Given I create a subplan-test config with retries disabled and retriable error
Then the subplan-test failure handler should not retry
Scenario: SubplanFailureHandler does not retry when max retries exceeded
Given I create a subplan-test config with max retries exceeded
Then the subplan-test failure handler should not retry
# CLI dict rendering for subplan hierarchy
Scenario: CLI dict includes parent_plan_id for child plans
Given I create a subplan-test child plan with a parent
And I build a subplan-test plan with the child identity
Then the subplan-test cli dict should contain parent_plan_id
Scenario: CLI dict includes subplan_count when subplans exist
Given I create a subplan-test plan with one subplan status
Then the subplan-test cli dict should contain subplan_count of 1
Scenario: CLI dict omits parent_plan_id for root plans
Given I create a subplan-test root plan
And I build a subplan-test plan with the root identity
Then the subplan-test cli dict should not contain parent_plan_id
# Validation guardrails
Scenario: Invalid ULID for parent_plan_id raises validation error
When I try to create a subplan-test identity with invalid parent ULID
Then a validation error should be raised
Scenario: SubplanConfig max_parallel below minimum raises validation error
When I try to create a subplan-test config with max_parallel 0
Then a validation error should be raised
Scenario: SubplanConfig max_parallel above maximum raises validation error
When I try to create a subplan-test config with max_parallel 51
Then a validation error should be raised
Scenario: SubplanConfig max_retries below minimum raises validation error
When I try to create a subplan-test config with max_retries -1
Then a validation error should be raised
# ============================================================
# Originally from: orguserconfig_coverage.feature
# Feature: Org User Config Domain Model Coverage
# ============================================================
Scenario: Import and instantiate OrgUserConfig with all fields
Given the cleveragents package is available
When I import OrgUserConfig from orguserconfig
Then the OrgUserConfig class should be available
And I can create an OrgUserConfig with all required fields
Scenario: Create OrgUserConfig with prompted_claude_max field
Given the cleveragents package is available
When I create an OrgUserConfig with prompted_claude_max set to True
Then the OrgUserConfig should have prompted_claude_max as True
And the field should be accessible via the alias "promptedClaudeMax"
Scenario: Create OrgUserConfig with use_claude_subscription field
Given the cleveragents package is available
When I create an OrgUserConfig with use_claude_subscription set to False
Then the OrgUserConfig should have use_claude_subscription as False
And the field should be accessible via the alias "useClaudeSubscription"
Scenario: Create OrgUserConfig with claude_subscription_cooldown_started_at field
Given the cleveragents package is available
When I create an OrgUserConfig with a specific cooldown timestamp
Then the OrgUserConfig should have the correct timestamp
And the field should be accessible via the alias "claudeSubscriptionCooldownStartedAt"
Scenario: Validate OrgUserConfig strips whitespace from string fields
Given the cleveragents package is available
When I create an OrgUserConfig with fields containing whitespace
Then OrgUserConfig string fields should have whitespace stripped
Scenario: Validate OrgUserConfig assignment validation
Given the cleveragents package is available
When I create an OrgUserConfig instance
And I update its fields with new values for OrgUserConfig
Then the OrgUserConfig assignment validation should be triggered
And the OrgUserConfig values should be properly validated
Scenario: Validate OrgUserConfig populate by name with camelCase aliases
Given the cleveragents package is available
When I create an OrgUserConfig using camelCase field aliases
Then the OrgUserConfig fields should be populated correctly by name
And both snake_case and camelCase access should work
Scenario: Validate OrgUserConfig with dictionary input using aliases
Given the cleveragents package is available
When I create an OrgUserConfig from a dictionary with camelCase keys
Then the model should correctly map aliased fields
And all fields should be accessible with snake_case names
Scenario: Test OrgUserConfig serialization with aliases
Given the cleveragents package is available
When I create an OrgUserConfig and serialize it
Then the serialized output should use the defined aliases
And the model should be deserializable from the serialized form
Scenario: Test OrgUserConfig validation with invalid datetime
Given the cleveragents package is available
When I try to create an OrgUserConfig with an invalid datetime string
Then an OrgUserConfig validation error should be raised
And the error should indicate the datetime field
Scenario: Test OrgUserConfig validation with missing required fields
Given the cleveragents package is available
When I try to create an OrgUserConfig without required fields
Then an OrgUserConfig validation error should be raised
And the error should list all missing required fields
Scenario: Test OrgUserConfig model_config settings
Given the cleveragents package is available
When I inspect the OrgUserConfig model configuration
Then OrgUserConfig str_strip_whitespace should be True
And OrgUserConfig validate_assignment should be True
And OrgUserConfig arbitrary_types_allowed should be False
And OrgUserConfig populate_by_name should be True
And OrgUserConfig use_enum_values should be True
Scenario: Test OrgUserConfig with timezone-aware datetime
Given the cleveragents package is available
When I create an OrgUserConfig with a timezone-aware datetime
Then the model should handle the timezone information correctly
And the datetime should be properly stored
Scenario: Test OrgUserConfig copy with update
Given the cleveragents package is available
When I create an OrgUserConfig and copy it with updates
Then the OrgUserConfig copy should have the updated values
And the OrgUserConfig original should remain unchanged
Scenario: Test OrgUserConfig model_dump with aliases
Given the cleveragents package is available
When I create an OrgUserConfig and call model_dump with by_alias=True
Then the output should use camelCase aliases
When I call model_dump with by_alias=False
Then the output should use snake_case field names
Scenario: Test OrgUserConfig model_dump_json
Given the cleveragents package is available
When I create an OrgUserConfig and call model_dump_json
Then the JSON output should be valid
And it should use the configured aliases by default
Scenario: Test OrgUserConfig field types validation
Given the cleveragents package is available
When I try to create an OrgUserConfig with wrong field types
Then validation errors should be raised for type mismatches
And the OrgUserConfig errors should clearly indicate the expected types
Scenario: Test OrgUserConfig __init__ coverage
Given the cleveragents package is available
When I import the orguserconfig __init__ module
Then the module should be loaded successfully
And the OrgUserConfig class should be importable from it