Files
cleveragents-core/features/steps/unified_context_models_steps.py
freemo fae438a7a7
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 17s
CI / security (pull_request) Successful in 33s
CI / typecheck (pull_request) Successful in 35s
CI / unit_tests (pull_request) Successful in 2m22s
CI / integration_tests (pull_request) Successful in 3m0s
CI / docker (pull_request) Successful in 39s
CI / coverage (pull_request) Successful in 4m21s
CI / lint (push) Successful in 14s
CI / quality (push) Successful in 16s
CI / build (push) Successful in 16s
CI / security (push) Successful in 30s
CI / typecheck (push) Successful in 36s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 3m38s
CI / docker (push) Successful in 39s
CI / integration_tests (push) Successful in 4m25s
CI / coverage (push) Successful in 4m19s
CI / benchmark-publish (push) Successful in 15m57s
CI / benchmark-regression (pull_request) Successful in 28m50s
refactor(acms): unify ContextFragment model hierarchies by extending CRP base types
Core domain types (FragmentProvenance, ContextFragment, ContextBudget,
ContextPayload) now extend their CRP counterparts via Pydantic v2
inheritance, ensuring isinstance compatibility across the model
hierarchy.

Key changes:
- CRP base types made frozen=True (no consumer mutates them)
- CRP AssembledContext fields changed from list to tuple (frozen consistency)
- Core types extend CRP bases: FragmentProvenance(CRPFragmentProvenance),
  ContextFragment(CRPContextFragment), ContextBudget(CRPContextBudget),
  ContextPayload(CRPAssembledContext)
- Removed duplicate ContextFragment dataclass from skeleton_compressor
- Updated project_context.py to pass tuples to frozen AssembledContext
- Added Behave tests (10 scenarios), Robot integration tests (3 cases),
  and ASV benchmarks for the unified hierarchy
- Updated Known Limitations table in docs/reference/acms.md

ISSUES CLOSED: #569
2026-03-05 21:38:55 +00:00

278 lines
9.5 KiB
Python

