@acms @acms_scope_resolution @scope_resolution
Feature: ACMS Pipeline Scope Resolution and Context Inheritance
  As a CleverAgents developer
  I want pipeline components to be overridable at global, project, and plan scope
  So that context assembly can be customized per-plan or per-project with correct precedence

  # ---------------------------------------------------------------------------
  # PipelineScopeResolver -- Global scope
  # ---------------------------------------------------------------------------

  @scope_chain @global
  Scenario: Global default skeleton compressor is resolved when no overrides exist
    Given a fresh PipelineScopeResolver
    And a global default SkeletonCompressor registered
    When I resolve the skeleton compressor with no plan or project context
    Then the resolved skeleton compressor should be the global default
    And the pipeline scope resolution scope should be "global"

  @scope_chain @global
  Scenario: Global default preamble generator is resolved when no overrides exist
    Given a fresh PipelineScopeResolver
    And a global default PreambleGenerator registered
    When I resolve the preamble generator with no plan or project context
    Then the resolved preamble generator should be the global default
    And the pipeline scope resolution scope should be "global"

  @scope_chain @global
  Scenario: Global default deduplicator is resolved when no overrides exist
    Given a fresh PipelineScopeResolver
    And a global default FragmentDeduplicator registered
    When I resolve the deduplicator with no plan or project context
    Then the resolved deduplicator should be the global default
    And the pipeline scope resolution scope should be "global"

  @scope_chain @global
  Scenario: Global default depth resolver is resolved when no overrides exist
    Given a fresh PipelineScopeResolver
    And a global default DetailDepthResolver registered
    When I resolve the depth resolver with no plan or project context
    Then the resolved depth resolver should be the global default
    And the pipeline scope resolution scope should be "global"

  # ---------------------------------------------------------------------------
  # PipelineScopeResolver -- Project scope
  # ---------------------------------------------------------------------------

  @scope_chain @project
  Scenario: Project override takes precedence over global for skeleton compressor
    Given a fresh PipelineScopeResolver
    And a global default SkeletonCompressor registered
    And a project-level SkeletonCompressor override for project "my-project"
    When I resolve the skeleton compressor for project "my-project"
    Then the resolved skeleton compressor should be the project override
    And the pipeline scope resolution scope should be "project"

  @scope_chain @project
  Scenario: Project without override falls through to global for skeleton compressor
    Given a fresh PipelineScopeResolver
    And a global default SkeletonCompressor registered
    When I resolve the skeleton compressor for project "other-project"
    Then the resolved skeleton compressor should be the global default
    And the pipeline scope resolution scope should be "global"

  @scope_chain @project
  Scenario: Project override takes precedence over global for preamble generator
    Given a fresh PipelineScopeResolver
    And a global default PreambleGenerator registered
    And a project-level PreambleGenerator override for project "proj-a"
    When I resolve the preamble generator for project "proj-a"
    Then the resolved preamble generator should be the project override
    And the pipeline scope resolution scope should be "project"

  # ---------------------------------------------------------------------------
  # PipelineScopeResolver -- Plan scope (highest priority)
  # ---------------------------------------------------------------------------

  @scope_chain @plan
  Scenario: Plan override takes highest precedence over project and global
    Given a fresh PipelineScopeResolver
    And a global default SkeletonCompressor registered
    And a project-level SkeletonCompressor override for project "my-project"
    And a plan-level SkeletonCompressor override for plan "plan-001"
    When I resolve the skeleton compressor for plan "plan-001" and project "my-project"
    Then the resolved skeleton compressor should be the plan override
    And the pipeline scope resolution scope should be "plan"

  @scope_chain @plan
  Scenario: Plan without override falls through to project then global
    Given a fresh PipelineScopeResolver
    And a global default SkeletonCompressor registered
    And a project-level SkeletonCompressor override for project "my-project"
    When I resolve the skeleton compressor for plan "plan-002" and project "my-project"
    Then the resolved skeleton compressor should be the project override
    And the pipeline scope resolution scope should be "project"

  @scope_chain @plan
  Scenario: Plan override for deduplicator takes highest precedence
    Given a fresh PipelineScopeResolver
    And a global default FragmentDeduplicator registered
    And a plan-level FragmentDeduplicator override for plan "plan-dedup"
    When I resolve the deduplicator for plan "plan-dedup"
    Then the resolved deduplicator should be the plan override
    And the pipeline scope resolution scope should be "plan"

  @scope_chain @plan
  Scenario: Plan override for depth resolver takes highest precedence
    Given a fresh PipelineScopeResolver
    And a global default DetailDepthResolver registered
    And a plan-level DetailDepthResolver override for plan "plan-depth"
    When I resolve the depth resolver for plan "plan-depth"
    Then the resolved depth resolver should be the plan override
    And the pipeline scope resolution scope should be "plan"

  # ---------------------------------------------------------------------------
  # PipelineScopeResolver -- resolution_scope introspection
  # ---------------------------------------------------------------------------

  @scope_chain @introspection
  Scenario: resolution_scope returns global when only global is registered
    Given a fresh PipelineScopeResolver
    And a global default SkeletonCompressor registered
    When I check the resolution scope for SkeletonCompressor with no context
    Then the pipeline scope resolution scope should be "global"

  @scope_chain @introspection
  Scenario: resolution_scope returns plan when plan override exists
    Given a fresh PipelineScopeResolver
    And a global default SkeletonCompressor registered
    And a plan-level SkeletonCompressor override for plan "scope-plan"
    When I check the resolution scope for SkeletonCompressor for plan "scope-plan"
    Then the pipeline scope resolution scope should be "plan"

  # ---------------------------------------------------------------------------
  # ContextInheritanceService -- basic inheritance
  # ---------------------------------------------------------------------------

  @context_inheritance @basic
  Scenario: Child plan receives skeleton context from parent payload
    Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
    And a ContextInheritanceService with default skeleton ratio 0.15
    And a parent payload with 3 fragments totaling 1000 tokens
    When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 2000
    Then the skeleton fragments should be non-empty

  @context_inheritance @basic
  Scenario: Empty parent payload produces empty skeleton
    Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
    And a ContextInheritanceService with default skeleton ratio 0.15
    And an empty parent payload
    When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 2000
    Then the skeleton fragments should be empty

  @context_inheritance @basic
  Scenario: Zero skeleton budget produces empty skeleton
    Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
    And a ContextInheritanceService with default skeleton ratio 0.0
    And a parent payload with 3 fragments totaling 1000 tokens
    When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 2000
    Then the skeleton fragments should be empty

  @context_inheritance @basic
  Scenario: inherit_from_fragments compresses raw fragments for child plan
    Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
    And a ContextInheritanceService with default skeleton ratio 0.15
    And parent fragments with 2 fragments totaling 500 tokens
    When I inherit from fragments for child plan "01JQTESTPN00000000000000AA" with budget 1000
    Then the skeleton fragments should be non-empty

  @context_inheritance @basic
  Scenario: Empty parent fragments produces empty skeleton from inherit_from_fragments
    Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
    And a ContextInheritanceService with default skeleton ratio 0.15
    And empty parent fragments
    When I inherit from fragments for child plan "01JQTESTPN00000000000000AA" with budget 1000
    Then the skeleton fragments should be empty

  # ---------------------------------------------------------------------------
  # ContextInheritanceService -- scope resolution during inheritance
  # ---------------------------------------------------------------------------

  @context_inheritance @scope_chain
  Scenario: Context inheritance uses plan-level compressor when registered
    Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
    And a plan-level tracking SkeletonCompressor for plan "01JQTESTPN00000000000000AA"
    And a ContextInheritanceService with default skeleton ratio 0.15
    And a parent payload with 2 fragments totaling 500 tokens
    When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 1000
    Then the plan-level tracking compressor should have been called

  @context_inheritance @scope_chain
  Scenario: Context inheritance uses project-level compressor when registered
    Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
    And a project-level tracking SkeletonCompressor for project "my-project"
    And a ContextInheritanceService with default skeleton ratio 0.15
    And a parent payload with 2 fragments totaling 500 tokens
    When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 1000 and project "my-project"
    Then the project-level tracking compressor should have been called

  # ---------------------------------------------------------------------------
  # ContextInheritanceService -- skeleton ratio
  # ---------------------------------------------------------------------------

  @context_inheritance @skeleton_ratio
  Scenario: Custom skeleton ratio governs the skeleton budget
    Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
    And a ContextInheritanceService with default skeleton ratio 0.15
    And a parent payload with 1 fragment of 100 tokens
    When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 1000 and skeleton ratio 0.5
    Then the skeleton fragments should be non-empty

  @context_inheritance @skeleton_ratio
  Scenario: Default skeleton ratio is used when none specified
    Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
    And a ContextInheritanceService with default skeleton ratio 0.2
    Then the service default skeleton ratio should be 0.2

  # ---------------------------------------------------------------------------
  # ContextInheritanceService -- budget-respecting compressor
  # ---------------------------------------------------------------------------

  @context_inheritance @budget
  Scenario: Tracking compressor respects budget during inheritance
    Given a PipelineScopeResolver with a global DefaultSkeletonCompressor
    And a plan-level tracking SkeletonCompressor for plan "01JQTESTPN00000000000000AA"
    And a ContextInheritanceService with default skeleton ratio 0.15
    And a parent payload with 3 fragments totaling 1000 tokens
    When I inherit context for child plan "01JQTESTPN00000000000000AA" with budget 2000
    Then the plan-level tracking compressor should have been called
    And the skeleton fragments total tokens should be at most 300
