Remove encryption; user-data filters; SQLite safety; recovery, retention, scheduler

- No client-side encryption: plaintext content-addressed remote (SHA-256
  names), no backup key, no cryptography dependency (server disk encryption
  is the trust model).
- Filters back user data: images, archives, databases, PDFs accepted; 10 MiB
  cap; binary sniffing removed; temp names hardened (~$, #..#, .temp).
- SQLite zero-error policy: backup-API snapshots + integrity_check, journal
  folding, locked/corrupt loud skips, verified restores.
- Recovery: reindex from manifests, remote adopt, on-demand blob fetch,
  5-day retention + thinning, GC, date-guarded remote purge, metrics.
- Scheduler with WebDAV quota signal and 70% pressure backstop (floor kept).
- 37 tests incl. live-monitor capture safety and DB safety.
This commit is contained in:
retoor
2026-10-10 03:41:36 +02:00
parent 7e05e3d26c
commit 3499f6cda0
35 changed files with 3148 additions and 203 deletions
+9 -7
View File
@@ -10,8 +10,7 @@ from fastapi.testclient import TestClient
from versiond.api import create_app
from versiond.config import Config, Paths
from versiond.crypto import KeyRing
from versiond.store import _codec
from versiond.store import decompress
from test_service import CONFIG, history, wait_for
@@ -100,23 +99,26 @@ def test_backup_to_webdav(remote_env):
directory = remote["directory"]
assert directory.startswith("/versioned/") and remote["adopted"] is False
assert "password" not in remote and remote["password_set"] is True
assert remote["encryption"] == "none"
owner = json.loads(dav.files[directory + "/meta/owner.json"])
assert owner["key_id"] == remote["key_id"]
assert "installation_id" in owner
assert "secret" not in cfg.paths.config_file.read_text() # password only in credentials
client.post("/api/v1/roots", json={"path": str(project)})
wait_for(lambda: progress(client)["upload"]["versions_local"] == 0
and progress(client)["upload"]["versions_durable"] == 2, timeout=10)
keys = KeyRing.load_or_create(cfg.key_file)
blob_paths = [p for p in dav.files if p.startswith(directory + "/blobs/")]
contents = {_codec.decompress(keys.decrypt(dav.files[p])) for p in blob_paths}
# Unencrypted: remote blobs are plain compressed file contents, named by SHA-256.
import re
assert blob_paths and all(re.fullmatch(r"[0-9a-f]{64}", p.rsplit("/", 1)[-1]) for p in blob_paths)
contents = {decompress(dav.files[p]) for p in blob_paths}
assert contents == {b"print('hello')\n", b"SECRET=1\n"}
assert all(b"SECRET" not in dav.files[p] for p in blob_paths) # encrypted at rest
manifests = [p for p in dav.files if p.startswith(directory + "/manifests/")]
assert manifests and all(p.endswith(".jsonl.zst") for p in manifests)
records = [json.loads(line) for p in manifests
for line in _codec.decompress(keys.decrypt(dav.files[p])).decode().splitlines()]
for line in decompress(dav.files[p]).decode().splitlines()]
assert {r["path"] for r in records if r["type"] == "version"} == {str(project / "main.py"), str(project / ".env")}
# reconfiguring the same machine adopts its own directory