chore: establish project baseline
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""Zero-write proof for a Firebird database (READ-ONLY tool).
|
||||
|
||||
python scripts/prove_zero_write.py snapshot <FDB> <out.json> # before the run
|
||||
python scripts/prove_zero_write.py compare <before.json> <after.json>
|
||||
|
||||
A snapshot records: (1) database-level DML counters (MON$RECORD_STATS inserts/updates/deletes), (2) a SHA-256 over every row/column of
|
||||
C000025 (and C000004), (3) row counts of C000025/C000004/C000030/NFCE/NFCE_ITENS, (4) the file SHA-256 and size. `compare` reports whether
|
||||
the DML counters, table digests and counts are identical. Only SELECT statements are executed. The file SHA-256 is informational only: any attach
|
||||
(even an idle read-only one) touches header/page metadata, so it is not part of the verdict.
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def load_env():
|
||||
f = ROOT / ".env"
|
||||
if f.exists():
|
||||
for line in f.read_text(encoding="utf-8").splitlines():
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, _, v = line.partition("=")
|
||||
os.environ.setdefault(k.strip(), v.strip())
|
||||
|
||||
|
||||
def table_digest(cur, table):
|
||||
cur.execute(f"SELECT * FROM {table} ORDER BY 1")
|
||||
h = hashlib.sha256()
|
||||
n = 0
|
||||
for row in cur.fetchall():
|
||||
h.update(repr([v.read() if hasattr(v, "read") else v for v in row]).encode("utf-8", "replace"))
|
||||
n += 1
|
||||
return {"rows": n, "sha256": h.hexdigest()}
|
||||
|
||||
|
||||
def snapshot(path):
|
||||
import fdb
|
||||
conn = fdb.connect(database=path, user=os.environ["FB_USER"], password=os.environ["FB_PASSWORD"], fb_library_name=os.environ.get("FIREBIRD_CLIENT_LIBRARY"),
|
||||
isolation_level=fdb.ISOLATION_LEVEL_READ_COMMITED_RO)
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
stats = None
|
||||
try:
|
||||
cur.execute("SELECT r.MON$RECORD_INSERTS, r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES FROM MON$RECORD_STATS r JOIN MON$DATABASE d ON d.MON$STAT_ID = r.MON$STAT_ID")
|
||||
row = cur.fetchone()
|
||||
stats = {"inserts": row[0], "updates": row[1], "deletes": row[2]}
|
||||
except Exception as e: # noqa: BLE001
|
||||
stats = {"unavailable": type(e).__name__}
|
||||
digests = {t: table_digest(cur, t) for t in ("C000025", "C000004")}
|
||||
counts = {}
|
||||
for t in ("C000030", "NFCE", "NFCE_ITENS", "C000061", "C000032"):
|
||||
try:
|
||||
cur.execute(f"SELECT COUNT(*) FROM {t}")
|
||||
counts[t] = cur.fetchone()[0]
|
||||
except Exception: # noqa: BLE001
|
||||
counts[t] = None
|
||||
finally:
|
||||
conn.close()
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
for block in iter(lambda: f.read(8 * 1024 * 1024), b""):
|
||||
h.update(block)
|
||||
return {"database": Path(path).name, "dml_counters": stats, "table_digests": digests, "row_counts": counts, "file_sha256": h.hexdigest(), "file_size": Path(path).stat().st_size}
|
||||
|
||||
|
||||
def compare(before, after):
|
||||
res = {
|
||||
"dml_counters_identical": before["dml_counters"] == after["dml_counters"],
|
||||
"table_digests_identical": before["table_digests"] == after["table_digests"],
|
||||
"row_counts_identical": before["row_counts"] == after["row_counts"],
|
||||
"file_sha256_identical": before["file_sha256"] == after["file_sha256"],
|
||||
"dml_counters": {"before": before["dml_counters"], "after": after["dml_counters"]},
|
||||
}
|
||||
res["ZERO_WRITE_PROVEN"] = res["table_digests_identical"] and res["row_counts_identical"] and res["dml_counters_identical"]
|
||||
return res
|
||||
|
||||
|
||||
def main(argv):
|
||||
load_env()
|
||||
if argv[0] == "snapshot":
|
||||
Path(argv[2]).write_text(json.dumps(snapshot(argv[1]), indent=1), encoding="utf-8")
|
||||
print("snapshot written")
|
||||
elif argv[0] == "compare":
|
||||
r = compare(json.loads(Path(argv[1]).read_text(encoding="utf-8")), json.loads(Path(argv[2]).read_text(encoding="utf-8")))
|
||||
print(json.dumps(r, indent=1))
|
||||
raise SystemExit(0 if r["ZERO_WRITE_PROVEN"] else 1)
|
||||
else:
|
||||
raise SystemExit(__doc__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
||||
Reference in New Issue
Block a user