ouroboros/tests/test_log_forwarding.py
Ouroboros 443d86c6d1 release: Ouroboros v6.12.0 — QA hardening
Marketplace: auto-rename a ClawHub skill that collides with another bucket
(e.g. native weather -> weather-clawhub), keeping the landed directory, sidecar,
manifest name, and translated_manifest_sha256 consistent; update pins the
existing directory. Map registry 404->404 / 429->429 across info/preview/search
and install/update. Label preview as registry-metadata-only. Offer an LLM-first
"Make runnable" repair affordance for instruction skills.

Review: scope review reserves output headroom inside the reviewer's 1M context
window (the shared 920K prompt SSOT is unchanged), so an oversize prompt routes
to the existing non-blocking budget_exceeded skip instead of a hard provider 400.

Supervisor: /evolve stop breaks the hard-timeout auto-retry chain and cancels
the running evolution worker (kill_pid_tree-first teardown, terminal cancelled,
no re-enqueue). Abnormal task termination reconstructs cost/rounds from durable
llm_usage events and _handle_task_done falls back to the persisted task result,
so per-task and evolution-campaign accounting no longer record zeros.

Preflight: guarantee full process-tree teardown (process group + recursive PID
tree + escaped session groups + temp-root sweep) behind platform_layer helpers,
and make the timeout configurable via OUROBOROS_PREFLIGHT_TIMEOUT_SEC (300s).

Logs: worker tasks forward append_jsonl log lines to the dashboard over EVENT_Q
(suppressing types already delivered live to avoid double broadcast), and the
Logs page backfills recent history on load/reconnect with exact-duplicate dedupe.

UI: drop the duplicate Files header; import apiFetch so the MCP status refresh works.

Bumps all version carriers to 6.12.0 and syncs ARCHITECTURE/DEVELOPMENT docs.
2026-06-03 04:00:21 +03:00

66 lines
2.8 KiB
Python

"""Coverage for the v6.12.0 dashboard log-delivery path: the worker-side log
sink (suppression contract), the main-side _handle_log_event consumer (broadcast
+ checkpoint-only persist), and the logs.js backfill/dedupe wiring."""
import inspect
import json
import pathlib
from types import SimpleNamespace
REPO = pathlib.Path(__file__).resolve().parent.parent
def test_worker_log_sink_suppresses_live_sibling_types():
from supervisor.workers import WORKER_LOG_SINK_SUPPRESSED_TYPES, worker_main
# These five already arrive live via a dedicated EVENT_Q sibling/handler;
# forwarding the worker's append_jsonl copy too would double-broadcast.
assert WORKER_LOG_SINK_SUPPRESSED_TYPES == frozenset(
{"tool_call", "llm_round", "task_checkpoint", "task_done", "llm_usage"}
)
src = inspect.getsource(worker_main)
assert "set_log_sink" in src
assert "emit_log_event" in src
assert "WORKER_LOG_SINK_SUPPRESSED_TYPES" in src
def test_handle_log_event_broadcasts_all_but_persists_only_checkpoints(tmp_path):
from supervisor import events as ev
from supervisor import state as supervisor_state
(tmp_path / "logs").mkdir()
events_file = tmp_path / "logs" / "events.jsonl"
broadcast = []
ctx = SimpleNamespace(
DRIVE_ROOT=tmp_path,
append_jsonl=supervisor_state.append_jsonl,
bridge=SimpleNamespace(push_log=lambda e: broadcast.append(e)),
)
# A previously-missing worker log type is forwarded live, never re-persisted.
ev._handle_log_event(
{"type": "log_event", "data": {"type": "task_received", "task_id": "t1"}}, ctx
)
assert any(e.get("type") == "task_received" for e in broadcast)
assert not events_file.exists()
# task_checkpoint is broadcast AND persisted exactly once (the worker
# suppresses its own copy, so this single main-side write is the only one).
ev._handle_log_event(
{"type": "log_event", "data": {"type": "task_checkpoint", "task_id": "t1", "round": 1}}, ctx
)
assert any(e.get("type") == "task_checkpoint" for e in broadcast)
lines = [ln for ln in events_file.read_text(encoding="utf-8").splitlines() if ln.strip()]
assert len(lines) == 1 and json.loads(lines[0])["type"] == "task_checkpoint"
def test_logs_js_backfills_all_streams_and_dedupes_without_dropping_preconnect():
src = (REPO / "web" / "modules" / "logs.js").read_text(encoding="utf-8")
for stream in ("'events'", "'tools'", "'progress'", "'supervisor'"):
assert stream in src, f"backfill must include the {stream} log stream"
# Exact-duplicate guard collapses backfill/live overlap…
assert "renderedLogKeys" in src
# …and backfill reruns on reconnect…
assert "ws.on('open'" in src
# …without a load-time timestamp skip that could drop the pre-connect window.
assert "loadStart" not in src