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).
217 lines
7.3 KiB
Python
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,
|
|
}
|