fix(state): quarantine 0-byte truncated state.db and record store provenance (#97568)

This commit is contained in:
loulanyue
2026-08-29 09:45:04 +08:00
committed by kshitij
parent 26f178e5fa
commit a071fc80da
4 changed files with 56 additions and 4 deletions
+4 -2
View File
@@ -449,15 +449,17 @@ def is_zeroed_sqlite_file(
size = path.stat().st_size
except OSError:
return False
if size <= 0:
if size < 0:
return False
from hermes_cli.sqlite_safe_read import read_header_bytes_preopen
head = read_header_bytes_preopen(
path, length=max(16, probe_bytes), force=force
)
if not head:
if head is None:
return False
if len(head) == 0:
return True
if head.startswith(b"SQLite format 3"):
return False
return all(byte == 0 for byte in head)
+6 -2
View File
@@ -3952,14 +3952,18 @@ def is_zeroed_state_db(
size = path.stat().st_size
except OSError:
return False
if size <= 0:
if size < 0:
return False
from hermes_cli.sqlite_safe_read import read_header_bytes_preopen
head = read_header_bytes_preopen(
path, length=max(16, probe_bytes), force=force
)
if not head or head.startswith(b"SQLite format 3"):
if head is None:
return False
if len(head) == 0:
return True
if head.startswith(b"SQLite format 3"):
return False
return all(byte == 0 for byte in head)
+14
View File
@@ -8,12 +8,15 @@ own; methods access the host's attributes (``self._conn``, ``self.db_path``,
module-level constants live in hermes_state_common.
"""
import datetime
import logging
import json
import sqlite3
import time
import uuid
from typing import Dict, Optional, Sequence
from hermes_constants import get_hermes_home
from hermes_state_common import (
DEFERRED_INDEX_SQL,
@@ -1032,6 +1035,17 @@ class SessionSchemaMixin:
"INSERT INTO schema_version (version) VALUES (?)",
(SCHEMA_VERSION,),
)
# Record store provenance on creation so fresh vs wiped stores are distinguishable (#97568)
now_iso = datetime.datetime.now(datetime.timezone.utc).isoformat()
instance_id = str(uuid.uuid4())
cursor.executemany(
"INSERT OR IGNORE INTO state_meta (key, value) VALUES (?, ?)",
[
("store_instance_id", instance_id),
("store_created_at_utc", now_iso),
],
)
else:
current_version = row["version"] if isinstance(row, sqlite3.Row) else row[0]
# Data migrations that can't be expressed declaratively (row
+32
View File
@@ -41,6 +41,38 @@ def test_sessiondb_opens_fresh_after_zeroed_quarantine(tmp_path, monkeypatch):
sdb.close()
def test_is_zeroed_state_db_zero_byte_quarantine(tmp_path, monkeypatch):
"""#97568: a 0-byte file must be detected as zeroed and quarantined."""
import hermes_state as hs
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
db = tmp_path / "state.db"
db.write_bytes(b"") # 0-byte truncated file
assert hs.is_zeroed_state_db(db) is True
sdb = hs.SessionDB(db_path=db)
try:
# Fresh DB should open and accept schema
assert db.exists()
assert not hs.is_zeroed_state_db(db)
# Quarantine retained for the 0-byte file
backups = list(tmp_path.glob("state.db.zeroed-*.bak"))
assert len(backups) == 1
assert backups[0].stat().st_size == 0
# Check store provenance was recorded in state_meta
row_instance = sdb._conn.execute(
"SELECT value FROM state_meta WHERE key = 'store_instance_id'"
).fetchone()
row_created = sdb._conn.execute(
"SELECT value FROM state_meta WHERE key = 'store_created_at_utc'"
).fetchone()
assert row_instance is not None and row_instance[0]
assert row_created is not None and row_created[0]
finally:
sdb.close()
def test_concurrent_quarantine_no_clobber(tmp_path):
"""#68805: two concurrent startups must not race on quarantine.