feat: tunable ranking, refresh status, chroma backup + admin endpoints

Three small improvements that move the operational baseline forward
without changing the existing trust model.

1. Tunable retrieval ranking weights
   - rank_project_match_boost, rank_query_token_step,
     rank_query_token_cap, rank_path_high_signal_boost,
     rank_path_low_signal_penalty are now Settings fields
   - all overridable via ATOCORE_* env vars
   - retriever no longer hard-codes 2.0 / 1.18 / 0.72 / 0.08 / 1.32
   - lets ranking be tuned per environment as Wave 1 is exercised
     without code changes

2. /projects/{name}/refresh status
   - refresh_registered_project now returns an overall status field
     ("ingested", "partial", "nothing_to_ingest") plus roots_ingested
     and roots_skipped counters
   - ProjectRefreshResponse advertises the new fields so callers can
     rely on them
   - covers the case where every configured root is missing on disk

3. Chroma cold snapshot + admin backup endpoints
   - create_runtime_backup now accepts include_chroma and writes a
     cold directory copy of the chroma persistence path
   - new list_runtime_backups() and validate_backup() helpers
   - new endpoints:
     - POST /admin/backup            create snapshot (optional chroma)
     - GET  /admin/backup            list snapshots
     - GET  /admin/backup/{stamp}/validate  structural validation
   - chroma snapshots are taken under exclusive_ingestion() so a refresh
     or ingest cannot race with the cold copy
   - backup metadata records what was actually included and how big

Tests:
- 8 new tests covering tunable weights, refresh status branches
  (ingested / partial / nothing_to_ingest), chroma snapshot, list,
  validate, and the API endpoints (including the lock-acquisition path)
- existing fake refresh stubs in test_api_storage.py updated for the
  expanded ProjectRefreshResponse model
- full suite: 105 passing (was 97)

next-steps doc updated to reflect that the chroma snapshot + restore
validation gap from current-state.md is now closed in code; only the
operational retention policy remains.
This commit is contained in:
2026-04-06 18:42:19 -04:00
parent 14ab7c8e9f
commit c9b9eede25
10 changed files with 615 additions and 13 deletions

View File

