"""Fresh source projections preserve route generation through schema migration.""" from __future__ import annotations import copy import importlib.util import re from collections.abc import Iterator from contextlib import contextmanager from pathlib import Path import pytest from alembic.migration import MigrationContext from alembic.operations import Operations from sqlalchemy import MetaData, Table, create_engine, inspect, select, text from sqlalchemy.engine import URL from sqlalchemy.orm import Session from modelforge_api.persistence.models import Base, Capability, CapabilityContract from modelforge_api.services.recovery_fingerprint import ( SERVING_GENERATION_SCHEMA, _without_model_changes, _without_portfolio, audit_fingerprint_differences_are_compatible, fingerprint_compatibility, ) from modelforge_api.services.recovery_fingerprint import ( semantic_fingerprint as _current_semantic_fingerprint, ) from tests.recovery_fixtures import recreate_0038_benchmark_runs def semantic_fingerprint(connection): return _without_portfolio(_without_model_changes(_current_semantic_fingerprint(connection))) def _remove_lab_serving_job_link(connection) -> None: create_sql = connection.scalar( text("SELECT sql FROM sqlite_master WHERE type='table' AND name='serving_jobs'") ) assert isinstance(create_sql, str) index_sql = connection.execute( text("SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name='serving_jobs' AND sql IS NOT NULL") ).scalars().all() columns = [ row[1] for row in connection.execute(text("PRAGMA table_info(serving_jobs)")).all() if row[1] != "lab_execution_run_id" ] legacy_sql = re.sub( r"^\s*lab_execution_run_id\b[^\n]*\n", "", create_sql, flags=re.MULTILINE ) legacy_sql = re.sub( r",\s*\n\s*FOREIGN KEY\(lab_execution_run_id\)[^\n]*", "", legacy_sql ).replace("CREATE TABLE serving_jobs", "CREATE TABLE serving_jobs_legacy", 1) connection.exec_driver_sql(legacy_sql) legacy_table = Table("serving_jobs_legacy", MetaData(), autoload_with=connection) source_table = Table("serving_jobs", MetaData(), autoload_with=connection) connection.execute( legacy_table.insert().from_select( columns, select(*(source_table.c[column] for column in columns)) ) ) connection.exec_driver_sql("DROP TABLE serving_jobs") connection.exec_driver_sql("ALTER TABLE serving_jobs_legacy RENAME TO serving_jobs") for statement in index_sql: if "lab_execution_run_id" not in statement: connection.exec_driver_sql(statement) connection.exec_driver_sql("DROP TABLE lab_execution_runs") def migration(): path = Path(__file__).parents[1] / "alembic/versions/20260910_0034_approved_serving_transition.py" spec = importlib.util.spec_from_file_location("serving_generation", path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module @pytest.fixture def upgraded(): engine = create_engine("sqlite://") Base.metadata.create_all(engine) with engine.begin() as connection: # Keep this historical 0033 fixture physically faithful to its declared schema. connection.execute(text('DROP TABLE "alert_notification_deliveries"')) recreate_0038_benchmark_runs(connection) _remove_lab_serving_job_link(connection) with Session(engine) as session: capability = Capability(key="generation.fixture", description="Isolated fixture") session.add(capability) session.flush() session.add(CapabilityContract(capability_id=capability.id, version=1, input_schema={}, output_schema={}, contract={}, upgrade_class="transparent")) session.commit() module = migration() with engine.begin() as connection: module.op = Operations(MigrationContext.configure(connection)) module.downgrade() connection.execute(text("CREATE TABLE alembic_version(version_num VARCHAR(32))")) connection.execute(text("INSERT INTO alembic_version VALUES ('20260909_0033')")) before = semantic_fingerprint(connection) module.upgrade() connection.execute(text("UPDATE alembic_version SET version_num='20260910_0034'")) yield connection, before engine.dispose() def test_populated_prior_contracts_gain_only_zero_generation(upgraded): connection, before = upgraded after = semantic_fingerprint(connection) assert connection.scalar(text("SELECT serving_generation FROM capability_contracts")) == 0 assert "serving_generation_projection" in before["tables"]["capability_contracts"] contract = fingerprint_compatibility(before, after, source_schema_revision="20260909_0033") assert contract.compatible assert audit_fingerprint_differences_are_compatible(before, after, contract) connection.execute(text("UPDATE capability_contracts SET serving_generation=1")) assert not audit_fingerprint_differences_are_compatible(before, semantic_fingerprint(connection), contract) def test_opaque_prior_digest_cannot_prove_default_generation(upgraded): connection, before = upgraded del before["tables"]["capability_contracts"]["serving_generation_projection"] after = semantic_fingerprint(connection) contract = fingerprint_compatibility(before, after, source_schema_revision="20260909_0033") assert not audit_fingerprint_differences_are_compatible(before, after, contract) def test_current_generation_is_preserved_and_cannot_be_projected_away(upgraded): connection, _ = upgraded connection.execute(text("UPDATE capability_contracts SET serving_generation=7")) before = semantic_fingerprint(connection) after = copy.deepcopy(before) contract = fingerprint_compatibility(before, after, source_schema_revision=SERVING_GENERATION_SCHEMA) assert contract.compatible and audit_fingerprint_differences_are_compatible(before, after, contract) forged = copy.deepcopy(before) forged["tables"]["capability_contracts"]["serving_generation_projection"] = after["tables"]["capability_contracts"] assert not fingerprint_compatibility(forged, after, source_schema_revision=SERVING_GENERATION_SCHEMA).compatible connection.execute(text("UPDATE capability_contracts SET serving_generation=0")) assert not audit_fingerprint_differences_are_compatible(before, semantic_fingerprint(connection), contract) module = migration() module.op = Operations(MigrationContext.configure(connection)) connection.execute(text("UPDATE capability_contracts SET serving_generation=7")) with pytest.raises(RuntimeError, match="history exists"): module.downgrade() @contextmanager def legacy_generation_fixture(owner: URL) -> Iterator[None]: """Seed isolated historical schemas using today's fixture code, then restore exact DDL. Current ORM selects and serving fixture calls require this future column. Only the fixture setup temporarily exposes it; fingerprints, dumps and migrations see the actual historical schema. The runtime role receives no DDL privilege. """ engine = create_engine(owner) introduced = False try: with engine.begin() as connection: if "serving_generation" not in {c["name"] for c in inspect(connection).get_columns("capability_contracts")}: connection.execute(text("ALTER TABLE capability_contracts ADD COLUMN serving_generation BIGINT NOT NULL DEFAULT 0")) introduced = True yield finally: if introduced: with engine.begin() as connection: connection.execute(text("ALTER TABLE capability_contracts DROP COLUMN serving_generation")) assert "serving_generation" not in {c["name"] for c in inspect(connection).get_columns("capability_contracts")} engine.dispose()