diff --git a/bench/prometheus_sigma_trigger_probe.py b/bench/prometheus_sigma_trigger_probe.py index 4c74eb8..2a68e9d 100644 --- a/bench/prometheus_sigma_trigger_probe.py +++ b/bench/prometheus_sigma_trigger_probe.py @@ -3,15 +3,16 @@ Phase 1 of #000037 (the recursive falsification controller) gates on one of four §12 triggers firing. Triggers 1-3 are measurable from already-committed state; trigger 4 is operator decision. This script -walks every shard's ``audit_events`` and ``capital_ledger`` tables, -computes the §12 quantities, and emits a markdown report. +walks every shard's ``audit_events``, ``capital_ledger``, and +``fork_score_branches`` (#000012 Phase 1c) tables, computes the §12 +quantities, and emits a markdown report. Pure measurement. No mutation. No LLM call. No schema change. The report is the deliverable; fox uses it to decide go/no-go on Phase 1. The probe is defensive about data-availability: it explicitly reports "no data" when a trigger structurally cannot fire yet (e.g. trigger 1 -on a single-validator deployment with no fork-score cache table) +when no branch sets have been persisted to ``fork_score_branches``) rather than synthesising a false signal. """ @@ -31,6 +32,7 @@ DIVERGENCE_VARIANCE_RATIO_FLOOR = 0.5 DIVERGENCE_ABS_STDDEV_FLOOR = 0.10 WITNESS_COST_SHARE_FLOOR = 0.30 DIVERGENCE_EPSILON = 1e-6 # max(mean, ε) guard against div-by-zero +BRANCH_DENSITY_FLOOR = 4 # §12 trigger 1: ≥4 candidate branches per checkpoint def _read_witness_events(shards: list[Path]) -> list[dict]: @@ -91,45 +93,110 @@ def _read_capital_ledger(shards: list[Path]) -> list[dict]: return rows -def _has_fork_score_table(shards: list[Path]) -> bool: - for sp in shards: - try: - conn = sqlite3.connect(sp) - row = conn.execute( - "SELECT name FROM sqlite_master " - "WHERE type='table' AND name LIKE '%fork_score%' LIMIT 1" - ).fetchone() - conn.close() - if row: - return True - except sqlite3.OperationalError: - continue - return False - - # -------------------------------------------------------------- triggers def trigger_1_branch_density(shards: list[Path]) -> dict: """Trigger 1 — branch density: ≥4 candidate branches per checkpoint. - Requires multi-branch ForkScore (#000012) to be persisting candidate - sets. Phase 1a (closed 2026-05-08) is single-validator, so no - branch-set table exists; the trigger structurally cannot fire yet. + Reads the ``fork_score_branches`` sibling table (#000012 Phase 1c) + via :func:`arborist.substrate.fork_score.branch_set_density`. Each + distinct ``branch_set_id`` is a checkpoint; the trigger fires when + the most-recently-recorded checkpoint carries ≥4 distinct branches. + The report also surfaces how many of *all* recorded checkpoints + clear the floor so §12's "regularly" qualifier stays visible. + + Density is summed across shards per ``branch_set_id`` (each + ``arborist substrate score --persist-shard`` invocation writes one + shard; a checkpoint normally lives in exactly one). When the table + is absent across every shard — the common case, since ForkScore + Phase 1a is single-validator and ``arborist substrate score`` only + persists branch sets when ``--branch-set`` is passed — the trigger + reports "no data" rather than synthesising a false signal. """ - has_table = _has_fork_score_table(shards) + from arborist.substrate.fork_score import branch_set_density + + density: dict[str, int] = {} + last_ts: dict[str, int] = {} + table_present = False + for sp in shards: + try: + conn = sqlite3.connect(sp) + except sqlite3.OperationalError: + continue + try: + checkpoints = conn.execute( + "SELECT branch_set_id, MAX(recorded_at) " + "FROM fork_score_branches GROUP BY branch_set_id" + ).fetchall() + except sqlite3.OperationalError: + # Pre-#000012-Phase-1c shard, or one that has never had a + # branch set persisted. Not a probe-failure mode. + conn.close() + continue + table_present = True + for sid, ts in checkpoints: + density[sid] = density.get(sid, 0) + branch_set_density(conn, sid) + last_ts[sid] = max(last_ts.get(sid, 0), ts or 0) + conn.close() + + if not table_present: + return { + "trigger": 1, + "name": "branch density (≥4 branches/checkpoint)", + "fires": False, + "reason": ( + "no fork_score_branches table across shards; ForkScore is " + "single-validator (Phase 1a) and `arborist substrate score` " + "only persists branch sets when `--branch-set` is passed. " + "Multi-branch fork-score persistence is the prerequisite — " + "see #000012 Phase 1c." + ), + "data_available": False, + "n_checkpoints": 0, + } + if not density: + return { + "trigger": 1, + "name": "branch density (≥4 branches/checkpoint)", + "fires": False, + "reason": ( + "fork_score_branches table present but empty across shards; " + "no branch sets persisted yet" + ), + "data_available": True, + "n_checkpoints": 0, + } + + latest_sid = max(density, key=lambda s: last_ts[s]) + latest_density = density[latest_sid] + max_density = max(density.values()) + n_clear = sum(1 for d in density.values() if d >= BRANCH_DENSITY_FLOOR) + fires = latest_density >= BRANCH_DENSITY_FLOOR + if fires: + reason = ( + f"latest checkpoint `{latest_sid}` carries {latest_density} " + f"branches ≥ {BRANCH_DENSITY_FLOOR}; " + f"{n_clear}/{len(density)} recorded checkpoints clear the floor" + ) + else: + reason = ( + f"latest checkpoint `{latest_sid}` carries {latest_density} " + f"branch(es) < {BRANCH_DENSITY_FLOOR}; " + f"{n_clear}/{len(density)} recorded checkpoints clear the floor " + f"(max density seen: {max_density})" + ) return { "trigger": 1, "name": "branch density (≥4 branches/checkpoint)", - "fires": False, - "reason": ( - "no fork_score branch-set table found across shards; " - "ForkScore Phase 1a is single-validator. Multi-branch " - "fork-score persistence is the prerequisite — see #000012." - ) - if not has_table - else "branch-set table found but density check not yet implemented", - "data_available": has_table, + "fires": fires, + "reason": reason, + "data_available": True, + "n_checkpoints": len(density), + "latest_branch_set_id": latest_sid, + "latest_density": latest_density, + "max_density": max_density, + "n_checkpoints_clearing_floor": n_clear, } @@ -349,8 +416,9 @@ def render_markdown( out.append("") out.append( "Phase 1 of ticket #000037 gates on one §12 trigger firing. " - "This probe walks `audit_events` + `capital_ledger` and reports " - "the empirical state of triggers 1–3. Trigger 4 is operator-stated." + "This probe walks `fork_score_branches` + `audit_events` + " + "`capital_ledger` and reports the empirical state of triggers " + "1–3. Trigger 4 is operator-stated." ) out.append("") out.append("## Verdict") @@ -381,6 +449,18 @@ def render_markdown( out.append("") t1 = triggers[0] out.append(f"- Data available: {t1['data_available']}") + if t1.get("n_checkpoints"): + out.append(f"- Recorded checkpoints: {t1['n_checkpoints']}") + out.append( + f"- Latest checkpoint: `{t1['latest_branch_set_id']}` " + f"→ {t1['latest_density']} branches " + f"(floor {BRANCH_DENSITY_FLOOR})" + ) + out.append(f"- Max density across checkpoints: {t1['max_density']}") + out.append( + f"- Checkpoints clearing the floor: " + f"{t1['n_checkpoints_clearing_floor']} / {t1['n_checkpoints']}" + ) out.append(f"- {t1['reason']}") out.append("") out.append("## Trigger 2 — divergence variance") diff --git a/bench/results/prometheus-sigma-triggers-2026-05-11.md b/bench/results/prometheus-sigma-triggers-2026-05-11.md new file mode 100644 index 0000000..7ec97e0 --- /dev/null +++ b/bench/results/prometheus-sigma-triggers-2026-05-11.md @@ -0,0 +1,59 @@ +# Prometheus-Σ §12 trigger probe + +**Date:** 2026-05-11T10:54:01Z +**Shards directory:** `/home/fox/.arborist/shards` +**Shards walked:** 7 (000.db, 001.db, 002.db, 003.db, crawl_appliedcombinatorics_org.db, qa.db, snapshots.db) + +Phase 1 of ticket #000037 gates on one §12 trigger firing. This probe walks `fork_score_branches` + `audit_events` + `capital_ledger` and reports the empirical state of triggers 1–3. Trigger 4 is operator-stated. + +## Verdict + +| # | Trigger | Fires? | Reason | +|---|---------|--------|--------| +| 1 | branch density (≥4 branches/checkpoint) | no | fork_score_branches table present but empty across shards; no branch sets persisted yet | +| 2 | divergence variance | **YES** | ratio 0.575 > 0.5; abs σ 0.435 > 0.1 | +| 3 | witness cost share (material > 0.30) | no | witness material / total material = 0.008158 / 0.540638 = 0.015 | +| 4 | operator mission need | n/a | operator decision; not measurable from committed state | + +**Phase 1 trigger has fired.** Prometheus-Σ Phase 1 may proceed. + +## Trigger 1 — branch density + +- Data available: True +- fork_score_branches table present but empty across shards; no branch sets persisted yet + +## Trigger 2 — divergence variance + +- Sample count (N): 37 (N_min = 30) +- Mean divergence rate: 0.7568 +- Stddev: 0.435 +- Ratio (σ/mean): 0.5748 +- Threshold ratio: > 0.5 OR absolute σ > 0.1 +- ratio 0.575 > 0.5; abs σ 0.435 > 0.1 + +Agreement-label distribution across all shards: + +| label | count | +|-------|-------| +| `KERNEL-LLM-DIVERGED` | 22 | +| `KERNEL-LLM-AGREE` | 6 | +| `LLM-DIVERGED` | 6 | +| `STRICT-WITNESSED` | 3 | + +## Trigger 3 — witness cost share + +- Witness material (kWh proxy): 0.008158 +- Total material: 0.540638 +- Ratio: 0.0151 (threshold > 0.3) +- Witness financial: 5e-06 +- Total financial: 5e-06 +- Witness rows: 37 +- Total ledger rows: 39 +- witness material / total material = 0.008158 / 0.540638 = 0.015 + +**Caveat:** ledger reflects ad-hoc activity, not a controlled #000026 sweep — ratio is informational. Re-run after a #000026 baseline sweep for a workload-anchored answer. + +## Trigger 4 — operator mission need + +- Operator-stated; not measurable from committed state. +- Bypasses §12 measured-pressure gates per ticket §12. diff --git a/docs/TICKETS.md b/docs/TICKETS.md index eaf4fba..6be24ed 100644 --- a/docs/TICKETS.md +++ b/docs/TICKETS.md @@ -100,7 +100,7 @@ Newest first. Update on every open/close. | #000040 | Phase 5 resolver fix — phrase + content-token cascade (Hilbert terminology mismatch surfaced) | closed · cascade landed 2026-05-09; lift blocked by 1902-vs-modern vocab; follow-up #000042 | 2026-05-09 | — | | #000039 | Optional `sqlite-vec` retrieval backend (A/B vs FTS5, hybrid not replacement) | open · awaiting go/no-go (doc-only Phase 0) | 2026-05-09 | — | | #000038 | Phase 4 content acquisition — proprietary textbook license decisions for warrant coverage | closed · obviated 2026-05-10 by alias-substitution sprint under #000031 (74 rows in #000041 + 13 rows in #000042); 92/92 records now resolve. Residue (multilingual PD, Hilbert-Ackermann OCR, Knuth permission, personal-copy path B) preserved as design log §8 | 2026-05-09 | — | -| #000037 | Prometheus-Σ recursive falsification controller (bicameral substrate) | in progress · Phases 0 + 1 + 1.b + 1.c + 2 landed 2026-05-10; **§12 Trigger 2 fired** (divergence variance 0.575 / N=37); §22 Findings 2 + 3 RESOLVED (kernel/llm cost split + sweep_weights §15.4 + per-mode τ_qa); `controller_events` carries 4 event kinds (decision · difficulty · budget_allocation · falsification_proposal) feeding `arborist controller-events` inspector + live-harvest third bucket in `bench/scripts/harvest_falsification_proposals.py`; Phase 3 sleep-sweep scheduler tracked under #000045 (gating ticket) | 2026-05-09 | — | +| #000037 | Prometheus-Σ recursive falsification controller (bicameral substrate) | in progress · Phases 0 + 1 + 1.b + 1.c + 2 landed 2026-05-10; **§12 Trigger 2 fired** (divergence variance 0.575 / N=37); §22 Findings 2 + 3 RESOLVED (kernel/llm cost split + sweep_weights §15.4 + per-mode τ_qa); `controller_events` carries 4 event kinds (decision · difficulty · budget_allocation · falsification_proposal) feeding `arborist controller-events` inspector + live-harvest third bucket in `bench/scripts/harvest_falsification_proposals.py`; §12 Trigger 1 probe wired 2026-05-11 (`trigger_1_branch_density` reads `fork_score_branches` — measurable, not yet fired); Phase 3 sleep-sweep scheduler tracked under #000045 (gating ticket) | 2026-05-09 | — | | #000036 | T3 per-window covert-channel budget bound | in progress · Phase 1 + dav1d review returned 2026-05-11; Tier-1 polish applied (wording → "CANNOT CERTIFY", structured `certification_status`/`b1_model` fields, bool/NaN/inf validation, `g=0` accepted, test cwd fix; 53 → 75 tests); **closure blockers: B1 conservative-envelope v2 calculator (awaits fox go/no-go) + active KAT fixture** | 2026-05-09 | — | | #000035 | PRG choice for φ_PRG (HMAC-SHA-512 expansion) | in progress · Phase 1 landed 2026-05-10; v7 §9.10 amendment awaits maintainer review | 2026-05-09 | — | | #000034 | Hessian alignment under φ_linear | in progress · Phase 1a landed 2026-05-10 (synthetic-ablation probe + KAT fixture); Phase 1b parks for v7 ramp-up | 2026-05-09 | — | @@ -125,7 +125,7 @@ Newest first. Update on every open/close. | #000015 | π* domain library + cross-domain composition | closed · landed 2026-05-07 | 2026-05-07 | — | | #000014 | SelfModel: schema, falsification, integration | closed · landed 2026-05-07 | 2026-05-07 | — | | #000013 | Spatial-temporal substrate (Merkle-AGI v7-W) | closed · landed 2026-05-09 (substrate paper + frontier catalog + namespace stub) | 2026-05-07 | — | -| #000012 | Selection & consensus protocol (Merkle-AGI v8) | in progress · Phase 1a (ForkScore) landed 2026-05-08; Phase 1b (consensus paper, `docs/_source/merkle-agi-v8-consensus.rst` 834 lines) landed 2026-05-10; Phase 1c (branch-set persistence — `fork_score_branches` sibling table, `persist_branch_score` + `branch_set_density`, 6 new CLI flags on `arborist substrate score`, default-off) landed 2026-05-10 — feeds #000037 §12 Trigger 1 | 2026-05-07 | — | +| #000012 | Selection & consensus protocol (Merkle-AGI v8) | in progress · Phase 1a (ForkScore) landed 2026-05-08; Phase 1b (consensus paper, `docs/_source/merkle-agi-v8-consensus.rst` 834 lines) landed 2026-05-10; Phase 1c (branch-set persistence — `fork_score_branches` sibling table, `persist_branch_score` + `branch_set_density`, 6 new CLI flags on `arborist substrate score`, default-off) landed 2026-05-10 — feeds #000037 §12 Trigger 1; Trigger 1 probe wired 2026-05-11 (`trigger_1_branch_density` reads `fork_score_branches` via `branch_set_density()` — measurable, not yet fired: no branch sets persisted) | 2026-05-07 | — | | #000011 | SOFT_PREFLIGHT_HINT model-assisted sidecar | closed · landed 2026-05-04 (zero-shot full impl) | 2026-05-04 | D1 (preserves) | | #000010 | Meta-Cognition Preflight Guard (M0 / MCTL) | closed · landed 2026-05-03 (Phases 1–4); DAG binding shipped via #000009 | 2026-05-03 | D1, D3 | | #000009 | Preflight run-DAG node binding (#000008+#000010) | closed · re-landed 2026-05-04 (§8 corrections: reject-path DAG, nested CTI clauses) | 2026-05-03 | D3, D4 | diff --git a/docs/tickets/ticket-000012-selection-consensus-protocol.md b/docs/tickets/ticket-000012-selection-consensus-protocol.md index 9c88080..9fe1764 100644 --- a/docs/tickets/ticket-000012-selection-consensus-protocol.md +++ b/docs/tickets/ticket-000012-selection-consensus-protocol.md @@ -374,15 +374,33 @@ empirical surface. Implementation pinned below. inputs (algorithm change, weight semantics, hard-regression policy). Persisted on every row so a reader can filter by estimator generation. -- **Tests:** 5 new in `tests/test_fork_score.py` — - migration-creates-table, persist-writes-one-row, upsert-on-pk, - branch_set_density-counts-by-set, breakdown_blob-round-trips-as- - json. Test count 18 → 23. +- **#000037 §12 Trigger 1 probe wired (2026-05-11):** original + proposal step 4 — `bench/prometheus_sigma_trigger_probe.py` + `trigger_1_branch_density` now reads `fork_score_branches` via + `branch_set_density()` instead of returning the + "density check not yet implemented" stub. It groups rows by + `branch_set_id`, fires when the most-recently-recorded checkpoint + carries ≥ 4 branches (`BRANCH_DENSITY_FLOOR`), and surfaces + `n_checkpoints` / `latest_density` / `max_density` / + `n_checkpoints_clearing_floor` in the markdown report so §12's + "regularly" qualifier stays visible. With zero branch sets + persisted yet, the probe correctly reports "table present but + empty across shards; no branch sets persisted yet" — + `data_available: True`, `fires: False` — not a false negative. +- **Tests:** 5 in `tests/test_fork_score.py` (migration-creates- + table, persist-writes-one-row, upsert-on-pk, branch_set_density- + counts-by-set, breakdown_blob-round-trips-as-json; suite 18 → 23) + + 6 in `tests/test_prometheus_trigger_probe.py` (probe loaded via + `importlib`; no-table → no data, empty-table → data-available-no- + fire, latest-checkpoint-≥4 → fires, earlier-dense-but-latest- + sparse → no fire, density-sums-across-shards, report-renders- + density-lines). - **Hard constraints honored:** sibling table never enters `audit_events.event_hash` preimage; no behavioral change to single-validator scoring; default-off CLI; no mesh wire format change; `weights_id` opaque (folding weights into a hash stays a - Phase 1b/wire concern). + Phase 1b/wire concern). The probe stays pure-measurement — no + mutation, no LLM call, no schema change. The original Phase-1c proposal text is preserved below for design- log continuity. Re-read it as the authoritative spec; the bullets diff --git a/docs/tickets/ticket-000037-prometheus-sigma-recursive-falsification-controller.md b/docs/tickets/ticket-000037-prometheus-sigma-recursive-falsification-controller.md index 042df0b..ab6b53b 100644 --- a/docs/tickets/ticket-000037-prometheus-sigma-recursive-falsification-controller.md +++ b/docs/tickets/ticket-000037-prometheus-sigma-recursive-falsification-controller.md @@ -725,7 +725,15 @@ quantities. **Trigger 1 — branch density.** ForkScore (#000012 Phase 1a) regularly receives ≥ 4 candidate branches per checkpoint AND the operator wants probability-weighted compute allocation across -them. +them. *Probe wired 2026-05-11 (#000012 Phase 1c follow-through):* +`prometheus_sigma_trigger_probe.py:trigger_1_branch_density` now +reads the `fork_score_branches` table via `branch_set_density()` — +it groups rows by `branch_set_id` and fires when the most-recently- +recorded checkpoint carries ≥ 4 branches. As of that date no branch +sets have been persisted (`arborist substrate score --branch-set` +has not been run on a production checkpoint), so the probe reports +"table present but empty" — `data_available: True`, `fires: False`. +The trigger is now measurable; it has not fired. **Trigger 2 — divergence variance.** #000028 witness sweep observes divergence-rate variance large enough that fixed diff --git a/tests/test_prometheus_trigger_probe.py b/tests/test_prometheus_trigger_probe.py new file mode 100644 index 0000000..a447fe5 --- /dev/null +++ b/tests/test_prometheus_trigger_probe.py @@ -0,0 +1,181 @@ +"""Probe-side tests for ``bench/prometheus_sigma_trigger_probe.py`` +trigger 1 (branch density), wired to the #000012 Phase 1c +``fork_score_branches`` table. + +Trigger 1 was a stub until #000012 Phase 1c landed the +``fork_score_branches`` sibling table and the ``branch_set_density`` +helper. These tests pin the wired behaviour: no-table → no data; +table-present-but-empty → data available, does not fire; latest +checkpoint with ≥4 branches → fires. + +The probe lives outside the ``arborist`` package, so it is loaded via +``importlib`` (same pattern as ``tests/test_bench_qa_sweep.py``). +""" +from __future__ import annotations + +import importlib.util +import sys +from pathlib import Path + +import pytest + +from arborist.store import connect, invalidate_migration_cache, transaction +from arborist.substrate.fork_score import ScoredFork, persist_branch_score + + +@pytest.fixture(scope="module") +def probe(): + path = ( + Path(__file__).parent.parent + / "bench" + / "prometheus_sigma_trigger_probe.py" + ) + spec = importlib.util.spec_from_file_location( + "prometheus_trigger_probe_under_test", path + ) + mod = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = mod + spec.loader.exec_module(mod) + return mod + + +def _scored(score: float = 0.5, verdict: str = "ACCEPT") -> ScoredFork: + return ScoredFork( + score=score, verdict=verdict, breakdown={"t": score}, flags=[], weights={} + ) + + +def _make_shard(tmp_path: Path, name: str) -> Path: + """Create a shard DB with the Phase 1c migration applied.""" + db = tmp_path / name + invalidate_migration_cache(db) + conn = connect(db) # runs _migrate_fork_score_branches + conn.close() + invalidate_migration_cache(db) + return db + + +def _persist(db: Path, *, branch_set_id: str, branch_id: str, ts: int) -> None: + invalidate_migration_cache(db) + conn = connect(db) + try: + with transaction(conn): + persist_branch_score( + conn, + branch_set_id=branch_set_id, + branch_id=branch_id, + parent_root="parent-root", + child_root=f"child-{branch_id}", + scored=_scored(), + ts=ts, + ) + finally: + conn.close() + invalidate_migration_cache(db) + + +def test_trigger1_no_table_reports_no_data(probe, tmp_path): + """A shard with no fork_score_branches table → data unavailable, + does not fire, points at #000012 Phase 1c.""" + import sqlite3 + + db = tmp_path / "bare.db" + sqlite3.connect(db).close() # empty DB, no migration + out = probe.trigger_1_branch_density([db]) + assert out["trigger"] == 1 + assert out["fires"] is False + assert out["data_available"] is False + assert out["n_checkpoints"] == 0 + assert "#000012" in out["reason"] + + +def test_trigger1_table_empty_reports_data_available_no_fire(probe, tmp_path): + """Table present (migration ran) but no branch sets persisted → + data available, does not fire.""" + db = _make_shard(tmp_path, "empty.db") + out = probe.trigger_1_branch_density([db]) + assert out["fires"] is False + assert out["data_available"] is True + assert out["n_checkpoints"] == 0 + assert "empty" in out["reason"] + + +def test_trigger1_fires_when_latest_checkpoint_has_floor_branches(probe, tmp_path): + """Latest checkpoint (by recorded_at) carries ≥4 branches → fires; + reason names the checkpoint and the floor count.""" + db = _make_shard(tmp_path, "dense.db") + # Older checkpoint with only 1 branch — should NOT be the one the + # trigger reads. + _persist(db, branch_set_id="cp-old", branch_id="solo", ts=1_700_000_000) + # Newer checkpoint with 4 branches — this is the latest. + for i in range(4): + _persist( + db, branch_set_id="cp-new", branch_id=f"b{i}", ts=1_700_001_000 + i + ) + out = probe.trigger_1_branch_density([db]) + assert out["fires"] is True + assert out["data_available"] is True + assert out["n_checkpoints"] == 2 + assert out["latest_branch_set_id"] == "cp-new" + assert out["latest_density"] == 4 + assert out["max_density"] == 4 + assert out["n_checkpoints_clearing_floor"] == 1 + assert "cp-new" in out["reason"] + + +def test_trigger1_no_fire_when_latest_checkpoint_below_floor(probe, tmp_path): + """An earlier checkpoint clearing the floor does NOT make the + trigger fire if the *latest* checkpoint is below it — §12 reads + the most recent checkpoint.""" + db = _make_shard(tmp_path, "mixed.db") + for i in range(5): # old, dense checkpoint + _persist( + db, branch_set_id="cp-dense", branch_id=f"d{i}", ts=1_700_000_000 + i + ) + for i in range(2): # newest checkpoint, sparse + _persist( + db, branch_set_id="cp-sparse", branch_id=f"s{i}", ts=1_700_009_000 + i + ) + out = probe.trigger_1_branch_density([db]) + assert out["fires"] is False + assert out["data_available"] is True + assert out["latest_branch_set_id"] == "cp-sparse" + assert out["latest_density"] == 2 + assert out["max_density"] == 5 + assert out["n_checkpoints_clearing_floor"] == 1 + assert "max density seen: 5" in out["reason"] + + +def test_trigger1_density_sums_across_shards(probe, tmp_path): + """A checkpoint split across two shards sums to its full density.""" + db_a = _make_shard(tmp_path, "a.db") + db_b = _make_shard(tmp_path, "b.db") + _persist(db_a, branch_set_id="cp-split", branch_id="b0", ts=1_700_000_000) + _persist(db_a, branch_set_id="cp-split", branch_id="b1", ts=1_700_000_001) + _persist(db_b, branch_set_id="cp-split", branch_id="b2", ts=1_700_000_002) + _persist(db_b, branch_set_id="cp-split", branch_id="b3", ts=1_700_000_003) + out = probe.trigger_1_branch_density([db_a, db_b]) + assert out["n_checkpoints"] == 1 + assert out["latest_density"] == 4 + assert out["fires"] is True + + +def test_trigger1_renders_density_lines_in_report(probe, tmp_path): + """render_markdown surfaces the density numbers when a checkpoint + exists.""" + db = _make_shard(tmp_path, "render.db") + for i in range(4): + _persist( + db, branch_set_id="cp-r", branch_id=f"b{i}", ts=1_700_000_000 + i + ) + triggers = [ + probe.trigger_1_branch_density([db]), + probe.trigger_2_divergence_variance([]), + probe.trigger_3_witness_cost_share([]), + probe.trigger_4_operator_need(), + ] + md = probe.render_markdown(tmp_path, [db], triggers, [], []) + assert "## Trigger 1 — branch density" in md + assert "Latest checkpoint: `cp-r`" in md + assert "Recorded checkpoints: 1" in md + assert "Checkpoints clearing the floor: 1 / 1" in md