mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-02 19:58:46 +00:00
582 lines
29 KiB
Python
582 lines
29 KiB
Python
"""ABI 7.0 (ABI-3): per-alias removal pins for the five gateway compat aliases.
|
|
|
|
One test class per alias (declaration / producer / stored tolerance /
|
|
migration surface), per docs/architecture/11-frozen-contracts-v1.md. These pins
|
|
are the REMOVAL side; the read-tolerance side lives in
|
|
tests/test_cost_projection.py and the endpoint behavior in
|
|
tests/test_ui_preferences_api.py / tests/test_gateway_history.py.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import get_type_hints
|
|
|
|
import pytest
|
|
|
|
|
|
class TestCostAliasRemoval:
|
|
def test_declaration_gone_from_chat_outbound(self):
|
|
from ouroboros.gateway.contracts import ChatOutbound
|
|
|
|
hints = set(get_type_hints(ChatOutbound, include_extras=True))
|
|
assert "cost_usd" not in hints
|
|
assert "cost_usd_with_children" not in hints
|
|
assert {"accounted_upper_bound_usd",
|
|
"accounted_upper_bound_usd_with_children"} <= hints
|
|
|
|
def test_ssot_emitters_never_emit_the_alias(self):
|
|
from ouroboros.cost_projection import (
|
|
carry_cost_meta,
|
|
cost_projection,
|
|
with_cost_aliases,
|
|
)
|
|
|
|
legacy_source = {"cost_usd": 1.0, "cost_usd_with_children": 2.0,
|
|
"cost_final": True}
|
|
for out in (with_cost_aliases(legacy_source),
|
|
carry_cost_meta(legacy_source),
|
|
cost_projection(legacy_source)):
|
|
assert "cost_usd" not in out and "cost_usd_with_children" not in out
|
|
assert out["accounted_upper_bound_usd"] == 1.0
|
|
|
|
def test_live_root_projection_emits_honest_names_only(self, tmp_path):
|
|
from ouroboros.cost_projection import live_root_cost_projection
|
|
|
|
out = live_root_cost_projection(
|
|
"t1", {"metadata": {}}, {}, tmp_path)
|
|
# Root with an empty ledger returns {}; a non-root returns {} — either
|
|
# way the alias never appears. Exercise the unavailable branch too.
|
|
assert "cost_usd" not in out and "cost_usd_with_children" not in out
|
|
|
|
def test_admission_failure_record_stamps_the_honest_name(self):
|
|
# gateway/tasks.py admission-failure producer switched off the alias.
|
|
import inspect
|
|
|
|
from ouroboros.gateway import tasks as gateway_tasks
|
|
|
|
source = inspect.getsource(gateway_tasks)
|
|
assert "cost_usd=0.0" not in source
|
|
assert "accounted_upper_bound_usd=0.0" in source
|
|
|
|
def test_stored_legacy_record_still_reads(self, tmp_path):
|
|
from ouroboros.cost_projection import cost_projection
|
|
from ouroboros.task_results import load_task_result, write_task_result
|
|
|
|
# A record authored by an older release (raw legacy field passthrough).
|
|
write_task_result(tmp_path, "legacy", "completed",
|
|
cost_usd=1.25, cost_final=True)
|
|
stored = load_task_result(tmp_path, "legacy")
|
|
assert cost_projection(stored)["accounted_upper_bound_usd"] == 1.25
|
|
|
|
|
|
class TestTelegramChatIdRemoval:
|
|
def test_declaration_gone_from_all_outbound_frames(self):
|
|
from ouroboros.gateway.contracts import (
|
|
ChatOutbound,
|
|
DocumentOutbound,
|
|
PhotoOutbound,
|
|
VideoOutbound,
|
|
)
|
|
|
|
for cls in (ChatOutbound, PhotoOutbound, VideoOutbound, DocumentOutbound):
|
|
hints = get_type_hints(cls, include_extras=True)
|
|
assert "telegram_chat_id" not in hints, cls.__name__
|
|
assert "transport" in hints, cls.__name__
|
|
|
|
def test_no_runtime_producer_left(self):
|
|
# The history mapper was the ONLY emitter; grep-level absence pin.
|
|
import inspect
|
|
|
|
from ouroboros.gateway import history as gateway_history
|
|
|
|
source = inspect.getsource(gateway_history)
|
|
assert '"telegram_chat_id": ' not in source
|
|
|
|
def test_legacy_stored_row_replays_without_reemitting_the_key(self, tmp_path):
|
|
from ouroboros.gateway.history import _collect_chat_rows
|
|
|
|
chat = tmp_path / "logs" / "chat.jsonl"
|
|
chat.parent.mkdir(parents=True, exist_ok=True)
|
|
row = {
|
|
"ts": "2026-01-01T00:00:00+00:00", "direction": "in",
|
|
"text": "legacy row", "type": "", "telegram_chat_id": 5,
|
|
"chat_id": 1,
|
|
}
|
|
chat.write_text(json.dumps(row) + "\n", encoding="utf-8")
|
|
rows, _quota = _collect_chat_rows(
|
|
chat, tmp_path / "logs" / "archive", 10,
|
|
lambda entry_chat, entry=None: True, {})
|
|
assert len(rows) == 1
|
|
assert rows[0]["text"] == "legacy row"
|
|
# Stored tolerance: the legacy key is read-and-ignored — the outbound
|
|
# history record never re-emits it (ABI-3), and replay is not rejected.
|
|
assert "telegram_chat_id" not in rows[0]
|
|
|
|
|
|
class TestUiPreferenceAliasRemoval:
|
|
def test_declaration_gone_from_response_contract(self):
|
|
from ouroboros.gateway.contracts import UiPreferencesResponse
|
|
|
|
hints = set(get_type_hints(UiPreferencesResponse, include_extras=True))
|
|
assert "project_last_viewed" not in hints
|
|
assert "project_hidden" not in hints
|
|
assert "project_seen_revision" in hints
|
|
|
|
def test_defaults_and_known_keys_dropped_the_aliases(self):
|
|
from ouroboros.gateway.ui_preferences import DEFAULT_UI_PREFERENCES
|
|
|
|
assert "project_last_viewed" not in DEFAULT_UI_PREFERENCES
|
|
assert "project_hidden" not in DEFAULT_UI_PREFERENCES
|
|
|
|
def test_stored_legacy_keys_are_ignored_not_fatal(self, tmp_path):
|
|
from ouroboros.gateway.ui_preferences import _normalize_preferences
|
|
|
|
prefs = _normalize_preferences({
|
|
"widget_order": ["a"],
|
|
"project_last_viewed": {"p": "2026-01-01T00:00:00Z"},
|
|
"project_hidden": {"p": True},
|
|
})
|
|
assert prefs["widget_order"] == ["a"]
|
|
assert "project_last_viewed" not in prefs
|
|
assert "project_hidden" not in prefs
|
|
|
|
|
|
class TestAliasProducerFanOutSweep:
|
|
"""Ф3.1 fix-round-2: fan-out-complete producer pin over the WHOLE runtime
|
|
tree (every ``ouroboros/**/*.py`` and ``supervisor/**/*.py``).
|
|
|
|
No production code emits a retired gateway alias key in an emission-shaped
|
|
AST position — a dict-literal key, a subscript assignment, a keyword
|
|
argument on ANY call (receipt constructors, event emitters, ``dict()``
|
|
builders), or anything on a ``write_task_result`` call (the durable ABI-3
|
|
store; NO allowlist there). Legacy READS stay legal and are naturally
|
|
invisible to this scan: ``resolve_cost_pair``/``.get``/``in``/``.pop``
|
|
never author a key. Generic passthrough (projections, deep copies) is
|
|
structurally invisible to ANY static scan — the projection-boundary
|
|
runtime pin (``TestProjectionBoundaryNormalization``) covers that shape
|
|
by feeding stored legacy bytes through the outbound projections.
|
|
|
|
The allowlist is PER-SITE and COUNT-ANCHORED (fix-round-3):
|
|
``(posix path, alias, enclosing scope) -> (reason, emission count)``. A
|
|
new emission in an already-allowlisted FILE but a different function is
|
|
NOT allowlisted; a NEW emission inside an already-allowlisted FUNCTION
|
|
breaks its count anchor and fails; a stale row (no emission matches it)
|
|
FAILS the test — the list can only shrink honestly. Every surviving row
|
|
is an INTERNAL non-gateway plane that merely shares the spelling:
|
|
physical usage-ledger rows, llm/usage observability events (converted to
|
|
the honest name at the /api/logs projection boundary on replay),
|
|
review/evidence receipt schemas, evolution checkpoint records, custody
|
|
settlement events, reflection/consciousness records. ``outcomes.py``
|
|
(loop-outcome usage snapshot) and the subagent envelope are deliberately
|
|
GONE from this list (fix-round-2), and the evolution campaign HISTORY
|
|
row producer is gone since fix-round-3 (it reaches /api/state, so it
|
|
stamps the honest name and the state projection boundary normalizes
|
|
stored legacy rows).
|
|
"""
|
|
|
|
RETIRED_ALIASES = frozenset({
|
|
"cost_usd", "cost_usd_with_children", "telegram_chat_id",
|
|
"project_last_viewed", "project_hidden",
|
|
})
|
|
# (posix path, alias, enclosing scope) -> (why this INTERNAL plane
|
|
# legitimately keeps the spelling, exact emission count at these sites).
|
|
INTERNAL_PLANE_ALLOWLIST = {
|
|
# physical usage ledger rows / legacy usage import (P7 monetary authority)
|
|
("ouroboros/usage_accounting.py", "cost_usd", "record_unmetered_external_dispatch"): ("ledger unmetered dispatch row", 1),
|
|
("ouroboros/usage_accounting.py", "cost_usd", "record_subscription_session"): ("ledger subscription session row", 1),
|
|
("ouroboros/usage_accounting.py", "cost_usd", "terminalize_abandoned_attempt"): ("ledger settlement transition", 1),
|
|
("ouroboros/usage_accounting.py", "cost_usd", "_settlement_fields"): ("ledger settlement fields shared by received and abandoned attempts", 1),
|
|
("ouroboros/server_maintenance.py", "cost_usd", "_reconcile_abandoned_usage"): ("existing ledger settlement call for the original recovered model operation", 1),
|
|
("ouroboros/usage_accounting.py", "cost_usd", "_terminalize_failed_attempt"): ("ledger settlement transitions + settle call for a rejected stream whose usage frame was read", 3),
|
|
("ouroboros/usage_accounting.py", "cost_usd", "execute_physical_attempt"): ("ledger settlement call", 1),
|
|
("ouroboros/usage_accounting.py", "cost_usd", "execute_physical_attempt_async"): ("ledger settlement call", 1),
|
|
("ouroboros/usage_legacy_import.py", "cost_usd", "_ensure_legacy_imported_locked"): ("legacy usage.json ledger import rows", 2),
|
|
("ouroboros/usage_compaction.py", "cost_usd", "_build_candidate"): ("ledger compaction baseline-group row (CPL4-C6; exact-decimal string sum)", 1),
|
|
("ouroboros/tools/search.py", "cost_usd", "_web_search"): ("ledger settlement call (web search attempt)", 1),
|
|
# usage/observability event streams (events.jsonl, live log frames;
|
|
# /api/logs replay converts to the honest name at the boundary)
|
|
("ouroboros/loop_llm_call.py", "cost_usd", "call_llm_with_retry"): ("llm_round usage event rows", 2),
|
|
("ouroboros/post_task_synthesis.py", "cost_usd", "_run_chat_consolidation"): ("chat_block_consolidation event row", 1),
|
|
("ouroboros/post_task_synthesis.py", "cost_usd", "_run_reflection"): ("reflection generation gate args", 1),
|
|
("supervisor/events_evolution_done.py", "cost_usd", "_handle_evolution_task_done"): ("internal lifecycle/checkpoint call kwargs + supervisor.jsonl observability row", 3),
|
|
# review/evidence receipt schemas (internal review plane)
|
|
("ouroboros/triad_review.py", "cost_usd", "to_dict"): ("triad review receipt serialization", 1),
|
|
("ouroboros/triad_review.py", "cost_usd", "_actor_record"): ("triad review actor record", 1),
|
|
("ouroboros/skill_loader.py", "cost_usd", "to_dict"): ("skill review outcome receipt serialization", 1),
|
|
("ouroboros/skill_loader.py", "cost_usd", "load_review_state"): ("skill review state load", 1),
|
|
("ouroboros/skill_review.py", "cost_usd", "_run_deterministic_preflight"): ("skill review preflight receipt", 1),
|
|
("ouroboros/skill_review.py", "cost_usd", "_persist_reviewed_outcome"): ("skill review outcome receipt", 1),
|
|
("ouroboros/skill_owner_attestation.py", "cost_usd", "run_owner_attestation"): ("owner attestation review receipt", 1),
|
|
("ouroboros/tools/claude_advisory_review.py", "cost_usd", "_run_advisory_native"): ("advisory review receipt", 1),
|
|
("ouroboros/tools/delegate_terminal_evidence.py", "cost_usd", "_reported_cost"): ("delegate terminal evidence rows", 4),
|
|
("ouroboros/tools/preflight_review_run.py", "cost_usd", "_llm_extract_advisory_items"): ("advisory preflight usage receipt", 1),
|
|
("ouroboros/tools/preflight_review_run.py", "cost_usd", "_advisory_failure"): ("internal advisory failure adapter; physical charges remain in usage/custody, not gateway fields", 1),
|
|
("ouroboros/tools/preflight_review_run.py", "cost_usd", "_run_advisory_delegated"): ("advisory preflight receipt", 1),
|
|
("ouroboros/tools/preflight_review_run.py", "cost_usd", "_run_claude_advisory"): ("single advisory receipt cost reused by event projections", 1),
|
|
("ouroboros/tools/review_admission.py", "cost_usd", "triad_not_dispatched_records"): ("review admission receipt", 1),
|
|
("ouroboros/tools/review_helpers.py", "cost_usd", "build_scope_actor_record"): ("review usage receipt", 1),
|
|
("ouroboros/tools/scope_review.py", "cost_usd", "run_scope_review"): ("scope review receipt", 4),
|
|
("ouroboros/tools/parallel_review.py", "cost_usd", "_run_scope"): ("scope review receipt", 1),
|
|
# evolution checkpoint plane (durable state files, never a gateway payload;
|
|
# the campaign HISTORY row producer left this list in fix-round-3)
|
|
("ouroboros/evolution_checkpoints.py", "cost_usd", "build_solve_capability_digest"): ("evolution capability digest", 1),
|
|
("ouroboros/evolution_checkpoints.py", "cost_usd", "append_evolution_checkpoint"): ("evolution checkpoint records", 1),
|
|
# custody settlement events
|
|
("ouroboros/delegate_custody.py", "cost_usd", "settle_run"): ("custody SETTLED event row", 1),
|
|
# reflection records
|
|
("ouroboros/reflection.py", "cost_usd", "generate_reflection"): ("task reflection record", 1),
|
|
}
|
|
|
|
@staticmethod
|
|
def _emission_hits():
|
|
import ast
|
|
import pathlib
|
|
|
|
repo_root = pathlib.Path(__file__).resolve().parents[1]
|
|
aliases = TestAliasProducerFanOutSweep.RETIRED_ALIASES
|
|
dict_hits: list = []
|
|
writer_kwarg_hits: list = []
|
|
|
|
def visit(node, rel, scope):
|
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
scope = node.name
|
|
if isinstance(node, ast.Dict):
|
|
for key in node.keys:
|
|
if isinstance(key, ast.Constant) and key.value in aliases:
|
|
dict_hits.append((rel, key.value, scope, key.lineno))
|
|
elif isinstance(node, (ast.Assign, ast.AugAssign)):
|
|
targets = node.targets if isinstance(node, ast.Assign) else [node.target]
|
|
for target in targets:
|
|
if (
|
|
isinstance(target, ast.Subscript)
|
|
and isinstance(target.slice, ast.Constant)
|
|
and target.slice.value in aliases
|
|
):
|
|
dict_hits.append((rel, target.slice.value, scope, target.lineno))
|
|
elif isinstance(node, ast.Call):
|
|
func = node.func
|
|
name = func.attr if isinstance(func, ast.Attribute) else getattr(func, "id", "")
|
|
for kw in node.keywords:
|
|
if kw.arg in aliases:
|
|
if name == "write_task_result":
|
|
writer_kwarg_hits.append((rel, kw.arg, scope, node.lineno))
|
|
else:
|
|
dict_hits.append((rel, kw.arg, scope, node.lineno))
|
|
if name == "write_task_result":
|
|
for arg in [kw.value for kw in node.keywords if kw.arg is None] + list(node.args):
|
|
if isinstance(arg, ast.Dict):
|
|
for key in arg.keys:
|
|
if isinstance(key, ast.Constant) and key.value in aliases:
|
|
writer_kwarg_hits.append((rel, key.value, scope, node.lineno))
|
|
for child in ast.iter_child_nodes(node):
|
|
visit(child, rel, scope)
|
|
|
|
for package in ("ouroboros", "supervisor"):
|
|
for path in sorted((repo_root / package).rglob("*.py")):
|
|
rel = path.relative_to(repo_root).as_posix()
|
|
visit(ast.parse(path.read_text(encoding="utf-8")), rel, "<module>")
|
|
return dict_hits, writer_kwarg_hits
|
|
|
|
def test_no_task_result_writer_passes_a_retired_alias(self):
|
|
_, writer_kwarg_hits = self._emission_hits()
|
|
assert writer_kwarg_hits == [], (
|
|
"write_task_result call sites must stamp honest names only "
|
|
f"(ABI-3); offending sites: {writer_kwarg_hits!r}"
|
|
)
|
|
|
|
def test_every_alias_key_emission_is_an_allowlisted_internal_plane(self):
|
|
import collections
|
|
|
|
dict_hits, _ = self._emission_hits()
|
|
counts = collections.Counter(
|
|
(rel, alias, scope) for rel, alias, scope, _lineno in dict_hits
|
|
)
|
|
unexpected = [
|
|
hit for hit in dict_hits
|
|
if (hit[0], hit[1], hit[2]) not in self.INTERNAL_PLANE_ALLOWLIST
|
|
]
|
|
assert unexpected == [], (
|
|
"new emission-shaped occurrence of a retired gateway alias; either "
|
|
"cut the producer over to the honest name or (only for a genuinely "
|
|
"internal non-gateway plane) add a PER-SITE allowlist row: "
|
|
f"{unexpected!r}"
|
|
)
|
|
drifted = {
|
|
site: {"actual": count, "anchored": self.INTERNAL_PLANE_ALLOWLIST[site][1]}
|
|
for site, count in sorted(counts.items())
|
|
if site in self.INTERNAL_PLANE_ALLOWLIST
|
|
and count != self.INTERNAL_PLANE_ALLOWLIST[site][1]
|
|
}
|
|
assert drifted == {}, (
|
|
"emission-count drift inside an allowlisted function — a NEW "
|
|
"emission in an already-allowlisted scope is NOT allowlisted "
|
|
f"(fix-round-3 anchor): {drifted!r}"
|
|
)
|
|
stale = sorted(set(self.INTERNAL_PLANE_ALLOWLIST) - set(counts))
|
|
assert stale == [], (
|
|
f"stale allowlist rows (no emission matches them any more): {stale!r}"
|
|
)
|
|
|
|
def test_no_gateway_alias_survives_outside_the_cost_pair(self):
|
|
"""The three non-cost aliases have zero emission-shaped occurrences at
|
|
all — no allowlist, no exceptions."""
|
|
dict_hits, _ = self._emission_hits()
|
|
non_cost = [
|
|
hit for hit in dict_hits
|
|
if hit[1] in {"telegram_chat_id", "project_last_viewed", "project_hidden"}
|
|
]
|
|
assert non_cost == []
|
|
|
|
def test_the_public_projection_planes_are_not_allowlisted(self):
|
|
"""Fix-round-2 pin: no allowlist row may name a plane whose data
|
|
reaches the public task-result projection — the loop-outcome usage
|
|
snapshot, the subagent envelope, or the projection module itself."""
|
|
banned_files = {
|
|
"ouroboros/outcomes.py", "ouroboros/subagents.py",
|
|
"ouroboros/agent_task_pipeline.py",
|
|
}
|
|
offending = sorted(
|
|
row for row in self.INTERNAL_PLANE_ALLOWLIST if row[0] in banned_files
|
|
)
|
|
assert offending == []
|
|
|
|
|
|
def _retired_alias_paths_deep(payload):
|
|
"""Every path in *payload* whose KEY is a retired cost alias, recursively.
|
|
|
|
This is the generic-passthrough catcher no AST sweep can be: it inspects
|
|
the actual outbound bytes, so a projection or deep copy that carried a
|
|
stored legacy spelling through shows up regardless of how the code
|
|
spelled the copy."""
|
|
found = []
|
|
|
|
def walk(node, path):
|
|
if isinstance(node, dict):
|
|
for key, value in node.items():
|
|
if key in {"cost_usd", "cost_usd_with_children"}:
|
|
found.append(f"{path}.{key}")
|
|
walk(value, f"{path}.{key}")
|
|
elif isinstance(node, list):
|
|
for index, value in enumerate(node):
|
|
walk(value, f"{path}[{index}]")
|
|
|
|
walk(payload, "$")
|
|
return found
|
|
|
|
|
|
class TestProjectionBoundaryNormalization:
|
|
"""Ф3.1 fix-round-2: the ABI carries NO alias — outbound payloads built
|
|
from STORED LEGACY rows contain only the honest cost names.
|
|
|
|
Stored tolerance is read-side only: the legacy spelling resolves
|
|
(deprecated-wins) and is NORMALIZED at the projection boundary
|
|
(public_task_result / task detail / the list row) and at re-write
|
|
(write_task_result strips aliases from the merged existing row)."""
|
|
|
|
LEGACY_ROW = {
|
|
"_schema_version": 1,
|
|
"task_id": "legacy-cost",
|
|
"status": "completed",
|
|
"result": "done",
|
|
"ts": "2026-01-01T00:00:00Z",
|
|
"cost_usd": 1.5,
|
|
"cost_usd_with_children": 2.75,
|
|
"cost_final": True,
|
|
"cost_accounting_status": "available",
|
|
"subagent_envelope": {
|
|
"task_id": "legacy-cost",
|
|
"status": "completed",
|
|
# Fix-round-3: the alias sits on the ACTUALLY SUPPORTED producer
|
|
# path (build_subagent_envelope embeds the stored usage snapshot)
|
|
# — envelope.usage.cost_usd — beside the envelope-root spelling.
|
|
"usage": {"prompt_tokens": 1, "completion_tokens": 2, "rounds": 3,
|
|
"cost_usd": 0.125},
|
|
"cost_usd": 0.25,
|
|
},
|
|
"loop_outcome": {
|
|
"reason_code": "final_answer",
|
|
"usage": {"cost_usd": 0.5, "prompt_tokens": 1,
|
|
"completion_tokens": 2, "total_rounds": 3},
|
|
},
|
|
}
|
|
|
|
def _write_legacy(self, data):
|
|
results = data / "task_results"
|
|
results.mkdir(parents=True, exist_ok=True)
|
|
(results / "legacy-cost.json").write_text(
|
|
json.dumps(self.LEGACY_ROW), encoding="utf-8"
|
|
)
|
|
|
|
def test_public_task_result_emits_honest_names_only(self):
|
|
from ouroboros.outcomes import public_task_result
|
|
|
|
out = public_task_result(dict(self.LEGACY_ROW))
|
|
assert _retired_alias_paths_deep(out) == []
|
|
assert out["accounted_upper_bound_usd"] == 1.5
|
|
assert out["accounted_upper_bound_usd_with_children"] == 2.75
|
|
assert out["cost_final"] is True
|
|
assert out["subagent_envelope"]["accounted_upper_bound_usd"] == 0.25
|
|
assert out["subagent_envelope"]["usage"]["accounted_upper_bound_usd"] == 0.125
|
|
assert out["loop_outcome"]["usage"]["accounted_upper_bound_usd"] == 0.5
|
|
# And the stored input was not mutated (projection, not conversion).
|
|
assert self.LEGACY_ROW["cost_usd"] == 1.5
|
|
assert self.LEGACY_ROW["subagent_envelope"]["usage"]["cost_usd"] == 0.125
|
|
|
|
def test_task_detail_and_list_row_emit_honest_names_only(self, tmp_path):
|
|
import asyncio
|
|
from types import SimpleNamespace
|
|
|
|
from ouroboros.gateway.tasks import api_task_get, api_tasks_list
|
|
|
|
data = tmp_path / "data"
|
|
self._write_legacy(data)
|
|
detail_request = SimpleNamespace(
|
|
path_params={"task_id": "legacy-cost"},
|
|
app=SimpleNamespace(state=SimpleNamespace(drive_root=data)),
|
|
)
|
|
detail = json.loads(
|
|
asyncio.run(api_task_get(detail_request)).body.decode("utf-8")
|
|
)
|
|
assert _retired_alias_paths_deep(detail) == []
|
|
assert detail["accounted_upper_bound_usd"] == 1.5
|
|
assert detail["subagent_envelope"]["accounted_upper_bound_usd"] == 0.25
|
|
assert detail["subagent_envelope"]["usage"]["accounted_upper_bound_usd"] == 0.125
|
|
assert detail["loop_outcome"]["usage"]["accounted_upper_bound_usd"] == 0.5
|
|
|
|
list_request = SimpleNamespace(
|
|
query_params={},
|
|
path_params={},
|
|
app=SimpleNamespace(state=SimpleNamespace(drive_root=data)),
|
|
)
|
|
payload = json.loads(
|
|
asyncio.run(api_tasks_list(list_request)).body.decode("utf-8")
|
|
)
|
|
assert _retired_alias_paths_deep(payload) == []
|
|
row = next(r for r in payload["tasks"] if r["task_id"] == "legacy-cost")
|
|
assert row["accounted_upper_bound_usd"] == 1.5
|
|
assert row["accounted_upper_bound_usd_with_children"] == 2.75
|
|
|
|
def test_rewrite_normalizes_the_stored_row_to_honest_names(self, tmp_path):
|
|
"""write_task_result strips aliases from the merged EXISTING row after
|
|
deprecated-wins, and a fresh honest write is never outranked by the
|
|
stored legacy spelling."""
|
|
from ouroboros.task_results import load_task_result, write_task_result
|
|
|
|
results = tmp_path / "task_results"
|
|
results.mkdir(parents=True, exist_ok=True)
|
|
(results / "legacy-rw.json").write_text(json.dumps({
|
|
"_schema_version": 1, "task_id": "legacy-rw", "status": "running",
|
|
"ts": "2026-01-01T00:00:00Z", "cost_usd": 1.0, "cost_final": False,
|
|
}), encoding="utf-8")
|
|
|
|
write_task_result(tmp_path, "legacy-rw", "completed",
|
|
accounted_upper_bound_usd=7.0, cost_final=True)
|
|
stored = load_task_result(tmp_path, "legacy-rw")
|
|
assert "cost_usd" not in stored and "cost_usd_with_children" not in stored
|
|
assert stored["accounted_upper_bound_usd"] == 7.0
|
|
assert stored["cost_final"] is True
|
|
|
|
def test_rewrite_honors_a_legacy_mutators_edit_then_strips_it(self, tmp_path):
|
|
"""A legacy spelling arriving IN the write itself still wins the pair
|
|
(deprecated-wins: the mutator's edit is honored) but is persisted
|
|
under the honest name only."""
|
|
from ouroboros.task_results import load_task_result, write_task_result
|
|
|
|
write_task_result(tmp_path, "legacy-mut", "running",
|
|
accounted_upper_bound_usd=1.0)
|
|
write_task_result(tmp_path, "legacy-mut", "completed",
|
|
**{"cost_usd": 9.0})
|
|
stored = load_task_result(tmp_path, "legacy-mut")
|
|
assert "cost_usd" not in stored
|
|
assert stored["accounted_upper_bound_usd"] == 9.0
|
|
|
|
def test_rewrite_normalizes_the_nested_public_cost_planes(self, tmp_path):
|
|
"""Fix-round-3: write_task_result's rewrite normalizes the KNOWN
|
|
nested public planes too — a stored legacy subagent envelope (root
|
|
and usage) and loop-outcome usage leave the next persisted row under
|
|
honest names only."""
|
|
from ouroboros.task_results import load_task_result, write_task_result
|
|
|
|
results = tmp_path / "task_results"
|
|
results.mkdir(parents=True, exist_ok=True)
|
|
(results / "legacy-deep.json").write_text(
|
|
json.dumps(self.LEGACY_ROW).replace("legacy-cost", "legacy-deep"),
|
|
encoding="utf-8",
|
|
)
|
|
write_task_result(tmp_path, "legacy-deep", "completed", result="rewritten")
|
|
stored = load_task_result(tmp_path, "legacy-deep")
|
|
assert _retired_alias_paths_deep(stored) == []
|
|
assert stored["subagent_envelope"]["accounted_upper_bound_usd"] == 0.25
|
|
assert stored["subagent_envelope"]["usage"]["accounted_upper_bound_usd"] == 0.125
|
|
assert stored["loop_outcome"]["usage"]["accounted_upper_bound_usd"] == 0.5
|
|
|
|
def test_envelope_builder_normalizes_the_stored_usage_snapshot(self):
|
|
"""Fix-round-3: build_subagent_envelope normalizes the stored usage
|
|
snapshot BEFORE embedding it — the envelope's usage plane carries the
|
|
honest name only, and the legacy amount still resolves into the
|
|
envelope's own accounted_upper_bound_usd."""
|
|
from ouroboros.subagents import build_subagent_envelope
|
|
|
|
envelope = build_subagent_envelope(
|
|
task_id="child-1",
|
|
status="completed",
|
|
usage={"prompt_tokens": 1, "completion_tokens": 2, "cost_usd": 0.75},
|
|
)
|
|
assert _retired_alias_paths_deep(envelope) == []
|
|
assert envelope["usage"]["accounted_upper_bound_usd"] == 0.75
|
|
assert envelope["accounted_upper_bound_usd"] == 0.75
|
|
|
|
|
|
class TestEvolutionHistoryPlane:
|
|
"""Ф3.1 fix-round-3: the evolution campaign history row reaches the
|
|
public ``/api/state`` payload — its producer stamps the honest name
|
|
(``update_evolution_campaign_after_task``, sweep allowlist row removed)
|
|
and the state projection boundary resolves STORED legacy rows
|
|
deprecated-wins, emitting honest names only."""
|
|
|
|
def test_state_projection_normalizes_stored_legacy_history_rows(self):
|
|
from ouroboros.gateway.state import _evolution_state_public
|
|
|
|
snapshot = {
|
|
"enabled": True,
|
|
"campaign": {
|
|
"id": "c1",
|
|
"history": [
|
|
{"task_id": "old", "cost_usd": 1.5,
|
|
"cost_accounting_status": "available"},
|
|
{"task_id": "new", "accounted_upper_bound_usd": 2.5,
|
|
"cost_accounting_status": "available"},
|
|
],
|
|
},
|
|
}
|
|
out = _evolution_state_public(snapshot)
|
|
assert _retired_alias_paths_deep(out) == []
|
|
rows = {row["task_id"]: row for row in out["campaign"]["history"]}
|
|
assert rows["old"]["accounted_upper_bound_usd"] == 1.5
|
|
assert rows["new"]["accounted_upper_bound_usd"] == 2.5
|
|
# Copy-on-write: the shared supervisor snapshot row is untouched.
|
|
assert snapshot["campaign"]["history"][0]["cost_usd"] == 1.5
|
|
|
|
def test_diverged_stored_pair_resolves_deprecated_wins(self):
|
|
from ouroboros.gateway.state import _evolution_state_public
|
|
|
|
out = _evolution_state_public({"campaign": {"history": [
|
|
{"task_id": "t", "cost_usd": 9.0, "accounted_upper_bound_usd": 1.0},
|
|
]}})
|
|
assert out["campaign"]["history"][0]["accounted_upper_bound_usd"] == 9.0
|
|
assert "cost_usd" not in out["campaign"]["history"][0]
|
|
|
|
|
|
class TestApiV1ShimRemoval:
|
|
def test_module_is_gone(self):
|
|
import importlib
|
|
|
|
with pytest.raises(ModuleNotFoundError):
|
|
importlib.import_module("ouroboros.contracts.api_v1")
|
|
|
|
def test_gateway_contracts_is_the_sole_ssot(self):
|
|
from ouroboros.gateway import contracts
|
|
|
|
assert "ChatOutbound" in contracts.__all__
|
|
assert "HTTP_ENDPOINTS" in contracts.__all__
|