Publish curated ITWorx-ModelForge source from 990401737f96c8d46a9f7b0d84994516cadcb6a0
This commit is contained in:
1 parent
7082ab955a
commit
848f0d8f8a
434 files changed
+67298
-4315
No files matched your search
@@ -0,0 +1,156 @@
|
||||
"""Actual owner/runtime PostgreSQL upgrade and dump/restore qualification."""
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import uuid
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import inspect, select, text
|
||||
from sqlalchemy.exc import DBAPIError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from modelforge_api.domain.model_updates import ModelWatchCreate, ModelWatchUpdate
|
||||
from modelforge_api.domain.release import TARGET_SCHEMA_REVISION
|
||||
from modelforge_api.persistence.models import Model, ModelRevision, ModelUpdateWatch
|
||||
from modelforge_api.services.acquisition import AcquisitionService
|
||||
from modelforge_api.services.audit import AuditContext, AuditWriter
|
||||
from modelforge_api.services.model_updates import ModelUpdateService
|
||||
from modelforge_api.services.recovery import RecoveryError, RecoveryService
|
||||
from modelforge_api.services.recovery_fingerprint import (
|
||||
audit_fingerprint_differences_are_compatible,
|
||||
fingerprint_compatibility,
|
||||
semantic_fingerprint,
|
||||
)
|
||||
from modelforge_api.settings import Settings
|
||||
from tests.test_managed_identities_postgres import (
|
||||
IdentityDatabase,
|
||||
ManagedPostgresTools,
|
||||
identity_database,
|
||||
)
|
||||
|
||||
__all__ = ["identity_database"]
|
||||
PRIOR = "20260909_0029"
|
||||
CURRENT = "20260910_0034"
|
||||
|
||||
|
||||
def test_model_watch_upgrade_prior_restore_and_runtime_permissions(
|
||||
identity_database: IdentityDatabase, tmp_path: Path,
|
||||
) -> None:
|
||||
database = identity_database
|
||||
alembic = importlib.import_module("alembic.command")
|
||||
alembic.upgrade(database.migration(), PRIOR)
|
||||
with database.runtime.connect() as connection:
|
||||
prior_tables = set(inspect(connection).get_table_names())
|
||||
assert not {"model_update_watches", "model_update_notices"} & prior_tables
|
||||
with Session(database.runtime) as session:
|
||||
model = Model(key="selected-model", display_name="Selected model",
|
||||
upstream_provider="huggingface", upstream_source="fixture/selected")
|
||||
session.add(model)
|
||||
session.flush()
|
||||
revision = ModelRevision(model_id=model.id, upstream_revision="main",
|
||||
resolved_commit_sha="a" * 40)
|
||||
session.add(revision)
|
||||
AuditWriter(session, "operator", "prior-release").write(
|
||||
"qualification.baseline", "model", str(model.id), {})
|
||||
session.commit()
|
||||
request = ModelWatchCreate(model_id=model.id, baseline_revision_id=revision.id)
|
||||
before = semantic_fingerprint(session.connection())
|
||||
archive = tmp_path / "pre-model-updates.dump"
|
||||
database.archive(archive)
|
||||
alembic.upgrade(database.migration(), CURRENT)
|
||||
with database.runtime.connect() as connection:
|
||||
after = semantic_fingerprint(connection)
|
||||
comparison = fingerprint_compatibility(before, after, source_schema_revision=PRIOR)
|
||||
assert comparison.compatible
|
||||
assert audit_fingerprint_differences_are_compatible(before, after, comparison)
|
||||
# Empty tables are mechanically reversible; populated history is deliberately retained.
|
||||
alembic.downgrade(database.migration(), PRIOR)
|
||||
alembic.upgrade(database.migration(), CURRENT)
|
||||
with Session(database.runtime) as session:
|
||||
service = ModelUpdateService(session, Mock(spec=AcquisitionService),
|
||||
AuditContext("operator", "qualified", "model-watch-pg"))
|
||||
watch = service.create(request)
|
||||
assert session.scalar(select(ModelUpdateWatch.baseline_commit_sha)) == "a" * 40
|
||||
assert watch.model_id == request.model_id
|
||||
assert watch.enabled and watch.version == 1
|
||||
paused = service.change(watch.id, ModelWatchUpdate(
|
||||
expected_version=watch.version, expected_baseline_revision_id=watch.baseline_revision_id,
|
||||
baseline_revision_id=watch.baseline_revision_id, enabled=False, reason="Qualified pause",
|
||||
))
|
||||
assert not paused.enabled and paused.version == 2
|
||||
with database.runtime.connect() as connection:
|
||||
for statement in (
|
||||
"DELETE FROM model_update_watches",
|
||||
"TRUNCATE model_update_notices CASCADE",
|
||||
"ALTER TABLE model_update_watches ADD COLUMN forbidden integer",
|
||||
):
|
||||
with pytest.raises(DBAPIError):
|
||||
connection.execute(text(statement))
|
||||
connection.rollback()
|
||||
with pytest.raises(RuntimeError, match="history exists"):
|
||||
alembic.downgrade(database.migration(), PRIOR)
|
||||
database.archive(archive, restore=True)
|
||||
alembic.upgrade(database.migration(restored=True), CURRENT)
|
||||
with database.restored_runtime.connect() as connection:
|
||||
restored = semantic_fingerprint(connection)
|
||||
comparison = fingerprint_compatibility(before, restored, source_schema_revision=PRIOR)
|
||||
assert comparison.compatible
|
||||
assert audit_fingerprint_differences_are_compatible(before, restored, comparison)
|
||||
assert connection.scalar(text("SELECT count(*) FROM model_update_watches")) == 0
|
||||
|
||||
|
||||
def test_model_watch_restore_clears_execution_claim_but_preserves_tracking(
|
||||
identity_database: IdentityDatabase, tmp_path: Path,
|
||||
) -> None:
|
||||
database = identity_database
|
||||
importlib.import_module("alembic.command").upgrade(database.migration(), TARGET_SCHEMA_REVISION)
|
||||
with Session(database.runtime) as session:
|
||||
model = Model(key="restore-watch", display_name="Restore watch",
|
||||
upstream_provider="huggingface", upstream_source="fixture/restore")
|
||||
session.add(model)
|
||||
session.flush()
|
||||
revision = ModelRevision(model_id=model.id, upstream_revision="main",
|
||||
resolved_commit_sha="a" * 40)
|
||||
session.add(revision)
|
||||
session.commit()
|
||||
watch = ModelUpdateService(session, Mock(spec=AcquisitionService),
|
||||
AuditContext("operator", "restore-test")).create(
|
||||
ModelWatchCreate(model_id=model.id, baseline_revision_id=revision.id))
|
||||
row = session.get(ModelUpdateWatch, watch.id)
|
||||
assert row is not None
|
||||
row.check_token = uuid.uuid4()
|
||||
row.check_until = datetime.now(UTC) + timedelta(minutes=5)
|
||||
session.commit()
|
||||
source = semantic_fingerprint(session.connection())
|
||||
archive = tmp_path / "model-watch.dump"
|
||||
database.archive(archive)
|
||||
database.archive(archive, restore=True)
|
||||
settings = Settings(_env_file=None, env="test",
|
||||
alembic_directory=Path(__file__).resolve().parents[1] / "alembic")
|
||||
with Session(database.runtime) as session:
|
||||
recovery = RecoveryService(session, settings)
|
||||
recovery.engine = ManagedPostgresTools(base_environment=dict(os.environ), timeout_seconds=60)
|
||||
record = SimpleNamespace(id=uuid.uuid4(), source_fingerprint=source)
|
||||
plan = SimpleNamespace(
|
||||
database_destination=database.restored_owner.render_as_string(hide_password=False),
|
||||
backup_set=SimpleNamespace(backup_id="watch-qualification", schema_revision=TARGET_SCHEMA_REVISION,
|
||||
completed_at=datetime.now(UTC)), validation_requirements={},
|
||||
)
|
||||
with pytest.raises(RecoveryError):
|
||||
recovery._validate_restored(record, plan)
|
||||
session.rollback()
|
||||
recovery._reconcile_restored(record, plan)
|
||||
session.commit()
|
||||
assert all(recovery._validate_restored(record, plan)["gate"].values())
|
||||
session.commit()
|
||||
with database.restored_runtime.connect() as connection:
|
||||
assert connection.execute(text(
|
||||
"SELECT check_token, check_until FROM model_update_watches"
|
||||
)).one() == (None, None)
|
||||
restored = semantic_fingerprint(connection)
|
||||
for table in ("model_update_watches", "model_update_notices"):
|
||||
assert restored["tables"][table] == source["tables"][table]
|
||||
Reference in new issue
Block a user