ca3399e177
ci.yml / fix(acms): invoke SkeletonCompressor in ContextAssembler.assemble() to propagate skeleton context to child plans (pull_request) Failing after 0s
CI / lint (pull_request) Failing after 31s
CI / quality (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 51s
CI / security (pull_request) Successful in 58s
CI / coverage (pull_request) Has been skipped
CI / build (pull_request) Successful in 19s
CI / helm (pull_request) Successful in 24s
CI / unit_tests (pull_request) Failing after 6m58s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 17m14s
CI / integration_tests (pull_request) Failing after 22m37s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
Added skeleton_fragments: tuple[ContextFragment, ...] field to ContextPayload in context_fragment.py - Enables carrying compressed skeleton fragments along with normal context. Extended ACMSPipeline.assemble() in acms_service.py - Introduced skeleton_ratio: float = 0.15 (default matching spec) and parent_fragments: tuple[ContextFragment, ...] | None = None parameters. - These same parameters are also added to ContextAssemblyPipeline.assemble() in acms_pipeline.py for consistency. Skeleton compression integration - In Phase 3 of both assemble() methods, computed skeleton_budget = int(budget.available_tokens * skeleton_ratio) and invoked self._skeleton_compressor.compress(parent_fragments, skeleton_budget). - Compressed skeleton fragments are included in the returned ContextPayload.skeleton_fragments, enabling propagation of skeleton context to child plans. Tests and behavior coverage - Added a TDD issue-capture Behave scenario (@tdd_issue @tdd_issue_3563) to demonstrate the fix. - Added four Behave unit test scenarios asserting: compressor invocation, correct arguments, skeleton presence in output, and skeleton_ratio budget enforcement. - Added a Robot Framework integration test: parent plan accumulates context → child plan spawned → child plan context contains non-empty skeleton. - Added skeleton-context-inheritance command to helper_acms_pipeline.py to support testing and manual verification. Key design decisions - skeleton_ratio defaults to 0.15 to align with the spec's --skeleton-ratio default. - parent_fragments is None by default to maintain backward compatibility (no skeleton compression when no parent context). - skeleton_budget is computed as skeleton_budget = int(budget.available_tokens * skeleton_ratio), deriving the skeleton budget from the total token budget. - Both ACMSPipeline and ContextAssemblyPipeline are fixed to maintain consistency across the codepath. ISSUES CLOSED: #3563
676 lines
32 KiB
Gherkin
676 lines
32 KiB
Gherkin
@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"
|