diff --git a/docs/generated/release-truth.json b/docs/generated/release-truth.json index c2711435..dc2525e3 100644 --- a/docs/generated/release-truth.json +++ b/docs/generated/release-truth.json @@ -144,7 +144,7 @@ "console_entrypoints": 9, "mcp_tools": 51, "ops_cli_commands": 5, - "pytest_test_functions": 5926 + "pytest_test_functions": 5928 }, "feature_profile_matrix": { "capture_hook": [ diff --git a/docs/generated/release-truth.md b/docs/generated/release-truth.md index 1b48b1d7..d28f2d26 100644 --- a/docs/generated/release-truth.md +++ b/docs/generated/release-truth.md @@ -13,7 +13,7 @@ Do not edit this file by hand. Run `python scripts/generate_release_truth.py`. - Main CLI commands: **127** - Operations CLI commands: **5** - Console entrypoints: **9** -- Pytest source test functions: **5926** +- Pytest source test functions: **5928** ## MCP tools diff --git a/memorymaster/operations/operational_review.py b/memorymaster/operations/operational_review.py index 22a8133c..97e59553 100644 --- a/memorymaster/operations/operational_review.py +++ b/memorymaster/operations/operational_review.py @@ -970,7 +970,14 @@ def check_backfill_burndown(config: ReviewConfig) -> ReviewResult: try: state_db, _ = _beat_paths(config) - status = read_backfill_status(state_db.parent) + state_dir = state_db.parent + ledgers = [path for path in (state_dir / "backfill").glob("*/dream_backfill_ledger.db") + if not path.parent.name.startswith("dryrun-")] + if ledgers: + ledger = max(ledgers, key=lambda path: path.stat().st_mtime) + status = read_backfill_status(ledger.parent, campaign_id=ledger.parent.name) + else: + status = read_backfill_status(state_dir) if status["state"] == "not_inventoried": return ReviewResult("backfill_burndown", Verdict.PASS, "backfill inactive (not inventoried)") diff --git a/tests/test_dreaming_backfill_operational_review.py b/tests/test_dreaming_backfill_operational_review.py index e70a2aea..34b68e04 100644 --- a/tests/test_dreaming_backfill_operational_review.py +++ b/tests/test_dreaming_backfill_operational_review.py @@ -14,8 +14,10 @@ def _config(tmp_path: Path) -> review.ReviewConfig: capture_state_db=tmp_path / "capture-state.db") -def _inventory(tmp_path: Path) -> Path: - path = tmp_path / "dream_backfill_ledger.db" +def _inventory(state_dir: Path, campaign_id: str = "ao7-x") -> Path: + campaign_dir = state_dir / "backfill" / campaign_id + campaign_dir.mkdir(parents=True) + path = campaign_dir / "dream_backfill_ledger.db" with sqlite3.connect(path) as conn: conn.executescript(""" CREATE TABLE backfill_inventory ( @@ -28,16 +30,16 @@ def _inventory(tmp_path: Path) -> Path: ); """) conn.executemany("INSERT INTO backfill_inventory VALUES (?,?,?,?,?,?,?,?,?,?,'fixture-0')", [ - ("ao7", "main", "claude", "one", "project:test", "applied", 100, 100, + (campaign_id, "main", "claude", "one", "project:test", "applied", 100, 100, "PRIVATE_SOURCE_PATH", '{"text":"PRIVATE_TRANSCRIPT"}'), - ("ao7", "child", "claude", "one", "project:test", "complete_sampled", 50, 50, + (campaign_id, "child", "claude", "one", "project:test", "complete_sampled", 50, 50, "PRIVATE_SOURCE_PATH", "{}"), - ("ao7", "next", "codex", "two", "project:other", "selected", 200, 100, + (campaign_id, "next", "codex", "two", "project:other", "selected", 200, 100, "PRIVATE_SOURCE_PATH", "{}"), - ("ao7", "unknown", "agy", "three", None, "unresolved_scope", 40, 0, + (campaign_id, "unknown", "agy", "three", None, "unresolved_scope", 40, 0, "PRIVATE_SOURCE_PATH", "{}"), ]) - conn.execute("INSERT INTO backfill_reservations VALUES ('ao7','extract',10000,1500)") + conn.execute("INSERT INTO backfill_reservations VALUES (?,'extract',10000,1500)", (campaign_id,)) return path @@ -49,6 +51,24 @@ def test_absent_backfill_is_inactive_without_creating_state(tmp_path): assert set(tmp_path.iterdir()) == before +def test_nested_campaign_ledger_is_reported_and_dryrun_only_is_inactive(tmp_path): + campaign_root = tmp_path / "campaign-state" + ledger = _inventory(campaign_root) + result = review.check_backfill_burndown(_config(campaign_root)) + assert result.verdict is review.Verdict.PASS + assert result.counts["sessions"] == 2 + assert result.counts["files"] == 4 + assert "sessions=1/2" in result.detail + assert "inactive" not in result.detail + + dryrun_root = tmp_path / "dryrun-state" + (dryrun_root / "backfill" / "dryrun-x").mkdir(parents=True) + dryrun_result = review.check_backfill_burndown(_config(dryrun_root)) + assert dryrun_result.verdict is review.Verdict.PASS + assert "inactive" in dryrun_result.detail + assert ledger.exists() + + def test_backfill_counts_independent_sessions_and_reports_safe_dimensions(tmp_path, monkeypatch): from memorymaster.dreaming import backfill @@ -88,7 +108,9 @@ def forbidden(*args, **kwargs): def test_unreadable_backfill_warns_without_disclosing_content_or_paths(tmp_path): - (tmp_path / "dream_backfill_ledger.db").write_text("PRIVATE_TRANSCRIPT", encoding="utf-8") + ledger = tmp_path / "backfill" / "ao7-x" / "dream_backfill_ledger.db" + ledger.parent.mkdir(parents=True) + ledger.write_text("PRIVATE_TRANSCRIPT", encoding="utf-8") result = review.check_backfill_burndown(_config(tmp_path)) assert result.verdict is review.Verdict.WARN assert result.detail == "probe_error=DatabaseError" @@ -115,7 +137,7 @@ def test_backfill_has_a_separate_registered_check(): def test_unknown_labels_cannot_disclose_source_locators(tmp_path, monkeypatch): from memorymaster.dreaming import backfill - monkeypatch.setattr(backfill, "read_backfill_status", lambda state_dir: { + monkeypatch.setattr(backfill, "read_backfill_status", lambda state_dir, **kwargs: { "state": "inventoried", "files": 1, "sessions": 1, "completed_sessions": 0, "sources": {"PRIVATE_SOURCE_PATH": {"files": 1}}, "projects": {"project:C:/PRIVATE_SOURCE_PATH": {"files": 1}}, diff --git a/tests/test_operational_review.py b/tests/test_operational_review.py index edfb7cff..49a76401 100644 --- a/tests/test_operational_review.py +++ b/tests/test_operational_review.py @@ -510,3 +510,28 @@ def test_snapshot_dir_probes_the_per_database_folder(tmp_path: Path, monkeypatch assert result.verdict is review.Verdict.PASS and result.counts == {"snapshot_files": 1, "newest_age_hours": 24} # An explicit snapshot_dir is probed as given (the base), never redirected. assert review.check_snapshot_dir(review.ReviewConfig(db=db, snapshot_dir=base), now=now).verdict is review.Verdict.WARN + + +def test_backfill_burndown_reads_the_newest_campaign_ledger_and_skips_dryruns(tmp_path: Path, monkeypatch) -> None: + # T-1764: the campaign ledger lives under backfill//, not in the beat state dir. + state_dir = tmp_path + old = state_dir / "backfill" / "ao7-old" / "dream_backfill_ledger.db" + new = state_dir / "backfill" / "ao7-new" / "dream_backfill_ledger.db" + dry = state_dir / "backfill" / "dryrun-x" / "dream_backfill_ledger.db" + for ledger in (old, new, dry): + ledger.parent.mkdir(parents=True) + ledger.write_text("", encoding="utf-8") + import os + os.utime(old, (1, 1)) + seen: list[tuple[Path, str | None]] = [] + from memorymaster.dreaming import backfill + + def fake_status(state_dir_arg, *, campaign_id=None, dry_run=False): + seen.append((Path(state_dir_arg), campaign_id)) + return {"state": "not_inventoried"} + + monkeypatch.setattr(backfill, "read_backfill_status", fake_status) + config = review.ReviewConfig(db=tmp_path / "never-open-authority.db", + capture_state_db=state_dir / "capture-state.db") + review.check_backfill_burndown(config) + assert seen == [(new.parent, "ao7-new")]