Plan review: hot-index eviction never drops the newest paid wave

Delta review round 4 found the last boundary of the same seam: the hot index
keeps at most 64 waves and evicted the OLDEST entries, so after 64 unpaid
revisions (every seat refused at $0) the only paid wave left the index, the
next revision judged against nothing and a refused objector's finding vanished
into GREEN. Eviction now drops the oldest entries other than the newest paid
wave, within the same limit; waves_omitted counts what was dropped.

Test: the 63/64 boundary pin through the engine (64 unpaid revisions, then a
refused objector still carries the paid wave's finding).

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
This commit is contained in:
Ouroboros 2026-09-26 19:19:14 +03:00
parent e09cb1851b
commit bb93c9eb34
2 changed files with 31 additions and 3 deletions

View file

@ -1502,9 +1502,11 @@ def record_plan_review_wave(
for idx, w in enumerate(waves)
]
overflow = max(0, len(waves) - _PLAN_REVIEW_MAX_WAVES)
if overflow:
state["waves_omitted"] = int(state.get("waves_omitted") or 0) + overflow
waves = waves[overflow:]
if overflow: # the newest PAID wave stays reachable: the next dispatch judges against it
keep = next((i for i in range(len(waves) - 1, -1, -1) if waves[i].get("paid")), None)
dropped = set([i for i in range(len(waves)) if i != keep][:overflow])
state["waves_omitted"] = int(state.get("waves_omitted") or 0) + len(dropped)
waves = [w for i, w in enumerate(waves) if i not in dropped]
# I-02: size-fitting (older-wave compaction, then the last-resort text cut) runs for
# EVERY writer in `_update_plan_review_state` → `_fit_plan_review_state`.
state["waves"] = waves

View file

@ -164,6 +164,32 @@ def test_a_predecessor_without_an_operative_spec_is_unresolved_history_not_a_cha
assert _walk([], None) == {} # no predecessor at all is a first wave
def test_the_last_paid_wave_survives_hot_index_eviction(harness, monkeypatch):
"""The hot index keeps at most 64 waves; eviction drops the oldest entries but never the
newest PAID wave, so after 64 unpaid revisions the next revision still judges against A
and a refused objector carries A's finding (the 63/64 boundary)."""
from ouroboros.task_results import _PLAN_REVIEW_MAX_WAVES
monkeypatch.setenv("OUROBOROS_REVIEW_MAX_CYCLES", "20")
_patch_health(monkeypatch, lambda slots: {})
_effort_aware_builder(harness, monkeypatch)
harness.install({"s1": _objection("n1", "Friday is impossible"), "s2": CLEAN, "s3": CLEAN})
ctx = harness.make_ctx()
assert _control(_call(ctx)) == {"outcome": "REVIEW_REQUIRED", "closed": False}
a_fp = _state(harness)["waves"][-1]["request_fingerprint"]
for n in range(_PLAN_REVIEW_MAX_WAVES):
_install_barrier_substrate(monkeypatch, [], refused={"s1", "s2", "s3"})
_call(ctx, plan=f"Revision {n}: outline, then draft.")
_collect(ctx, _state(harness)["waves"][-1]["request_fingerprint"])
state = _state(harness)
assert state["waves_omitted"] >= 1 and len(state["waves"]) == _PLAN_REVIEW_MAX_WAVES
assert any(w["request_fingerprint"] == a_fp and w.get("paid") for w in state["waves"])
_install_barrier_substrate(monkeypatch, [], refused={"s1"})
_call(ctx, plan="The final revision: draft, then outline.")
assert _control(_collect(ctx, _state(harness)["waves"][-1]["request_fingerprint"])) == {"outcome": "REVIEW_REQUIRED", "closed": False}
assert _carried(harness) == ["s1:n1"]
def test_a_compacted_last_paid_wave_still_owes_its_objection(harness, monkeypatch):
"""The hot index keeps eight full waves and compacts older ones. After A (paid, s1
objecting) and eight unpaid revisions (every seat refused at $0), A is compact; the