"""Pre-open schema/integrity/count proof; stdlib only, never constructs Store.""" import argparse from contextlib import closing import hashlib import os import re import stat import json from pathlib import Path import sqlite3 def inspect_readonly(path, *, _closed_snapshot=False): path = Path(path) if not path.is_file() or path.is_symlink(): raise ValueError('existing nonsymlink private database required') uri = path.resolve().as_uri() + '?mode=ro' + ('&immutable=1' if _closed_snapshot else '') # sqlite3.Connection context management ends transactions; it does not close. with closing(sqlite3.connect(uri, uri=True, timeout=5)) as db: db.execute('PRAGMA query_only=ON') db.execute('BEGIN') version = db.execute('PRAGMA user_version').fetchone()[0] if version != 5: raise ValueError('pre-open database is not schema5; refuse migration') if db.execute('PRAGMA integrity_check').fetchall() != [('ok',)]: raise ValueError('read-only integrity check refused') lifecycle = {name: db.execute('SELECT COUNT(*) FROM ' + table).fetchone()[0] for name, table in (('records', 'lifecycle_records'), ('events', 'lifecycle_events'), ('pending', 'lifecycle_pending'))} result = {'schema_version': version, 'lifecycle': lifecycle, 'outbox': dict(db.execute('SELECT state,COUNT(*) FROM outbox GROUP BY state')), 'submissions': dict(db.execute('SELECT state,COUNT(*) FROM submissions GROUP BY state')), 'integrity': 'ok', 'read_only_pre_open': True, 'store_constructed': False} db.rollback() return result def inspect_closed_snapshot(path, expected_sha256): """Only a privately retained, frozen main-file snapshot with no WAL frames. Caller must establish exclusive snapshot creation/admin exit/no writer, and supply the recorded frozen main digest. Never use for the live database. Immutable SQLite reads ignore stale SHM and never create/change sidecars. Existing sidecars remain custody evidence; nothing is deleted/checkpointed. """ path = Path(path) if re.fullmatch(r'[0-9a-f]{64}', expected_sha256) is None: raise ValueError('exact frozen snapshot digest required') def metadata(target): info = target.lstat() if not stat.S_ISREG(info.st_mode) or stat.S_IMODE(info.st_mode) != 0o600 or info.st_nlink != 1: raise ValueError('private single-link snapshot custody required') return info main = metadata(path) if not 0 < main.st_size <= 512 * 1024**2: raise ValueError('bounded frozen snapshot required') def sidecars(): identities = {} for suffix in ('-wal', '-shm'): target = Path(str(path) + suffix) if os.path.lexists(target): info = metadata(target) if info.st_uid != main.st_uid or info.st_gid != main.st_gid: raise ValueError('snapshot sidecar custody changed') if suffix == '-wal' and info.st_size != 0: raise ValueError('snapshot has WAL frames; immutable read refused') identities[suffix] = (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns) return identities before_sidecars = sidecars() def digest(): value = hashlib.sha256() with path.open('rb') as stream: for block in iter(lambda: stream.read(1024**2), b''): value.update(block) return value.hexdigest() if digest() != expected_sha256: raise ValueError('frozen main-file digest changed before inspection') proof = inspect_readonly(path, _closed_snapshot=True) after = metadata(path) if (after.st_dev, after.st_ino, after.st_uid, after.st_gid, after.st_size) != (main.st_dev, main.st_ino, main.st_uid, main.st_gid, main.st_size): raise ValueError('frozen main-file identity changed') if digest() != expected_sha256 or sidecars() != before_sidecars: raise ValueError('frozen snapshot changed during inspection') return proof if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--db', type=Path, required=True) args = parser.parse_args() try: print(json.dumps(inspect_readonly(args.db), sort_keys=True)) except Exception: raise SystemExit('read-only schema5/integrity/count gate refused; no Store/admin open') from None