@phase2 @acms @acms_pipeline
Feature: ACMS v1 Context Assembly Pipeline
  As a CleverAgents developer
  I want to assemble context fragments into budget-constrained payloads
  So that actors receive relevant, prioritized context within token limits

  # ---------------------------------------------------------------------------
  # ContextFragment — Domain Model
  # ---------------------------------------------------------------------------

  @acms_fragment
  Scenario: Create a context fragment with defaults
    Given a context fragment with uko_node "project://app/main.py" and content "hello world" and token_count 10
    Then the fragment uko_node should be "project://app/main.py"
    And the fragment content should be "hello world"
    And the fragment relevance score should be 0.5
    And the fragment token count should be 10
    And the fragment detail depth should be 0
    And the fragment tier should be "warm"
    And the fragment metadata should be empty
    And the fragment id should be set
    And the fragment created_at should be a datetime
    And the fragment provenance resource_uri should be set

  @acms_fragment
  Scenario: Create a fragment with all fields specified
    Given a context fragment with all fields:
      | field         | value                 |
      | uko_node      | project://app/io      |
      | content       | Use async IO pattern  |
      | score         | 0.95                  |
      | tokens        | 150                   |
      | detail_depth  | 5                     |
      | tier          | hot                   |
      | meta_key      | priority              |
      | meta_val      | high                  |
      | resource_uri  | project://app/io      |
    Then the fragment uko_node should be "project://app/io"
    And the fragment content should be "Use async IO pattern"
    And the fragment relevance score should be 0.95
    And the fragment token count should be 150
    And the fragment detail depth should be 5
    And the fragment tier should be "hot"
    And the fragment metadata key "priority" should be "high"

  @acms_fragment @validation
  Scenario: Invalid relevance score rejected
    When I create a fragment with relevance score 1.5
    Then an ACMS validation error should be raised

  @acms_fragment @validation
  Scenario: Invalid token count rejected
    When I create a fragment with token count -1
    Then an ACMS validation error should be raised

  @acms_fragment
  Scenario: Invalid tier rejected
    Given the ACMS pipeline modules are available
    When I create a fragment with invalid tier "lukewarm"
    Then an ACMS validation error should be raised

  @acms_fragment @validation
  Scenario: Missing uko_node rejected
    When I create a fragment with empty uko_node
    Then an ACMS validation error should be raised

  @acms_fragment @validation
  Scenario: Detail depth out of range rejected
    When I create a fragment with detail depth 10
    Then an ACMS validation error should be raised

  # ---------------------------------------------------------------------------
  # Structural field assertions — verify payload fields exist
  # ---------------------------------------------------------------------------

  @acms_fragment @structural
  Scenario: ContextFragment has all spec-required fields
    Given a context fragment with uko_node "project://app/main.py" and content "x" and token_count 1
    Then the fragment should have field "fragment_id"
    And the fragment should have field "uko_node"
    And the fragment should have field "content"
    And the fragment should have field "detail_depth"
    And the fragment should have field "token_count"
    And the fragment should have field "relevance_score"
    And the fragment should have field "provenance"
    And the fragment should have field "tier"
    And the fragment should have field "metadata"
    And the fragment should have field "created_at"

  @acms_payload @structural
  Scenario: ContextPayload has all spec-required fields
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/main.py | alpha   | 0.9   | 100    |
    And a context budget with max_tokens 4096 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload should have field "payload_id"
    And the payload should have field "plan_id"
    And the payload should have field "fragments"
    And the payload should have field "total_tokens"
    And the payload should have field "budget"
    And the payload should have field "budget_used"
    And the payload should have field "strategies_used"
    And the payload should have field "context_hash"
    And the payload should have field "preamble"
    And the payload should have field "provenance_map"
    And the payload should have field "assembled_at"

  # ---------------------------------------------------------------------------
  # ContextBudget — Domain Model
  # ---------------------------------------------------------------------------

  @acms_budget
  Scenario: Context budget calculates available tokens
    Given a context budget with max_tokens 4096 and reserved_tokens 512
    Then the available tokens should be 3584

  @acms_budget
  Scenario: Context budget with zero reserved tokens
    Given a context budget with max_tokens 2048 and reserved_tokens 0
    Then the available tokens should be 2048

  @acms_budget
  Scenario: Budget rejects reserved_tokens >= max_tokens
    Given the ACMS pipeline modules are available
    When I create a budget with reserved_tokens equal to max_tokens
    Then an ACMS validation error should be raised

  @acms_budget @validation
  Scenario: Budget rejects reserved_tokens greater than max_tokens
    When I create a budget with reserved_tokens greater than max_tokens
    Then an ACMS validation error should be raised

  # ---------------------------------------------------------------------------
  # ACMSPipeline — Assembly
  # ---------------------------------------------------------------------------

  @acms_assemble
  Scenario: Assemble with relevance strategy selects highest-relevance fragments
    Given the following context fragments:
      | uko_node                  | content   | score | tokens |
      | project://app/file.py     | alpha     | 0.9   | 100    |
      | project://app/decision/1  | beta      | 0.3   | 100    |
      | project://app/resource/1  | gamma     | 0.7   | 100    |
    And a context budget with max_tokens 250 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload should contain 2 fragments
    And the first fragment content should be "alpha"
    And the second fragment content should be "gamma"

  @acms_assemble
  Scenario: Assemble with recency strategy selects most recent fragments
    Given context fragments with different timestamps:
      | uko_node              | content | tokens | created_at               |
      | project://app/old.py  | old     | 100    | 2024-01-01T00:00:00+00:00 |
      | project://app/mid.py  | mid     | 100    | 2024-06-01T00:00:00+00:00 |
      | project://app/new.py  | new     | 100    | 2025-01-01T00:00:00+00:00 |
    And a context budget with max_tokens 200 and reserved_tokens 0
    When I assemble with strategy "recency"
    Then the payload should contain 2 fragments
    And the first fragment content should be "new"
    And the second fragment content should be "mid"

  @acms_assemble
  Scenario: Assemble with tiered strategy prioritizes hot tier
    Given the following tiered context fragments:
      | uko_node                  | content    | score | tokens | tier |
      | project://app/cold.py     | cold-item  | 0.9   | 100    | cold |
      | project://app/hot.py      | hot-item   | 0.85  | 100    | hot  |
      | project://app/warm.py     | warm-item  | 0.8   | 100    | warm |
    And a context budget with max_tokens 200 and reserved_tokens 0
    When I assemble with strategy "tiered"
    Then the payload should contain 2 fragments
    And the first fragment content should be "hot-item"
    And the second fragment content should be "warm-item"

  @acms_assemble
  Scenario: Assembly respects token budget
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | a       | 0.9   | 500    |
      | project://app/b.py    | b       | 0.8   | 500    |
      | project://app/c.py    | c       | 0.7   | 500    |
    And a context budget with max_tokens 1000 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload should contain 2 fragments
    And the payload total tokens should be 1000
    And the payload should be within budget

  @acms_assemble
  Scenario: All fragments exceed budget individually
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/huge.py | huge    | 0.9   | 5000   |
    And a context budget with max_tokens 100 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload should contain 0 fragments
    And the payload total tokens should be 0

  @acms_assemble
  Scenario: Assembly with empty fragments returns empty payload
    Given no context fragments
    And a context budget with max_tokens 4096 and reserved_tokens 512
    When I assemble with strategy "relevance"
    Then the payload should contain 0 fragments
    And the payload total tokens should be 0

  @acms_assemble
  Scenario: Assembly with single fragment within budget includes it
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/only.py | only    | 0.5   | 100    |
    And a context budget with max_tokens 4096 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload should contain 1 fragments
    And the first fragment content should be "only"

  @acms_assemble
  Scenario: Register and use custom context strategy
    Given a custom context strategy that reverses fragment order
    And the following context fragments:
      | uko_node                | content | score | tokens |
      | project://app/first.py  | first   | 0.5   | 50     |
      | project://app/second.py | second  | 0.9   | 50     |
    And a context budget with max_tokens 200 and reserved_tokens 0
    When I assemble with strategy "reverse"
    Then the payload should contain 2 fragments
    And the first fragment content should be "second"

  @acms_assemble
  Scenario: Assemble with default strategy when none specified
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/alpha.py | alpha  | 0.9   | 100    |
      | project://app/beta.py  | beta   | 0.3   | 100    |
    And a context budget with max_tokens 250 and reserved_tokens 0
    When I assemble without specifying a strategy
    Then the payload strategies used should include "relevance"
    And the payload should contain 2 fragments

  @acms_assemble @validation
  Scenario: Non-existent strategy name raises error
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/alpha.py | alpha  | 0.9   | 100    |
    And a context budget with max_tokens 4096 and reserved_tokens 0
    When I assemble with strategy "nonexistent"
    Then an ACMS strategy error should be raised

  # ---------------------------------------------------------------------------
  # Strategy overwrite test
  # ---------------------------------------------------------------------------

  @acms_assemble
  Scenario: Registering strategy with existing name overwrites it
    Given a custom context strategy that reverses fragment order
    And the following context fragments:
      | uko_node                | content | score | tokens |
      | project://app/first.py  | first   | 0.5   | 50     |
      | project://app/second.py | second  | 0.9   | 50     |
    And a context budget with max_tokens 200 and reserved_tokens 0
    When I register the reverse strategy as "relevance"
    And I assemble with strategy "relevance"
    Then the first fragment content should be "second"

  # ---------------------------------------------------------------------------
  # ContextPayload — Properties
  # ---------------------------------------------------------------------------

  @acms_payload
  Scenario: Payload is_within_budget property is correct
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | a       | 0.9   | 100    |
    And a context budget with max_tokens 200 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload should be within budget

  @acms_payload
  Scenario: Payload remaining_tokens property is correct
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | a       | 0.9   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload remaining tokens should be 400

  @acms_payload
  Scenario: Payload budget_used is correct
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | a       | 0.9   | 250    |
    And a context budget with max_tokens 1000 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload budget used should be 0.25

  @acms_payload
  Scenario: Payload context_hash is non-empty
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | a       | 0.9   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload context hash should be non-empty

  @acms_payload
  Scenario: Payload provenance_map maps fragment IDs
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | a       | 0.9   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload provenance map should map each fragment ID

  @acms_payload
  Scenario: Payload strategies_used lists the strategy name
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | a       | 0.9   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "tiered"
    Then the payload strategies used should include "tiered"

  # ---------------------------------------------------------------------------
  # Payload structural assertions
  # ---------------------------------------------------------------------------

  @acms_payload @structural
  Scenario: Payload plan_id, payload_id, and assembled_at are populated
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | a       | 0.9   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload plan_id should be "01JQTESTPN00000000000000AA"
    And the payload payload_id should be a non-empty ULID
    And the payload assembled_at should be a recent UTC datetime

  # ---------------------------------------------------------------------------
  # Budget validation edge case
  # ---------------------------------------------------------------------------

  @acms_budget @validation
  Scenario: Budget rejects reserved_tokens strictly greater than max_tokens
    When I create a budget with reserved_tokens 200 and max_tokens 100
    Then an ACMS validation error should be raised

  # ---------------------------------------------------------------------------
  # Pipeline component DI injection (T1)
  # ---------------------------------------------------------------------------

  @acms_assemble @di
  Scenario: Custom strategy selector is invoked during assembly
    Given a pipeline with a custom tracking strategy selector
    And the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.9   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the custom strategy selector should have been called
    # v1: strategies_used is hardcoded from the caller's input, not the
    # selector output.  This assertion validates the v1 pass-through path;
    # multi-strategy fusion in a future milestone will make it meaningful.
    And the payload strategies used should include "relevance"

  @acms_assemble @di
  Scenario: Custom budget allocator is invoked during assembly
    Given a pipeline with a custom budget allocator that halves the budget
    And the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.9   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the custom budget allocator should have been called

  @acms_assemble @di
  Scenario: Custom strategy executor is invoked during assembly
    Given a pipeline with a custom strategy executor that reverses fragments
    And the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.5   | 50     |
      | project://app/b.py    | beta    | 0.9   | 50     |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the custom strategy executor should have been called
    And the first fragment content should be "beta"

  @acms_assemble @di
  Scenario: Custom deduplicator is invoked during assembly
    Given a pipeline with a custom deduplicator that removes duplicates by uko_node
    And the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.9   | 100    |
      | project://app/a.py    | alpha2  | 0.5   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the custom deduplicator should have been called
    And the payload should contain 1 fragments

  @acms_assemble @di
  Scenario: Custom preamble generator is invoked during assembly
    Given a pipeline with a custom preamble generator
    And the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.9   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload preamble should be "Custom preamble: 1 fragments"

  # ---------------------------------------------------------------------------
  # SkeletonCompressor (T2)
  # ---------------------------------------------------------------------------

  @acms_assemble @skeleton
  Scenario: DefaultSkeletonCompressor returns fragments unchanged
    Given the ACMS pipeline modules are available
    When I compress fragments with the default skeleton compressor and budget 100
    Then the compressed fragments should equal the original fragments

  @acms_assemble @skeleton
  Scenario: Custom skeleton compressor can truncate fragments
    Given the ACMS pipeline modules are available
    When I compress fragments with a truncating skeleton compressor and budget 50
    Then the compressed fragments should have reduced content

  # ---------------------------------------------------------------------------
  # Multi-candidate allocation (T3)
  # ---------------------------------------------------------------------------

  @acms_assemble @allocation
  Scenario: Budget allocator distributes proportionally across multiple candidates
    Given the ACMS pipeline modules are available
    When I allocate budget 1000 across candidates with confidences 0.8 and 0.2
    Then the first candidate should receive approximately 800 tokens
    And the second candidate should receive approximately 200 tokens
    And the total allocated should not exceed 1000

  @acms_assemble @allocation
  Scenario: Strategy selector returns all strategies when no hint given
    Given the ACMS pipeline modules are available
    When I select strategies with no strategy hint from 3 registered strategies
    Then all 3 strategies should be returned with confidence scores

  # ---------------------------------------------------------------------------
  # plan_id ULID validation (SEC-1, SPEC-1, TEST-GAP-1)
  # ---------------------------------------------------------------------------

  @acms_payload @validation
  Scenario: Payload rejects empty plan_id
    Given the ACMS pipeline modules are available
    When I create a payload with an empty plan_id
    Then an ACMS validation error should be raised

  @acms_payload @validation
  Scenario: Payload accepts valid ULID plan_id
    Given the ACMS pipeline modules are available
    When I create a payload with plan_id "01JQTESTPN00000000000000AA"
    Then the created payload plan_id should be "01JQTESTPN00000000000000AA"

  @acms_payload @validation
  Scenario: Payload rejects 25-character plan_id (too short for ULID)
    Given the ACMS pipeline modules are available
    When I create a payload with plan_id "01JQTESTPN0000000000000AA"
    Then an ACMS validation error should be raised

  @acms_payload @validation
  Scenario: Payload rejects 27-character plan_id (too long for ULID)
    Given the ACMS pipeline modules are available
    When I create a payload with plan_id "01JQTESTPN000000000000000AA"
    Then an ACMS validation error should be raised

  @acms_payload @validation
  Scenario: Payload rejects lowercase plan_id
    Given the ACMS pipeline modules are available
    When I create a payload with plan_id "01jqtestpn00000000000000aa"
    Then an ACMS validation error should be raised

  @acms_payload @validation
  Scenario Outline: Payload rejects plan_id with excluded Crockford char <char>
    Given the ACMS pipeline modules are available
    When I create a payload with plan_id "<plan_id>"
    Then an ACMS validation error should be raised

    Examples:
      | char | plan_id                    |
      | I    | 01JQTESTPN000000000000I0AA |
      | L    | 01JQTESTPN000000000000L0AA |
      | O    | 01JQTESTPN000000000000O0AA |
      | U    | 01JQTESTPN000000000000U0AA |

  @acms_payload @validation @security
  Scenario: Payload rejects path traversal in plan_id
    Given the ACMS pipeline modules are available
    When I create a payload with plan_id "01JQTESTPN0000000/../00AAA"
    Then an ACMS validation error should be raised

  @acms_payload @validation @security
  Scenario: Payload rejects plan_id with slashes
    Given the ACMS pipeline modules are available
    When I create a payload with plan_id "01JQTESTPN00000/0000000AAA"
    Then an ACMS validation error should be raised

  @acms_payload @validation
  Scenario: Payload rejects plan_id with whitespace
    Given the ACMS pipeline modules are available
    When I create a payload with plan_id "   "
    Then an ACMS validation error should be raised

  # ---------------------------------------------------------------------------
  # Strategy introspection (coverage: capabilities, can_handle, explain)
  # ---------------------------------------------------------------------------

  @acms_strategy @introspection
  Scenario: Each built-in strategy declares distinct capabilities
    Given the ACMS pipeline modules are available
    When I inspect the capabilities of "relevance"
    Then the strategy should declare supports_semantic_search as true
    When I inspect the capabilities of "recency"
    Then the strategy should declare supports_temporal_archaeology as true
    When I inspect the capabilities of "tiered"
    Then the strategy should declare supports_semantic_search as false

  @acms_strategy @introspection
  Scenario: Strategy confidence ranking determines selector priority
    Given the ACMS pipeline modules are available
    When I query can_handle on all three built-in strategies
    Then "relevance" should have the highest confidence
    And "tiered" should have higher confidence than "recency"

  @acms_strategy @introspection
  Scenario: Strategy explain returns strategy-specific descriptions
    Given the ACMS pipeline modules are available
    When I call explain on each built-in strategy
    Then the "relevance" explanation should mention "relevance score"
    And the "recency" explanation should mention "creation time"
    And the "tiered" explanation should mention "tier priority"

  # ---------------------------------------------------------------------------
  # Fragment metadata overflow
  # ---------------------------------------------------------------------------

  @acms_fragment @validation
  Scenario: Fragment rejects metadata with more than 64 entries
    Given the ACMS pipeline modules are available
    When I create a fragment with 65 metadata entries
    Then an ACMS validation error should be raised

  @acms_fragment @validation
  Scenario: Fragment accepts metadata with exactly 64 entries
    Given the ACMS pipeline modules are available
    When I create a fragment with 64 metadata entries
    Then the fragment should have 64 metadata entries

  # ---------------------------------------------------------------------------
  # Early plan_id rejection in assemble()
  # ---------------------------------------------------------------------------

  @acms_assemble @validation
  Scenario: Pipeline rejects invalid plan_id before executing the pipeline
    Given the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.9   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with plan_id "not-a-valid-ulid"
    Then an ACMS ValueError should be raised mentioning "ULID"

  # ---------------------------------------------------------------------------
  # Provenance map immutability (TEST-2)
  # ---------------------------------------------------------------------------

  @acms_payload @immutability
  Scenario: Provenance map is immune to external mutation after payload creation
    Given the ACMS pipeline modules are available
    When I create a payload with a mutable provenance map and mutate the source
    Then the payload provenance map should be unchanged

  # ---------------------------------------------------------------------------
  # Allocator rounding edge cases (TEST-3)
  # ---------------------------------------------------------------------------

  @acms_assemble @allocation
  Scenario: Budget allocator uses full budget with odd split across 3 candidates
    Given the ACMS pipeline modules are available
    When I allocate budget 999 across 3 candidates with equal confidence
    Then the total allocated should equal exactly 999
    And each allocation should be 333 or 334

  @acms_assemble @allocation
  Scenario: Budget allocator handles zero-confidence equal split without token loss
    Given the ACMS pipeline modules are available
    When I allocate budget 100 across 3 candidates with zero confidence
    Then the total allocated should equal exactly 100

  @acms_assemble @allocation
  Scenario: Budget allocator distributes full budget with unequal confidences
    Given the ACMS pipeline modules are available
    When I allocate budget 10 across 3 candidates with confidences 0.5 0.3 0.2
    Then the total allocated should equal exactly 10

  # ---------------------------------------------------------------------------
  # Strategy selector fallback (TEST-4)
  # ---------------------------------------------------------------------------

  @acms_assemble @di
  Scenario: Assembly falls back when custom selector omits the requested strategy
    Given a pipeline with a custom strategy selector that returns no candidates
    And the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.9   | 100    |
      | project://app/b.py    | beta    | 0.5   | 100    |
    And a context budget with max_tokens 500 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the empty strategy selector should have been called
    And the payload strategies used should include "relevance"
    And the payload should contain 2 fragments

  # ---------------------------------------------------------------------------
  # Issue #3563 — SkeletonCompressor invocation in assemble() (TDD capture)
  # ---------------------------------------------------------------------------

  @acms_assemble @skeleton @tdd_issue @tdd_issue_3563 @issue_3563
  Scenario: TDD capture — assemble() invokes skeleton compressor when parent_fragments provided
    Given the ACMS pipeline modules are available
    And a spy skeleton compressor is registered
    And the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.9   | 100    |
    And a context budget with max_tokens 1000 and reserved_tokens 0
    And parent fragments for skeleton compression:
      | uko_node              | content       | score | tokens |
      | project://parent/x.py | parent code   | 0.8   | 200    |
    When I assemble with parent fragments and skeleton_ratio 0.15
    Then the spy skeleton compressor should have been called
    And the spy compressor should have received the parent fragments
    And the spy compressor skeleton_budget should be 150

  @acms_assemble @skeleton @issue_3563
  Scenario: assemble() includes skeleton_fragments in the returned ContextPayload
    Given the ACMS pipeline modules are available
    And a spy skeleton compressor is registered
    And the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.9   | 100    |
    And a context budget with max_tokens 1000 and reserved_tokens 0
    And parent fragments for skeleton compression:
      | uko_node              | content       | score | tokens |
      | project://parent/x.py | parent code   | 0.8   | 200    |
    When I assemble with parent fragments and skeleton_ratio 0.15
    Then the payload skeleton_fragments should be non-empty
    And the payload skeleton_fragments should equal the spy compressor output

  @acms_assemble @skeleton @issue_3563
  Scenario: assemble() skeleton_fragments is empty when no parent_fragments provided
    Given the ACMS pipeline modules are available
    And the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.9   | 100    |
    And a context budget with max_tokens 1000 and reserved_tokens 0
    When I assemble with strategy "relevance"
    Then the payload skeleton_fragments should be empty

  @acms_assemble @skeleton @issue_3563
  Scenario: skeleton_ratio governs the skeleton budget passed to the compressor
    Given the ACMS pipeline modules are available
    And a spy skeleton compressor is registered
    And the following context fragments:
      | uko_node              | content | score | tokens |
      | project://app/a.py    | alpha   | 0.9   | 100    |
    And a context budget with max_tokens 2000 and reserved_tokens 0
    And parent fragments for skeleton compression:
      | uko_node              | content       | score | tokens |
      | project://parent/x.py | parent code   | 0.8   | 200    |
    When I assemble with parent fragments and skeleton_ratio 0.25
    Then the spy compressor skeleton_budget should be 500
    And the first fragment content should be "alpha"
