UAT: ContextTierService.promote() emits TIER_PROMOTED event before budget enforcement — event ordering is incorrect when promotion is reversed by budget #2106

Open
opened 2026-04-03 04:05:57 +00:00 by freemo · 1 comment
Owner

Metadata

  • Branch: fix/acms-tier-promote-event-ordering
  • Commit Message: fix(acms): fix TIER_PROMOTED event ordering when hot budget enforcement reverses promotion
  • Milestone: v3.7.0
  • Parent Epic: #396

Summary

In ContextTierService.promote() in src/cleveragents/application/services/context_tiers.py, when promoting a fragment from warm to hot tier, the code:

  1. Moves the fragment to _hot
  2. Emits TIER_PROMOTED event (warm → hot)
  3. Calls _enforce_hot_budget() which may evict the just-promoted fragment
  4. If the fragment was evicted, restores it to _warm with a warning

The comment in the code says: "Emit PROMOTED before budget enforcement so event ordering reflects the causal chain (promotion caused eviction)."

However, this creates a misleading event sequence: observers receive a TIER_PROMOTED (warm → hot) event, but the fragment is actually back in warm tier. There is no compensating TIER_DEMOTED (hot → warm) event emitted when the budget enforcement reverses the promotion.

Expected Behavior

When a promotion is reversed by budget enforcement, either:

  1. A compensating TIER_DEMOTED event should be emitted (hot → warm), OR
  2. The TIER_PROMOTED event should NOT be emitted until after budget enforcement confirms the fragment stays in hot tier

The spec defines tier events as reflecting the actual state of fragments, not intermediate states.

Actual Behavior

Event sequence when promotion is reversed:

  1. TIER_PROMOTED (warm → hot) — emitted
  2. Budget enforcement evicts the fragment from hot
  3. Fragment is restored to warm
  4. No TIER_DEMOTED event emitted — observers think the fragment is in hot tier

This leaves event subscribers with an incorrect view of tier state.

Code Location

src/cleveragents/application/services/context_tiers.py, promote() method:

if fragment_id in self._warm:
    frag = self._warm.pop(fragment_id)
    promoted = frag.model_copy(update={"tier": ContextTier.HOT})
    self._hot[fragment_id] = promoted
    # Emit PROMOTED before budget enforcement so event ordering
    # reflects the causal chain (promotion caused eviction).
    self._emit_tier_event(
        EventType.TIER_PROMOTED,
        fragment_id,
        from_tier=ContextTier.WARM,
        to_tier=ContextTier.HOT,
    )
    self._enforce_hot_budget()
    # If budget enforcement evicted the just-promoted fragment,
    # restore it to the warm tier to prevent silent data loss.
    if fragment_id not in self._hot:
        restored = promoted.model_copy(
            update={"tier": ContextTier.WARM},
        )
        self._warm[fragment_id] = restored
        logger.warning(
            "tier.promotion_budget_fallback",
            fragment_id=fragment_id,
            reason="hot budget exceeded after promotion",
        )
        return restored
    return promoted

Impact

  • Medium severity: Event subscribers (e.g., audit logs, monitoring dashboards) will show incorrect tier state for fragments whose promotions were reversed.
  • The TIER_PROMOTED event without a compensating TIER_DEMOTED event creates an inconsistent event log.

Subtasks

  • Emit a compensating TIER_DEMOTED (hot → warm) event when budget enforcement reverses a promotion
  • Add a BDD scenario testing the event sequence when promotion is reversed by budget
  • Verify that event subscribers receive a consistent view of tier state

Definition of Done

  • When promotion is reversed by budget enforcement, a TIER_DEMOTED event is emitted
  • BDD test added and passing
  • No regressions

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: ca-uat-tester

