Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license

This commit is contained in:
2026-09-05 13:26:46 +03:00
commit 03634b1ca3
11340 changed files with 3442369 additions and 0 deletions
+784
View File
@@ -0,0 +1,784 @@
"""Regression: the state.db repair-loop guards must survive an mtime change.
Incident (2026-08-17): a malformed-SCHEMA state.db sent Hermes into an
unbounded repair loop that wrote a fresh 98MB forensic copy every ~10s —
2.3GB in 20 minutes, disk heading to zero, whole agent fleet at risk.
The #86747 guards were already present and did NOT hold, because both keyed
on ``size:mtime_ns``:
* ``_db_fingerprint`` -> the ledger's attempt counter reset to 1 on every
pass, so ``_MAX_PERSISTENT_REPAIR_ATTEMPTS`` was never reached;
* ``_backup_db_file``'s dedupe compared mtime, so it never matched and each
pass wrote another full-size copy.
Unlike the b-tree damage of #86747, the malformed-SCHEMA class still opens
and accepts writes (only ``sqlite_master`` is unreadable), so live writers,
WAL checkpoints and the in-place repair strategies all move mtime between
passes. These tests pin the guards to content, not mtime, and add the
missing free-space refusal.
"""
from __future__ import annotations
import os
import shutil
import sqlite3
import time
from pathlib import Path
from unittest.mock import patch
import hermes_state
from hermes_state import (
_MAX_MALFORMED_BACKUPS,
_MAX_PERSISTENT_REPAIR_ATTEMPTS,
_REPAIR_BACKUP_MIN_FREE_BYTES,
_backup_content_identity,
_backup_db_file,
_db_fingerprint,
_existing_malformed_backups,
_persistent_repair_attempts_exhausted,
_record_repair_outcome,
_repair_backup_headroom_bytes,
)
def _damaged_db(tmp_path: Path, size: int = 200_000) -> Path:
db = tmp_path / "state.db"
db.write_bytes(b"SQLite format 3\x00" + os.urandom(size))
return db
# ---------------------------------------------------------------------------
# Fingerprint stability
# ---------------------------------------------------------------------------
def test_fingerprint_survives_mtime_change(tmp_path):
"""A touched-but-unchanged file keeps its identity (the incident's core)."""
db = _damaged_db(tmp_path)
before = _db_fingerprint(db)
time.sleep(0.01)
os.utime(db, None) # live writer / WAL checkpoint / in-place repair pass
assert _db_fingerprint(db) == before
def test_fingerprint_changes_when_contents_change(tmp_path):
"""Genuine recovery must still reset the attempt budget."""
db = _damaged_db(tmp_path)
before = _db_fingerprint(db)
db.write_bytes(b"SQLite format 3\x00" + os.urandom(200_000))
assert _db_fingerprint(db) != before
def test_fingerprint_changes_on_truncation(tmp_path):
db = _damaged_db(tmp_path)
before = _db_fingerprint(db)
with open(db, "r+b") as fh:
fh.truncate(1024)
assert _db_fingerprint(db) != before
# ---------------------------------------------------------------------------
# Attempt ledger
# ---------------------------------------------------------------------------
def test_attempt_budget_exhausts_despite_mtime_churn(tmp_path):
"""The loop must terminate even when every pass touches the file."""
db = _damaged_db(tmp_path)
for _ in range(_MAX_PERSISTENT_REPAIR_ATTEMPTS):
assert not _persistent_repair_attempts_exhausted(db)
_record_repair_outcome(db, repaired=False)
time.sleep(0.01)
os.utime(db, None)
assert _persistent_repair_attempts_exhausted(db)
def test_successful_repair_clears_budget(tmp_path):
db = _damaged_db(tmp_path)
for _ in range(_MAX_PERSISTENT_REPAIR_ATTEMPTS):
_record_repair_outcome(db, repaired=False)
assert _persistent_repair_attempts_exhausted(db)
_record_repair_outcome(db, repaired=True)
assert not _persistent_repair_attempts_exhausted(db)
# ---------------------------------------------------------------------------
# Backup dedupe
# ---------------------------------------------------------------------------
def test_backup_dedupes_across_mtime_change(tmp_path):
"""Repeated passes over identical bytes must not each write a new copy."""
db = _damaged_db(tmp_path)
first, err = _backup_db_file(db)
assert err is None and first is not None
for _ in range(5):
time.sleep(0.01)
os.utime(db, None)
again, err = _backup_db_file(db)
assert err is None
assert again == first, "a touched-but-identical DB was copied again"
assert len(_existing_malformed_backups(db)) == 1
def test_backup_retention_cap_still_holds(tmp_path):
"""Genuinely different damaged states are kept, but bounded."""
db = _damaged_db(tmp_path)
for _ in range(_MAX_MALFORMED_BACKUPS + 3):
db.write_bytes(b"SQLite format 3\x00" + os.urandom(200_000))
_backup_db_file(db)
assert len(_existing_malformed_backups(db)) <= _MAX_MALFORMED_BACKUPS
# ---------------------------------------------------------------------------
# Free-space guard
# ---------------------------------------------------------------------------
def test_backup_refused_when_disk_would_be_exhausted(tmp_path):
"""A nearly-full volume must not be finished off by the forensic copy."""
db = _damaged_db(tmp_path)
tight = type(
"Usage",
(),
{"total": 10_000_000_000, "used": 0, "free": _REPAIR_BACKUP_MIN_FREE_BYTES // 2},
)()
with patch("shutil.disk_usage", return_value=tight):
path, reason = _backup_db_file(db)
assert path is None
assert reason is not None and "free" in reason.lower()
assert not _existing_malformed_backups(db)
def test_backup_allowed_on_small_volume_with_room(tmp_path):
"""A flat multi-GB floor would disable repair on small VMs/containers.
50MB DB on a 10GB volume with 1.5GB free fits with ~30x headroom; the
guard must allow it rather than hard-stopping repair forever.
"""
# Sparse: this test DOES copy the file, but the guard and copy both care
# about st_size, not content — 50MB of os.urandom would only cost CI time.
db = tmp_path / "state.db"
with open(db, "wb") as handle:
handle.write(b"SQLite format 3\x00")
handle.truncate(50_000_000)
assert db.stat().st_size == 50_000_000
small_vm = type(
"Usage", (), {"total": 10_000_000_000, "used": 8_500_000_000, "free": 1_500_000_000}
)()
with patch("shutil.disk_usage", return_value=small_vm):
path, reason = _backup_db_file(db)
assert reason is None and path is not None
def test_headroom_scales_with_volume_size():
"""Big volumes reserve proportionally; small ones keep a modest floor."""
assert _repair_backup_headroom_bytes(1_000_000_000) == _REPAIR_BACKUP_MIN_FREE_BYTES
assert _repair_backup_headroom_bytes(1_000_000_000_000) > _REPAIR_BACKUP_MIN_FREE_BYTES
def test_disk_guard_accounts_for_sidecars(tmp_path):
"""The copy includes -wal/-shm, so the space check must count them."""
db = _damaged_db(tmp_path, size=1_000_000)
# Sparse: the guard reads st_size, so allocating 400MB of real bytes would
# only buy CI cost (and an ENOSPC risk on tmpfs runners).
wal = db.with_name(db.name + "-wal")
with open(wal, "wb") as handle:
handle.truncate(400_000_000)
assert wal.stat().st_size == 400_000_000
usage = type(
"Usage",
(),
{"total": 10_000_000_000, "used": 0, "free": _REPAIR_BACKUP_MIN_FREE_BYTES + 300_000_000},
)()
with patch("shutil.disk_usage", return_value=usage):
path, reason = _backup_db_file(db)
assert path is None, "sidecar bytes were ignored by the free-space check"
assert reason is not None
def test_failed_copy_leaves_no_countable_debris(tmp_path):
"""Prune only runs on success, so a failed copy must self-clean.
Otherwise partials matching the backup prefix accumulate unbounded and,
on a later successful pass, are KEPT (newest by name) while intact
forensic copies get pruned away.
"""
db = _damaged_db(tmp_path, size=1_000_000)
db.with_name(db.name + "-wal").write_bytes(os.urandom(1_000_000))
roomy = type(
"Usage", (), {"total": 500_000_000_000, "used": 0, "free": 400_000_000_000}
)()
real_copy2 = shutil.copy2
def sidecar_fails(src, dst, *a, **kw):
if str(src).endswith("-wal"):
Path(dst).write_bytes(b"PARTIAL" * 100)
raise OSError(28, "No space left on device")
return real_copy2(src, dst, *a, **kw)
with patch("shutil.disk_usage", return_value=roomy), \
patch("shutil.copy2", sidecar_fails):
for _ in range(6):
_backup_db_file(db)
time.sleep(0.01)
os.utime(db, None)
assert len(_existing_malformed_backups(db)) <= _MAX_MALFORMED_BACKUPS
# a later successful pass must sweep any staging debris
with patch("shutil.disk_usage", return_value=roomy):
path, reason = _backup_db_file(db)
assert reason is None and path is not None
strays = list(tmp_path.glob("*.backup-staging-*")) + list(
tmp_path.glob("*.incomplete*")
)
assert not strays, f"staging debris survived: {strays}"
def test_backup_allowed_with_ample_disk(tmp_path):
db = _damaged_db(tmp_path)
roomy = type(
"Usage", (), {"total": 0, "used": 0, "free": _REPAIR_BACKUP_MIN_FREE_BYTES * 10}
)()
with patch("shutil.disk_usage", return_value=roomy):
path, reason = _backup_db_file(db)
assert reason is None and path is not None
def test_repair_aborts_when_backup_refused_for_disk(tmp_path):
"""Refused backup is a HARD STOP — never mutate the only damaged copy."""
db = _damaged_db(tmp_path)
tight = type(
"Usage", (), {"total": 0, "used": 0, "free": _REPAIR_BACKUP_MIN_FREE_BYTES // 2}
)()
with patch("shutil.disk_usage", return_value=tight):
report = hermes_state.repair_state_db_schema(db)
assert not report.get("repaired")
assert "free" in (report.get("error") or "").lower()
# ---------------------------------------------------------------------------
# Lock safety: the content fingerprint must not cancel POSIX advisory locks
# ---------------------------------------------------------------------------
def test_fingerprint_takes_no_raw_fd_while_a_connection_is_live(tmp_path):
"""The content read must not ``open()`` a DB that has a live connection.
``close()`` on ANY descriptor cancels every POSIX advisory lock this
process holds on the file (https://sqlite.org/howtocorrupt.html), so a
peer connection's RESERVED lock is silently dropped and another process
can write into a file the holder still believes it owns. The exhaustion
probe runs BEFORE ``_backup_db_file``'s ``has_live_connection`` guard, so
the fingerprint has to guard itself.
"""
import builtins
from hermes_cli.sqlite_safe_read import connect_tracked
db = tmp_path / "state.db"
conn = sqlite3.connect(str(db))
conn.execute("CREATE TABLE t(a)")
conn.commit()
conn.close()
live = connect_tracked(db, isolation_level=None, check_same_thread=False)
try:
opened: list[str] = []
real_open = builtins.open
def spy(target, *a, **kw):
if str(target).endswith("state.db"):
opened.append(str(target))
return real_open(target, *a, **kw)
with patch.object(builtins, "open", spy):
fp = _db_fingerprint(db)
assert not opened, f"raw fd taken on a live DB: {opened}"
# None is the correct answer here — see
# test_budget_exhausts_when_liveness_alternates_across_passes for why a
# substitute key shape would be worse than no key at all. The ledger
# keeps counting against the key already on record.
assert fp is None
finally:
live.close()
def test_live_connection_keeps_its_write_lock_across_a_repair_pass(tmp_path):
"""End-to-end: a peer must not be able to steal the holder's write lock.
The peer runs in a SUBPROCESS on purpose. POSIX advisory locks are owned
per-process, so a same-process peer shares the holder's lock ownership and
cannot demonstrate the cancellation — it stays blocked either way, which
makes the test vacuous.
Rollback-journal mode only — WAL coordinates through ``-shm`` rather than
POSIX advisory locks, so it is immune. DELETE mode is what Hermes falls
back to on NFS/SMB/FUSE/ZFS and on SQLite builds vulnerable to the
WAL-reset bug, so it is a real deployment shape, not a corner case.
"""
import subprocess
import sys
import textwrap
from hermes_cli.sqlite_safe_read import connect_tracked
db = tmp_path / "state.db"
conn = sqlite3.connect(str(db))
conn.execute("PRAGMA journal_mode=DELETE")
conn.execute("CREATE TABLE sessions(id TEXT)")
conn.commit()
conn.close()
peer_script = tmp_path / "peer.py"
peer_script.write_text(
textwrap.dedent(
"""
import sqlite3, sys
con = sqlite3.connect(sys.argv[1], timeout=0.3, isolation_level=None)
try:
con.execute("BEGIN IMMEDIATE")
con.execute("INSERT INTO sessions VALUES('peer')")
con.execute("COMMIT")
print("WROTE")
except sqlite3.OperationalError:
print("BLOCKED")
"""
)
)
def _peer_can_write() -> bool:
out = subprocess.run(
[sys.executable, str(peer_script), str(db)],
capture_output=True,
text=True,
timeout=60,
).stdout.strip()
assert out in {"WROTE", "BLOCKED"}, f"unexpected peer output: {out!r}"
return out == "WROTE"
live = connect_tracked(db, isolation_level=None, check_same_thread=False)
try:
live.execute("BEGIN IMMEDIATE")
live.execute("INSERT INTO sessions VALUES('holder')")
assert not _peer_can_write(), "peer wrote before the fingerprint (bad fixture)"
_db_fingerprint(db)
assert not _peer_can_write(), (
"the fingerprint cancelled the holder's POSIX advisory lock"
)
live.execute("COMMIT")
finally:
live.close()
# ---------------------------------------------------------------------------
# Staging must never be mistaken for a forensic backup
# ---------------------------------------------------------------------------
def test_staging_name_is_outside_the_backup_prefix(tmp_path):
"""Whatever staging name the code picks must not be counted as a backup.
Observes the REAL staging path (captured from the copy call) rather than
hardcoding it, so the assertion binds to the invariant instead of to
today's spelling. ``_existing_malformed_backups`` matches
``startswith(f"{db}.malformed-backup-")`` and excludes only ``-wal``/
``-shm``, so a staging name derived from the backup name sorts NEWEST and
prune keeps partials while deleting intact copies.
"""
db = _damaged_db(tmp_path, size=20_000)
roomy = type(
"Usage", (), {"total": 500_000_000_000, "used": 0, "free": 400_000_000_000}
)()
real_copy2 = shutil.copy2
staging_names: list[str] = []
def capture(src, dst, *a, **kw):
staging_names.append(Path(dst).name)
return real_copy2(src, dst, *a, **kw)
with patch("shutil.disk_usage", return_value=roomy), \
patch("shutil.copy2", capture):
path, reason = _backup_db_file(db)
assert reason is None and path is not None
assert staging_names, "no copy was made (fixture problem)"
prefix = f"{db.name}.malformed-backup-"
for name in staging_names:
assert not name.startswith(prefix), (
f"staging name {name!r} matches the backup prefix — it would be "
"counted by _existing_malformed_backups, sort NEWEST, and let "
"prune keep partials while deleting intact forensic copies"
)
def test_orphaned_staging_is_never_returned_as_the_backup_path(tmp_path):
"""A kill mid-copy leaves a byte-identical staging file; the dedupe must
not hand it back as the official ``backup_path``.
It would pass the #69603 hard-stop gate — repair then runs destructive
surgery believing a forensic copy exists — and the next pass's sweep
deletes that very file.
"""
db = _damaged_db(tmp_path, size=20_000)
roomy = type(
"Usage", (), {"total": 500_000_000_000, "used": 0, "free": 400_000_000_000}
)()
# Discover the staging name the implementation actually uses, then plant an
# orphan under it — so this binds to the code's scheme, not to a literal.
real_copy2 = shutil.copy2
seen: list[Path] = []
def capture(src, dst, *a, **kw):
seen.append(Path(dst))
return real_copy2(src, dst, *a, **kw)
with patch("shutil.disk_usage", return_value=roomy), \
patch("shutil.copy2", capture):
first, _ = _backup_db_file(db)
assert first is not None
Path(first).unlink(missing_ok=True)
orphan = seen[0]
shutil.copy2(db, orphan) # identical bytes => fingerprint matches
assert orphan.exists()
with patch("shutil.disk_usage", return_value=roomy):
path, reason = _backup_db_file(db)
assert reason is None and path is not None
assert Path(path) != orphan, f"staging returned as the backup: {path}"
assert not str(path).endswith(".incomplete")
assert "staging" not in Path(path).name
assert Path(path).exists()
assert not orphan.exists(), "stale staging debris was not swept"
def test_backup_refused_when_free_space_cannot_be_determined(tmp_path):
"""Fail CLOSED: a nearly-full volume is where disk_usage is likeliest to
fail, and proceeding is the multi-GB copy that finishes off the disk."""
db = _damaged_db(tmp_path)
with patch("shutil.disk_usage", side_effect=OSError("statvfs failed")):
path, reason = _backup_db_file(db)
assert path is None
assert reason is not None and "free space" in reason.lower()
assert not _existing_malformed_backups(db)
def test_budget_exhausts_when_liveness_alternates_across_passes(tmp_path):
"""A peer connection must not reset the attempt budget.
``_db_fingerprint`` returns None when a live connection makes the content
read unsafe. If the ledger treated that as "no identity" (skip the record)
or substituted a differently-shaped key (``size:mtime_ns``), then a gateway
peer connecting and disconnecting between passes would reset the counter to
1 forever — the exact unbounded loop this whole ledger exists to stop.
"""
from hermes_cli.sqlite_safe_read import connect_tracked
db = tmp_path / "state.db"
conn = sqlite3.connect(str(db))
conn.execute("CREATE TABLE t(a)")
conn.commit()
conn.close()
for index in range(_MAX_PERSISTENT_REPAIR_ATTEMPTS):
live = None
if index % 2 == 1: # a peer holds the DB on alternate passes
live = connect_tracked(db, isolation_level=None, check_same_thread=False)
try:
assert not _persistent_repair_attempts_exhausted(db)
_record_repair_outcome(db, repaired=False)
finally:
if live is not None:
live.close()
assert _persistent_repair_attempts_exhausted(db), (
"alternating live/offline passes reset the repair budget"
)
# And an exhausted budget must stay visible even while a peer is connected.
live = connect_tracked(db, isolation_level=None, check_same_thread=False)
try:
assert _persistent_repair_attempts_exhausted(db)
finally:
live.close()
def test_fingerprint_returns_none_rather_than_a_mtime_shaped_key(tmp_path):
"""Never mint a second key SHAPE — the ledger compares for equality."""
from hermes_cli.sqlite_safe_read import connect_tracked
db = tmp_path / "state.db"
conn = sqlite3.connect(str(db))
conn.execute("CREATE TABLE t(a)")
conn.commit()
conn.close()
offline = _db_fingerprint(db)
assert offline is not None
live = connect_tracked(db, isolation_level=None, check_same_thread=False)
try:
assert _db_fingerprint(db) is None, (
"a live connection produced a fingerprint; if its shape differs "
"from the offline key the ledger can never match across passes"
)
finally:
live.close()
# ---------------------------------------------------------------------------
# The content sample must exclude SQLite's commit counters
# ---------------------------------------------------------------------------
def _populated_db(path: Path, journal_mode: str, rows: int = 600) -> None:
"""A DB comfortably larger than the fingerprint sample window (~270KB)."""
conn = sqlite3.connect(str(path))
conn.execute(f"PRAGMA journal_mode={journal_mode}")
conn.execute("CREATE TABLE sessions(id TEXT, blob TEXT)")
conn.executemany(
"INSERT INTO sessions VALUES(?,?)",
[(str(i), "x" * 400) for i in range(rows)],
)
conn.commit()
conn.close()
def test_ordinary_commit_does_not_rekey_the_fingerprint(tmp_path):
"""A malformed-SCHEMA DB still accepts writes, so commits must not re-key.
In rollback-journal (DELETE) mode a commit writes the main file directly and
bumps the header's file change counter (bytes 24-27) and version-valid-for
(92-95). Those live inside the head sample, so an unmasked fingerprint
changed on every ordinary session write — resetting the repair budget to 1
forever, which is exactly the unbounded loop this suite exists to pin.
"""
for journal_mode in ("DELETE", "WAL"):
db = tmp_path / f"state_{journal_mode}.db"
_populated_db(db, journal_mode)
before = _db_fingerprint(db)
writer = sqlite3.connect(str(db), isolation_level=None)
try:
writer.execute("UPDATE sessions SET blob='peer' WHERE id='20000'")
finally:
writer.close()
assert _db_fingerprint(db) == before, (
f"{journal_mode} mode: an ordinary commit re-keyed the ledger"
)
def test_budget_exhausts_while_a_writer_commits_between_passes(tmp_path):
"""End-to-end shape of the original incident, in DELETE mode."""
db = tmp_path / "state.db"
_populated_db(db, "DELETE")
for index in range(_MAX_PERSISTENT_REPAIR_ATTEMPTS):
assert not _persistent_repair_attempts_exhausted(db)
_record_repair_outcome(db, repaired=False)
writer = sqlite3.connect(str(db), isolation_level=None)
try:
writer.execute("UPDATE sessions SET blob=? WHERE id='20000'", (f"v{index}",))
finally:
writer.close()
assert _persistent_repair_attempts_exhausted(db), (
"a live writer's commits reset the repair budget every pass"
)
def test_genuine_recovery_still_resets_the_budget(tmp_path):
"""Masking the commit counters must not blind us to real repair."""
db = tmp_path / "state.db"
_populated_db(db, "DELETE")
def _mutate(sql: str) -> None:
conn = sqlite3.connect(str(db), isolation_level=None)
try:
conn.execute(sql)
finally:
conn.close()
for label, sql in (
("sqlite_master rewrite", "CREATE TABLE healed(x)"),
("index rebuild", "CREATE INDEX ix_sessions_id ON sessions(id)"),
("VACUUM", "VACUUM"),
):
before = _db_fingerprint(db)
_mutate(sql)
assert _db_fingerprint(db) != before, f"{label} left the fingerprint unchanged"
before = _db_fingerprint(db)
with open(db, "r+b") as handle:
handle.truncate(4096)
assert _db_fingerprint(db) != before, "truncation left the fingerprint unchanged"
def test_forensic_backup_includes_the_rollback_journal(tmp_path):
"""DELETE mode leaves a hot -journal, and that file interprets the damage.
Rollback-journal mode is Hermes's fallback on NFS/SMB/FUSE/ZFS and on
WAL-reset-vulnerable SQLite builds. A forensic copy without the journal
cannot be rolled back to a consistent state by hand.
"""
db = _damaged_db(tmp_path, size=20_000)
for suffix, payload in (("-wal", b"WALDATA"), ("-journal", b"JOURNALDATA")):
db.with_name(db.name + suffix).write_bytes(payload)
roomy = type(
"Usage", (), {"total": 500_000_000_000, "used": 0, "free": 400_000_000_000}
)()
with patch("shutil.disk_usage", return_value=roomy):
path, reason = _backup_db_file(db)
assert reason is None and path is not None
journal_copy = path.with_name(path.name + "-journal")
assert journal_copy.exists(), "the rollback journal was left out of the backup"
assert journal_copy.read_bytes() == b"JOURNALDATA"
assert path.with_name(path.name + "-wal").read_bytes() == b"WALDATA"
# Sidecar copies must not inflate the retention count.
assert len(_existing_malformed_backups(db)) == 1
def test_prune_removes_journal_sidecars_too(tmp_path):
"""Otherwise the retention cap leaks one -journal per pruned backup."""
db = _damaged_db(tmp_path, size=20_000)
db.with_name(db.name + "-journal").write_bytes(b"J")
roomy = type(
"Usage", (), {"total": 500_000_000_000, "used": 0, "free": 400_000_000_000}
)()
for _ in range(_MAX_MALFORMED_BACKUPS + 2):
db.write_bytes(b"SQLite format 3\x00" + os.urandom(20_000))
with patch("shutil.disk_usage", return_value=roomy):
_backup_db_file(db)
kept = _existing_malformed_backups(db)
assert len(kept) <= _MAX_MALFORMED_BACKUPS
# Assert on what is ON DISK rather than on the paths returned earlier: a
# same-second stamp collision means an earlier return value can name a file
# a later pass legitimately recreated.
kept_names = {p.name for p in kept}
orphans = [
p.name
for p in tmp_path.iterdir()
if p.name.endswith("-journal")
and ".malformed-backup-" in p.name
and p.name[: -len("-journal")] not in kept_names
]
assert not orphans, f"pruned backups left journals behind: {orphans}"
# And every surviving backup keeps its journal.
for survivor in kept:
assert survivor.with_name(survivor.name + "-journal").exists()
# ---------------------------------------------------------------------------
# Backup identity vs repair-epoch fingerprint are different equivalence
# relations (the forensic dedupe must NOT reuse _db_fingerprint).
# ---------------------------------------------------------------------------
def test_backup_not_deduped_after_interior_page_write(tmp_path):
"""An interior-page write must force a fresh forensic backup.
``_db_fingerprint`` deliberately samples only head/tail and masks commit
counters so an ordinary write does not re-key the repair budget. If the
forensic dedupe reused THAT identity, a live writer committing new rows
into an interior page (size preserved, first/last 64KiB untouched) would
be handed the STALE earlier backup as "identical" — a recovery point that
predates real user data. The dedupe must use ``_backup_content_identity``
(whole file), which detects the interior change.
"""
# Larger than 2x the 64KiB head/tail sample so a middle region exists
# outside the sampled windows.
db = tmp_path / "state.db"
db.write_bytes(b"SQLite format 3\x00" + os.urandom(300_000))
size = db.stat().st_size
roomy = type(
"Usage", (), {"total": 500_000_000_000, "used": 0, "free": 400_000_000_000}
)()
with patch("shutil.disk_usage", return_value=roomy):
first, err = _backup_db_file(db)
assert err is None and first is not None
# Mutate an interior byte far from both sampled windows; keep size + mtime.
raw = bytearray(db.read_bytes())
mid = len(raw) // 2
raw[mid] ^= 0xFF
st = db.stat()
db.write_bytes(bytes(raw))
os.utime(db, ns=(st.st_atime_ns, st.st_mtime_ns))
assert db.stat().st_size == size
# Guard the test's own premise: the repair-epoch fingerprint is BLIND to
# this change (that is why it must not be the dedupe key), while the
# backup-content identity SEES it.
assert _backup_content_identity(db) != _backup_content_identity(first)
with patch("shutil.disk_usage", return_value=roomy):
second, err = _backup_db_file(db)
assert err is None and second is not None
assert second != first, "an interior-page write was wrongly deduped to a stale backup"
assert len(_existing_malformed_backups(db)) == 2
def test_publication_failure_leaves_no_countable_partial_bundle(tmp_path):
"""A mid-publish os.replace failure must not leave a countable main backup.
The bundle is published sidecars-first, main-DB-last (the main name is the
commit marker ``_existing_malformed_backups`` counts). If a promotion after
the first fails, cleanup must roll back every already-published
destination — otherwise an incomplete bundle (main present, a sidecar
missing) survives, passes the #69603 hard stop, and is deduped/reused as a
legitimate forensic copy on the next pass.
Distinct from ``test_failed_copy_leaves_no_countable_debris``, which fails
during ``copy2`` (before any ``os.replace``); this exercises the
publication window.
"""
db = _damaged_db(tmp_path, size=200_000)
db.with_name(db.name + "-wal").write_bytes(os.urandom(50_000))
roomy = type(
"Usage", (), {"total": 500_000_000_000, "used": 0, "free": 400_000_000_000}
)()
real_replace = os.replace
calls = {"n": 0}
def replace_fails_after_first(src, dst, *a, **kw):
# Let the first promotion (a sidecar) land, fail the next one.
calls["n"] += 1
if calls["n"] == 2:
raise OSError(28, "No space left on device")
return real_replace(src, dst, *a, **kw)
with patch("shutil.disk_usage", return_value=roomy), \
patch("os.replace", replace_fails_after_first):
try:
_backup_db_file(db)
except OSError:
pass # the failure is re-raised by design; we assert on-disk state
# No countable main backup, and no orphaned promoted sidecar, may survive.
assert not _existing_malformed_backups(db), "a partial bundle was left countable"
promoted = [
p for p in tmp_path.iterdir()
if ".malformed-backup-" in p.name and p.name != "state.db"
]
assert not promoted, f"partial promoted files survived: {promoted}"
# A later clean pass must still succeed and must not dedupe onto debris.
with patch("shutil.disk_usage", return_value=roomy):
path, reason = _backup_db_file(db)
assert reason is None and path is not None
strays = list(tmp_path.glob("*.backup-staging-*"))
assert not strays, f"staging debris survived: {strays}"