77fef24be6
CI / push-validation (pull_request) Successful in 29s
CI / lint (pull_request) Successful in 42s
CI / build (pull_request) Successful in 40s
CI / helm (pull_request) Successful in 50s
CI / quality (pull_request) Successful in 58s
CI / typecheck (pull_request) Successful in 1m14s
CI / security (pull_request) Successful in 1m22s
CI / unit_tests (pull_request) Successful in 5m36s
CI / docker (pull_request) Successful in 1m39s
CI / integration_tests (pull_request) Successful in 9m15s
CI / coverage (pull_request) Successful in 11m22s
CI / status-check (pull_request) Successful in 4s
Introduced PipelineScopeResolver in src/cleveragents/application/services/acms_scope_resolver.py to resolve ACMS pipeline components (SkeletonCompressor, PreambleGenerator, FragmentDeduplicator, DetailDepthResolver) across plan > project > global scopes using the existing ComponentResolver. Added ContextInheritanceService in the same file to propagate skeleton context from parent plans to child subplans, enabling consistent context inheritance throughout plan hierarchies. Added a new BDD feature file features/acms_scope_resolution.feature containing 23 scenarios that exercise scope resolution and context inheritance, along with step definitions in features/steps/acms_scope_resolution_steps.py to drive behavior-driven tests. ISSUES CLOSED: #10016
230 lines
12 KiB
Gherkin
230 lines
12 KiB
Gherkin
@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
|