## Metadata - **Branch**: `fix/acms-tier-promote-event-ordering` - **Commit Message**: `fix(acms): fix TIER_PROMOTED event ordering when hot budget enforcement reverses promotion` - **Milestone**: v3.7.0 - **Parent Epic**: #396 ## Summary In `ContextTierService.promote()` in `src/cleveragents/application/services/context_tiers.py`, when promoting a fragment from warm to hot tier, the code: 1. Moves the fragment to `_hot` 2. Emits `TIER_PROMOTED` event (warm → hot) 3. Calls `_enforce_hot_budget()` which may evict the just-promoted fragment 4. If the fragment was evicted, restores it to `_warm` with a warning The comment in the code says: `"Emit PROMOTED before budget enforcement so event ordering reflects the causal chain (promotion caused eviction)."` However, this creates a misleading event sequence: observers receive a `TIER_PROMOTED` (warm → hot) event, but the fragment is actually back in warm tier. There is no compensating `TIER_DEMOTED` (hot → warm) event emitted when the budget enforcement reverses the promotion. ## Expected Behavior When a promotion is reversed by budget enforcement, either: 1. A compensating `TIER_DEMOTED` event should be emitted (hot → warm), OR 2. The `TIER_PROMOTED` event should NOT be emitted until after budget enforcement confirms the fragment stays in hot tier The spec defines tier events as reflecting the actual state of fragments, not intermediate states. ## Actual Behavior Event sequence when promotion is reversed: 1. `TIER_PROMOTED` (warm → hot) — emitted 2. Budget enforcement evicts the fragment from hot 3. Fragment is restored to warm 4. **No `TIER_DEMOTED` event emitted** — observers think the fragment is in hot tier This leaves event subscribers with an incorrect view of tier state. ## Code Location `src/cleveragents/application/services/context_tiers.py`, `promote()` method: ```python if fragment_id in self._warm: frag = self._warm.pop(fragment_id) promoted = frag.model_copy(update={"tier": ContextTier.HOT}) self._hot[fragment_id] = promoted # Emit PROMOTED before budget enforcement so event ordering # reflects the causal chain (promotion caused eviction). self._emit_tier_event( EventType.TIER_PROMOTED, fragment_id, from_tier=ContextTier.WARM, to_tier=ContextTier.HOT, ) self._enforce_hot_budget() # If budget enforcement evicted the just-promoted fragment, # restore it to the warm tier to prevent silent data loss. if fragment_id not in self._hot: restored = promoted.model_copy( update={"tier": ContextTier.WARM}, ) self._warm[fragment_id] = restored logger.warning( "tier.promotion_budget_fallback", fragment_id=fragment_id, reason="hot budget exceeded after promotion", ) return restored return promoted ``` ## Impact - Medium severity: Event subscribers (e.g., audit logs, monitoring dashboards) will show incorrect tier state for fragments whose promotions were reversed. - The `TIER_PROMOTED` event without a compensating `TIER_DEMOTED` event creates an inconsistent event log. ## Subtasks - [ ] Emit a compensating `TIER_DEMOTED` (hot → warm) event when budget enforcement reverses a promotion - [ ] Add a BDD scenario testing the event sequence when promotion is reversed by budget - [ ] Verify that event subscribers receive a consistent view of tier state ## Definition of Done - [ ] When promotion is reversed by budget enforcement, a `TIER_DEMOTED` event is emitted - [ ] BDD test added and passing - [ ] No regressions --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-uat-tester
freemo added this to the v3.7.0 milestone 2026-04-03 04:06:02 +00:00
freemo self-assigned this 2026-04-03 16:58:06 +00:00
Author
Owner

MoSCoW classification: Should Have

Rationale: This issue addresses a spec requirement or important quality improvement. It should be included in the milestone if possible.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: ca-project-owner

MoSCoW classification: **Should Have** Rationale: This issue addresses a spec requirement or important quality improvement. It should be included in the milestone if possible. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: ca-project-owner
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Blocks
#396 Epic: ACMS Context Pipeline
cleveragents/cleveragents-core
Reference
cleveragents/cleveragents-core#2106
No description provided.