Files
rune/tests/test_testing_mode.py
Nick 3fb28e2461 feat(t19): testing mode + self-hosted /testing command + T19 layer model (19L-19U)
- testing_mode.py (19L/M/N/O/P): TestingSession (coordination-only,
  pausable — durable truth stays in the Behaviour Ledger); dynamic
  queue (fix-for-B-reopens-A via structural closure, 19S-9 proven);
  Observation→closure gate (closure requires retest AND user
  confirmation — automated green alone recorded as NOT closed);
  forge projection through the T18 boundary (failure never rewrites
  truth, 19S-14 proven); cross-project aggregate.
- 19R LIVE_PROOF: /testing command implemented through the full
  system — self-scaffold lookup → capability-ledger packet (bound
  exactly ONE file) → qualified INWARD basis → predictive impact →
  preview → contract 19r-testing-command → event-bridged verification
  → descriptive impact (prediction matched, delta persisted) →
  selective invalidation + obligations → ledgers updated → closed.
- 19T benchmark (honest): cold agent found the answer in 6 tool calls
  (4 files) — on this well-organized repo raw speed ≈ equal; the
  scaffold advantage is correctness/continuity (validation state,
  obligations, invalidation), recorded without inflation.
- 19U: V1 definition gains the T19 layer model + both governing
  objectives verbatim; implementation audit gains T19 classifications.
- type ratchet: forge.py gitea_auth import made resolvable via
  pyrightconfig extraPaths (the +1 warning FIXED, not re-baselined).

Suite 2002 green; ratchets PASS; Henxels rc=0.
2026-09-16 01:24:29 +01:00

134 lines
5.6 KiB
Python

"""test_testing_mode.py — T19 §19L/19M/19N/19O/19P held-outs."""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import behaviour_ledger as bl # noqa: E402
from rune import forge # noqa: E402
from rune import testing_mode as tm # noqa: E402
@pytest.fixture()
def env(tmp_path):
beh = bl.BehaviourLedger(tmp_path)
beh.define(behaviour_id="T-1", owning_project="p",
intended_behaviour="first behaviour",
procedure="run the thing; expect output X",
pass_criteria="output == X", fail_criteria="output != X",
owner_component="component:shared",
dependencies=["component:shared"])
beh.define(behaviour_id="T-2", owning_project="p",
intended_behaviour="second behaviour")
beh.save()
return beh
def test_19n_session_is_coordination_not_authority(tmp_path, env):
s = tm.TestingSession(tmp_path, project="p")
item = s.next_item(env)
assert item["behaviour_id"] == "behaviour:T-1" # FAIL items none yet;
# UNVALIDATED sorts after — both outstanding, T-1 first alphabetically
s.record(env, "T-1", passed=True, detail="checked by hand")
assert env.get("T-1")["state"] == bl.USER_CONFIRMED
s.pause()
assert s.next_item(env) is None # paused
s.resume()
assert s.next_item(env)["behaviour_id"] == "behaviour:T-2"
# session file holds coordination, NOT spec truth
import json
sess = json.loads((tmp_path / "testing_session.json").read_text())
assert "intended_behaviour" not in str(sess)
# durable truth intact in the ledger
assert env.get("T-1")["intended_behaviour"] == "first behaviour"
def test_19m_fix_reopens_dependent_confirmation(tmp_path, env):
"""Test-B-fix-invalidates-Test-A (19S-9): A confirmed; B fails; the
B fix touches a dependency shared with A → A re-enters the queue."""
env.record_auto_verification("T-1", passed=True, generation="g1")
env.record_real_world("T-1", passed=True, generation="g1") # A hardened
# B fails in the field
env.record_real_world("T-2", passed=False, detail="broke")
assert env.get("T-2")["state"] == bl.TEST_FAILED
# the fix structurally touches A's dependency
invalidated = env.invalidate_for_change(
changed_entities={"component:shared"}, generation="g2",
reasons={"behaviour:T-1": "fix for T-2 rewrote shared component"})
assert [r["behaviour_id"] for r in invalidated] == ["behaviour:T-1"]
assert env.get("T-1")["state"] == bl.RETEST_REQUIRED
# dynamic queue now contains BOTH
ids = [b["behaviour_id"] for b in env.outstanding()]
assert "behaviour:T-1" in ids and "behaviour:T-2" in ids
# A's generation-1 confirmation remains queryable
hist = env.retest_history("T-1")
assert any(h["generation"] == "g1" and h["event"].endswith(
"USER_CONFIRMED") for h in hist)
def test_19s10_no_blanket_invalidation(tmp_path, env):
env.record_real_world("T-1", passed=True)
env.record_real_world("T-2", passed=True)
# a change to an unrelated entity invalidates NOTHING
invalidated = env.invalidate_for_change(
changed_entities={"component:unrelated-thing"}, generation="g")
assert invalidated == []
assert env.get("T-1")["state"] == bl.USER_CONFIRMED
assert env.get("T-2")["state"] == bl.USER_CONFIRMED
def test_19o_observation_to_closure(tmp_path):
obs = tm.record_observation(
tmp_path, observed="button does nothing",
expected="button saves the form",
behaviour_id="behaviour:T-1", environment="production",
reproduction="click save with empty field",
candidate_areas=["component:form-handler"]) # hypothesis only
assert obs["candidate_areas"] == ["component:form-handler"]
# premature closure (no user confirm) is recorded as NOT closed
r = tm.close_observation(tmp_path, obs["observation_id"],
behaviour_retest_passed=True,
user_confirmed=False)
assert r["closure"]["closed"] is False # automated green ≠ closure
# full closure requires retest AND user confirmation
r2 = tm.close_observation(tmp_path, obs["observation_id"],
behaviour_retest_passed=True,
user_confirmed=True)
assert r2["closure"]["closed"] is True
def test_19p_forge_projection_never_rewrites_truth(tmp_path, env):
"""19S-14: forge unavailable → projection fails loudly; ledger truth
unchanged."""
obs = tm.record_observation(tmp_path, observed="x", expected="y",
behaviour_id="behaviour:T-1")
before = env.get("T-1")["state"]
bad = forge.GiteaForge(server="https://invalid.invalid")
with pytest.raises(forge.ForgeUnavailable):
tm.project_failure_to_forge(bad, "conductor/rune",
behaviour_id="T-1",
observation=obs)
assert env.get("T-1")["state"] == before # truth untouched
def test_19l_aggregate_across_projects(tmp_path):
reg = tm.ProjectRegistry(tmp_path)
p1 = tmp_path / "p1"
p1.mkdir()
beh = bl.BehaviourLedger(p1)
beh.define(behaviour_id="X-1", owning_project="p1",
intended_behaviour="x")
beh.save()
reg.register(project_id="p1", scaffold_location=str(p1))
reg.register(project_id="p2", scaffold_location=str(tmp_path / "p2"))
rows = tm.aggregate(reg, tmp_path)
by_p = {r["project"]: r for r in rows}
assert by_p["p1"]["outstanding"] == 1
assert by_p["p2"]["outstanding"] is None # no ledger yet