from __future__ import annotations import copy from sqlalchemy import create_engine, text from sqlalchemy.orm import Session from modelforge_api.persistence.models import Base from modelforge_api.services.recovery_fingerprint import ( ALERT_NOTIFICATION_SCHEMA, audit_fingerprint_differences_are_compatible, fingerprint_compatibility, fingerprint_session, ) from tests.recovery_fixtures import recreate_0038_benchmark_runs LAB_EXECUTION_SCHEMA = "20260924_0038" LAB_EXECUTION_TABLE = "lab_execution_runs" def _as_schema_0037(fingerprint: dict[str, object]) -> dict[str, object]: source = copy.deepcopy(fingerprint) tables = source["tables"] assert isinstance(tables, dict) tables.pop(LAB_EXECUTION_TABLE, None) serving_jobs = tables.get("serving_jobs") if isinstance(serving_jobs, dict): projection = serving_jobs.pop("lab_execution_projection", None) if isinstance(projection, dict): base = projection.get("base") assert isinstance(base, dict) tables["serving_jobs"] = base physical_tables = source.get("physical_tables") assert isinstance(physical_tables, list) source["physical_tables"] = [ name for name in physical_tables if name != LAB_EXECUTION_TABLE ] return source def _database_fingerprint() -> dict[str, object]: engine = create_engine("sqlite+pysqlite:///:memory:") Base.metadata.create_all(engine) with engine.begin() as connection: recreate_0038_benchmark_runs(connection) connection.execute(text("CREATE TABLE alembic_version (version_num VARCHAR(32))")) connection.execute(text("INSERT INTO alembic_version VALUES (:revision)"), {"revision": LAB_EXECUTION_SCHEMA}) with Session(engine) as session: result = fingerprint_session(session) engine.dispose() return result def _schema_0037_fingerprint_with_future_job_link() -> dict[str, object]: engine = create_engine("sqlite+pysqlite:///:memory:") Base.metadata.create_all(engine) with engine.begin() as connection: recreate_0038_benchmark_runs(connection) connection.execute(text("DROP TABLE lab_execution_runs")) connection.execute(text("CREATE TABLE alembic_version (version_num VARCHAR(32))")) connection.execute(text("INSERT INTO alembic_version VALUES ('20260923_0037')")) with Session(engine) as session: result = fingerprint_session(session) engine.dispose() return result def test_0037_backup_upgrades_only_with_empty_lab_history_and_unlinked_jobs() -> None: restored = _database_fingerprint() source = _as_schema_0037(restored) contract = fingerprint_compatibility( source, restored, source_schema_revision=ALERT_NOTIFICATION_SCHEMA ) assert contract.compatible assert contract.mode.startswith("LAB_EXECUTION_UPGRADE|") assert audit_fingerprint_differences_are_compatible(source, restored, contract) populated = copy.deepcopy(restored) populated["tables"][LAB_EXECUTION_TABLE]["row_count"] = 1 populated["tables"][LAB_EXECUTION_TABLE]["digest"] = "f" * 64 rejected_history = fingerprint_compatibility( source, populated, source_schema_revision=ALERT_NOTIFICATION_SCHEMA ) assert not rejected_history.compatible linked = copy.deepcopy(restored) linked["tables"]["serving_jobs"]["lab_execution_projection"]["run_link_count"] = 1 rejected_link = fingerprint_compatibility( source, linked, source_schema_revision=ALERT_NOTIFICATION_SCHEMA ) assert not rejected_link.compatible wrong_empty_link_digest = copy.deepcopy(restored) wrong_empty_link_digest["tables"]["serving_jobs"]["lab_execution_projection"][ "run_link_digest" ] = "f" * 64 rejected_digest = fingerprint_compatibility( source, wrong_empty_link_digest, source_schema_revision=ALERT_NOTIFICATION_SCHEMA ) assert not rejected_digest.compatible def test_0037_upgrade_preserves_preexisting_jobs_with_null_lab_links() -> None: restored = _database_fingerprint() serving_jobs = restored["tables"]["serving_jobs"] projection = serving_jobs["lab_execution_projection"] serving_jobs["row_count"] = 1 serving_jobs["digest"] = "a" * 64 projection["base"]["row_count"] = 1 projection["base"]["digest"] = "b" * 64 source = _as_schema_0037(restored) contract = fingerprint_compatibility( source, restored, source_schema_revision=ALERT_NOTIFICATION_SCHEMA ) assert contract.compatible assert contract.mode.startswith("LAB_EXECUTION_UPGRADE|") assert audit_fingerprint_differences_are_compatible(source, restored, contract) def test_pre_0038_source_with_future_lab_table_fails_closed() -> None: fingerprint = _database_fingerprint() contract = fingerprint_compatibility( fingerprint, fingerprint, source_schema_revision=ALERT_NOTIFICATION_SCHEMA ) assert not contract.compatible assert "future LAB" in contract.reason def test_pre_0038_source_with_future_job_link_column_fails_closed() -> None: source = _schema_0037_fingerprint_with_future_job_link() restored = _database_fingerprint() contract = fingerprint_compatibility( source, restored, source_schema_revision=ALERT_NOTIFICATION_SCHEMA ) assert source["tables"]["serving_jobs"]["lab_execution_schema_observed"] is True assert not contract.compatible assert "future LAB" in contract.reason def test_0038_strict_restore_preserves_lab_run_history_and_job_links() -> None: source = _database_fingerprint() restored = copy.deepcopy(source) contract = fingerprint_compatibility( source, restored, source_schema_revision=LAB_EXECUTION_SCHEMA ) assert contract.compatible assert contract.mode.startswith("LAB_EXECUTION_STRICT|") assert audit_fingerprint_differences_are_compatible(source, restored, contract) altered = copy.deepcopy(restored) altered["tables"][LAB_EXECUTION_TABLE]["digest"] = "f" * 64 rejected = fingerprint_compatibility( source, altered, source_schema_revision=LAB_EXECUTION_SCHEMA ) assert not rejected.compatible def test_pre_0038_fingerprint_rejects_lab_history_or_job_links() -> None: current = _database_fingerprint() populated = copy.deepcopy(current) populated["tables"][LAB_EXECUTION_TABLE]["row_count"] = 1 populated["tables"][LAB_EXECUTION_TABLE]["digest"] = "f" * 64 rejected_history = fingerprint_compatibility( populated, populated, source_schema_revision=ALERT_NOTIFICATION_SCHEMA ) assert not rejected_history.compatible linked = copy.deepcopy(current) linked["tables"]["serving_jobs"]["lab_execution_projection"]["run_link_count"] = 1 rejected_link = fingerprint_compatibility( linked, linked, source_schema_revision=ALERT_NOTIFICATION_SCHEMA ) assert not rejected_link.compatible malformed_projection = copy.deepcopy(current) malformed_projection["tables"]["serving_jobs"]["lab_execution_projection"][ "base" ] = {"row_count": 0, "digest": "bad"} rejected_shape = fingerprint_compatibility( malformed_projection, malformed_projection, source_schema_revision=LAB_EXECUTION_SCHEMA, ) assert not rejected_shape.compatible