"""Tests for the #000025 §10.11 SelfModel snapshot-chain — the persistent lineage that replaces Phase-1a's synthetic parent→child pairs in the Finetuning sub-battery. Two surfaces: - ``bench/scripts/selfmodel_chain_snapshot.py`` — the bootstrapper that appends one chained snapshot per run (capability claims = the 5S/5T/5F battery rates). - ``bench.batteries.b_5f._chain_finetuning_measure`` + the ``run_finetuning`` "shard-chain" dispatch — reads the two latest snapshots and measures improvement on ``target_capability``. All against temp shards (``tmp_path`` / ``tmp_path_factory``) — never the real ``~/.arborist/shards/selfmodel-chain.db``. """ from __future__ import annotations from pathlib import Path import pytest from bench.batteries import b_5f from bench.scripts.selfmodel_chain_snapshot import append_snapshot _F5F = Path(__file__).parent.parent / "bench" / "fixtures" / "5f" _SHARDCHAIN_PACK = _F5F / "finetuning-shardchain-v1.jsonl" @pytest.fixture(scope="module") def chain_shard(tmp_path_factory) -> Path: """A persistent 2-deep SelfModel chain on a temp shard.""" shard = tmp_path_factory.mktemp("smchain") / "chain.db" s1 = append_snapshot(shard, ts=1_700_000_000) s2 = append_snapshot(shard, ts=1_700_000_001) assert s1["depth"] == 1 and s1["parent_root"] is None assert s2["depth"] == 2 and s2["parent_root"] == s1["root"] return shard # ---- bootstrapper ------------------------------------------------------- def test_bootstrapper_grows_chain_one_per_run(tmp_path): shard = tmp_path / "c.db" a = append_snapshot(shard, ts=1_700_000_000) assert a["depth"] == 1 assert a["parent_root"] is None # 15 canonical sub-batteries → 15 capability claims. assert len(a["claims"]) == 15 assert all(v == 1.0 for v in a["claims"].values()) # packs at ceiling b = append_snapshot(shard, ts=1_700_000_001) assert b["depth"] == 2 assert b["parent_root"] == a["root"] assert b["root"] != a["root"] # distinct parent ⇒ distinct root def test_bootstrapper_claims_carry_real_metric_names(chain_shard: Path): a = append_snapshot(chain_shard, ts=1_700_000_002) # depth 3 assert a["depth"] == 3 assert {"5S-syntax", "5T-time", "5F-feedback-loop"} <= set(a["claims"]) # ---- _chain_finetuning_measure ----------------------------------------- def test_chain_measure_reads_two_latest(chain_shard: Path): out = b_5f._chain_finetuning_measure( {"target_capability": "5F-feedback-loop", "selfmodel_shard": str(chain_shard)} ) assert out["parent_measured"] == 1.0 assert out["child_measured"] == 1.0 assert out["parent_root"] != out["child_root"] def test_chain_measure_env_var_overrides_fixture(chain_shard: Path, monkeypatch): monkeypatch.setenv("ARBORIST_SELFMODEL_CHAIN_DB", str(chain_shard)) out = b_5f._chain_finetuning_measure( {"target_capability": "5S-syntax", "selfmodel_shard": "/nonexistent/ignored.db"} ) assert out["child_measured"] == 1.0 def test_chain_measure_absent_shard_raises(tmp_path): with pytest.raises(FileNotFoundError, match="chain shard not found"): b_5f._chain_finetuning_measure( {"target_capability": "5S-syntax", "selfmodel_shard": str(tmp_path / "nope.db")} ) def test_chain_measure_single_snapshot_raises(tmp_path): shard = tmp_path / "one.db" append_snapshot(shard, ts=1_700_000_000) # depth 1 with pytest.raises(ValueError, match="only one snapshot"): b_5f._chain_finetuning_measure( {"target_capability": "5S-syntax", "selfmodel_shard": str(shard)} ) def test_chain_measure_unknown_metric_raises(chain_shard: Path): with pytest.raises(ValueError, match="no claim for"): b_5f._chain_finetuning_measure( {"target_capability": "NOT-A-REAL-METRIC", "selfmodel_shard": str(chain_shard)} ) # ---- run_finetuning shard-chain dispatch ------------------------------- def test_run_finetuning_shardchain_pack_passes(chain_shard: Path, monkeypatch): monkeypatch.setenv("ARBORIST_SELFMODEL_CHAIN_DB", str(chain_shard)) res = b_5f.run_finetuning(_SHARDCHAIN_PACK) assert res.pass_count == 6 assert res.fail_count == 0 for t in res.per_task: assert t.detail["source"] == "shard-chain" assert t.detail["improvement"] == 0.0 # packs at ceiling ⇒ no drift assert t.detail["chain_parent_root"] != t.detail["chain_child_root"] def test_run_finetuning_shardchain_pack_fails_honestly_without_chain(tmp_path, monkeypatch): monkeypatch.setenv("ARBORIST_SELFMODEL_CHAIN_DB", str(tmp_path / "absent.db")) res = b_5f.run_finetuning(_SHARDCHAIN_PACK) assert res.pass_count == 0 assert res.fail_count == 6 assert "FileNotFoundError" in res.per_task[0].detail["reason"] def test_embedded_and_live_finetuning_unaffected(): """The shard-chain dispatch is gated on ``selfmodel_shard``; the embedded + live packs still route through their original paths.""" emb = b_5f.run_finetuning(_F5F / "finetuning-v1.jsonl") assert emb.pass_count == 50 for t in emb.per_task: assert t.detail["source"] == "embedded"