mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-02 19:58:46 +00:00
The gate decision records why a plan review stayed open as a closed class on the typed execution axis (only some reviewers answered, none answered, answered but never closed), beside the existing awaiting case; the normal delivery rail stamps terminal_plan_review_open on the result exactly as the forced rails do. The owner cause table, in Python and in its browser twin, gains the sentences for those classes, for a task held by a blocking review, and for the standing limitations of an answer (a plan review still open at delivery, deferred sub-task results, an unrecovered tool failure, an internal error); phrases that named internal actors are reworded. The completion verdict becomes an assembler on both twins: one clause per fact, joined, never one fact chosen over another; the parity fixture carries the new production-shaped cases. The durable task_summary row carries the same clause as a reason_detail field for transports without a card. Forced prompts hand their one model call the same limitations as typed facts, priced by the existing reservation probe; a hold the owner's hurry released reaches the model once before its last word. Delivery no longer splits a terminal into an answer and a System notice: the terminal_host_notice field keeps its bytes on the result for every machine reader, and only the custody audit rides the send event as its own typed row. A Telegram-only owner, who has no task card, receives one short line on a non-clean root finish built from the stamped phase and the reason sentence; clean finishes stay opt-in. In the Reviews group a settled plan wave whose reviewers were too few for a verdict reads no verdict with its counts in the neutral tone instead of the stored DEGRADED word; reviewer rows name the model and quote the engine's reported sentence, and the slot id and failure code stay in the task detail and Logs. DESIGN.md and the architecture chapters state the same; two chapter byte budgets rise with their reasons.
198 lines
11 KiB
Python
198 lines
11 KiB
Python
"""The existing final-call price is compared with live shared-wallet evidence."""
|
|
|
|
from dataclasses import replace
|
|
|
|
import pytest
|
|
|
|
from ouroboros import task_pacing, usage_accounting as accounting
|
|
from ouroboros.loop_budget import _wrapup_global_remaining
|
|
|
|
|
|
def _request(amount, task="other"):
|
|
return accounting.AttemptRequest(model="fixture-model", provider="openai",
|
|
reservation_usd=amount, task_id=task, root_task_id=task)
|
|
|
|
|
|
@pytest.mark.parametrize("root_cap", [None, 50.0])
|
|
def test_actual_global_holds_leave_one_same_route_final_then_the_atomic_fence_binds(tmp_path, root_cap):
|
|
scope = accounting.UsageScope(drive_root=tmp_path, task_id="main", root_task_id="main",
|
|
global_limit_usd=10.0, root_limit_usd=root_cap)
|
|
with accounting.usage_scope(scope):
|
|
other = accounting.reserve_attempt(_request(8.0))
|
|
request = _request(1.5, "main")
|
|
remaining = _wrapup_global_remaining()
|
|
assert remaining == 2.0
|
|
args = dict(request=request, root_cap_usd=root_cap, deciding_usd=0.0,
|
|
global_remaining_usd=remaining)
|
|
assert task_pacing.wrapup_reservation_fits(**args) is True
|
|
assert task_pacing.wrapup_reservation_fits(**args, reservation_count=2) is False
|
|
final = accounting.reserve_attempt(request)
|
|
with pytest.raises(accounting.BudgetExceeded) as refused:
|
|
accounting.reserve_attempt(request)
|
|
assert refused.value.limit_scope == "global"
|
|
assert _wrapup_global_remaining() == 0.5
|
|
accounting.mark_dispatched(other)
|
|
accounting.mark_unresolved(other, "provider outcome unknown")
|
|
assert _wrapup_global_remaining() == 0.5 # a dead/unknown call is not a refund
|
|
accounting.release_attempt(final, "controlled test did not send")
|
|
assert _wrapup_global_remaining() == 2.0
|
|
|
|
|
|
@pytest.mark.parametrize("global_remaining,root_cap,deciding,expected", [
|
|
(20.0, 2.0, 1.0, False), (1.0, 50.0, 0.0, False),
|
|
(2.0, None, None, True), (0.0, None, None, False), (None, None, None, None),
|
|
])
|
|
def test_all_known_remainders_bind_and_unknown_tree_spend_is_not_invented(
|
|
tmp_path, global_remaining, root_cap, deciding, expected,
|
|
):
|
|
with accounting.usage_scope(accounting.UsageScope(drive_root=tmp_path, task_id="main", root_task_id="main")):
|
|
assert task_pacing.wrapup_reservation_fits(request=_request(1.5, "main"), root_cap_usd=root_cap,
|
|
deciding_usd=deciding, global_remaining_usd=global_remaining) is expected
|
|
|
|
|
|
def test_a_concurrent_reservation_after_the_check_can_still_refuse_the_final(tmp_path):
|
|
scope = accounting.UsageScope(drive_root=tmp_path, task_id="main", root_task_id="main", global_limit_usd=2.0)
|
|
with accounting.usage_scope(scope):
|
|
request = _request(1.5, "main")
|
|
assert task_pacing.wrapup_reservation_fits(request=request, root_cap_usd=None, deciding_usd=0,
|
|
global_remaining_usd=_wrapup_global_remaining()) is True
|
|
accounting.reserve_attempt(_request(1.0))
|
|
with pytest.raises(accounting.BudgetExceeded):
|
|
accounting.reserve_attempt(request) # observation did not reserve a share
|
|
|
|
|
|
def test_explicit_scope_limit_and_canonical_root_own_the_wallet_read(tmp_path):
|
|
scope = accounting.UsageScope(drive_root=tmp_path, task_id="main", root_task_id="main", global_limit_usd=10.0)
|
|
with accounting.usage_scope(scope):
|
|
accounting.reserve_attempt(_request(3.0))
|
|
assert _wrapup_global_remaining() == 7.0
|
|
with accounting.usage_scope(replace(scope, global_limit_usd=20.0)):
|
|
assert _wrapup_global_remaining() == 17.0
|
|
|
|
|
|
def test_projection_failure_and_torn_ledger_are_not_a_known_zero(tmp_path, monkeypatch):
|
|
scope = accounting.UsageScope(drive_root=tmp_path, task_id="main", root_task_id="main", global_limit_usd=10.0)
|
|
with accounting.usage_scope(scope):
|
|
monkeypatch.setattr(accounting, "usage_projection", lambda *_a, **_k: {
|
|
"remaining_known_usd": 0.0, "integrity_degraded": True})
|
|
assert _wrapup_global_remaining() is None
|
|
def fail(*args, **kwargs):
|
|
raise OSError("ledger unavailable")
|
|
monkeypatch.setattr(accounting, "usage_projection", fail)
|
|
assert _wrapup_global_remaining() is None
|
|
|
|
|
|
def test_global_remaining_is_disclosed_without_fabricating_a_tree_amount():
|
|
ceiling = task_pacing.CostCeiling(state="active", ceiling_usd=5.0)
|
|
text = task_pacing.wrapup_last_fit_text(None, ceiling, 2.0)
|
|
assert "spend is unavailable" in text and "$2.00 left across all tasks" in text
|
|
|
|
|
|
def test_the_stop_sentence_opens_with_the_bound_that_binds():
|
|
"""An owner whose WALLET ran dry at $125 of a $400 cap must not read a per-task-cap story."""
|
|
capped = task_pacing.CostCeiling(state="active", ceiling_usd=239.66, root_cap_usd=400.0)
|
|
wallet = task_pacing.wrapup_last_fit_text(125.661, capped, 21.834)
|
|
assert wallet.startswith("The shared Total budget is nearly used up: $21.83 left across all tasks.")
|
|
assert "$125.66 of its own $400.00 cap, so the task cap is not what stopped it" in wallet
|
|
assert wallet.endswith("Raise Total budget in Settings for more room.")
|
|
cap = task_pacing.wrapup_last_fit_text(390.0, capped, 500.0)
|
|
assert cap.startswith("This task's tree spent $390.00 of its own $400.00 cap; the shared Total budget still has $500.00.")
|
|
assert "Raise Total budget" not in cap
|
|
assert task_pacing.wrapup_unaffordable_text(125.661, capped, 0.4).startswith("The shared Total budget is nearly used up")
|
|
|
|
|
|
def test_local_final_call_path_does_not_request_an_unneeded_wallet_projection(tmp_path, monkeypatch):
|
|
from ouroboros import loop
|
|
from tests.test_tree_cost_ceiling import _ctx
|
|
|
|
reads = []
|
|
monkeypatch.setattr(accounting, "usage_projection", lambda *_a, **_k: reads.append(True) or {})
|
|
monkeypatch.setattr(accounting, "_ROOT_ACCOUNTING_TELEMETRY", {})
|
|
scope = accounting.UsageScope(drive_root=tmp_path, task_id="local", root_task_id="local", global_limit_usd=10.0)
|
|
with accounting.usage_scope(scope):
|
|
accounting._stash_root_accounting("local", 0.0, None)
|
|
assert loop._check_budget_limits(_ctx(active_use_local=True), 10.0,
|
|
task_pacing.CostCeiling(state="active", ceiling_usd=5.0)) is None
|
|
assert reads == []
|
|
|
|
|
|
def test_local_soft_landing_keeps_the_same_local_affordability_contract(tmp_path, monkeypatch):
|
|
from types import SimpleNamespace
|
|
from ouroboros import loop
|
|
from tests.test_tree_cost_ceiling import _ctx
|
|
|
|
ctx = _ctx(active_use_local=True, drive_root=tmp_path, drive_logs=tmp_path / "logs", llm=SimpleNamespace())
|
|
monkeypatch.setattr(loop, "_prepare_forced_prompt", lambda _ctx, text, _trace: text)
|
|
monkeypatch.setattr(task_pacing, "prepared_wrapup_candidate", lambda _ctx, messages, **_k: (_request(1.5, "local"), messages))
|
|
monkeypatch.setattr(loop, "_forced_final_answer", lambda *_a, **_k: ("local final", {}, {}))
|
|
ceiling = task_pacing.resolve_cost_ceiling(10.0, {"cost_hard_stop_pct": 50}, root_cap_usd=0.5)
|
|
scope = accounting.UsageScope(drive_root=tmp_path, task_id="local-soft", root_task_id="local-soft", global_limit_usd=0.0)
|
|
with accounting.usage_scope(scope):
|
|
assert loop._soft_land_exhausted_ceiling(ctx, ceiling)[0] == "local final"
|
|
|
|
|
|
def test_forced_prompt_facts_are_priced_before_the_wrap_up_is_admitted(tmp_path, monkeypatch):
|
|
"""The typed facts block is added INSIDE ``_prepare_forced_prompt``, so the tokens
|
|
it adds go through the existing wrap-up reservation probe, never around it."""
|
|
from types import SimpleNamespace
|
|
from ouroboros import loop
|
|
from tests.test_tree_cost_ceiling import _ctx
|
|
|
|
decision = {"required": True, "status": "advisory_open", "allow": True, "closed": False,
|
|
"outcome": "DEGRADED", "enforcement": "advisory", "reviewer_slots_degraded": True}
|
|
ctx = _ctx(active_use_local=True, drive_root=tmp_path, drive_logs=tmp_path / "logs",
|
|
llm=SimpleNamespace(), llm_trace={"force_plan_decision": decision})
|
|
priced = []
|
|
monkeypatch.setattr(task_pacing, "prepared_wrapup_candidate",
|
|
lambda _ctx, messages, **_k: (priced.append(messages), (_request(1.5, "local"), messages))[1])
|
|
monkeypatch.setattr(loop, "_forced_final_answer", lambda *_a, **_k: ("local final", {}, {}))
|
|
ceiling = task_pacing.resolve_cost_ceiling(10.0, {"cost_hard_stop_pct": 50}, root_cap_usd=0.5)
|
|
scope = accounting.UsageScope(drive_root=tmp_path, task_id="local-soft", root_task_id="local-soft", global_limit_usd=0.0)
|
|
with accounting.usage_scope(scope):
|
|
assert loop._soft_land_exhausted_ceiling(ctx, ceiling)[0] == "local final"
|
|
(messages,) = priced
|
|
last = next(m for m in reversed(messages) if m["role"] == "user")
|
|
assert "[TASK_STATE_FACTS]" in str(last["content"]) and "plan_review_open=true" in str(last["content"])
|
|
assert "[BUDGET LIMIT]" in str(last["content"])
|
|
# Nothing to state, nothing priced: a closed gate adds no block to the probe.
|
|
priced.clear()
|
|
ctx.llm_trace = {"force_plan_decision": {**decision, "status": "closed", "closed": True}}
|
|
with accounting.usage_scope(scope):
|
|
loop._soft_land_exhausted_ceiling(ctx, ceiling)
|
|
assert "[TASK_STATE_FACTS]" not in str(next(m for m in reversed(priced[0]) if m["role"] == "user")["content"])
|
|
|
|
|
|
@pytest.mark.parametrize("root_cap", [None, 50.0])
|
|
def test_live_wallet_triggers_the_existing_final_call_path_without_a_root_cap(tmp_path, monkeypatch, root_cap):
|
|
from types import SimpleNamespace
|
|
from ouroboros import loop
|
|
from tests.test_tree_cost_ceiling import _ctx
|
|
|
|
# Controlled reservation cost, shared by prediction and the actual ledger fence.
|
|
monkeypatch.setattr(accounting, "_reservation_cost", lambda request: (
|
|
request.reservation_usd if request.reservation_usd is not None else 1.5))
|
|
scope = accounting.UsageScope(drive_root=tmp_path, task_id="main", root_task_id="main",
|
|
global_limit_usd=10.0, root_limit_usd=root_cap)
|
|
ctx = _ctx(task_id="main", drive_root=tmp_path, drive_logs=tmp_path / "logs", llm=SimpleNamespace())
|
|
ceiling = task_pacing.CostCeiling(state="active", ceiling_usd=5.0, root_cap_usd=root_cap)
|
|
prepared = []
|
|
monkeypatch.setattr(loop, "_prepare_forced_prompt", lambda _ctx, text, _trace: (prepared.append(text), text)[1])
|
|
request = _request(1.5, "main")
|
|
monkeypatch.setattr(task_pacing, "prepared_wrapup_candidate", lambda _ctx, messages, **_k: (request, messages))
|
|
admitted = []
|
|
def finish(actual_ctx, **kwargs):
|
|
assert kwargs["_admitted_request"] is request
|
|
admitted.append(accounting.reserve_attempt(request))
|
|
return "verified final", actual_ctx.accumulated_usage, actual_ctx.llm_trace
|
|
monkeypatch.setattr(loop, "_forced_final_answer", finish)
|
|
with accounting.usage_scope(scope):
|
|
accounting.reserve_attempt(_request(6.0))
|
|
assert loop._check_budget_limits(ctx, 10.0, ceiling) is None
|
|
assert prepared == [] and admitted == []
|
|
accounting.reserve_attempt(_request(2.0, "another-root"))
|
|
result = loop._check_budget_limits(ctx, 10.0, ceiling)
|
|
assert result[0] == "verified final" and len(admitted) == 1 and len(prepared) == 1
|
|
assert "$2.00 left across all tasks" in prepared[0]
|
|
assert ctx.accumulated_usage["cost_stop_rail"] == "wrapup_reservation_last_fit"
|
|
assert _wrapup_global_remaining() == 0.5
|
|
accounting.release_attempt(admitted[0], "controlled final callback did not send")
|