#!/usr/bin/env python3 """READ-ONLY state capture of an OpenFUT Core SQLite DB, for promotion gating. Opened `mode=ro` and never written to, so it is safe against live production. Emits a deterministic JSON document: run it before a promotion and again after, then `diff` the two. Every table is counted, so a delta cannot hide in a table nobody thought to list. Usage: fifa17-promotion-snapshot.py [out.json] """ import hashlib import json import sqlite3 import sys def snapshot(db_path): con = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True) con.row_factory = sqlite3.Row one = lambda s: con.execute(s).fetchone()[0] snap = { "db_path": db_path, "journal_mode": one("PRAGMA journal_mode"), "page_size": one("PRAGMA page_size"), "page_count": one("PRAGMA page_count"), "sqlite_version": sqlite3.sqlite_version, } # Schema version + the full applied-migration ledger. A promotion that # claims "0028 applied" must show it here, with success=1. snap["migration_max"] = one("SELECT MAX(version) FROM _sqlx_migrations") snap["migration_count"] = one("SELECT COUNT(*) FROM _sqlx_migrations") snap["migrations_failed"] = one( "SELECT COUNT(*) FROM _sqlx_migrations WHERE success <> 1") # Every table, counted. Deliberately not a hand-picked list. tables = [r[0] for r in con.execute( "SELECT name FROM sqlite_master WHERE type='table' " "AND name NOT LIKE 'sqlite_%' ORDER BY name")] snap["table_counts"] = {t: one(f'SELECT COUNT(*) FROM "{t}"') for t in tables} # The economically load-bearing figures, called out so a diff is readable. snap["coins"] = dict(con.execute("SELECT id, coins FROM clubs ORDER BY id").fetchall()) snap["owned_total"] = one("SELECT COUNT(*) FROM owned_cards") snap["owned_by_kind"] = dict( con.execute("SELECT content_kind, COUNT(*) FROM owned_cards " "GROUP BY 1 ORDER BY 1").fetchall()) snap["squad_players"] = one("SELECT COUNT(*) FROM squad_players") snap["market_listings"] = one("SELECT COUNT(*) FROM market_listings") snap["market_listings_sold"] = one("SELECT COUNT(*) FROM market_listings WHERE sold = 1") snap["market_listings_npc"] = one("SELECT COUNT(*) FROM market_listings WHERE is_npc = 1") snap["game_entity_ext"] = one("SELECT COUNT(*) FROM game_entity_ext") snap["consumable_applications"] = one("SELECT COUNT(*) FROM consumable_applications") snap["packs_total"] = one("SELECT COUNT(*) FROM packs") snap["packs_unopened"] = one("SELECT COUNT(*) FROM packs WHERE opened = 0") snap["match_completions"] = one("SELECT COUNT(*) FROM match_completions") # Present only after 0028. Absent => pre-0028 DB, which is itself the signal. cols = [r[1] for r in con.execute("PRAGMA table_info(owned_cards)")] snap["owned_cards_columns"] = cols snap["has_0028"] = "contract_matches" in cols if snap["has_0028"]: snap["contract_matches_set"] = one( "SELECT COUNT(*) FROM owned_cards WHERE contract_matches IS NOT NULL") snap["contract_matches_sum"] = one( "SELECT COALESCE(SUM(contract_matches), 0) FROM owned_cards") # A content fingerprint over ownership: catches a row silently rewritten # even when every count stays identical. h = hashlib.sha256() for r in con.execute( "SELECT id, club_id, card_id, is_loan, loan_matches_remaining, " "chemistry_style, position_override, training_bonus, content_kind, " "quantity FROM owned_cards ORDER BY id"): h.update(("|".join("" if v is None else str(v) for v in r)).encode()) snap["owned_cards_fingerprint_pre0028_columns"] = h.hexdigest() snap["integrity_check"] = one("PRAGMA integrity_check") snap["foreign_key_check"] = len(con.execute("PRAGMA foreign_key_check").fetchall()) con.close() return snap if __name__ == "__main__": if len(sys.argv) < 2: print(__doc__) raise SystemExit(2) snap = snapshot(sys.argv[1]) text = json.dumps(snap, indent=2, sort_keys=True) print(text) if len(sys.argv) > 2: open(sys.argv[2], "w").write(text + "\n")