"""Step definitions for features/unified_context_models.feature.
Tests isinstance compatibility and construction of the unified
ContextFragment model hierarchy.
"""
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from behave import given, then, when
from behave.runner import Context
from pydantic import ValidationError
from cleveragents.domain.models.acms.crp import (
AssembledContext as CRPAssembledContext,
)
from cleveragents.domain.models.acms.crp import (
ContextBudget as CRPContextBudget,
)
from cleveragents.domain.models.acms.crp import (
ContextFragment as CRPContextFragment,
)
from cleveragents.domain.models.acms.crp import (
FragmentProvenance as CRPFragmentProvenance,
)
from cleveragents.domain.models.core.context_fragment import (
ContextBudget,
ContextFragment,
ContextPayload,
FragmentProvenance,
)
# Default provenance for test fragments.
_DEFAULT_PROVENANCE = FragmentProvenance(resource_uri="test://unified")
# ---------------------------------------------------------------------------
# Given — Module availability
# ---------------------------------------------------------------------------
@given("the unified model modules are available")
def step_modules_available(context: Context) -> None:
pass
# ---------------------------------------------------------------------------
# Given — FragmentProvenance
# ---------------------------------------------------------------------------
@given('a core FragmentProvenance with resource_uri "{uri}"')
def step_core_provenance(context: Context, uri: str) -> None:
context.provenance = FragmentProvenance(resource_uri=uri)
@given('a core FragmentProvenance for strategy "{strategy}" with resource_uri "{uri}"')
def step_core_provenance_with_strategy(
context: Context, strategy: str, uri: str
) -> None:
context.provenance = FragmentProvenance(resource_uri=uri, strategy=strategy)
@given('a CRP FragmentProvenance with resource_uri "{uri}"')
def step_crp_provenance(context: Context, uri: str) -> None:
context.crp_provenance = CRPFragmentProvenance(resource_uri=uri)
# ---------------------------------------------------------------------------
# Given — ContextFragment
# ---------------------------------------------------------------------------
@given('a core ContextFragment with uko_node "{uko_node}" and content "{content}"')
def step_core_fragment(context: Context, uko_node: str, content: str) -> None:
context.unified_fragment = ContextFragment(
uko_node=uko_node,
content=content,
token_count=len(content),
provenance=_DEFAULT_PROVENANCE,
)
@given("two core ContextFragments with identical fields")
def step_two_identical_fragments(context: Context) -> None:
fixed_time = datetime.now(UTC)
frag_a = ContextFragment(
fragment_id="FIXEDIDFOREQUALITYTEST12345",
uko_node="project://eq-test",
content="equal",
token_count=5,
provenance=_DEFAULT_PROVENANCE,
created_at=fixed_time,
)
frag_b = ContextFragment(
fragment_id="FIXEDIDFOREQUALITYTEST12345",
uko_node="project://eq-test",
content="equal",
token_count=5,
provenance=_DEFAULT_PROVENANCE,
created_at=fixed_time,
)
context.frag_a = frag_a
context.frag_b = frag_b
# ---------------------------------------------------------------------------
# Given — ContextBudget
# ---------------------------------------------------------------------------
@given("a core ContextBudget with max_tokens {max_tok:d} and reserved_tokens {res:d}")
def step_core_budget(context: Context, max_tok: int, res: int) -> None:
context.unified_budget = ContextBudget(max_tokens=max_tok, reserved_tokens=res)
# ---------------------------------------------------------------------------
# Given — ContextPayload
# ---------------------------------------------------------------------------
@given('a core ContextPayload with plan_id "{plan_id}"')
def step_core_payload(context: Context, plan_id: str) -> None:
context.unified_payload = ContextPayload(plan_id=plan_id)
# ---------------------------------------------------------------------------
# When — Mutation attempts
# ---------------------------------------------------------------------------
@when("I attempt to mutate the fragment uko_node")
def step_mutate_fragment(context: Context) -> None:
context.mutation_error = None
try:
context.unified_fragment.uko_node = "project://mutated" # type: ignore[misc]
except ValidationError as exc:
context.mutation_error = exc
@when("I attempt to mutate the budget max_tokens")
def step_mutate_budget(context: Context) -> None:
context.mutation_error = None
try:
context.unified_budget.max_tokens = 9999 # type: ignore[misc]
except ValidationError as exc:
context.mutation_error = exc
@when("I attempt to mutate the CRP provenance resource_uri")
def step_mutate_crp_provenance(context: Context) -> None:
context.mutation_error = None
try:
context.crp_provenance.resource_uri = "test://mutated" # type: ignore[misc]
except ValidationError as exc:
context.mutation_error = exc
@when("I create a core ContextBudget with reserved_tokens equal to max_tokens")
def step_create_budget_equal(context: Context) -> None:
context.strict_budget_error = None
try:
ContextBudget(max_tokens=100, reserved_tokens=100)
except ValidationError as exc:
context.strict_budget_error = exc
# ---------------------------------------------------------------------------
# Then — isinstance checks
# ---------------------------------------------------------------------------
@then("the provenance should be an instance of CRP FragmentProvenance")
def step_provenance_isinstance(context: Context) -> None:
assert isinstance(context.provenance, CRPFragmentProvenance), (
f"Expected isinstance of CRP FragmentProvenance, got {type(context.provenance)}"
)
@then("the fragment should be an instance of CRP ContextFragment")
def step_fragment_isinstance(context: Context) -> None:
assert isinstance(context.unified_fragment, CRPContextFragment), (
f"Expected isinstance of CRP ContextFragment, "
f"got {type(context.unified_fragment)}"
)
@then("the budget should be an instance of CRP ContextBudget")
def step_budget_isinstance(context: Context) -> None:
assert isinstance(context.unified_budget, CRPContextBudget), (
f"Expected isinstance of CRP ContextBudget, got {type(context.unified_budget)}"
)
@then("the payload should be an instance of CRP AssembledContext")
def step_payload_isinstance(context: Context) -> None:
assert isinstance(context.unified_payload, CRPAssembledContext), (
f"Expected isinstance of CRP AssembledContext, "
f"got {type(context.unified_payload)}"
)
# ---------------------------------------------------------------------------
# Then — Field value assertions
# ---------------------------------------------------------------------------
@then('the provenance resource_type should be "{value}"')
def step_provenance_resource_type(context: Context, value: str) -> None:
assert context.provenance.resource_type == value
@then('the provenance strategy should be "{value}"')
def step_provenance_strategy(context: Context, value: str) -> None:
assert context.provenance.strategy == value
@then('the unified fragment tier should be "{tier}"')
def step_fragment_tier(context: Context, tier: str) -> None:
assert context.unified_fragment.tier == tier
@then("the unified fragment fragment_id should be non-empty")
def step_fragment_id_nonempty(context: Context) -> None:
assert context.unified_fragment.fragment_id
assert len(context.unified_fragment.fragment_id) > 0
@then("the unified fragment fragment_id should be {length:d} characters")
def step_fragment_id_length(context: Context, length: int) -> None:
fid = context.unified_fragment.fragment_id
assert len(fid) == length, (
f"Expected fragment_id of {length} chars, got {len(fid)}: {fid!r}"
)
@then("the unified fragment created_at should be a recent datetime")
def step_fragment_created_at_recent(context: Context) -> None:
created = context.unified_fragment.created_at
assert isinstance(created, datetime)
now = datetime.now(UTC)
assert now - created < timedelta(seconds=60)
@then("the unified budget available_tokens should be {tokens:d}")
def step_budget_available(context: Context, tokens: int) -> None:
assert context.unified_budget.available_tokens == tokens
# ---------------------------------------------------------------------------
# Then — Mutation error assertions
# ---------------------------------------------------------------------------
@then("a frozen mutation error should be raised")
def step_frozen_mutation_error(context: Context) -> None:
assert context.mutation_error is not None, (
"Expected a frozen mutation error but none was raised"
)
# ---------------------------------------------------------------------------
# Then — Equality assertions
# ---------------------------------------------------------------------------
@then("the two fragments should be equal")
def step_fragments_equal(context: Context) -> None:
assert context.frag_a == context.frag_b
@then("a validation error should be raised for strict budget")
def step_strict_budget_error(context: Context) -> None:
assert context.strict_budget_error is not None, (
"Expected a validation error for reserved == max but none was raised"
)