@phase2 @acms @strategy Feature: Context Strategy Registry As a CleverAgents developer I want to register and configure context strategies So that the ACMS pipeline can select and execute them # --------------------------------------------------------------------------- # Strategy protocol conformance # --------------------------------------------------------------------------- @strategy_protocol Scenario: All built-in strategies satisfy the ContextStrategy protocol Given all built-in strategies are instantiated Then every strategy should satisfy the ContextStrategy protocol And every strategy should have a non-empty name And every strategy should have a capabilities object @strategy_protocol Scenario: Built-in strategy quality scores match specification Given all built-in strategies are instantiated Then the strategy "simple-keyword" should have quality score 0.3 And the strategy "semantic-embedding" should have quality score 0.6 And the strategy "breadth-depth-navigator" should have quality score 0.85 And the strategy "arce" should have quality score 0.95 And the strategy "temporal-archaeology" should have quality score 0.5 And the strategy "plan-decision-context" should have quality score 0.7 # --------------------------------------------------------------------------- # Registry registration # --------------------------------------------------------------------------- @strategy_registry Scenario: Register a single strategy Given an empty strategy registry When I register the "simple-keyword" built-in strategy Then the strategy registry should contain "simple-keyword" And the registry size should be 1 @strategy_registry Scenario: Register all built-in strategies Given an empty strategy registry When I register all 6 built-in strategies Then the registry should contain 6 strategies And the strategy registry should contain "simple-keyword" And the strategy registry should contain "semantic-embedding" And the strategy registry should contain "breadth-depth-navigator" And the strategy registry should contain "arce" And the strategy registry should contain "temporal-archaeology" And the strategy registry should contain "plan-decision-context" @strategy_registry @error_handling Scenario: Reject duplicate strategy registration Given an empty strategy registry And I register the "simple-keyword" built-in strategy When I attempt to register "simple-keyword" again Then a StrategyRegistrationError should be raised @strategy_registry @error_handling Scenario: Reject strategy with empty name Given an empty strategy registry When I attempt to register a strategy with an empty name Then a StrategyRegistrationError should be raised # --------------------------------------------------------------------------- # Registry query # --------------------------------------------------------------------------- @strategy_query Scenario: Get a registered strategy by name Given a registry with all built-in strategies When I get the strategy "simple-keyword" Then the strategy name should be "simple-keyword" @strategy_query @error_handling Scenario: Get a non-existent strategy raises error Given a registry with all built-in strategies When I attempt to get the strategy "nonexistent" Then a StrategyNotFoundError should be raised @strategy_query Scenario: List all registered strategy names Given a registry with all built-in strategies Then the registry should list 6 strategies And the list should be sorted alphabetically # --------------------------------------------------------------------------- # Enable / disable # --------------------------------------------------------------------------- @strategy_config Scenario: Default enabled list matches spec Given a registry with all built-in strategies using default enabled list Then the enabled strategies should be "simple-keyword", "semantic-embedding", "breadth-depth-navigator" @strategy_config Scenario: Override enabled list per project Given a registry with all built-in strategies using default enabled list When I set the enabled list to "simple-keyword", "arce" Then the enabled strategies should be "simple-keyword", "arce" @strategy_config Scenario: Set enabled list deduplicates duplicate names Given a registry with all built-in strategies using default enabled list When I set the enabled list to "simple-keyword", "arce", "simple-keyword" Then the enabled strategies should be "simple-keyword", "arce" @strategy_config @error_handling Scenario: Set enabled list with unknown strategy raises error Given a registry with all built-in strategies using default enabled list When I attempt to set the enabled list to "nonexistent" Then a StrategyNotFoundError should be raised @strategy_config Scenario: Disabled strategy excluded from enabled list Given a registry with all built-in strategies using default enabled list When I disable the strategy "semantic-embedding" Then the enabled strategies should not include "semantic-embedding" # --------------------------------------------------------------------------- # Per-strategy configuration # --------------------------------------------------------------------------- @strategy_config Scenario: Default strategy config values Given a registry with all built-in strategies When I get the config for "simple-keyword" Then the timeout_seconds should be 30 And the max_fragments should be 100 And the circuit_breaker_threshold should be 3 @strategy_config Scenario: Update per-strategy timeout Given a registry with all built-in strategies When I update config for "simple-keyword" with timeout_seconds 10 Then the config for "simple-keyword" should have timeout_seconds 10 # --------------------------------------------------------------------------- # can_handle with backends # --------------------------------------------------------------------------- @strategy_can_handle Scenario: simple-keyword returns quality score with text backend Given a BackendSet with text backend only And a default ContextRequest Then "simple-keyword" can_handle should return 0.3 @strategy_can_handle Scenario: simple-keyword returns 0 without text backend Given a BackendSet with no backends And a default ContextRequest Then "simple-keyword" can_handle should return 0.0 @strategy_can_handle Scenario: semantic-embedding returns quality score with vector backend Given a BackendSet with vector backend only And a default ContextRequest Then "semantic-embedding" can_handle should return 0.6 @strategy_can_handle Scenario: arce requires all backends Given a BackendSet with all backends And a default ContextRequest Then "arce" can_handle should return 0.95 @strategy_can_handle Scenario: arce returns 0 when missing a backend Given a BackendSet with text backend only And a default ContextRequest Then "arce" can_handle should return 0.0 @strategy_can_handle Scenario: plan-decision-context returns quality score with temporal backend Given a BackendSet with temporal backend only And a default ContextRequest Then "plan-decision-context" can_handle should return 0.7 # --------------------------------------------------------------------------- # Strategy assemble — empty backends return empty fragment lists # --------------------------------------------------------------------------- @strategy_assemble Scenario: Strategies return empty fragment lists when backends return no data Given all built-in strategies are instantiated And a BackendSet with all backends And a default ContextRequest And a default PlanContext When each strategy assembles with budget 1000 Then every strategy should return an empty fragment list # --------------------------------------------------------------------------- # Strategy assemble — populated backends return non-empty fragment lists # --------------------------------------------------------------------------- @strategy_assemble @real_retrieval Scenario: SimpleKeywordStrategy returns fragments from text backend Given all built-in strategies are instantiated And a BackendSet with a populated text backend And a ContextRequest with query "authentication" And a default PlanContext When the "simple-keyword" strategy assembles with budget 10000 Then the "simple-keyword" strategy should return at least 1 fragment @strategy_assemble @real_retrieval Scenario: SemanticEmbeddingStrategy returns fragments from vector backend Given all built-in strategies are instantiated And a BackendSet with a populated vector backend And a ContextRequest with query "authentication" And a default PlanContext When the "semantic-embedding" strategy assembles with budget 10000 Then the "semantic-embedding" strategy should return at least 1 fragment @strategy_assemble @real_retrieval Scenario: BreadthDepthNavigatorStrategy returns fragments from graph backend Given all built-in strategies are instantiated And a BackendSet with a populated graph backend And a ContextRequest with focus "uko:class/AuthManager" And a default PlanContext When the "breadth-depth-navigator" strategy assembles with budget 10000 Then the "breadth-depth-navigator" strategy should return at least 1 fragment @strategy_assemble @real_retrieval Scenario: ARCEStrategy returns fragments from all backends Given all built-in strategies are instantiated And a BackendSet with all populated backends And a ContextRequest with query "authentication" And a default PlanContext When the "arce" strategy assembles with budget 10000 Then the "arce" strategy should return at least 1 fragment @strategy_assemble @real_retrieval Scenario: TemporalArchaeologyStrategy returns fragments from temporal backend Given all built-in strategies are instantiated And a BackendSet with populated graph and temporal backends And a default ContextRequest And a default PlanContext When the "temporal-archaeology" strategy assembles with budget 10000 Then the "temporal-archaeology" strategy should return at least 1 fragment @strategy_assemble @real_retrieval Scenario: PlanDecisionContextStrategy returns fragments from temporal backend Given all built-in strategies are instantiated And a BackendSet with a populated temporal backend And a default ContextRequest And a default PlanContext When the "plan-decision-context" strategy assembles with budget 10000 Then the "plan-decision-context" strategy should return at least 1 fragment # --------------------------------------------------------------------------- # ContextStrategyResult deterministic ordering # --------------------------------------------------------------------------- @strategy_result Scenario: ContextStrategyResult sorts fragments deterministically Given a ContextStrategyResult with unordered fragments Then the fragments should be sorted by relevance_score DESC then uko_node ASC @strategy_result Scenario: ContextStrategyResult with no fragments Given a ContextStrategyResult with no fragments Then the total_fragments should be 0 And the tokens_used should be 0 # --------------------------------------------------------------------------- # Registry validation # --------------------------------------------------------------------------- @strategy_validation Scenario: Validate registry warns about missing resource types Given a registry with a strategy that declares no resource types When I validate the registry Then validation warnings should mention "resource types" @strategy_validation Scenario: Validate registry warns about missing capabilities Given a registry with a strategy that declares no backend capabilities When I validate the registry Then validation warnings should mention "backend capabilities" @strategy_validation Scenario: Valid registry returns no warnings for built-in strategies Given a registry with all built-in strategies When I validate the registry Then there should be no validation warnings # --------------------------------------------------------------------------- # Unregister # --------------------------------------------------------------------------- # --------------------------------------------------------------------------- # Plugin discovery (register_from_module) # --------------------------------------------------------------------------- @strategy_registry @plugin Scenario: Register a strategy from a module path Given an empty strategy registry When I register a strategy from module "cleveragents.domain.models.acms.strategy_stubs:SimpleKeywordStrategy" Then the strategy registry should contain "simple-keyword" And the registry size should be 1 @strategy_registry @plugin @error_handling Scenario: Reject module path without colon separator Given an empty strategy registry When I attempt to register a strategy from module "no_colon_here" Then a StrategyRegistrationError should be raised @strategy_registry @plugin @error_handling Scenario: Reject module path with missing module Given an empty strategy registry When I attempt to register a strategy from module "nonexistent.module:FakeClass" Then a StrategyRegistrationError should be raised @strategy_registry @plugin @error_handling Scenario: Reject module path with missing class Given an empty strategy registry When I attempt to register a strategy from module "cleveragents.domain.models.acms.strategy_stubs:NonExistentClass" Then a StrategyRegistrationError should be raised @strategy_registry @plugin @error_handling Scenario: Reject module path when instantiation fails Given an empty strategy registry When I attempt to register a strategy from module "cleveragents.domain.models.acms.strategy_stubs:BUILTIN_STRATEGY_CLASSES" Then a StrategyRegistrationError should be raised # --------------------------------------------------------------------------- # Additional query coverage # --------------------------------------------------------------------------- @strategy_query Scenario: Get entry for a registered strategy Given a registry with all built-in strategies When I get the entry for "simple-keyword" Then the entry name should be "simple-keyword" And the entry should be marked as builtin @strategy_query @error_handling Scenario: Get entry for non-existent strategy raises error Given a registry with all built-in strategies When I attempt to get the entry for "nonexistent" Then a StrategyNotFoundError should be raised @strategy_query Scenario: List built-in strategies Given a registry with all built-in strategies Then the builtin list should contain 6 strategies @strategy_query Scenario: Registry supports the in operator Given a registry with all built-in strategies Then "simple-keyword" should be in the registry via contains And "nonexistent" should not be in the registry via contains # --------------------------------------------------------------------------- # Validation edge case: enabled-but-unregistered # --------------------------------------------------------------------------- @strategy_validation Scenario: Validate warns when enabled list references unregistered strategy Given a registry with a stale enabled list entry When I validate the registry Then validation warnings should mention "not registered" # --------------------------------------------------------------------------- # Unregister error path # --------------------------------------------------------------------------- @strategy_registry @error_handling Scenario: Unregister non-existent strategy raises error Given a registry with all built-in strategies When I attempt to unregister "nonexistent" Then a StrategyNotFoundError should be raised # --------------------------------------------------------------------------- # Unregister # --------------------------------------------------------------------------- @strategy_registry Scenario: Unregister a strategy Given a registry with all built-in strategies When I unregister "temporal-archaeology" Then the registry should contain 5 strategies And the strategy registry should not contain "temporal-archaeology" @strategy_registry Scenario: Clear the registry Given a registry with all built-in strategies When I clear the registry Then the registry should contain 0 strategies # --------------------------------------------------------------------------- # Boundary tests for Pydantic-validated config fields # --------------------------------------------------------------------------- @strategy_config @boundary Scenario Outline: Reject invalid StrategyConfig numeric values When I attempt to create a StrategyConfig with set to Then a Pydantic ValidationError should be raised Examples: | field | value | | timeout_seconds | 0 | | timeout_seconds | -1 | | max_fragments | 0 | | max_fragments | -1 | | max_workers | 0 | | circuit_breaker_threshold | 0 | @strategy_config @boundary Scenario Outline: Reject invalid quality_score values When I attempt to create a StrategyCapabilities with quality_score Then a Pydantic ValidationError should be raised Examples: | value | | -0.1 | | 1.1 | @strategy_config @boundary Scenario: Accept quality_score at lower bound 0.0 When I create a StrategyCapabilities with quality_score 0.0 Then the quality_score should be 0.0 @strategy_config @boundary Scenario: Accept quality_score at upper bound 1.0 When I create a StrategyCapabilities with quality_score 1.0 Then the quality_score should be 1.0 # --------------------------------------------------------------------------- # Bug regression tests # --------------------------------------------------------------------------- @strategy_config @regression Scenario: update_config enabled=True adds strategy to enabled list Given an empty strategy registry When I register the "simple-keyword" built-in strategy with enabled False Then the enabled strategies should be "" When I update config for "simple-keyword" with enabled True Then the enabled strategies should include "simple-keyword" @strategy_config @regression Scenario: update_config enabled=False removes strategy from enabled list Given a registry with all built-in strategies using default enabled list When I update config for "simple-keyword" with enabled False Then the enabled strategies should not include "simple-keyword" @strategy_registry @plugin @regression Scenario: register_from_module uses the provided name parameter Given an empty strategy registry When I register a strategy named "custom-search" from module "cleveragents.domain.models.acms.strategy_stubs:SimpleKeywordStrategy" Then the strategy registry should contain "custom-search" And the registry size should be 1 @strategy_config @regression Scenario: update_config with max_workers and circuit_breaker_threshold Given a registry with all built-in strategies When I update config for "simple-keyword" with max_workers 8 and circuit_breaker_threshold 5 Then the config for "simple-keyword" should have max_workers 8 And the config for "simple-keyword" should have circuit_breaker_threshold 5 @strategy_registry @error_handling Scenario: Reject object that does not satisfy ContextStrategy protocol Given an empty strategy registry When I attempt to register a non-protocol object Then a StrategyRegistrationError should be raised @strategy_registry @error_handling Scenario: Reject non-protocol object without explicit name Given an empty strategy registry When I attempt to register a non-protocol object without a name Then a StrategyRegistrationError should be raised And the error message should contain "does not satisfy" # --------------------------------------------------------------------------- # explain() coverage # --------------------------------------------------------------------------- @strategy_protocol Scenario: Every built-in strategy returns a non-empty explain string Given all built-in strategies are instantiated Then every strategy should return a non-empty explain string # --------------------------------------------------------------------------- # can_handle coverage for strategies not tested elsewhere # --------------------------------------------------------------------------- @strategy_can_handle Scenario: breadth-depth-navigator returns quality score with graph backend Given a BackendSet with graph backend only And a default ContextRequest Then "breadth-depth-navigator" can_handle should return 0.85 @strategy_can_handle Scenario: breadth-depth-navigator returns 0 without graph backend Given a BackendSet with no backends And a default ContextRequest Then "breadth-depth-navigator" can_handle should return 0.0 @strategy_can_handle Scenario: temporal-archaeology returns quality score with graph and temporal backends Given a BackendSet with graph and temporal backends And a default ContextRequest Then "temporal-archaeology" can_handle should return 0.5 @strategy_can_handle Scenario: temporal-archaeology returns 0 without graph backend Given a BackendSet with no backends And a default ContextRequest Then "temporal-archaeology" can_handle should return 0.0 @strategy_can_handle Scenario: semantic-embedding returns 0 without vector backend Given a BackendSet with no backends And a default ContextRequest Then "semantic-embedding" can_handle should return 0.0 @strategy_config @regression Scenario: update_config rejects invalid timeout via Pydantic validation Given a registry with all built-in strategies When I attempt to update config for "simple-keyword" with timeout_seconds 0 Then a Pydantic ValidationError should be raised @strategy_config @regression Scenario: update_config rejects negative max_fragments via Pydantic validation Given a registry with all built-in strategies When I attempt to update config for "simple-keyword" with max_fragments -1 Then a Pydantic ValidationError should be raised # --------------------------------------------------------------------------- # update_config: resource_types and extra parameters # --------------------------------------------------------------------------- @strategy_config @regression Scenario: update_config with resource_types Given a registry with all built-in strategies When I update config for "simple-keyword" with resource_types "code", "doc" Then the config for "simple-keyword" should have resource_types "code", "doc" @strategy_config @regression Scenario: update_config with extra dict Given a registry with all built-in strategies When I update config for "simple-keyword" with extra key "boost" value "2" Then the config for "simple-keyword" extra should contain key "boost" And the config for "simple-keyword" extra should be immutable # --------------------------------------------------------------------------- # MappingProxyType coercion validators # --------------------------------------------------------------------------- @strategy_config @boundary Scenario: StrategyConfig accepts plain dict for extra field When I create a StrategyConfig with extra as a plain dict Then the extra field should be a MappingProxyType @strategy_config @boundary Scenario: StrategyConfig rejects non-dict non-MappingProxyType extra When I attempt to create a StrategyConfig with extra as an integer Then a Pydantic ValidationError should be raised @strategy_result @boundary Scenario: ContextStrategyResult accepts plain dict for stats field When I create a ContextStrategyResult with stats as a plain dict Then the stats field should be a MappingProxyType # --------------------------------------------------------------------------- # Thread safety # --------------------------------------------------------------------------- @strategy_registry @thread_safety Scenario: Concurrent register and list_enabled do not raise Given a registry with all built-in strategies When 10 threads concurrently register and query the registry Then no thread should have raised an exception And the registry should contain at least 10 strategies And every thread-registered strategy should be present # --------------------------------------------------------------------------- # temporal/cold-tier can_handle regression # --------------------------------------------------------------------------- @strategy_can_handle @regression Scenario: temporal-archaeology returns 0 without temporal backend Given a BackendSet with graph backend only And a default ContextRequest Then "temporal-archaeology" can_handle should return 0.0 @strategy_can_handle @regression Scenario: plan-decision-context returns 0 without temporal backend Given a BackendSet with no backends And a default ContextRequest Then "plan-decision-context" can_handle should return 0.0