@mock_only Feature: LLM-powered Strategy Actor Exercises the StrategyActor that uses an LLM to produce hierarchical execution strategies with dependencies, resource requirements, complexity estimates, and risk scores. Falls back to stub behaviour when no LLM provider is configured. Forgejo: #828 # --------------------------------------------------------------- # StrategyActor initialization # --------------------------------------------------------------- Scenario: StrategyActor initializes without provider (stub mode) When I create a StrategyActor without a provider registry Then the StrategyActor should be created successfully And the StrategyActor should not have LLM capability Scenario: StrategyActor initializes with provider (LLM mode) Given a mock provider registry for strategy actor When I create a StrategyActor with the provider registry Then the StrategyActor should be created successfully And the StrategyActor should have LLM capability # --------------------------------------------------------------- # StrategyActor.execute validation # --------------------------------------------------------------- Scenario: StrategyActor rejects empty plan_id Given a StrategyActor in stub mode When I call strategy actor execute with empty plan_id Then sa828 a ValidationError should be raised with message "plan_id must not be empty" # --------------------------------------------------------------- # Stub mode execution # --------------------------------------------------------------- Scenario: StrategyActor stub mode parses bullet points Given a StrategyActor in stub mode When I execute strategy for plan "01HX00000000005T8B1NE00001" with definition "- Setup project\n- Implement feature\n- Write tests" Then the strategy result should contain 3 decisions And strategy decision 0 should contain "Setup project" Scenario: StrategyActor stub mode uses default when no definition Given a StrategyActor in stub mode When I execute strategy for plan "01HX00000000005T8B1NE00002" with None definition Then the strategy result should contain at least 1 decision Scenario: StrategyActor stub mode with stream callback Given a StrategyActor in stub mode When I execute strategy for plan "01HX00000000005T8B1NE00003" with callback and definition "- Step one\n- Step two" Then the strategy stream callback should have received "strategize_started" And the strategy stream callback should have received "strategize_decisions" And the strategy stream callback should have received "strategize_complete" Scenario: StrategyActor stub mode with invariants Given a StrategyActor in stub mode When I execute strategy for plan "01HX00000000005T8B1NE00004" with invariants Then the strategy result should contain invariant records And the strategy invariant records should have enforcement notes # --------------------------------------------------------------- # LLM mode execution # --------------------------------------------------------------- Scenario: StrategyActor LLM mode generates structured strategy from JSON Given a StrategyActor with a mock LLM returning JSON strategy When I execute strategy for plan "01HX0000000000QQMRNE000001" with definition "Build a REST API with authentication" Then the strategy result should contain 5 decisions And strategy decision 0 should contain "Set up project scaffolding" Scenario: StrategyActor LLM mode falls back on numbered list response Given a StrategyActor with a mock LLM returning numbered list When I execute strategy for plan "01HX0000000000QQMRNE000002" with definition "Build a module" Then the strategy result should contain 5 decisions Scenario: StrategyActor LLM mode falls back on non-JSON text response Given a StrategyActor with a mock LLM returning non-JSON text When I execute strategy for plan "01HX0000000000QQMRNE000003" with definition "Build a feature" Then the strategy result should contain 3 decisions Scenario: StrategyActor LLM mode falls back to stub on LLM error Given a StrategyActor with a failing mock LLM When I execute strategy for plan "01HX0000000000QQMRNE000004" with definition "- Step A\n- Step B" Then the strategy result should contain 2 decisions And strategy decision 0 should contain "Step A" Scenario: StrategyActor LLM mode with ACMS context Given a StrategyActor with a mock LLM and ACMS pipeline When I execute strategy for plan "01HX0000000000QQMRNE000005" with definition "Refactor the service layer" Then the strategy result should contain 5 decisions Scenario: StrategyActor LLM mode with failing ACMS pipeline Given a StrategyActor with a mock LLM and failing ACMS pipeline When I execute strategy for plan "01HX0000000000QQMRNE000006" with definition "Refactor the service layer" Then the strategy result should contain 5 decisions Scenario: StrategyActor LLM mode with resources and project context Given a StrategyActor with a mock LLM returning JSON strategy When I execute strategy for plan "01HX0000000000QQMRNE000007" with resources and context Then the strategy result should contain 5 decisions # --------------------------------------------------------------- # Dependency graph validation # --------------------------------------------------------------- Scenario: Validate acyclic dependency graph passes When I validate a dependency graph with no cycles Then the dependency validation should pass Scenario: Validate empty dependency graph passes When I validate an empty dependency graph Then the dependency validation should pass Scenario: Validate cyclic dependency graph raises PlanError When I validate a dependency graph with a cycle Then sa828 a PlanError should be raised with cycle message # --------------------------------------------------------------- # Strategy response parsing # --------------------------------------------------------------- Scenario: Parse valid JSON strategy response When I parse a JSON strategy response Then I should get 5 strategy actions And strategy action 0 should have description "Set up project scaffolding and configuration" And strategy action 0 should have complexity "low" And strategy action 0 should have risk score 0.1 And strategy action 1 should have resource requirements Scenario: Parse numbered list strategy response When I parse a numbered list strategy response Then I should get 5 strategy actions Scenario: Parse empty strategy response When I parse an empty strategy response Then I should get the default strategy action Scenario: Parse JSON with partial fields When I parse a JSON strategy response with partial fields Then I should get 2 strategy actions And strategy action 1 should have complexity "medium" And strategy action 1 should have risk score 0.3 # --------------------------------------------------------------- # Strategy tree and Decision conversion # --------------------------------------------------------------- Scenario: Build Decision objects from strategy tree Given a StrategyActor in stub mode When I build decisions from strategy tree for plan "01HX0000000000DEC1NE000001" Then I should get Decision objects of type strategy_choice And the first Decision should be of type prompt_definition And all decisions should have plan_id "01HX0000000000DEC1NE000001" And decision sequence_numbers should be monotonically increasing from zero # --------------------------------------------------------------- # Prompt construction # --------------------------------------------------------------- Scenario: Build strategy prompt with all context When I build a strategy prompt with definition resources and context Then the prompt should contain the definition of done And the prompt should contain resource information And the prompt should contain project context And the prompt should contain ACMS context Scenario: Build strategy prompt with minimal context When I build a strategy prompt with only definition Then the prompt should contain the definition of done And the prompt should not contain resource information # --------------------------------------------------------------- # resolve_strategy_actor integration point # --------------------------------------------------------------- Scenario: Resolve strategy actor with LLM config When I resolve strategy actor with config "llm" Then the resolved actor should be a StrategyActor Scenario: Resolve strategy actor with stub config When I resolve strategy actor with config "stub" Then the resolved actor should be None Scenario: Resolve strategy actor with provider registry When I resolve strategy actor with a provider registry Then the resolved actor should be a StrategyActor Scenario: Resolve strategy actor with no config and no registry When I resolve strategy actor with no config and no registry Then the resolved actor should be None # --------------------------------------------------------------- # LLM mode with empty response # --------------------------------------------------------------- Scenario: StrategyActor LLM mode handles empty LLM response Given a StrategyActor with a mock LLM returning empty response When I execute strategy for plan "01HX0000000000QQMRNE000008" with definition "Build a feature" Then the strategy result should contain at least 1 decision # --------------------------------------------------------------- # _parse_actor_name helper # --------------------------------------------------------------- Scenario: Parse strategy actor name with provider/model When I parse strategy actor name "anthropic/claude-3" Then the strategy parsed provider should be "anthropic" And the strategy parsed model should be "claude-3" Scenario: Parse strategy actor name with only model When I parse strategy actor name "gpt-4" Then the strategy parsed provider should be "openai" And the strategy parsed model should be "gpt-4" Scenario: Parse strategy actor name with empty string When I parse strategy actor name "" Then the strategy parsed provider should be "openai" And the strategy parsed model should be "gpt-4" @tdd_issue @tdd_issue_11254 Scenario: Parse strategy actor name with namespace/name format When I parse strategy actor name "local/my-strategist" Then the strategy parsed provider should be "openai" And the strategy parsed model should be "local/my-strategist" @tdd_issue @tdd_issue_11254 Scenario: Parse strategy actor name with org namespace/name format When I parse strategy actor name "cleverthis/my-executor" Then the strategy parsed provider should be "openai" And the strategy parsed model should be "cleverthis/my-executor" @tdd_issue @tdd_issue_11254 Scenario: Parse strategy actor name with unknown-provider-like segment When I parse strategy actor name "unknown/gpt-4" Then the strategy parsed provider should be "openai" And the strategy parsed model should be "unknown/gpt-4" @tdd_issue @tdd_issue_11254 Scenario: _is_known_provider recognises valid providers When I check if "openai" is a known strategy provider Then the strategy provider check should be true @tdd_issue @tdd_issue_11254 Scenario: _is_known_provider rejects namespace prefix When I check if "local" is a known strategy provider Then the strategy provider check should be false # --------------------------------------------------------------- # Cyclic dependency detection through LLM execute path (H8) # --------------------------------------------------------------- Scenario: StrategyActor LLM mode raises PlanError on cyclic dependencies Given a StrategyActor with a mock LLM returning cyclic dependencies When I execute strategy for plan "01HX0000000000QQMRNE000009" expecting cycle error Then sa828 a PlanError should be raised with cycle message # --------------------------------------------------------------- # Dependency edge resolution (M8) # --------------------------------------------------------------- Scenario: LLM JSON strategy resolves dependency edges correctly Given a StrategyActor with a mock LLM returning JSON strategy When I execute strategy for plan "01HX0000000000QQMRNE00000A" and inspect the tree Then the strategy tree should have dependency edges And strategy action 1 should depend on action 0 # --------------------------------------------------------------- # LLM response without content attribute (M9) # --------------------------------------------------------------- Scenario: Parse strategy response from LLM without content attribute When I parse a strategy response from an LLM without content attribute Then the strategy result should contain decisions # --------------------------------------------------------------- # build_decisions with empty plan_id (M10) # --------------------------------------------------------------- Scenario: build_decisions rejects empty plan_id Given a StrategyActor in stub mode When I call build_decisions with empty plan_id Then sa828 a ValidationError should be raised with message "plan_id must not be empty" # --------------------------------------------------------------- # Empty JSON array response (M11) # --------------------------------------------------------------- Scenario: Parse empty JSON array returns default action When I parse an empty JSON array response Then I should get the default strategy action # --------------------------------------------------------------- # LLM response with list content attribute (T1) # --------------------------------------------------------------- Scenario: StrategyActor handles LLM response where content is a list When I parse a strategy response from an LLM with list content attribute Then the strategy result should contain decisions # --------------------------------------------------------------- # build_decisions multi-level hierarchy (T2) # --------------------------------------------------------------- Scenario: build_decisions maps parent_id correctly across hierarchy levels Given a StrategyActor in stub mode When I build decisions from a multi-level strategy tree for plan "01HX0000000000DEC1NE000002" Then the second-level decisions should reference the mid-level parent decision # --------------------------------------------------------------- # LLM prompt content verification (M6) # --------------------------------------------------------------- Scenario: LLM receives correct SystemMessage and HumanMessage prompt Given a StrategyActor with a mock LLM returning JSON strategy When I execute strategy for plan "01HX0000000000QQMRNE00000D" with resources and context Then the mock LLM should have been called with a SystemMessage and a HumanMessage And the HumanMessage should contain "Build REST API" And the HumanMessage should contain "source-code" And the HumanMessage should contain "FastAPI project with PostgreSQL backend" # --------------------------------------------------------------- # Prompt truncation (M7) # --------------------------------------------------------------- Scenario: build_strategy_prompt truncates oversized definition_of_done When I build a strategy prompt with a definition exceeding the max length Then the prompt length should be within the truncation limit Scenario: build_strategy_prompt truncates oversized definition at word boundary When I build a strategy prompt with a word-spaced definition exceeding the max length Then the prompt length should be within the truncation limit And the truncated definition should end with an ellipsis # --------------------------------------------------------------- # Risk score clamping (M9) # --------------------------------------------------------------- Scenario: Parse JSON with out-of-range risk score clamps to valid range When I parse a JSON strategy response with risk score above 1.0 Then strategy action 0 should have risk score 1.0 # --------------------------------------------------------------- # Self-dependency filtering (M10) # --------------------------------------------------------------- Scenario: Parse JSON with self-referencing dependency silently filters it When I parse a JSON strategy response with self-dependency Then the strategy tree should have no self-dependency edges # --------------------------------------------------------------- # Multi-slash actor name parsing (L2) # --------------------------------------------------------------- Scenario: Parse strategy actor name with multiple slashes When I parse strategy actor name "provider/model/version" Then the strategy parsed provider should be "openai" And the strategy parsed model should be "provider/model/version" # --------------------------------------------------------------- # Actor name with empty segments (M2 fix) # --------------------------------------------------------------- Scenario: Parse strategy actor name with slash only defaults When I parse strategy actor name "/" Then the strategy parsed provider should be "openai" And the strategy parsed model should be "gpt-4" Scenario: Parse strategy actor name with empty provider uses default provider When I parse strategy actor name "/model-x" Then the strategy parsed provider should be "openai" And the strategy parsed model should be "model-x" Scenario: Parse strategy actor name with empty model uses default model When I parse strategy actor name "provider/" Then the strategy parsed provider should be "openai" And the strategy parsed model should be "provider/" # --------------------------------------------------------------- # Whitespace-only definition_of_done (L5 fix) # --------------------------------------------------------------- Scenario: StrategyActor stub mode with whitespace-only definition Given a StrategyActor in stub mode When I execute strategy for plan "01HX00000000005T8B1NE00005" with definition " " Then the strategy result should contain at least 1 decision # --------------------------------------------------------------- # build_decisions with empty actions list (L3 fix) # --------------------------------------------------------------- Scenario: build_decisions with empty actions returns empty list Given a StrategyActor in stub mode When I call build_decisions with empty actions for plan "01HX0000000000DEC1NE000003" Then the decisions list should be empty # --------------------------------------------------------------- # Resource list truncation (L6 / M1 fix) # --------------------------------------------------------------- Scenario: build_strategy_prompt truncates oversized resource list When I build a strategy prompt with a resource list exceeding the max count Then the prompt should contain truncation indicator # --------------------------------------------------------------- # Lifecycle with strategy_actor=None (L4 fix) # --------------------------------------------------------------- Scenario: StrategyActor LLM mode with lifecycle returning null strategy_actor Given a StrategyActor with a mock LLM and lifecycle with null strategy_actor When I execute strategy for plan "01HX0000000000QQMRNE00000F" with definition "Build a feature" Then the strategy result should contain decisions # --------------------------------------------------------------- # Duplicate step number collision (H1 review fix) # --------------------------------------------------------------- Scenario: LLM JSON with duplicate step numbers produces unique action IDs When I parse a JSON strategy response with duplicate step numbers Then all strategy tree actions should have unique action IDs # --------------------------------------------------------------- # _MAX_CONTEXT_CHARS truncation (M9 review fix) # --------------------------------------------------------------- Scenario: build_strategy_prompt truncates oversized project_context When I build a strategy prompt with project_context exceeding the max length Then the project_context in the prompt should be within the truncation limit # --------------------------------------------------------------- # ACMS context prompt inclusion (M10 review fix) # --------------------------------------------------------------- Scenario: LLM prompt includes ACMS context when pipeline is available Given a StrategyActor with a mock LLM and ACMS pipeline When I execute strategy for plan "01HX0000000000QQMRNE00000G" with resources and context Then the mock LLM should have been called with a SystemMessage and a HumanMessage And the HumanMessage should contain "Python 3.12, FastAPI, SQLAlchemy" # --------------------------------------------------------------- # LLM response str() fallback (L1 review fix) # --------------------------------------------------------------- Scenario: Parse strategy response from LLM with no content or text attribute When I parse a strategy response from an LLM with no content or text attribute Then the strategy result should contain decisions # --------------------------------------------------------------- # SystemMessage content verification (L2 review fix) # --------------------------------------------------------------- Scenario: LLM receives correct SystemMessage content Given a StrategyActor with a mock LLM returning JSON strategy When I execute strategy for plan "01HX0000000000QQMRNE00000H" with resources and context Then the mock LLM should have been called with a SystemMessage and a HumanMessage And the SystemMessage should contain "expert software architect" # --------------------------------------------------------------- # Resolve strategy actor with unknown config value (M5 cycle-2) # --------------------------------------------------------------- Scenario: Resolve strategy actor with unrecognised config value When I resolve strategy actor with config "auto" Then the resolved actor should be None # --------------------------------------------------------------- # Negative risk score clamping (M6 cycle-2) # --------------------------------------------------------------- Scenario: Parse JSON with negative risk score clamps to zero When I parse a JSON strategy response with negative risk score Then strategy action 0 should have risk score 0.0 # --------------------------------------------------------------- # Non-sequential step numbers (L2 cycle-2) # --------------------------------------------------------------- Scenario: LLM JSON with non-sequential step numbers resolves correctly When I parse a JSON strategy response with non-sequential step numbers Then the strategy result should contain 3 decisions And the strategy tree should have dependency edges # --------------------------------------------------------------- # Null JSON description filtering (H3 cycle-2) # --------------------------------------------------------------- Scenario: Parse JSON with null description drops the action When I parse a JSON strategy response with null description Then I should get 1 strategy actions # --------------------------------------------------------------- # Trailing bracketed commentary in LLM response (H4 cycle-2) # --------------------------------------------------------------- Scenario: Parse JSON with trailing bracketed commentary succeeds When I parse a JSON strategy response with trailing brackets Then I should get 2 strategy actions And strategy action 0 should have description "Setup project" # --------------------------------------------------------------- # Review-fix scenarios (cycle-3) # --------------------------------------------------------------- Scenario: StrategyActor LLM mode emits stream callback events Given a StrategyActor with a mock LLM returning JSON strategy When I execute strategy for plan "01HX0000000000QQMRNE00000N" with callback and definition "Build a feature" Then the strategy stream callback should have received "strategize_started" And the strategy stream callback should have received "strategize_decisions" And the strategy stream callback should have received "strategize_complete" Scenario: build_strategy_prompt truncates oversized acms_context When I build a strategy prompt with acms_context exceeding the max length Then the acms_context in the prompt should be within the truncation limit Scenario: LLM mode decision_root_id is a valid ULID Given a StrategyActor with a mock LLM returning JSON strategy When I execute strategy for plan "01HX0000000000QQMRNE00000P" with definition "Build a feature" Then the strategy result decision_root_id should be a valid ULID Scenario: build_decisions computes confidence_score from risk_score Given a StrategyActor in stub mode When I build decisions with known risk scores for plan "01HX0000000000DEC1NE000004" Then each decision confidence_score should equal 1.0 minus risk_score # --------------------------------------------------------------- # Review-fix scenarios (cycle-4 — code review hardening) # --------------------------------------------------------------- Scenario: Parse JSON with NaN risk score defaults to 0.3 When I parse a JSON strategy response with NaN risk score Then strategy action 0 should have risk score 0.3 Scenario: Parse JSON with Infinity risk score defaults to 0.3 When I parse a JSON strategy response with Inf risk score Then strategy action 0 should have risk score 0.3 Scenario: Parse JSON with non-dict items filters them out When I parse a JSON strategy response with non-dict items Then I should get 1 strategy actions And strategy action 0 should have description "Valid action among non-dict items" Scenario: Parse numbered list with star and bullet markers When I parse a strategy response with star and bullet markers Then I should get 4 strategy actions And strategy action 0 should have description "Set up project structure" Scenario: LLM returning more than _MAX_ACTIONS truncates to cap When I parse a JSON strategy response exceeding the action cap Then the parsed action count should equal _MAX_ACTIONS # --------------------------------------------------------------- # Review-fix: hierarchical parent_id from dependency graph (B2) # --------------------------------------------------------------- Scenario: Strategy tree infers parent_id from first dependency Given a StrategyActor with a mock LLM returning JSON strategy When I execute strategy for plan "01HX0000000000QQMRNE0000B2" and inspect the tree Then strategy action 1 parent_id should equal action 0 action_id And strategy action 4 parent_id should equal action 2 action_id Scenario: Strategy tree actions without dependencies parent to root Given a StrategyActor with a mock LLM returning JSON strategy When I execute strategy for plan "01HX0000000000QQMRNE0000B3" and inspect the tree Then strategy action 0 parent_id should be None # --------------------------------------------------------------- # Review-fix: downstream_decision_ids from dependency edges (B3) # --------------------------------------------------------------- Scenario: build_decisions populates downstream_decision_ids from edges Given a StrategyActor with a mock LLM returning JSON strategy When I build decisions from LLM tree for plan "01HX0000000000QQMRNE0000B4" Then root decision downstream_decision_ids should not be empty And leaf decision downstream_decision_ids should be empty Scenario: build_decisions with no edges leaves downstream empty Given a StrategyActor in stub mode When I build decisions from strategy tree for plan "01HX0000000000DEC1NE0000B5" Then all decisions should have empty downstream_decision_ids # --------------------------------------------------------------- # Review-fix: _truncate_at_word edge cases (L3) # --------------------------------------------------------------- Scenario: _truncate_at_word returns short text unchanged When I truncate "hello world" at 50 characters Then the truncated text should be "hello world" Scenario: _truncate_at_word cuts at word boundary When I truncate "hello world foo bar" at 12 characters Then the truncated text should be "hello..." Scenario: _truncate_at_word handles empty string When I truncate an empty string at 10 characters Then the truncated text should be an empty string # --------------------------------------------------------------- # Review-fix: create_llm called with correct args (L4) # --------------------------------------------------------------- Scenario: LLM mode calls create_llm with resolved provider and model Given a StrategyActor with a mock LLM returning JSON strategy When I execute strategy for plan "01HX0000000000QQMRNEK4TEST" with definition "Build a feature" Then create_llm should have been called with provider "openai" and model "gpt-4" # --------------------------------------------------------------- # Review-fix: non-numeric step fallback in _build_tree (L5) # --------------------------------------------------------------- Scenario: LLM JSON with non-numeric step field falls back to index When I parse a JSON strategy response with non-numeric step field Then the strategy result should contain decisions # --------------------------------------------------------------- # Review-fix: lifecycle exception fallback (R1) # --------------------------------------------------------------- Scenario: StrategyActor LLM mode falls back to default actor when lifecycle raises Given a StrategyActor with a mock LLM and a failing lifecycle service When I execute strategy for plan "01HX0000000000QQMRNER1TEST" with definition "Build a feature" Then the strategy result should contain decisions And create_llm should have been called with provider "openai" and model "gpt-4" # --------------------------------------------------------------- # Review-fix: PydanticValidationError re-raise (R2) # --------------------------------------------------------------- Scenario: StrategyActor LLM mode re-raises PydanticValidationError Given a StrategyActor with a mock LLM returning Pydantic-invalid data When I execute strategy for plan "01HX0000000000QQMRNER2TEST" expecting Pydantic error Then a PydanticValidationError should have been raised # --------------------------------------------------------------- # Review-fix: self-loop cycle detection (R3) # --------------------------------------------------------------- Scenario: Validate self-loop dependency graph raises PlanError When I validate a dependency graph with a self-loop Then sa828 a PlanError should be raised with cycle message # --------------------------------------------------------------- # Review-fix: whitespace-only actor name (R4) # --------------------------------------------------------------- Scenario: Parse strategy actor name with whitespace-only defaults When I parse strategy actor name " " Then the strategy parsed provider should be "openai" And the strategy parsed model should be "gpt-4" # --------------------------------------------------------------- # Post code-review hardening (PR #1175, review cycle 3) # --------------------------------------------------------------- # M1: XML tag injection sanitisation Scenario: build_strategy_prompt escapes XML special characters in user content When I build a strategy prompt with XML injection in definition_of_done Then the prompt should not contain a raw closing XML tag # M2: JSON parser retry on false-start preamble Scenario: Parse JSON with preamble containing bracket fragment When I parse a JSON strategy response with preamble bracket fragment Then I should get 2 strategy actions And strategy action 0 should have description "Real action one" # L2: _truncate_at_word with max_chars below 3 Scenario: _truncate_at_word with max_chars below ellipsis length When I truncate "hello world" at 2 characters Then the truncated text should be "he" # CR5-L1: _truncate_at_word with max_chars exactly at ellipsis length Scenario: _truncate_at_word with max_chars exactly 3 returns only ellipsis When I truncate "hello world" at 3 characters Then the truncated text should be "..." # L5: resolve_strategy_actor with both llm config and registry Scenario: Resolve strategy actor with llm config and provider registry When I resolve strategy actor with config "llm" and a provider registry Then the resolved actor should be a StrategyActor And the resolved actor should have LLM capability # L7: build_decisions with unresolvable parent_id Scenario: build_decisions falls back to root for unresolvable parent_id Given a StrategyActor in stub mode When I build decisions from a tree with unresolvable parent_id for plan "01HX0000000000DEC1NE000006" Then the orphan decision parent should fall back to root decision # --------------------------------------------------------------- # Post code-review hardening (PR #1175, review cycle 4) # --------------------------------------------------------------- # CR4-S1a: XML injection sanitisation in resources field Scenario: build_strategy_prompt escapes XML in resources field When I build a strategy prompt with XML injection in resources Then the prompt should not contain a raw closing available_resources tag # CR4-S1b: XML injection sanitisation in project_context field Scenario: build_strategy_prompt escapes XML in project_context field When I build a strategy prompt with XML injection in project_context Then the prompt should not contain a raw closing project_context tag # CR4-S1c: XML injection sanitisation in acms_context field Scenario: build_strategy_prompt escapes XML in acms_context field When I build a strategy prompt with XML injection in acms_context Then the prompt should not contain a raw closing code_analysis_context tag # CR4-S1d: Ampersand sanitisation in user content Scenario: build_strategy_prompt escapes ampersand in user content When I build a strategy prompt with ampersand in definition_of_done Then the prompt should contain the escaped ampersand entity # CR4-T3: Retry budget exhaustion gracefully returns None Scenario: Parse JSON with many false-start anchors degrades gracefully When I parse a JSON strategy response with many false-start bracket anchors Then the parsed actions should contain the real JSON action # CR4-T4: Non-sequential step edge specificity Scenario: LLM JSON with non-sequential steps resolves specific dependency edges When I parse a JSON strategy response with non-sequential step numbers and inspect the tree Then strategy tree action with step 20 should depend on action with step 10 And strategy tree action with step 30 should depend on action with step 20 # --------------------------------------------------------------- # Post code-review hardening (PR #1175, review cycle 6) # --------------------------------------------------------------- # CR6-M2: Invariant constraints appear in LLM prompt Scenario: LLM prompt includes constraints section when invariants are provided When I build a strategy prompt with invariants Then the prompt should contain a constraints section And the prompt constraints should include invariant text "Must use Python 3.12+" And the prompt constraints should include source label "plan" # CR6-M3: XML sanitization of invariant text in prompt Scenario: build_strategy_prompt escapes XML in invariant text When I build a strategy prompt with XML injection in invariant text Then the prompt should not contain a raw closing constraints tag # CR6-M4: Invariant truncation cap Scenario: build_strategy_prompt truncates oversized invariant list When I build a strategy prompt with invariant list exceeding the max count Then the prompt should contain invariant truncation indicator # CR6-M5: _truncate_at_word with negative max_chars Scenario: _truncate_at_word with negative max_chars returns empty string When I truncate "hello world" at -5 characters Then the truncated text should be an empty string # CR6-L8: _truncate_at_word with text containing no spaces Scenario: _truncate_at_word with no-space text truncates at hard limit When I truncate "abcdefghijklmnopqrstuvwxyz" at 10 characters Then the truncated text should be "abcdefg..." # --------------------------------------------------------------- # Post code-review hardening (PR #1175, review cycle 7) # --------------------------------------------------------------- # CR7-L3: Global JSON parse attempt cap terminates gracefully Scenario: Parse JSON with pathological input exhausts global attempt cap When I parse a JSON strategy response that exhausts the global attempt cap Then the strategy result should contain at least 1 decision # CR7-L4: build_decisions silently drops orphaned dependency edges Scenario: build_decisions drops dependency edges referencing non-existent actions Given a StrategyActor in stub mode When I build decisions from a tree with orphaned dependency edges for plan "01HX0000000000DEC1NE000007" Then the orphan edge should be silently dropped # CR7-M2: execute rejects non-ULID plan_id Scenario: StrategyActor rejects non-ULID plan_id format Given a StrategyActor in stub mode When I call strategy actor execute with non-ULID plan_id "not-a-valid-ulid" Then sa828 a ValidationError should be raised with message "plan_id must be a valid ULID" # CR7-M2b: build_decisions rejects non-ULID plan_id Scenario: build_decisions rejects non-ULID plan_id format Given a StrategyActor in stub mode When I call build_decisions with non-ULID plan_id "not-a-valid-ulid" Then sa828 a ValidationError should be raised with message "plan_id must be a valid ULID"