core/tests/test_discovery_yield.py
Shay de6e392953 feat(telemetry): discovery-yield-per-served-turn baseline (ADR-0255)
2026-07-23 directive: instrument candidates-proposed-per-served-turn on
clean, post-reset traffic. Adds an additive-optional turn_count_baseline
manifest field (engine_state) stamping the turn_count at the last clean
reset of the discovery-candidate ledger, a pure compute_discovery_yield
function (teaching/discovery_yield.py), and a read-only `core teaching
discovery-yield` CLI surface. Fails closed (returns None / exits 1) when
no baseline has been stamped rather than fabricating a denominator.

scripts/ops/stamp_discovery_yield_baseline_20260723.py stamps the T11
reset epoch into the live store, mirroring reset_candidate_corpus_t11's
atomic generation-dir pattern. Dry-run verified against a throwaway copy
of the live store's gen-21703; not yet executed against the real store
(see PR body).
2026-07-23 07:27:00 -07:00

217 lines
7.3 KiB
Python

"""discovery-yield-per-served-turn telemetry (2026-07-23 directive).
Each test includes a ``*_bites`` mutation variant where the predicate
could otherwise pass vacuously (CLAUDE.md schema-as-proof discipline).
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from core.cli import main
from engine_state import EngineStateStore
from teaching.discovery import DiscoveryCandidate
from teaching.discovery_yield import DiscoveryYield, compute_discovery_yield
def _candidate(candidate_id: str) -> DiscoveryCandidate:
return DiscoveryCandidate(
candidate_id=candidate_id,
proposed_chain={},
trigger="would_have_grounded",
source_turn_trace="deadbeef",
pack_consistent=True,
boundary_clean=True,
)
def _write_gen(
store: EngineStateStore,
*,
turn_count: int,
turn_count_baseline: int | None,
candidates: list[DiscoveryCandidate],
) -> None:
gen_num, gen_dir = store.begin_generation()
gs = EngineStateStore(gen_dir)
gs.save_recognizers([])
gs.save_discovery_candidates(candidates)
gs.save_manifest(turn_count, turn_count_baseline=turn_count_baseline)
store.commit_generation(gen_num, keep=1)
# ---------------------------------------------------------------------------
# Gate: no baseline stamped -> fail-closed None, never a fabricated denominator
# ---------------------------------------------------------------------------
def test_no_baseline_returns_none(tmp_path: Path) -> None:
store = EngineStateStore(tmp_path)
_write_gen(store, turn_count=100, turn_count_baseline=None, candidates=[])
assert compute_discovery_yield(store) is None
def test_no_baseline_returns_none_bites(tmp_path: Path) -> None:
"""A store WITH a baseline must not also return None."""
store = EngineStateStore(tmp_path)
_write_gen(store, turn_count=100, turn_count_baseline=100, candidates=[])
assert compute_discovery_yield(store) is not None
def test_no_committed_generation_returns_none(tmp_path: Path) -> None:
store = EngineStateStore(tmp_path)
assert compute_discovery_yield(store) is None
# ---------------------------------------------------------------------------
# Gate: served_turns_since_reset = turn_count - baseline
# ---------------------------------------------------------------------------
def test_served_turns_since_reset_is_delta(tmp_path: Path) -> None:
store = EngineStateStore(tmp_path)
_write_gen(store, turn_count=14995, turn_count_baseline=14990, candidates=[])
result = compute_discovery_yield(store)
assert result is not None
assert result.served_turns_since_reset == 5
def test_served_turns_since_reset_is_delta_bites(tmp_path: Path) -> None:
"""Must be the DELTA, not the raw turn_count."""
store = EngineStateStore(tmp_path)
_write_gen(store, turn_count=14995, turn_count_baseline=14990, candidates=[])
result = compute_discovery_yield(store)
assert result is not None
assert result.served_turns_since_reset != result.turn_count
# ---------------------------------------------------------------------------
# Gate: yield_rate = candidates_since_reset / served_turns_since_reset
# ---------------------------------------------------------------------------
def test_yield_rate_divides_candidates_by_served_turns(tmp_path: Path) -> None:
store = EngineStateStore(tmp_path)
_write_gen(
store,
turn_count=14994,
turn_count_baseline=14990,
candidates=[_candidate("a"), _candidate("b")],
)
result = compute_discovery_yield(store)
assert result is not None
assert result.candidates_since_reset == 2
assert result.served_turns_since_reset == 4
assert result.yield_rate == 0.5
def test_yield_rate_divides_candidates_by_served_turns_bites(tmp_path: Path) -> None:
"""Must not be the reciprocal or the raw candidate count."""
store = EngineStateStore(tmp_path)
_write_gen(
store,
turn_count=14994,
turn_count_baseline=14990,
candidates=[_candidate("a"), _candidate("b")],
)
result = compute_discovery_yield(store)
assert result is not None
assert result.yield_rate != 2.0
# ---------------------------------------------------------------------------
# Gate: zero served turns since reset -> yield_rate is None, not a ZeroDivisionError
# ---------------------------------------------------------------------------
def test_zero_served_turns_yields_none_rate_not_division_error(tmp_path: Path) -> None:
store = EngineStateStore(tmp_path)
_write_gen(store, turn_count=14990, turn_count_baseline=14990, candidates=[])
result = compute_discovery_yield(store)
assert result is not None
assert result.served_turns_since_reset == 0
assert result.yield_rate is None
def test_zero_served_turns_yields_none_rate_not_division_error_bites(
tmp_path: Path,
) -> None:
"""A store WITH served turns must produce a numeric rate, not None."""
store = EngineStateStore(tmp_path)
_write_gen(
store,
turn_count=14991,
turn_count_baseline=14990,
candidates=[_candidate("a")],
)
result = compute_discovery_yield(store)
assert result is not None
assert result.yield_rate is not None
# ---------------------------------------------------------------------------
# Gate: as_dict is a plain, JSON-safe mapping
# ---------------------------------------------------------------------------
def test_as_dict_round_trips_json_safe_types() -> None:
dy = DiscoveryYield(
turn_count=14994,
turn_count_baseline=14990,
served_turns_since_reset=4,
candidates_since_reset=2,
yield_rate=0.5,
)
payload = dy.as_dict()
assert payload == {
"turn_count": 14994,
"turn_count_baseline": 14990,
"served_turns_since_reset": 4,
"candidates_since_reset": 2,
"yield_rate": 0.5,
}
# ---------------------------------------------------------------------------
# Gate: `core teaching discovery-yield` CLI surface
# ---------------------------------------------------------------------------
def test_cli_fails_closed_without_baseline(
capsys: pytest.CaptureFixture[str],
) -> None:
# conftest's autouse `_isolate_engine_state_default` already points the
# bare-default store at a fresh per-test dir; nothing committed yet.
exit_code = main(["teaching", "discovery-yield"])
assert exit_code == 1
err = capsys.readouterr().err
assert "turn_count_baseline" in err
def test_cli_fails_closed_without_baseline_bites() -> None:
"""A store WITH a baseline must not also exit 1."""
_write_gen(
EngineStateStore(),
turn_count=14990,
turn_count_baseline=14990,
candidates=[],
)
exit_code = main(["teaching", "discovery-yield"])
assert exit_code == 0
def test_cli_json_emits_the_computed_snapshot(
capsys: pytest.CaptureFixture[str],
) -> None:
_write_gen(
EngineStateStore(),
turn_count=14992,
turn_count_baseline=14990,
candidates=[_candidate("a")],
)
exit_code = main(["teaching", "discovery-yield", "--json"])
assert exit_code == 0
payload = json.loads(capsys.readouterr().out)
assert payload == {
"turn_count": 14992,
"turn_count_baseline": 14990,
"served_turns_since_reset": 2,
"candidates_since_reset": 1,
"yield_rate": 0.5,
}