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
@@ -6,13 +6,15 @@ import base64
|
||||
import hashlib
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import uuid
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, func, select
|
||||
from sqlalchemy import create_engine, event, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from modelforge_api.domain.recovery import (
|
||||
@@ -45,6 +47,8 @@ from modelforge_api.persistence.models import (
|
||||
ModelRevision,
|
||||
RecoveryAssetRecord,
|
||||
RecoveryPolicyRevision,
|
||||
RestoreOperation,
|
||||
RestorePlan,
|
||||
StorageRoot,
|
||||
UpstreamSnapshot,
|
||||
)
|
||||
@@ -328,6 +332,7 @@ def seed_backup(
|
||||
encrypted: bool = False,
|
||||
payload: bytes = b"PGDMP-fake-custom-dump-payload",
|
||||
milestone: str | None = None,
|
||||
legal_hold: bool = False,
|
||||
) -> tuple[RecoveryService, BackupSet]:
|
||||
"""Create a VERIFIED-shaped backup on disk without invoking PostgreSQL tooling."""
|
||||
|
||||
@@ -403,6 +408,7 @@ def seed_backup(
|
||||
encryption_key_id="modelforge-backup-key-1" if encrypted else None,
|
||||
verification_details={},
|
||||
milestone=milestone,
|
||||
legal_hold=legal_hold,
|
||||
reason="unit test fixture",
|
||||
created_by="test",
|
||||
started_at=datetime.now(UTC),
|
||||
@@ -554,6 +560,50 @@ def test_an_encrypted_backup_verified_with_the_wrong_key_fails_closed(
|
||||
assert not any(path.name.startswith("database.dump.enc.verify-") for path in directory.iterdir())
|
||||
|
||||
|
||||
def test_encrypted_verification_stages_only_on_restore_volume(
|
||||
session: Session, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
subject, record = seed_backup(
|
||||
session, tmp_path, backup_id="m15-read-only-source", encrypted=True
|
||||
)
|
||||
observed: list[Path] = []
|
||||
|
||||
def inspect_dump(path: Path) -> int:
|
||||
observed.append(path)
|
||||
assert path.parent == (tmp_path / "restore").resolve()
|
||||
assert path.read_bytes() == b"PGDMP-fake-custom-dump-payload"
|
||||
if os.name == "posix":
|
||||
assert stat.S_IMODE(path.stat().st_mode) == 0o600
|
||||
return 1
|
||||
|
||||
monkeypatch.setattr(subject.engine, "list_dump_contents", inspect_dump)
|
||||
result = subject.verify_backup(record.id)
|
||||
|
||||
assert result.state is BackupState.VERIFIED
|
||||
assert len(observed) == 1
|
||||
assert not observed[0].exists()
|
||||
assert not list((tmp_path / "restore").iterdir())
|
||||
assert sorted(path.name for path in (tmp_path / "backups" / "m15-read-only-source").iterdir()) == [
|
||||
"database.dump.enc", "manifest.json"
|
||||
]
|
||||
|
||||
|
||||
def test_encrypted_verification_refuses_staging_inside_backup_root(
|
||||
session: Session, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
subject, record = seed_backup(
|
||||
session, tmp_path, backup_id="m15-overlapping-roots", encrypted=True
|
||||
)
|
||||
subject.settings = settings_for(tmp_path, backup_restore_root=tmp_path / "backups" / "staging")
|
||||
monkeypatch.setattr(subject.engine, "list_dump_contents", lambda _payload: 1)
|
||||
|
||||
result = subject.verify_backup(record.id)
|
||||
|
||||
assert result.state is BackupState.FAILED
|
||||
assert result.failure_code == RecoveryFailureCode.PATH_NOT_ALLOWED.value
|
||||
assert not list((tmp_path / "backups" / "staging").iterdir())
|
||||
|
||||
|
||||
def test_an_encrypted_backup_without_a_key_reports_key_unavailable(
|
||||
session: Session, tmp_path: Path
|
||||
) -> None:
|
||||
@@ -605,6 +655,12 @@ def test_retention_never_expires_the_last_verified_backup(
|
||||
record.expires_at = datetime.now(UTC) - timedelta(days=1)
|
||||
session.commit()
|
||||
|
||||
preview = subject.preview_retention()
|
||||
assert preview.retained_backup_ids == [record.backup_id]
|
||||
assert preview.expiring_backup_ids == []
|
||||
assert preview.decisions[0].reason == "MINIMUM_VERIFIED_BACKUPS"
|
||||
assert subject.backup(record.id).state is BackupState.VERIFIED
|
||||
|
||||
result = subject.apply_retention()
|
||||
assert result["expired"] == 0
|
||||
assert subject.backup(record.id).state is BackupState.VERIFIED
|
||||
@@ -637,6 +693,178 @@ def test_retention_expires_surplus_backups_but_keeps_the_policy_minimum(
|
||||
assert states["m15-retention-0"] is BackupState.EXPIRED
|
||||
|
||||
|
||||
def test_retention_preview_matches_apply_and_does_not_commit_or_write_audit(
|
||||
session: Session, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
observed_at = datetime(2026, 9, 23, 12, 0, tzinfo=UTC)
|
||||
records: list[BackupSet] = []
|
||||
for backup_id, milestone, legal_hold in (
|
||||
("m15-retention-expire", None, False),
|
||||
("m15-retention-hold", None, True),
|
||||
("m15-retention-milestone", "rc-baseline", False),
|
||||
("m15-retention-minimum-a", None, False),
|
||||
("m15-retention-minimum-b", None, False),
|
||||
("m15-retention-created", None, False),
|
||||
):
|
||||
subject, record = seed_backup(
|
||||
session,
|
||||
tmp_path,
|
||||
backup_id=backup_id,
|
||||
milestone=milestone,
|
||||
legal_hold=legal_hold,
|
||||
)
|
||||
records.append(record)
|
||||
monkeypatch.setattr(subject.engine, "list_dump_contents", lambda _payload: 1)
|
||||
for index, record in enumerate(records[:-1]):
|
||||
subject.verify_backup(record.id)
|
||||
record.verified_at = observed_at - timedelta(days=len(records) - index)
|
||||
record.expires_at = observed_at - timedelta(days=1)
|
||||
records[-1].expires_at = observed_at - timedelta(days=1)
|
||||
session.commit()
|
||||
|
||||
states_before = {item.backup_id: item.state for item in records}
|
||||
audit_count_before = session.scalar(select(func.count()).select_from(AuditEvent))
|
||||
pending_policy = RecoveryPolicyRevision(
|
||||
key="control-plane.preview-pending-test",
|
||||
revision=1,
|
||||
name="Pending preview policy",
|
||||
asset_class="AUTHORITATIVE",
|
||||
backup_method="POSTGRES_LOGICAL_CUSTOM",
|
||||
retention_days=1,
|
||||
minimum_verified_backups=1,
|
||||
rpo_seconds=60,
|
||||
rto_target_seconds=60,
|
||||
restore_verification="FULL_RESTORE",
|
||||
encryption_required=True,
|
||||
external_dependency=False,
|
||||
rehydration_allowed=False,
|
||||
secret_class=None,
|
||||
rationale="A pending row detects accidental autoflush in the read-only preview.",
|
||||
active=True,
|
||||
fingerprint="c" * 64,
|
||||
created_by="test",
|
||||
)
|
||||
session.add(pending_policy)
|
||||
commits: list[None] = []
|
||||
|
||||
def after_commit(_session: Session) -> None:
|
||||
commits.append(None)
|
||||
|
||||
event.listen(session, "after_commit", after_commit)
|
||||
try:
|
||||
preview = subject.preview_retention(observed_at)
|
||||
finally:
|
||||
event.remove(session, "after_commit", after_commit)
|
||||
|
||||
assert preview.observed_at == observed_at
|
||||
assert preview.minimum_verified_backups == 2
|
||||
assert preview.retained_backup_ids == [
|
||||
"m15-retention-hold",
|
||||
"m15-retention-milestone",
|
||||
"m15-retention-minimum-a",
|
||||
"m15-retention-minimum-b",
|
||||
]
|
||||
assert preview.expiring_backup_ids == ["m15-retention-created", "m15-retention-expire"]
|
||||
reasons = {item.backup_id: item.reason for item in preview.decisions}
|
||||
assert reasons["m15-retention-hold"] == "LEGAL_HOLD"
|
||||
assert reasons["m15-retention-milestone"] == "MILESTONE"
|
||||
assert reasons["m15-retention-minimum-a"] == "MINIMUM_VERIFIED_BACKUPS"
|
||||
assert {item.backup_id: item.state for item in records} == states_before
|
||||
assert pending_policy in session.new
|
||||
session.expunge(pending_policy)
|
||||
assert session.scalar(select(func.count()).select_from(AuditEvent)) == audit_count_before
|
||||
assert commits == []
|
||||
assert not session.new and not session.dirty and not session.deleted
|
||||
|
||||
applied = subject.apply_retention(observed_at)
|
||||
assert applied == {"expired": 2, "protected": 4, "skipped_active_restore": 0}
|
||||
expired = session.scalars(
|
||||
select(AuditEvent).where(AuditEvent.action == "BACKUP_EXPIRED")
|
||||
)
|
||||
assert {item.details["backup_id"] for item in expired} == set(preview.expiring_backup_ids)
|
||||
assert {
|
||||
item.backup_id for item in records if item.state == BackupState.EXPIRED.value
|
||||
} == set(preview.expiring_backup_ids)
|
||||
|
||||
|
||||
def test_retention_preview_reports_active_restore_and_apply_skips_without_policy_seeding(
|
||||
session: Session, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
subject, record = seed_backup(session, tmp_path, backup_id="m15-retention-active")
|
||||
monkeypatch.setattr(subject.engine, "list_dump_contents", lambda _payload: 1)
|
||||
subject.verify_backup(record.id)
|
||||
record.expires_at = datetime.now(UTC) - timedelta(days=1)
|
||||
|
||||
plan = RestorePlan(
|
||||
backup_set_id=record.id,
|
||||
mode="VALIDATION",
|
||||
state="PREFLIGHT_PASSED",
|
||||
target_environment="ISOLATED",
|
||||
target_label="active-retention-preview",
|
||||
database_destination="postgresql+psycopg://modelforge:secret@postgres:5432/mf_restore",
|
||||
artifact_strategy="NONE",
|
||||
secret_strategy="MANUAL", # noqa: S106 - recovery strategy label is not a secret
|
||||
node_strategy="NONE",
|
||||
preflight={},
|
||||
validation_requirements={},
|
||||
fingerprint="a" * 64,
|
||||
reason="active restore retention guard test",
|
||||
created_by="test",
|
||||
)
|
||||
session.add(plan)
|
||||
session.flush()
|
||||
session.add(
|
||||
RestoreOperation(
|
||||
plan_id=plan.id,
|
||||
backup_set_id=record.id,
|
||||
state="RESTORING_DATABASE",
|
||||
attempt=1,
|
||||
idempotency_key="b" * 64,
|
||||
preflight_result={},
|
||||
phase_durations={},
|
||||
source_fingerprint={},
|
||||
restored_fingerprint={},
|
||||
fingerprint_diff={},
|
||||
validation_result={},
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
|
||||
preview = subject.preview_retention()
|
||||
assert preview.active_restore_in_progress is True
|
||||
assert preview.retained_backup_ids == [record.backup_id]
|
||||
assert preview.expiring_backup_ids == []
|
||||
assert preview.decisions[0].reason == "ACTIVE_RESTORE_IN_PROGRESS"
|
||||
|
||||
policy = session.scalar(
|
||||
select(RecoveryPolicyRevision).where(
|
||||
RecoveryPolicyRevision.key == "control-plane.database",
|
||||
RecoveryPolicyRevision.active.is_(True),
|
||||
)
|
||||
)
|
||||
assert policy is not None
|
||||
policy.active = False
|
||||
session.commit()
|
||||
|
||||
with pytest.raises(RecoveryError) as error:
|
||||
subject.preview_retention()
|
||||
assert error.value.code == "recovery_policy_missing"
|
||||
|
||||
# Historically the active-restore guard returned before looking up or seeding a policy.
|
||||
assert subject.apply_retention() == {
|
||||
"expired": 0,
|
||||
"protected": 1,
|
||||
"skipped_active_restore": 1,
|
||||
}
|
||||
assert session.scalar(
|
||||
select(func.count()).select_from(RecoveryPolicyRevision).where(
|
||||
RecoveryPolicyRevision.key == "control-plane.database",
|
||||
RecoveryPolicyRevision.active.is_(True),
|
||||
)
|
||||
) == 0
|
||||
assert record.state == BackupState.VERIFIED.value
|
||||
|
||||
|
||||
def test_milestone_and_legal_hold_backups_are_never_expired(
|
||||
session: Session, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
@@ -1057,7 +1285,7 @@ def test_the_alembic_tree_is_found_from_the_repository_layout() -> None:
|
||||
assert root is not None and (root / "versions").is_dir()
|
||||
revisions = _known_alembic_revisions()
|
||||
assert revisions[0] == "20260824_0001"
|
||||
assert revisions[-1] == "20260830_0024"
|
||||
assert revisions[-1] == "20260924_0039"
|
||||
assert len(revisions) == len(set(revisions))
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user