ouroboros/tests/test_commit_gate_wave_admission.py
Ouroboros 2c2586496f Admit review waves against the global budget axis, not only the root fence
rc.14 audit point 2 (verified with a real-code repro): review_wave_admission
read only the root projection, so a commit-gate wave that fit the per-task
fence was admitted while the global TOTAL_BUDGET remainder — the FIRST fence
reserve_attempt enforces — could not hold it: two seats reserved and paid,
the third was refused mid-wave by the ledger's global check, and the commit
blocked on quorum with money spent. The owner's accepted contract is "no
reviewer seat is dispatched unless the WHOLE wave fits"; the wording
"against the task's current root fence" in the 2026-09-05 decision was an
omission of the global axis, not an exclusion (operator's reading,
disclosed here).

review_wave_admission gains a kw-only global_limit_usd resolved through the
same resolve_total_budget_usd path as the ledger's own global fence (None
reads the setting; a non-positive setting leaves the axis unbounded), reads
the global projection with include_roots=False, binds on the tighter of the
root and global remainders and discloses global_limit_usd /
global_accounted_usd / global_reserved_usd / global_remaining_usd and
binding_axis in {"root", "global"}. A caller with no root fence and no root
rows is still bound by a finite global budget (before, admission returned
the fail-open skeleton there — exactly the configuration where the global
axis is the only one the ledger enforces). The override branch for the
managed-update floor and the fail-open skeleton on an internal exception are
unchanged.