@@ -129,6 +129,9 @@ def test_project_refresh_endpoint_uses_registered_roots(tmp_data_dir, monkeypatc
"aliases": ["p05"],
"description": "P05 docs",
"purge_deleted": purge_deleted,
"status": "ingested",
"roots_ingested": 1,
"roots_skipped": 0,
"roots": [
{
"source": "vault",
@@ -173,6 +176,9 @@ def test_project_refresh_endpoint_serializes_ingestion(tmp_data_dir, monkeypatch
"aliases": ["p05"],
"description": "P05 docs",
"purge_deleted": purge_deleted,
"status": "nothing_to_ingest",
"roots_ingested": 0,
"roots_skipped": 0,
"roots": [],
}
@@ -429,6 +435,125 @@ def test_project_update_endpoint_rejects_collisions(tmp_data_dir, monkeypatch):
assert "collisions" in response.json()["detail"]
def test_admin_backup_create_without_chroma(tmp_data_dir, monkeypatch):
config.settings = config.Settings()
captured = {}
def fake_create_runtime_backup(timestamp=None, include_chroma=False):
captured["include_chroma"] = include_chroma
return {
"created_at": "2026-04-06T23:00:00+00:00",
"backup_root": "/tmp/fake",
"db_snapshot_path": "/tmp/fake/db/atocore.db",
"db_size_bytes": 0,
"registry_snapshot_path": "",
"chroma_snapshot_path": "",
"chroma_snapshot_bytes": 0,
"chroma_snapshot_files": 0,
"chroma_snapshot_included": False,
"vector_store_note": "skipped",
}
monkeypatch.setattr("atocore.api.routes.create_runtime_backup", fake_create_runtime_backup)
client = TestClient(app)
response = client.post("/admin/backup", json={})
assert response.status_code == 200
assert captured == {"include_chroma": False}
body = response.json()
assert body["chroma_snapshot_included"] is False
def test_admin_backup_create_with_chroma_holds_lock(tmp_data_dir, monkeypatch):
config.settings = config.Settings()
events = []
@contextmanager
def fake_lock():
events.append("enter")
try:
yield
finally:
events.append("exit")
def fake_create_runtime_backup(timestamp=None, include_chroma=False):
events.append(("backup", include_chroma))
return {
"created_at": "2026-04-06T23:30:00+00:00",
"backup_root": "/tmp/fake",
"db_snapshot_path": "/tmp/fake/db/atocore.db",
"db_size_bytes": 0,
"registry_snapshot_path": "",
"chroma_snapshot_path": "/tmp/fake/chroma",
"chroma_snapshot_bytes": 4,
"chroma_snapshot_files": 1,
"chroma_snapshot_included": True,
"vector_store_note": "included",
}
monkeypatch.setattr("atocore.api.routes.exclusive_ingestion", fake_lock)
monkeypatch.setattr("atocore.api.routes.create_runtime_backup", fake_create_runtime_backup)
client = TestClient(app)
response = client.post("/admin/backup", json={"include_chroma": True})
assert response.status_code == 200
assert events == ["enter", ("backup", True), "exit"]
assert response.json()["chroma_snapshot_included"] is True
def test_admin_backup_list_and_validate_endpoints(tmp_data_dir, monkeypatch):
config.settings = config.Settings()
def fake_list_runtime_backups():
return [
{
"stamp": "20260406T220000Z",
"path": "/tmp/fake/snapshots/20260406T220000Z",
"has_metadata": True,
"metadata": {"db_snapshot_path": "/tmp/fake/snapshots/20260406T220000Z/db/atocore.db"},
}
]
def fake_validate_backup(stamp):
if stamp == "missing":
return {
"stamp": stamp,
"path": f"/tmp/fake/snapshots/{stamp}",
"exists": False,
"errors": ["snapshot_directory_missing"],
}
return {
"stamp": stamp,
"path": f"/tmp/fake/snapshots/{stamp}",
"exists": True,
"db_ok": True,
"registry_ok": True,
"chroma_ok": None,
"valid": True,
"errors": [],
}
monkeypatch.setattr("atocore.api.routes.list_runtime_backups", fake_list_runtime_backups)
monkeypatch.setattr("atocore.api.routes.validate_backup", fake_validate_backup)
client = TestClient(app)
listing = client.get("/admin/backup")
assert listing.status_code == 200
listing_body = listing.json()
assert "backup_dir" in listing_body
assert listing_body["backups"][0]["stamp"] == "20260406T220000Z"
valid = client.get("/admin/backup/20260406T220000Z/validate")
assert valid.status_code == 200
assert valid.json()["valid"] is True
missing = client.get("/admin/backup/missing/validate")
assert missing.status_code == 404
def test_query_endpoint_accepts_project_hint(monkeypatch):
def fake_retrieve(prompt, top_k=10, filter_tags=None, project_hint=None):
assert prompt == "architecture"

View File

@@ -6,7 +6,11 @@ from datetime import UTC, datetime
import atocore.config as config
from atocore.models.database import init_db
from atocore.ops.backup import create_runtime_backup
from atocore.ops.backup import (
create_runtime_backup,
list_runtime_backups,
validate_backup,
)
def test_create_runtime_backup_copies_db_and_registry(tmp_path, monkeypatch):
@@ -53,6 +57,89 @@ def test_create_runtime_backup_copies_db_and_registry(tmp_path, monkeypatch):
assert metadata["registry_snapshot_path"] == str(registry_snapshot)
def test_create_runtime_backup_includes_chroma_when_requested(tmp_path, monkeypatch):
monkeypatch.setenv("ATOCORE_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setenv("ATOCORE_BACKUP_DIR", str(tmp_path / "backups"))
monkeypatch.setenv(
"ATOCORE_PROJECT_REGISTRY_PATH", str(tmp_path / "config" / "project-registry.json")
)
original_settings = config.settings
try:
config.settings = config.Settings()
init_db()
# Create a fake chroma directory tree with a couple of files.
chroma_dir = config.settings.chroma_path
(chroma_dir / "collection-a").mkdir(parents=True, exist_ok=True)
(chroma_dir / "collection-a" / "data.bin").write_bytes(b"\x00\x01\x02\x03")
(chroma_dir / "metadata.json").write_text('{"ok":true}', encoding="utf-8")
result = create_runtime_backup(
datetime(2026, 4, 6, 20, 0, 0, tzinfo=UTC),
include_chroma=True,
)
finally:
config.settings = original_settings
chroma_snapshot_root = (
tmp_path / "backups" / "snapshots" / "20260406T200000Z" / "chroma"
)
assert result["chroma_snapshot_included"] is True
assert result["chroma_snapshot_path"] == str(chroma_snapshot_root)
assert result["chroma_snapshot_files"] >= 2
assert result["chroma_snapshot_bytes"] > 0
assert (chroma_snapshot_root / "collection-a" / "data.bin").exists()
assert (chroma_snapshot_root / "metadata.json").exists()
def test_list_and_validate_runtime_backups(tmp_path, monkeypatch):
monkeypatch.setenv("ATOCORE_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setenv("ATOCORE_BACKUP_DIR", str(tmp_path / "backups"))
monkeypatch.setenv(
"ATOCORE_PROJECT_REGISTRY_PATH", str(tmp_path / "config" / "project-registry.json")
)
original_settings = config.settings
try:
config.settings = config.Settings()
init_db()
first = create_runtime_backup(datetime(2026, 4, 6, 21, 0, 0, tzinfo=UTC))
second = create_runtime_backup(datetime(2026, 4, 6, 22, 0, 0, tzinfo=UTC))
listing = list_runtime_backups()
first_validation = validate_backup("20260406T210000Z")
second_validation = validate_backup("20260406T220000Z")
missing_validation = validate_backup("20260101T000000Z")
finally:
config.settings = original_settings
assert len(listing) == 2
assert {entry["stamp"] for entry in listing} == {
"20260406T210000Z",
"20260406T220000Z",
}
for entry in listing:
assert entry["has_metadata"] is True
assert entry["metadata"]["db_snapshot_path"]
assert first_validation["valid"] is True
assert first_validation["db_ok"] is True
assert first_validation["errors"] == []
assert second_validation["valid"] is True
assert missing_validation["exists"] is False
assert "snapshot_directory_missing" in missing_validation["errors"]
# both metadata paths are reachable on disk
assert json.loads(
(tmp_path / "backups" / "snapshots" / "20260406T210000Z" / "backup-metadata.json")
.read_text(encoding="utf-8")
)["db_snapshot_path"] == first["db_snapshot_path"]
assert second["db_snapshot_path"].endswith("atocore.db")
def test_create_runtime_backup_handles_missing_registry(tmp_path, monkeypatch):
monkeypatch.setenv("ATOCORE_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setenv("ATOCORE_BACKUP_DIR", str(tmp_path / "backups"))

View File

@@ -44,6 +44,22 @@ def test_settings_keep_legacy_db_path_when_present(tmp_path, monkeypatch):
assert settings.db_path == legacy_db.resolve()
def test_ranking_weights_are_tunable_via_env(monkeypatch):
monkeypatch.setenv("ATOCORE_RANK_PROJECT_MATCH_BOOST", "3.5")
monkeypatch.setenv("ATOCORE_RANK_QUERY_TOKEN_STEP", "0.12")
monkeypatch.setenv("ATOCORE_RANK_QUERY_TOKEN_CAP", "1.5")
monkeypatch.setenv("ATOCORE_RANK_PATH_HIGH_SIGNAL_BOOST", "1.25")
monkeypatch.setenv("ATOCORE_RANK_PATH_LOW_SIGNAL_PENALTY", "0.5")
settings = config.Settings()
assert settings.rank_project_match_boost == 3.5
assert settings.rank_query_token_step == 0.12
assert settings.rank_query_token_cap == 1.5
assert settings.rank_path_high_signal_boost == 1.25
assert settings.rank_path_low_signal_penalty == 0.5
def test_ensure_runtime_dirs_creates_machine_dirs_only(tmp_path, monkeypatch):
monkeypatch.setenv("ATOCORE_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(tmp_path / "vault-source"))

View File

@@ -154,6 +154,110 @@ def test_refresh_registered_project_ingests_registered_roots(tmp_path, monkeypat
assert calls[0][0].endswith("p06-polisher")
assert calls[0][1] is False
assert result["roots"][0]["status"] == "ingested"
assert result["status"] == "ingested"
assert result["roots_ingested"] == 1
assert result["roots_skipped"] == 0
def test_refresh_registered_project_reports_nothing_to_ingest_when_all_missing(
tmp_path, monkeypatch
):
vault_dir = tmp_path / "vault"
drive_dir = tmp_path / "drive"
config_dir = tmp_path / "config"
vault_dir.mkdir()
drive_dir.mkdir()
config_dir.mkdir()
registry_path = config_dir / "project-registry.json"
registry_path.write_text(
json.dumps(
{
"projects": [
{
"id": "p07-ghost",
"aliases": ["ghost"],
"description": "Project whose roots do not exist on disk",
"ingest_roots": [
{"source": "vault", "subpath": "incoming/projects/p07-ghost"}
],
}
]
}
),
encoding="utf-8",
)
def fail_ingest_folder(path, purge_deleted=True):
raise AssertionError(f"ingest_folder should not be called for missing root: {path}")
monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(vault_dir))
monkeypatch.setenv("ATOCORE_DRIVE_SOURCE_DIR", str(drive_dir))
monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path))
original_settings = config.settings
try:
config.settings = config.Settings()
monkeypatch.setattr("atocore.projects.registry.ingest_folder", fail_ingest_folder)
result = refresh_registered_project("ghost")
finally:
config.settings = original_settings
assert result["status"] == "nothing_to_ingest"
assert result["roots_ingested"] == 0
assert result["roots_skipped"] == 1
assert result["roots"][0]["status"] == "missing"
def test_refresh_registered_project_reports_partial_status(tmp_path, monkeypatch):
vault_dir = tmp_path / "vault"
drive_dir = tmp_path / "drive"
config_dir = tmp_path / "config"
real_root = vault_dir / "incoming" / "projects" / "p08-mixed"
real_root.mkdir(parents=True)
drive_dir.mkdir()
config_dir.mkdir()
registry_path = config_dir / "project-registry.json"
registry_path.write_text(
json.dumps(
{
"projects": [
{
"id": "p08-mixed",
"aliases": ["mixed"],
"description": "One root present, one missing",
"ingest_roots": [
{"source": "vault", "subpath": "incoming/projects/p08-mixed"},
{"source": "vault", "subpath": "incoming/projects/p08-mixed-missing"},
],
}
]
}
),
encoding="utf-8",
)
def fake_ingest_folder(path, purge_deleted=True):
return [{"file": str(path / "README.md"), "status": "ingested"}]
monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(vault_dir))
monkeypatch.setenv("ATOCORE_DRIVE_SOURCE_DIR", str(drive_dir))
monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path))
original_settings = config.settings
try:
config.settings = config.Settings()
monkeypatch.setattr("atocore.projects.registry.ingest_folder", fake_ingest_folder)
result = refresh_registered_project("mixed")
finally:
config.settings = original_settings
assert result["status"] == "partial"
assert result["roots_ingested"] == 1
assert result["roots_skipped"] == 1
statuses = sorted(root["status"] for root in result["roots"])
assert statuses == ["ingested", "missing"]
def test_project_registry_template_has_expected_shape():