mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-02 19:58:46 +00:00
Background Consciousness redesign, P3 (accounting). The daily allowance (`OUROBOROS_CONSCIOUSNESS_DAILY_USD`, owner decisions В11/В18) gates NEW starts on the money the whole consciousness tree accounted in the last 24 h. There is no root index: `consciousness_allowance.allowance_window` names the roots by the category of their FINAL ledger rows younger than the fold horizon (`consciousness` = a wake's own rows, `consciousness_task` = a started root's), then sums every money row kind under those roots (attempt / subscription_session / external_unmetered — never a folded usage_baseline_group or legacy_* row) with `ts` inside the window through the ONE money reducer `_usage_rows._summary`. `accounted_usd` is the number (reservations included, disclosed), `unknown_unmetered` makes it "at least", `resets_at` = the oldest counted spend + 24 h. The row selection is memoized by ledger fingerprint through the same `_render_cached` seam as `usage_projection`; the time filter runs on every call, so an exhausted window frees itself without a new write. An unreadable ledger is the typed `allowance_unknown` outcome; `remaining_allowance_usd()` is the P2 helper. The compactor makes the window readable: `_foldable_attempt_ids` keeps every attempt whose final row is younger than `USAGE_LEDGER_FOLD_MIN_AGE_SEC` (48 h, SSOT in runtime_limits, re-exported from config) in the live file with its true `ts`; a group row carries the compaction instant and stays outside the window by kind. The fold's clock is a module attribute, so the existing compaction pins run on a fixture clock one horizon ahead and every money-conservation assertion is unchanged. `_usage_rows.row_ts_epoch` is the one ISO reader for both. Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
132 lines
5.5 KiB
Python
132 lines
5.5 KiB
Python
"""Shared fixtures and helpers for the CPL4-C6 compaction pins.
|
|
|
|
Imported by ``tests/test_usage_compaction.py`` (the compaction pass) and
|
|
``tests/test_usage_compaction_archive.py`` (the archive reader / CPL-5 join
|
|
surface). Module, not conftest, following the ``tests/fixtures_*.py``
|
|
convention already used by ``tests/fixtures_e2e_cancellation.py``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from ouroboros import usage_accounting as ua
|
|
from ouroboros import usage_compaction as uc
|
|
|
|
|
|
# The fold keeps attempts younger than USAGE_LEDGER_FOLD_MIN_AGE_SEC unfolded; the
|
|
# fixtures write rows NOW and the pins expect them to fold, so the compactor's
|
|
# clock runs one horizon ahead — the money assertions stay exactly as they are.
|
|
FOLD_CLOCK_AHEAD_SEC = 2 * uc.USAGE_LEDGER_FOLD_MIN_AGE_SEC
|
|
|
|
|
|
def age_fixture_clock(monkeypatch, ahead_sec=FOLD_CLOCK_AHEAD_SEC):
|
|
monkeypatch.setattr(uc, "_fold_clock", lambda: time.time() + ahead_sec)
|
|
|
|
|
|
@pytest.fixture
|
|
def data_root_any_tier(tmp_path, monkeypatch):
|
|
root = tmp_path / "data"
|
|
age_fixture_clock(monkeypatch)
|
|
monkeypatch.setenv("OUROBOROS_DATA_DIR", str(root))
|
|
monkeypatch.setenv("OUROBOROS_SETTINGS_PATH", str(root / "settings.json"))
|
|
monkeypatch.setenv("TOTAL_BUDGET", "100")
|
|
(root / "state").mkdir(parents=True)
|
|
(root / ua.IMPORT_REL).parent.mkdir(parents=True, exist_ok=True)
|
|
(root / ua.IMPORT_REL).write_text(
|
|
json.dumps({"completed": True}), encoding="utf-8"
|
|
)
|
|
return root
|
|
|
|
|
|
@pytest.fixture
|
|
def data_root(data_root_any_tier):
|
|
return data_root_any_tier # the pass runs on every OS since the 7.0 Windows kernel tier
|
|
|
|
|
|
def _request(data_root, **overrides):
|
|
values = {"model": "openai/gpt-5.2", "provider": "openai", "reservation_usd": 1.0,
|
|
"drive_root": data_root, "task_id": "child", "root_task_id": "root", "source": "test"}
|
|
values.update(overrides)
|
|
return ua.AttemptRequest(**values)
|
|
|
|
|
|
def _ledger_lines(data_root):
|
|
path = data_root / ua.LEDGER_REL
|
|
if not path.exists():
|
|
return []
|
|
return [line for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
|
|
|
|
|
def _ledger_rows(data_root):
|
|
return [json.loads(line) for line in _ledger_lines(data_root)]
|
|
|
|
|
|
def _settle(data_root, *, cost=None, usage=None, cost_final=False, **request_overrides):
|
|
reservation = ua.reserve_attempt(_request(data_root, **request_overrides))
|
|
ua.mark_dispatched(reservation)
|
|
ua.settle_attempt(reservation, usage or {"prompt_tokens": 10, "completion_tokens": 5},
|
|
cost_usd=cost, cost_final=cost_final)
|
|
return reservation
|
|
|
|
|
|
def _seed_mixed_ledger(data_root):
|
|
"""A realistic ledger: settled (weird floats, unknown costs), unresolved,
|
|
released, in-flight, sessions, external, review-attributed."""
|
|
_settle(data_root, cost=0.123456789012345, cost_final=True)
|
|
_settle(data_root, cost=1.1, task_id="t2", root_task_id="root2", root_limit_usd=50.0)
|
|
_settle(data_root, cost=2.2, task_id="t2", root_task_id="root2", root_limit_usd=40.0)
|
|
_settle(data_root, cost=None, usage={}, model="openai/gpt-5.2-mini")
|
|
reservation = ua.reserve_attempt(_request(data_root, task_id="t3"))
|
|
ua.mark_dispatched(reservation)
|
|
ua.mark_unresolved(reservation, "provider went dark")
|
|
reservation = ua.reserve_attempt(_request(data_root, task_id="t4"))
|
|
ua.release_attempt(reservation, "not_dispatched")
|
|
ua.record_subscription_session("sess-1", drive_root=data_root, route="claudexor:claude", model="fable",
|
|
task_id="t5", root_task_id="root", spend_usd=0.5, reset_at="2026-09-02T00:00:00Z")
|
|
ua.record_unmetered_external_dispatch("ext-1", drive_root=data_root, model="ext-model", task_id="t6",
|
|
prompt_tokens=7, completion_tokens=3)
|
|
with ua.usage_scope(ua.UsageScope(
|
|
drive_root=data_root, task_id="rv", root_task_id="root",
|
|
review_skill="skill-x", review_wave_id="w1", review_slot_id="s1",
|
|
)):
|
|
_settle(data_root, cost=3.5, cost_final=True)
|
|
# In-flight chains that MUST survive: one reserved, one dispatched.
|
|
reserved = ua.reserve_attempt(_request(data_root, task_id="open-r"))
|
|
dispatched = ua.reserve_attempt(_request(data_root, task_id="open-d"))
|
|
ua.mark_dispatched(dispatched)
|
|
return reserved, dispatched
|
|
|
|
|
|
def _compact(data_root):
|
|
with ua._locked(data_root) as heartbeat:
|
|
return uc.compact_usage_ledger_locked(data_root, heartbeat=heartbeat)
|
|
|
|
|
|
@pytest.fixture
|
|
def compacted(data_root):
|
|
"""A seeded, compacted ledger: its live header and the segment that header names."""
|
|
_seed_mixed_ledger(data_root)
|
|
assert _compact(data_root) is not None
|
|
header = _ledger_rows(data_root)[0]
|
|
return header, data_root / header["archive_rel"]
|
|
|
|
|
|
def _append_raw_row(data_root, row):
|
|
"""Append one already-legal row straight to the live ledger bytes."""
|
|
path = data_root / ua.LEDGER_REL
|
|
row = {**row, "seq": len(_ledger_lines(data_root)) + 1}
|
|
with path.open("ab") as handle:
|
|
handle.write((json.dumps(row, sort_keys=True) + "\n").encode("utf-8"))
|
|
return row
|
|
|
|
|
|
def _raced_row(attempt_id):
|
|
"""A settled charge a concurrent holder lands: the row a stale swap erases."""
|
|
return {"kind": "subscription_session", "attempt_id": attempt_id, "state": "settled",
|
|
"ts": "2026-09-01T00:00:00+00:00", "cost_usd": 0.25, "cost_final": True, "model": "fable",
|
|
"provider": "claudexor", "category": "task", "source": "subscription", "task_id": "t",
|
|
"root_task_id": "root", "parent_task_id": ""}
|