review_wave_budget_gate passes scope.global_limit_usd and the typed
review_wave_budget_insufficient payload carries the binding axis and both
remainders, which fixes every caller of the shared gate at once. The
commit-gate REVIEW_BLOCKED text names the binding fence ("global budget
TOTAL_BUDGET" vs "per-task budget fence") with accounted, holds, remaining,
shortfall, what the other axis alone would leave and the knob that moves the
binding fence; the skill and plan refusals phrase the fence and remedy
through one shared review_wave_binding_fence, so no refusal tells the owner
to raise a fence the wave would have fit.

Admission stays a read-only pre-check without a wave-level hold: the
per-seat reservation remains the enforcement, and money a concurrent task
takes on the global axis between admission and a seat's reservation can
still refuse that seat — a disclosed residual (ARCHITECTURE), not closed by
a new ledger row kind.

Tests pin the audit repro through the real gate (root $10, global $4 left
after $96 under another root, wave $5: refused before any seat reserves,
binding_axis global, $0 spent), the root-bound shape, both-fit admission,
the unbounded global axis under a non-positive TOTAL_BUDGET, the
no-root-fence global binding and the fail-open skeleton.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-05 17:22:43 +00:00

740 lines
43 KiB
Python

"""Commit-gate wave admission (owner decision 2026-09-05, answer 2 = A).
The scope reviewer — the only constitutionally blocking seat — reserves its
budget FIRST, and the commit-gate wave (scope seats + triad seats) is admitted
all-or-nothing against every fence the ledger enforces at reservation — the
global TOTAL_BUDGET remainder and the task's root fence (the earlier wording
"against the task's current root fence" omitted the global axis; rc.14 audit
point 2) — BEFORE any paid seat is dispatched. A wave that does not fit is a
typed $0 pre-dispatch refusal naming the binding axis and the shortfall, never
a half-dispatched panel (the 4 September paid run: two triad seats held the
money, the third seat and the scope seat were refused mid-wave, and the commit
blocked with ~$5 of the $8 fence never spent).
"""
from __future__ import annotations
import json
import threading
import time
from types import SimpleNamespace
import pytest
from ouroboros import usage_accounting as ua
from ouroboros.review_execution import ReviewRouteKind
from ouroboros.reviewer_window import ReviewerWindow
from ouroboros.tools import parallel_review, review, review_admission
from ouroboros.tools import scope_review as scope_mod
from ouroboros.tools.scope_review_contract import SCOPE_REQUIRED_ITEMS
SCOPE_MODEL = "scope/model"
TRIAD_MODELS = ["triad/a", "triad/b"]
BOUNDS = {SCOPE_MODEL: 3.0, "triad/a": 1.0, "triad/b": 1.0}
ROOT = "wave-root"
def _scope_matrix() -> str:
return json.dumps([
{"item": item, "verdict": "PASS", "severity": "advisory",
"reason": "checked the relevant code path and its consumers thoroughly"}
for item in sorted(SCOPE_REQUIRED_ITEMS)
])
class LedgerLLM:
"""Every chat performs ONE real ledger attempt in the bound review scope
(the exact seam the substrate's api executor drives), so the ledger order
and the fence are the product's own, not a mock's."""
def __init__(self, delays=None):
self.calls = []
self.delays = dict(delays or {})
self.lock = threading.Lock()
def chat(self, **kwargs):
model = kwargs["model"]
time.sleep(self.delays.get(model, 0.0))
reply = {"content": _scope_matrix() if model == SCOPE_MODEL else "[]"}
ua.execute_physical_attempt(
ua.AttemptRequest(model=model, provider="test", reservation_usd=BOUNDS[model]),
lambda: reply,
extractor=lambda _r: ({"prompt_tokens": 4, "completion_tokens": 2}, 0.01, True),
)
with self.lock:
self.calls.append(model)
return reply, {"prompt_tokens": 4, "completion_tokens": 2, "cost": 0.01}
@pytest.fixture
def gate(tmp_path, monkeypatch):
root = tmp_path / "data"
(root / "state").mkdir(parents=True)
monkeypatch.setenv("OUROBOROS_DATA_DIR", str(root))
monkeypatch.setenv("OUROBOROS_SETTINGS_PATH", str(root / "settings.json"))
monkeypatch.setenv("TOTAL_BUDGET", "100")
ua._reset_task_cache_splits()
ua._ROOT_ACCOUNTING_TELEMETRY.pop(ROOT, None)
# The reservation math is the product's; only the price catalog is pinned
# (no live pricing fetch under test).
monkeypatch.setattr(ua, "_reservation_cost", lambda request: BOUNDS[request.model])
monkeypatch.setattr(scope_mod, "_scope_window", lambda *_a, **_k: ReviewerWindow(
window_tokens=1_000_000, status="confirmed"))
monkeypatch.setattr(parallel_review, "run_cmd", lambda *_a, **_k: "staged diff")
monkeypatch.setattr(parallel_review, "scope_reviewer_slots", lambda *_a, **_k: [
SimpleNamespace(model=SCOPE_MODEL, slot_id="scope_slot_1", route=ReviewRouteKind.API_CHAT,
effort="", session_target="", session_profile="", subagent_id="",
retrieves=False),
])
monkeypatch.setattr(review_admission, "prepare_scope_review", lambda *_a, **_k: ({
"prompt": "SCOPE PACK " * 50, "session_task": "", "repo_dir": tmp_path,
"scope_model_id": SCOPE_MODEL, "delegated": False, "slot_id": "scope_slot_1",
"route": ReviewRouteKind.API_CHAT, "slot_effort": "", "session_target": "",
"session_profile": "", "subagent_id": "", "context_manifest": {}, "stable_prefix_len": 0,
}, None))
row_plan = {
"models": list(TRIAD_MODELS), "routes": [ReviewRouteKind.API_CHAT] * 2,
"slot_ids": ["slot_1", "slot_2"], "efforts": ["", ""], "subagent_ids": ["", ""],
}
monkeypatch.setattr(review, "_prepare_unified_review", lambda *_a, **_k: ({
"prompt": "TRIAD PACK " * 20, "stable_prefix_len": 0, "models": list(TRIAD_MODELS),
"routes": [ReviewRouteKind.API_CHAT] * 2, "row_plan": row_plan, "session_task": "",
"target_repo": tmp_path, "blocking_review": True,
}, None, False))
from ouroboros import config as cfg
monkeypatch.setattr(cfg, "get_review_enforcement", lambda: "blocking")
return root
def _ctx(root, tmp_path):
return SimpleNamespace(
repo_dir=tmp_path, drive_root=root, task_id=ROOT,
task_metadata={"root_task_id": ROOT, "budget_drive_root": str(root)},
pending_events=[], _review_history=[], _review_advisory=[], _scope_review_history={},
_review_iteration_count=0, _last_review_critical_findings=[], _review_degraded_reasons=[],
)
def _run(root, tmp_path, monkeypatch, llm, *, fence: float, env_fence: float | None = None):
"""Run the commit gate with the task's usage scope bound on the orchestrator
thread (``fence``: the root task's bound limit, the one admission uses) while
the environment carries ``env_fence`` (a hot-reloaded setting; the same
value unless a test pins the divergence)."""
monkeypatch.setenv("OUROBOROS_PER_TASK_COST_USD", str(fence if env_fence is None else env_fence))
monkeypatch.setattr(review, "LLMClient", lambda: llm)
monkeypatch.setattr(scope_mod, "LLMClient", lambda: llm)
ctx = _ctx(root, tmp_path)
scope = ua.UsageScope(drive_root=root, task_id=ROOT, root_task_id=ROOT, root_limit_usd=fence)
with ua.usage_scope(scope):
outcome = parallel_review.run_parallel_review(ctx, "wave admission commit")
return ctx, outcome
def _ledger(root):
path = root / ua.LEDGER_REL
if not path.exists():
return []
return [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
def test_fence_that_fits_the_whole_wave_dispatches_every_seat(gate, tmp_path, monkeypatch):
llm = LedgerLLM()
ctx, (review_err, scope_result, _reason, _adv) = _run(gate, tmp_path, monkeypatch, llm, fence=10.0)
assert review_err is None, review_err
assert scope_result.blocked is False and scope_result.status == "responded"
assert sorted(llm.calls) == sorted([SCOPE_MODEL, *TRIAD_MODELS])
rows = _ledger(gate)
assert sorted(row["model"] for row in rows if row["state"] == "settled") == sorted(llm.calls)
assert not [e for e in ctx.pending_events if e.get("type") == "review_wave_budget_insufficient"]
def test_fence_that_fits_only_the_triad_refuses_the_wave_at_zero_dollars(gate, tmp_path, monkeypatch):
"""Scope $3 + triad $1 + $1 = $5 against a $4 fence: the triad alone would
have fit (the 4 September failure shape), so the WHOLE wave is refused
before dispatch — no seat reserved, no ledger row, every seat typed $0."""
llm = LedgerLLM()
ctx, (review_err, scope_result, block_reason, _adv) = _run(gate, tmp_path, monkeypatch, llm, fence=4.0)
assert llm.calls == [] and _ledger(gate) == []
assert review_err and "commit-gate review wave declined before dispatch ($0 spent)" in review_err
assert "reservation upper bound $5.000000" in review_err
assert "per-task budget fence $4.000000" in review_err
assert "accounted=$0.000000 (of which $0.000000 is reserved by other in-flight attempts)" in review_err
assert "remaining=$4.000000, shortfall=$1.000000" in review_err
# The root axis binds; the global axis is disclosed beside it with its own remainder.
assert "the global budget $100.000000 alone would leave $100.000000" in review_err
assert "raise the per-task budget (OUROBOROS_PER_TASK_COST_USD)" in review_err
# Every seat is named with its own bound, scope first.
assert review_err.index("scope_review:scope_slot_1 scope/model $3.000000") < review_err.index(
"multi_model_review:slot_1 triad/a $1.000000")
assert block_reason == "review_wave_budget_insufficient"
assert [r["status"] for r in ctx._last_triad_raw_results] == ["not_dispatched", "not_dispatched"]
assert [r["slot_id"] for r in ctx._last_triad_raw_results] == ["slot_1", "slot_2"]
assert scope_result.blocked is True and scope_result.status == "not_dispatched"
assert scope_result.block_message.startswith("⚠️ SCOPE_REVIEW_BLOCKED: ")
assert ctx._last_scope_raw_results[0]["status"] == "not_dispatched"
assert any("triad_not_dispatched_budget_admission" in r for r in ctx._review_degraded_reasons)
events = [e for e in ctx.pending_events if e.get("type") == "review_wave_budget_insufficient"]
assert len(events) == 1 and events[0]["surface"] == "commit_gate"
assert events[0]["seats"] == ["scope_review:scope_slot_1", "multi_model_review:slot_1",
"multi_model_review:slot_2"]
assert events[0]["slot_bounds"] == [3.0, 1.0, 1.0]
assert events[0]["binding_axis"] == "root" and events[0]["remaining_usd"] == 4.0
assert (events[0]["global_limit_usd"], events[0]["global_remaining_usd"]) == (100.0, 100.0)
def test_global_budget_that_does_not_fit_refuses_the_wave_before_any_seat_reserves(gate, tmp_path, monkeypatch):
"""rc.14 audit point 2 (verified repro): root fence $10 fits the $5 wave, the
global TOTAL_BUDGET $100 has $4 left after $96 settled under ANOTHER root.
Before the fix admission read only the root axis, two seats reserved and
paid, and the ledger's global check — the FIRST one ``reserve_attempt``
runs — refused the third mid-wave. Now the wave is refused before any seat
reserves, and the refusal names the global axis and its own knob, never the
per-task fence the wave would have fit."""
with ua.usage_scope(ua.UsageScope(drive_root=gate, task_id="other-root", root_task_id="other-root")):
other = ua.reserve_attempt(ua.AttemptRequest(model="triad/a", provider="test", source="main"))
ua.mark_dispatched(other)
ua.settle_attempt(other, {"prompt_tokens": 1, "completion_tokens": 1}, cost_usd=96.0, cost_final=True)
llm = LedgerLLM()
ctx, (review_err, scope_result, block_reason, _adv) = _run(gate, tmp_path, monkeypatch, llm, fence=10.0)
assert llm.calls == [] and len(_ledger(gate)) == 3 # the other root's attempt only
assert review_err and "commit-gate review wave declined before dispatch ($0 spent)" in review_err
assert "reservation upper bound $5.000000" in review_err
assert "does not fit the global budget TOTAL_BUDGET $100.000000: accounted=$96.000000 across every task" in review_err
assert "(of which $0.000000 is reserved by other in-flight attempts)" in review_err
assert "remaining=$4.000000, shortfall=$1.000000" in review_err
assert "the per-task budget fence $10.000000 alone would leave $10.000000" in review_err
assert "raise TOTAL_BUDGET" in review_err and "OUROBOROS_PER_TASK_COST_USD" not in review_err
assert block_reason == "review_wave_budget_insufficient"
assert [r["status"] for r in ctx._last_triad_raw_results] == ["not_dispatched", "not_dispatched"]
assert scope_result.blocked is True and scope_result.status == "not_dispatched"
events = [e for e in ctx.pending_events if e.get("type") == "review_wave_budget_insufficient"]
assert len(events) == 1 and events[0]["binding_axis"] == "global"
assert (events[0]["remaining_usd"], events[0]["global_remaining_usd"]) == (4.0, 4.0)
assert (events[0]["global_limit_usd"], events[0]["global_accounted_usd"]) == (100.0, 96.0)
assert (events[0]["limit_usd"], events[0]["accounted_usd"]) == (10.0, 0.0)
def test_review_wave_admission_binds_on_the_tighter_of_root_and_global_axes(gate, monkeypatch):
"""Direct pins of the two-axis contract: root and global remainders are
read the way ``reserve_attempt`` will enforce them, the tighter one binds
and is named, a non-positive TOTAL_BUDGET leaves the global axis unbounded,
a caller with no root fence and no root rows is still bound by the global
axis, and an internal exception keeps the fail-open skeleton."""
wave = dict(root_task_id=ROOT, models=[SCOPE_MODEL, *TRIAD_MODELS], prompt_chars=10, task_id=ROOT)
root_bound = ua.review_wave_admission(gate, root_limit_usd=4.0, global_limit_usd=10.0, **wave)
assert (root_bound["fits"], root_bound["binding_axis"], root_bound["estimated_wave_usd"]) == (False, "root", 5.0)
assert (root_bound["remaining_usd"], root_bound["global_remaining_usd"]) == (4.0, 10.0)
global_bound = ua.review_wave_admission(gate, root_limit_usd=10.0, global_limit_usd=4.0, **wave)
assert (global_bound["fits"], global_bound["binding_axis"]) == (False, "global")
assert (global_bound["remaining_usd"], global_bound["limit_usd"], global_bound["global_limit_usd"]) == (
4.0, 10.0, 4.0)
assert (global_bound["global_accounted_usd"], global_bound["global_reserved_usd"]) == (0.0, 0.0)
admitted = ua.review_wave_admission(gate, root_limit_usd=10.0, global_limit_usd=10.0, **wave)
assert admitted["fits"] is True and admitted["remaining_usd"] == 10.0
monkeypatch.setenv("TOTAL_BUDGET", "0") # no finite global budget: the root axis alone
root_only = ua.review_wave_admission(gate, root_limit_usd=4.0, **wave)
assert (root_only["fits"], root_only["binding_axis"], root_only["remaining_usd"]) == (False, "root", 4.0)
assert (root_only["global_limit_usd"], root_only["global_remaining_usd"]) == (None, None)
unfenced_unbounded = ua.review_wave_admission(gate, **wave) # no fence, no rows, no global budget
assert unfenced_unbounded["fits"] is True and unfenced_unbounded["binding_axis"] is None
monkeypatch.setenv("TOTAL_BUDGET", "4")
unfenced = ua.review_wave_admission(gate, **wave) # no root fence: the global axis still binds
assert (unfenced["fits"], unfenced["binding_axis"], unfenced["limit_usd"]) == (False, "global", None)
assert (unfenced["remaining_usd"], unfenced["global_limit_usd"]) == (4.0, 4.0)
monkeypatch.setattr(ua, "usage_projection", lambda *_a, **_k: (_ for _ in ()).throw(RuntimeError("ledger")))
skeleton = ua.review_wave_admission(gate, root_limit_usd=4.0, global_limit_usd=4.0, **wave)
assert skeleton["fits"] is True and skeleton["estimated_wave_usd"] is None
assert skeleton["binding_axis"] is None and skeleton["global_remaining_usd"] is None
def test_refusal_names_money_held_by_other_in_flight_attempts(gate, tmp_path, monkeypatch):
"""The fence compares against settled + reserved holds: a refusal must say
how much of `accounted` is a hold, not a bill."""
with ua.usage_scope(ua.UsageScope(drive_root=gate, task_id=ROOT, root_task_id=ROOT, root_limit_usd=8.0)):
held = ua.reserve_attempt(ua.AttemptRequest(model="triad/a", provider="test", source="main"))
ua.mark_dispatched(held) # an in-flight main-loop send: $1 upper bound held
settled = ua.reserve_attempt(ua.AttemptRequest(model="triad/b", provider="test", source="main"))
ua.mark_dispatched(settled)
ua.settle_attempt(settled, {"prompt_tokens": 1, "completion_tokens": 1}, cost_usd=2.5, cost_final=True)
llm = LedgerLLM()
ctx, (review_err, _scope, _reason, _adv) = _run(gate, tmp_path, monkeypatch, llm, fence=8.0)
assert llm.calls == []
assert "accounted=$3.500000 (of which $1.000000 is reserved by other in-flight attempts)" in review_err
assert "remaining=$4.500000, shortfall=$0.500000" in review_err
assert len(_ledger(gate)) == 5 # the two seeded attempts only (reserved/dispatched/settled rows)
def test_scope_reserves_before_the_triad_even_when_it_is_slower(gate, tmp_path, monkeypatch):
"""Scope-first ordering is enforced by the orchestrator, not by luck: the
scope seat is deliberately the slow one, and the triad is still held until
the scope reservation is on the ledger."""
llm = LedgerLLM(delays={SCOPE_MODEL: 0.4})
ctx, (review_err, scope_result, _reason, _adv) = _run(gate, tmp_path, monkeypatch, llm, fence=10.0)
assert review_err is None and scope_result.status == "responded"
reserved = [row for row in _ledger(gate) if row["state"] == "reserved"]
assert [row["category"] for row in reserved][0] == "scope_review_review"
assert [row["model"] for row in reserved] == [SCOPE_MODEL, *sorted(TRIAD_MODELS)] or \
[row["model"] for row in reserved] == [SCOPE_MODEL, *reversed(sorted(TRIAD_MODELS))]
assert reserved[0]["review_slot_id"] == "scope_slot_1"
def test_paid_seats_are_priced_seat_by_seat_scope_first(gate, tmp_path, monkeypatch):
"""The admission prices each seat with ITS pack and output reservation —
the scope pack beside the triad pack — through the shared gate, one value
per slot, and asks the gate as ONE wave."""
seen = {}
def _gate(ctx, *, surface, models, prompt_chars, max_completion_tokens, extra=None,
categories="", slot_ids=""):
seen.update(surface=surface, models=models, prompt_chars=prompt_chars,
max_completion_tokens=max_completion_tokens, extra=extra,
categories=categories, slot_ids=slot_ids)
return None
monkeypatch.setattr("ouroboros.tools.review_helpers.review_wave_budget_gate", _gate)
from ouroboros.tools.review_multi_model import _review_output_budget
llm = LedgerLLM()
_run(gate, tmp_path, monkeypatch, llm, fence=10.0)
assert seen["surface"] == "commit_gate"
assert seen["models"] == [SCOPE_MODEL, *TRIAD_MODELS]
scope_chars, triad_chars = seen["prompt_chars"][0], seen["prompt_chars"][1]
assert seen["prompt_chars"] == [scope_chars, triad_chars, triad_chars]
# The scope pack is measured as the exact message pair the substrate sends;
# the triad pack carries the constitutional preamble + BIBLE ahead of it.
assert scope_chars > len("SCOPE PACK " * 50) and triad_chars > len("TRIAD PACK " * 20) + 1000
assert seen["max_completion_tokens"] == [100_000, _review_output_budget(), _review_output_budget()]
# Every seat names the usage scope its substrate sends under (category + slot),
# so its bound is read under the seat's own cache split, never the caller's.
assert seen["categories"] == ["scope_review_review", "multi_model_review_review",
"multi_model_review_review"]
assert seen["slot_ids"] == ["scope_slot_1", "slot_1", "slot_2"]
def test_review_wave_admission_prices_per_slot_and_discloses_holds(gate, monkeypatch):
requests = []
monkeypatch.setattr(ua, "_reservation_cost", lambda request: requests.append(request) or 0.5)
admission = ua.review_wave_admission(
gate, root_task_id="fresh-root", models=["prov/a", "prov/b"],
prompt_chars=[400, 1600], max_completion_tokens=[1000, 2000], task_id="fresh-root",
root_limit_usd=0.75,
)
assert [(r.prompt_tokens_estimate, r.max_completion_tokens, r.task_id) for r in requests] == [
(100, 1000, "fresh-root"), (400, 2000, "fresh-root"),
]
# No ledger row yet: the caller's bound fence is the limit, not a fail-open.
assert admission["limit_usd"] == 0.75 and admission["accounted_usd"] == 0.0
assert admission["estimated_wave_usd"] == 1.0 and admission["fits"] is False
assert admission["slot_bounds"] == [0.5, 0.5] and admission["reserved_usd"] == 0.0
# A scalar still broadcasts (the task-level callers are unchanged), and a
# root with neither a ledger row nor a bound fence keeps failing open.
requests.clear()
scalar = ua.review_wave_admission(
gate, root_task_id="fresh-root", models=["prov/a", "prov/b"], prompt_chars=400,
root_limit_usd=10.0,
)
assert [r.prompt_tokens_estimate for r in requests] == [100, 100]
assert [r.max_completion_tokens for r in requests] == [65536, 65536]
assert scalar["fits"] is True and scalar["limit_usd"] == 10.0
unfenced = ua.review_wave_admission(gate, root_task_id="fresh-root", models=["prov/a"], prompt_chars=400)
assert unfenced["fits"] is True and unfenced["limit_usd"] is None
def test_wave_without_paid_seats_neither_waits_nor_refuses(gate, tmp_path, monkeypatch):
"""An all-retrieving wave rides the owner's subscription: nothing to price."""
from ouroboros.tools.review_admission import admit_commit_gate_wave, commit_gate_paid_seats
session_slot = SimpleNamespace(model="m/session", slot_id="scope_slot_1",
route=ReviewRouteKind.AGENT_SESSION, subagent_id="")
seats = commit_gate_paid_seats(
{"prompt": "", "models": ["m/session"], "routes": [ReviewRouteKind.AGENT_SESSION],
"row_plan": {"models": ["m/session"], "routes": [ReviewRouteKind.AGENT_SESSION]}},
False, [{"slot": session_slot, "prepared": {"prompt": ""}, "final": None}],
)
assert seats == []
ctx = _ctx(gate, tmp_path)
assert admit_commit_gate_wave(ctx, seats) is None
started = time.monotonic()
parallel_review._await_scope_reservation(ctx, SimpleNamespace(done=lambda: False), seats, started)
assert time.monotonic() - started < 0.5 and ctx.pending_events == []
# ---------------------------------------------------------------------------
# rc.14 audit findings (astra MAJOR 1-4, fable minors on e27bc3b5)
# ---------------------------------------------------------------------------
def _native_first_send_size(repo, *, surface, session_task, role_hint, output_contract, slot_id, model):
"""The executor's OWN opening send, measured by its own `_open_episode`."""
from ouroboros.review_execution import ReviewAssignment
from ouroboros.review_native_episode import NativeToolRoundReviewExecutor
from ouroboros.review_substrate import ReviewRequest, ReviewSlot
request = ReviewRequest(surface=surface, goal="g", task_id=ROOT, session_root=str(repo),
session_task=session_task, policy={"output_contract": output_contract})
slot = ReviewSlot(slot_id=slot_id, model=model, effort="low", role_hint=role_hint,
route=ReviewRouteKind.API_CHAT, subagent_id="critic")
executor = NativeToolRoundReviewExecutor(ReviewAssignment(request=request, slot=slot, call_id="op"), llm=None)
return executor._open_episode(str(repo), str(repo))[3]
def test_native_episode_seats_are_paid_and_priced_by_their_first_send(gate, tmp_path):
"""Finding 1: a configured-subagent api row (native episode) reserves every
round on the ledger exactly like a packet row, so it is a PAID seat priced
by its first send — measured by the executor's own opening — while an
agent-session row (subscription, ledger row written at settlement) is not."""
from ouroboros.reviewer_slot_config import SCOPE_ROLE_HINT
from ouroboros.tools.review_admission import commit_gate_paid_seats
from ouroboros.tools.review_multi_model import TRIAD_ROLE_HINT, _review_output_budget
from ouroboros.triad_review import REVIEW_JSON_ARRAY_CONTRACT
repo = tmp_path / "subject"
repo.mkdir()
native_scope = SimpleNamespace(model=SCOPE_MODEL, slot_id="scope_slot_1", route=ReviewRouteKind.API_CHAT,
subagent_id="critic", retrieves=True)
session_scope = SimpleNamespace(model="m/session", slot_id="scope_slot_2",
route=ReviewRouteKind.AGENT_SESSION, subagent_id="", retrieves=True)
scope_rows = [
{"slot": native_scope, "final": None, "prepared": {
"prompt": "SCOPE TASK", "session_task": "SCOPE TASK", "repo_dir": repo,
"scope_model_id": SCOPE_MODEL, "stable_prefix_len": 0}},
{"slot": session_scope, "final": None, "prepared": {"prompt": "", "session_task": "SCOPE TASK"}},
]
triad = {
"prompt": "TRIAD PACK", "stable_prefix_len": 0, "session_task": "TRIAD TASK", "target_repo": repo,
"models": ["triad/a", "triad/native", "m/session"],
"routes": [ReviewRouteKind.API_CHAT, ReviewRouteKind.API_CHAT, ReviewRouteKind.AGENT_SESSION],
"row_plan": {"slot_ids": ["slot_1", "slot_2", "slot_3"], "subagent_ids": ["", "critic", ""]},
}
seats = commit_gate_paid_seats(triad, False, scope_rows)
assert [(s["surface"], s["slot_id"], s["model"]) for s in seats] == [
("scope_review", "scope_slot_1", SCOPE_MODEL),
("multi_model_review", "slot_1", "triad/a"),
("multi_model_review", "slot_2", "triad/native"),
]
assert seats[0]["prompt_chars"] == _native_first_send_size(
repo, surface="scope_review", session_task="SCOPE TASK", role_hint=SCOPE_ROLE_HINT,
output_contract=scope_mod.SCOPE_RETRIEVING_OUTPUT_CONTRACT, slot_id="scope_slot_1", model=SCOPE_MODEL)
assert seats[2]["prompt_chars"] == _native_first_send_size(
repo, surface="multi_model_review", session_task="TRIAD TASK", role_hint=TRIAD_ROLE_HINT,
output_contract=REVIEW_JSON_ARRAY_CONTRACT, slot_id="slot_2", model="triad/native")
# A native first send carries instructions, the work-order AND the six tool
# schemas (the packet row beside it carries the constitutional pack instead).
from ouroboros.review_native_episode import native_episode_prompt, native_first_send_messages
work_order_only = json.dumps(native_first_send_messages(native_episode_prompt(
"multi_model_review", TRIAD_ROLE_HINT, "TRIAD TASK", REVIEW_JSON_ARRAY_CONTRACT, "slot_2")),
ensure_ascii=False)
assert seats[2]["prompt_chars"] > len(work_order_only) > 0 and seats[1]["prompt_chars"] > 0
assert [s["max_completion_tokens"] for s in seats] == [100_000, _review_output_budget(), _review_output_budget()]
def test_scope_seat_is_measured_as_the_cached_block_pair_it_sends(gate, monkeypatch):
"""Fable minor: the scope send wraps the prompt in cached blocks at the
recorded stable boundary; the admission measures THAT pair, not a plain
system string, and the triad's user turn is one literal for both."""
import ouroboros.review_substrate as rs
from ouroboros.tools.review_admission import commit_gate_paid_seats
from ouroboros.tools.review_multi_model import TRIAD_USER_TURN
prefix, dynamic = "STABLE GOVERNANCE " * 20, "DYNAMIC DIFF " * 5
prompt = prefix + dynamic
sent = {}
class _StubLLM:
def chat(self, **kwargs):
sent["messages"] = kwargs["messages"]
return {"content": _scope_matrix()}, {"prompt_tokens": 4, "completion_tokens": 2}
original = rs.ReviewCoordinator.__init__
monkeypatch.setattr(rs.ReviewCoordinator, "__init__",
lambda self, *, llm=None, drive_root=None, usage_ctx=None:
original(self, llm=_StubLLM(), drive_root=drive_root, usage_ctx=usage_ctx))
ctx = SimpleNamespace(task_id=ROOT, event_queue=None, pending_events=[], drive_root=str(gate))
token = scope_mod._SCOPE_STABLE_PREFIX_LEN.set(len(prefix))
try:
_raw, _usage, err = scope_mod._call_scope_llm(prompt, scope_model=SCOPE_MODEL, ctx=ctx)
finally:
scope_mod._SCOPE_STABLE_PREFIX_LEN.reset(token)
assert err == ""
expected = scope_mod.scope_api_messages(prompt, len(prefix))
assert sent["messages"] == expected
assert isinstance(expected[0]["content"], list) and expected[0]["content"][0].get("cache_control")
slot = SimpleNamespace(model=SCOPE_MODEL, slot_id="scope_slot_1", route=ReviewRouteKind.API_CHAT, subagent_id="")
seats = commit_gate_paid_seats(None, True, [{"slot": slot, "final": None, "prepared": {
"prompt": prompt, "session_task": "", "scope_model_id": SCOPE_MODEL, "stable_prefix_len": len(prefix)}}])
measured = json.dumps({"messages": sent["messages"]}, ensure_ascii=False, default=str)
plain = json.dumps({"messages": [{"role": "system", "content": prompt},
{"role": "user", "content": scope_mod.SCOPE_USER_TURN}]}, ensure_ascii=False)
assert seats[0]["prompt_chars"] == len(measured) != len(plain)
captured = {}
def _fanout(ctx, **kwargs):
captured.update(kwargs)
raise RuntimeError("captured")
monkeypatch.setattr(review, "_handle_multi_model_review", _fanout)
review._dispatch_unified_review(
SimpleNamespace(task_id=ROOT, _review_history=[], _review_advisory=[], pending_events=[]),
"m", {"blocking_review": True, "prompt": "p", "models": ["triad/a"], "stable_prefix_len": 0,
"routes": [ReviewRouteKind.API_CHAT], "session_task": "", "target_repo": ".", "row_plan": {}})
assert captured["content"] == TRIAD_USER_TURN
def test_each_seat_is_priced_under_its_own_cache_split_not_the_callers(tmp_path, monkeypatch):
"""Finding 2: the observed cache split is keyed by the SENDING scope
(category + review slot). A warm split of the caller's own transcript must
not price a reviewer seat's cold prefix — the seat's bound is the full
write until the seat's OWN scope has observed a split. Real
``_reservation_cost``; only the price catalog is pinned."""
from dataclasses import replace
from ouroboros import pricing as pricing_mod
from ouroboros.pricing import infer_provider_from_model
class _P(tuple):
tiers = ()
model = "anthropic/claude-fable-5"
monkeypatch.setattr(pricing_mod, "get_pricing", lambda **k: {model: _P((10.0, 1.0, 12.5, 50.0))})
ua._reset_task_cache_splits()
provider = infer_provider_from_model(model)
caller = ua.UsageScope(drive_root=tmp_path, task_id=ROOT, root_task_id=ROOT, root_limit_usd=1000.0)
seat_scope = replace(caller, category="scope_review_review", review_slot_id="scope_slot_1")
kwargs = dict(root_task_id=ROOT, models=[model], prompt_chars=400_000, max_completion_tokens=1000,
task_id=ROOT, root_limit_usd=1000.0)
with ua.usage_scope(caller):
# The caller's transcript is warm (90% of the prompt read from cache).
ua.stash_task_cache_split(ROOT, model, 90_000, provider=provider, ttl_seconds=300.0)
callers_own = ua.review_wave_admission(tmp_path, **kwargs)["slot_bounds"][0]
cold_seat = ua.review_wave_admission(
tmp_path, categories="scope_review_review", slot_ids="scope_slot_1", **kwargs)["slot_bounds"][0]
with ua.usage_scope(seat_scope):
expected_cold = ua._reservation_cost(ua.AttemptRequest(
model=model, provider=provider, prompt_tokens_estimate=100_000,
max_completion_tokens=1000, task_id=ROOT))
ua.stash_task_cache_split(ROOT, model, 90_000, provider=provider, ttl_seconds=300.0)
warm_seat = ua.review_wave_admission(
tmp_path, categories="scope_review_review", slot_ids="scope_slot_1", **kwargs)["slot_bounds"][0]
assert cold_seat == pytest.approx(expected_cold)
assert cold_seat > callers_own # the caller's warm split never priced the seat
assert warm_seat == pytest.approx(callers_own) # the seat's OWN observed split does
def test_current_root_fence_governs_admission_over_the_ledgers_historical_minimum(gate, monkeypatch):
"""Finding 3: ``reserve_attempt`` enforces the CURRENT scope fence; the
ledger projection carries the minimum of historical row limits. Admission
must compare against the fence the reservation will use, whether it was
raised or lowered since the earlier rows — the projection serves only a
caller that binds no fence of its own."""
with ua.usage_scope(ua.UsageScope(drive_root=gate, task_id=ROOT, root_task_id=ROOT, root_limit_usd=8.0)):
held = ua.reserve_attempt(ua.AttemptRequest(model="triad/a", provider="test", source="main"))
ua.mark_dispatched(held)
ua.settle_attempt(held, {"prompt_tokens": 1, "completion_tokens": 1}, cost_usd=3.0, cost_final=True)
monkeypatch.setattr(ua, "_reservation_cost", lambda request: 4.5)
kwargs = dict(root_task_id=ROOT, models=["triad/a"], prompt_chars=10, task_id=ROOT)
raised = ua.review_wave_admission(gate, root_limit_usd=50.0, **kwargs)
assert (raised["limit_usd"], raised["accounted_usd"], raised["remaining_usd"]) == (50.0, 3.0, 47.0)
assert raised["fits"] is True
lowered = ua.review_wave_admission(gate, root_limit_usd=6.0, **kwargs)
assert (lowered["limit_usd"], lowered["remaining_usd"], lowered["fits"]) == (6.0, 3.0, False)
unfenced = ua.review_wave_admission(gate, **kwargs) # no fence of its own: the ledger's $8 row
assert (unfenced["limit_usd"], unfenced["remaining_usd"], unfenced["fits"]) == (8.0, 5.0, True)
def _reservation_ids() -> frozenset:
return frozenset(r["attempt_id"] for r in (ua.last_root_accounting(ROOT) or {}).get("reservations") or [])
def test_scope_first_hold_observes_only_the_scope_seats_own_reservation(gate, tmp_path, monkeypatch):
"""Finding 4: the hold releases on the scope seat's OWN appended reservation
(category + slot identity after the wave started) — a refresh, a
settlement, a refused reservation or another seat's reservation never
releases it — and a hold that ends without it is a typed event."""
from ouroboros import config as cfg
monkeypatch.setattr(cfg, "NESTED_SETTLEMENT_MARGIN_SEC", 0.3)
seats = [{"surface": "scope_review", "slot_id": "scope_slot_1", "model": SCOPE_MODEL,
"prompt_chars": 10, "max_completion_tokens": 10}]
never_done = SimpleNamespace(done=lambda: False)
base = ua.UsageScope(drive_root=gate, task_id=ROOT, root_task_id=ROOT, root_limit_usd=8.0)
scope_seat = ua.UsageScope(drive_root=gate, task_id=ROOT, root_task_id=ROOT, root_limit_usd=8.0,
category="scope_review_review", review_slot_id="scope_slot_1")
triad_seat = ua.UsageScope(drive_root=gate, task_id=ROOT, root_task_id=ROOT, root_limit_usd=8.0,
category="multi_model_review_review", review_slot_id="slot_1")
with ua.usage_scope(base):
earlier = ua.reserve_attempt(ua.AttemptRequest(model="triad/a", provider="test", source="main"))
ua.mark_dispatched(earlier)
started = time.monotonic()
known = _reservation_ids() # the wave's baseline: everything reserved before it
# Non-scope root telemetry updates after the wave started: none may release the hold.
ua.refresh_root_accounting(gate, ROOT)
with ua.usage_scope(base):
ua.settle_attempt(earlier, {"prompt_tokens": 1, "completion_tokens": 1}, cost_usd=0.5, cost_final=True)
with ua.usage_scope(ua.UsageScope(drive_root=gate, task_id=ROOT, root_task_id=ROOT, root_limit_usd=0.75)):
with pytest.raises(ua.BudgetExceeded): # refused: pre-fence refresh only, no identity
ua.reserve_attempt(ua.AttemptRequest(model="triad/a", provider="test", source="main"))
with ua.usage_scope(triad_seat):
ua.reserve_attempt(ua.AttemptRequest(model="triad/a", provider="test"))
assert not any(r["category"] == "scope_review_review"
for r in ua.last_root_accounting(ROOT)["reservations"])
with ua.usage_scope(base):
ctx = _ctx(gate, tmp_path)
parallel_review._await_scope_reservation(ctx, never_done, seats, started, known_ids=known)
assert time.monotonic() - started >= 0.3
events = [e for e in ctx.pending_events if e.get("type") == "review_scope_lead_unobserved"]
assert len(events) == 1 and events[0]["scope_slot_ids"] == ["scope_slot_1"]
assert events[0]["scope_seat_done"] is False and events[0]["root_task_id"] == ROOT
# The scope seat's own reservation, appended after the start, releases at once.
started = time.monotonic()
known = _reservation_ids()
with ua.usage_scope(scope_seat):
own = ua.reserve_attempt(ua.AttemptRequest(model=SCOPE_MODEL, provider="test"))
identities = ua.last_root_accounting(ROOT)["reservations"]
assert any(r["attempt_id"] == own.attempt_id and r["review_slot_id"] == "scope_slot_1" for r in identities)
with ua.usage_scope(base):
ctx = _ctx(gate, tmp_path)
hold_started = time.monotonic() # the pin times the HOLD, not the ledger append before it
parallel_review._await_scope_reservation(ctx, never_done, seats, started, known_ids=known)
assert time.monotonic() - hold_started < 0.25 and ctx.pending_events == []
# A scope seat that finished without ever reserving (e.g. refused) ends the hold typed;
# the earlier own reservation is in this wave's baseline, so it cannot release it —
# an identity check, not a clock one (Windows' monotonic tick would merge the two).
started = time.monotonic()
known = _reservation_ids()
with ua.usage_scope(base):
ctx = _ctx(gate, tmp_path)
parallel_review._await_scope_reservation(ctx, SimpleNamespace(done=lambda: True), seats, started, known_ids=known)
events = [e for e in ctx.pending_events if e.get("type") == "review_scope_lead_unobserved"]
assert len(events) == 1 and events[0]["scope_seat_done"] is True
def _seed_settled(root, *, fence: float, cost: float) -> None:
"""One settled main-loop row under ``fence`` (a ledger history row)."""
with ua.usage_scope(ua.UsageScope(drive_root=root, task_id=ROOT, root_task_id=ROOT, root_limit_usd=fence)):
row = ua.reserve_attempt(ua.AttemptRequest(model="triad/a", provider="test", source="main"))
ua.mark_dispatched(row)
ua.settle_attempt(row, {"prompt_tokens": 1, "completion_tokens": 1}, cost_usd=cost, cost_final=True)
def test_seats_reserve_against_exactly_the_fence_the_wave_was_admitted_with(gate, tmp_path, monkeypatch):
"""rc.14 audit (astra MAJOR on G1): admission prefers the caller scope's
bound fence, so the seats must reserve against THAT fence — not against a
setting hot-reloaded mid-turn that the reviewer threads would re-read from
the environment once the executor transitions dropped the usage scope.
Bound $50, environment $8, a $4 history row at $8: the $5 wave is admitted
against $50 AND every seat's own ``reserve_attempt`` binds $50 — dispatched
whole, never a partial paid wave."""
_seed_settled(gate, fence=8.0, cost=4.0)
llm = LedgerLLM()
ctx, (review_err, scope_result, _reason, _adv) = _run(
gate, tmp_path, monkeypatch, llm, fence=50.0, env_fence=8.0)
assert review_err is None, review_err
assert scope_result.blocked is False and scope_result.status == "responded"
assert sorted(llm.calls) == sorted([SCOPE_MODEL, *TRIAD_MODELS])
seats = [row for row in _ledger(gate) if row["state"] == "reserved" and row["source"] != "main"]
assert sorted(row["model"] for row in seats) == sorted([SCOPE_MODEL, *TRIAD_MODELS])
assert {row["root_limit_usd"] for row in seats} == {50.0}
assert not [e for e in ctx.pending_events if e.get("type") == "review_wave_budget_insufficient"]
assert not [r for r in ctx._last_triad_raw_results if r.get("status") != "ok"] or all(
"BudgetExceeded" not in str(r.get("error") or "") for r in ctx._last_triad_raw_results)
def test_bound_fence_that_does_not_fit_refuses_even_when_the_environment_is_roomier(gate, tmp_path, monkeypatch):
"""The converse: bound $8 (admission's fence), environment $50, a $4 history
row: the $5 wave does not fit the bound fence and nothing is dispatched —
the roomier setting never buys a seat the admitting fence refused."""
_seed_settled(gate, fence=8.0, cost=4.0)
llm = LedgerLLM()
ctx, (review_err, scope_result, block_reason, _adv) = _run(
gate, tmp_path, monkeypatch, llm, fence=8.0, env_fence=50.0)
assert llm.calls == [] and len(_ledger(gate)) == 3 # the seeded row's reserved/dispatched/settled only
assert review_err and "per-task budget fence $8.000000" in review_err
assert "remaining=$4.000000, shortfall=$1.000000" in review_err
assert block_reason == "review_wave_budget_insufficient"
assert scope_result.status == "not_dispatched"
def test_scope_first_hold_ignores_a_sibling_tasks_same_named_scope_slot(gate, tmp_path, monkeypatch):
"""rc.14 audit (fable MINOR 1): two tasks under one root each run their own
commit gate with the default ``scope_slot_1``; the sibling's reservation
carries the right category, slot and time but ITS task id, and must not
release this task's hold — only this task's own scope seat does."""
from ouroboros import config as cfg
monkeypatch.setattr(cfg, "NESTED_SETTLEMENT_MARGIN_SEC", 0.3)
seats = [{"surface": "scope_review", "slot_id": "scope_slot_1", "model": SCOPE_MODEL,
"prompt_chars": 10, "max_completion_tokens": 10}]
never_done = SimpleNamespace(done=lambda: False)
base = ua.UsageScope(drive_root=gate, task_id=ROOT, root_task_id=ROOT, root_limit_usd=8.0)
sibling_seat = ua.UsageScope(drive_root=gate, task_id="sibling-task", root_task_id=ROOT, root_limit_usd=8.0,
category="scope_review_review", review_slot_id="scope_slot_1")
own_seat = ua.UsageScope(drive_root=gate, task_id=ROOT, root_task_id=ROOT, root_limit_usd=8.0,
category="scope_review_review", review_slot_id="scope_slot_1")
started = time.monotonic()
with ua.usage_scope(sibling_seat):
sibling = ua.reserve_attempt(ua.AttemptRequest(model=SCOPE_MODEL, provider="test"))
identities = ua.last_root_accounting(ROOT)["reservations"]
assert any(r["attempt_id"] == sibling.attempt_id and r["task_id"] == "sibling-task" for r in identities)
with ua.usage_scope(base):
ctx = _ctx(gate, tmp_path)
parallel_review._await_scope_reservation(ctx, never_done, seats, started)
assert time.monotonic() - started >= 0.3
events = [e for e in ctx.pending_events if e.get("type") == "review_scope_lead_unobserved"]
assert len(events) == 1 and events[0]["task_id"] == ROOT
started = time.monotonic()
with ua.usage_scope(own_seat):
ua.reserve_attempt(ua.AttemptRequest(model=SCOPE_MODEL, provider="test"))
with ua.usage_scope(base):
ctx = _ctx(gate, tmp_path)
hold_started = time.monotonic() # the pin times the HOLD, not the ledger append before it
parallel_review._await_scope_reservation(ctx, never_done, seats, started)
assert time.monotonic() - hold_started < 0.25 and ctx.pending_events == []
def test_admission_that_raises_fails_open_loudly_and_typed(gate, tmp_path, monkeypatch, caplog):
"""rc.14 audit (fable MINOR 2): an exception inside the money admission keeps
the owner's fail-open (the wave dispatches, unadmitted and without the
scope-first hold) but is a warning plus ONE typed
``review_wave_admission_unavailable`` event naming the error — never a
debug line indistinguishable from an admitted wave."""
import logging
def _boom(*_a, **_k):
raise RuntimeError("no inspection tool schemas are projectable")
monkeypatch.setattr(review_admission, "commit_gate_paid_seats", _boom)
llm = LedgerLLM()
with caplog.at_level(logging.WARNING, logger="ouroboros.tools.parallel_review"):
ctx, (review_err, scope_result, _reason, _adv) = _run(gate, tmp_path, monkeypatch, llm, fence=10.0)
assert review_err is None and scope_result.status == "responded"
assert sorted(llm.calls) == sorted([SCOPE_MODEL, *TRIAD_MODELS])
events = [e for e in ctx.pending_events if e.get("type") == "review_wave_admission_unavailable"]
assert len(events) == 1
assert events[0]["surface"] == "commit_gate" and events[0]["task_id"] == ROOT
assert events[0]["error"] == "RuntimeError: no inspection tool schemas are projectable"
assert not [e for e in ctx.pending_events if e.get("type") in {
"review_wave_budget_insufficient", "review_scope_lead_unobserved"}]
assert any("commit-gate wave admission unavailable (RuntimeError" in r.getMessage()
and r.levelno == logging.WARNING for r in caplog.records)