from __future__ import annotations import hashlib import json import pytest from sqlmodel import Session, SQLModel, create_engine, select from backend.app.db.models import ( AppSetting, Enrollment, Policy, RepoPulseAdoptionJournal, ) from backend.app.schemas.enrollment import EnrollmentPlanV1 from backend.app.services import repopulse_adoption_journal as journal from backend.app.services import repopulse_adoption_ownership as ownership from backend.app.services.repopulse_legacy_adoption_protocol import ( APPLICATION_ID, CONTROLLER_RECEIPT_VERSION, LEGACY_VOLUME, REPOSITORY, intent_binding_sha256, ) from backend.app.services.secret_store import set_secret def _sha(char: str) -> str: return char * 64 def _plan_payload(revision: str) -> dict: return EnrollmentPlanV1.model_validate( { "application_id": "repopulse", "display_name": "RepoPulse", "environment": "production", "repository": "Jens/RepoPulse", "revision": revision, "profile": "repopulse-authenticated", "target": { "target_identity": "unraid-controller", "container_name": "repopulse", }, "storage": {"runtime_uid": 10001, "runtime_gid": 10001}, "network": {"docker_network": "itworx-mcp-hub-npm", "internal_port": 8000}, "artifact": { "release_version": "1.5.0", "image_repository": "repopulse-managed", "image_tag": "production", "health_path": "/health", }, "exposure": { "hostname": "repopulse.example.com", "upstream_service": "repopulse", }, "secrets": {"additional_secret_refs": ["REPOPULSE_GITEA_TOKEN"]}, } ).model_dump(mode="json") def _plan_sha(revision: str) -> str: payload = _plan_payload(revision) canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() return hashlib.sha256(canonical).hexdigest() def _preflight() -> dict: check_codes = ( "legacy_registry", "controller_state", "gitea_ancestry", "runtime_identity_and_health", "legacy_storage_topology", "legacy_appdata", "product_api", "database_health", "queue_quiescence", "database_baseline", ) return { "apiVersion": "itworx.repopulse-legacy-adoption-preflight/v1", "repository": REPOSITORY, "status": "preflight_passed", "legacyCompatibility": { "status": "passed", "checks": [ {"code": code, "status": "passed", "gate": "legacy_compatibility"} for code in check_codes ], }, "targetAcceptance": {"status": "blocked", "checks": []}, "readOnly": True, "adoptionAuthorized": False, "evidence": { "main_revision": "a" * 40, "live_revision": "b" * 40, "container_id_sha256": _sha("d"), "container_image_sha256": _sha("1"), "container_state": "running", "container_health": "healthy", "queue": {"queued": 0, "active": 0, "completed": 569, "failed": 0}, "database": { "database_bytes": 44_576_768, "journal_bytes": 0, "scan_count": 572, "compacted_scan_count": 96, }, }, } def _target_gates() -> dict: return { "profileId": "repopulse-authenticated", "profileVersion": "enrollment-profile/v1", "planSha256": _plan_sha("a" * 40), **{ key: True for key in ( "profileReady", "controllerTrustReady", "controllerBootstrapSecretImported", "typedEnrollmentTrustReady", "targetSecretReady", "exactPolicyReady", "authenticatedRouteReady", "accessListReady", "mutationBoundaryReady", "managedRuntimeReady", "scanSourceMatches", "scanMountReadOnly", "scanPermissionPolicyPersistent", "scanAccessForRuntimeIdentityReady", "ownedDataTargetReady", "managedEnvironmentReady", "legacyEnvironmentRetained", "maintenanceAuthReady", "controllerAdoptionCapabilityReady", "controllerRegistryWriterReady", "controllerSecretWriterReady", ) }, } def _quiescence(intent: dict) -> dict: return { "intentId": intent["intentId"], "globalDeployLockHeld": True, "projectLockHeld": True, "maintenanceModeActive": True, "queueFrozen": True, "queuedJobs": 0, "activeJobs": 0, "maintenanceModeAuthenticated": True, "containerName": "repopulse", "containerIdSha256": _sha("d"), "containerImageSha256": _sha("1"), "containerState": "exited", "legacyVolume": LEGACY_VOLUME, "controllerIntentDurable": True, "controllerIntentBindingSha256": intent_binding_sha256(intent), } def _snapshot(intent: dict) -> dict: return { "intentId": intent["intentId"], "globalDeployLockHeld": True, "projectLockHeld": True, "legacyContainerStopped": True, "legacyVolume": LEGACY_VOLUME, "legacyVolumeRetained": True, "sqliteBackupMethod": "sqlite_online_backup", "quickCheck": "ok", "integrityCheck": "ok", "sourceLogicalSha256": _sha("e"), "snapshotLogicalSha256": _sha("e"), "snapshotFileSha256": _sha("f"), "sourceScanCount": 572, "snapshotScanCount": 572, "sourceCompactedScanCount": 96, "snapshotCompactedScanCount": 96, "sourceQueueSha256": _sha("1"), "snapshotQueueSha256": _sha("1"), "sourceAuxiliaryTreeSha256": _sha("3"), "sourceAuxiliaryFileCount": 42, "targetDataPathWasAbsent": True, "snapshotReceiptDurable": True, "legacyEnvironmentRetained": True, "managedEnvironmentFilePrivate": True, "controllerIntentBindingSha256": intent_binding_sha256(intent), } def _registration(intent: dict, *, enrollment_id: str | None = None) -> dict: gates = intent["evidence"]["targetGates"] snapshot = intent["evidence"]["snapshot"] return { "receiptVersion": CONTROLLER_RECEIPT_VERSION, "intentId": intent["intentId"], "receiptSha256": _sha("2"), "repository": REPOSITORY, "applicationId": APPLICATION_ID, "enrollmentId": enrollment_id or f"enr-{intent['intentId'][4:]}", "profileId": gates["profileId"], "planSha256": gates["planSha256"], "snapshotFileSha256": snapshot["snapshotFileSha256"], "targetLogicalSha256": snapshot["snapshotLogicalSha256"], "targetScanCount": snapshot["snapshotScanCount"], "targetCompactedScanCount": snapshot["snapshotCompactedScanCount"], "targetQueueSha256": snapshot["snapshotQueueSha256"], "targetAuxiliaryTreeSha256": snapshot["sourceAuxiliaryTreeSha256"], "targetAuxiliaryFileCount": snapshot["sourceAuxiliaryFileCount"], "targetQueuedJobs": 0, "targetActiveJobs": 0, "targetQuickCheck": "ok", "targetIntegrityCheck": "ok", "targetRecoveryStatus": "healthy", "targetDataUid": 10001, "targetDataGid": 10001, "managedDataMountMatches": True, "copySourceIsVerifiedSnapshot": True, "registryTargetMatches": True, "controllerStateReceiptVerified": True, "deploymentReceiptSchema": "gitea-deploy-receipt.v1", "deploymentReceiptSha256": _sha("4"), "deploymentReceiptVerified": True, "runtimeHealthy": True, "publishedHostPorts": [], "globalDeployLockHeld": True, "projectLockHeld": True, "legacyVolume": LEGACY_VOLUME, "legacyVolumeRetained": True, "legacyEnvironmentRetained": True, "managedEnvironmentFilePrivate": True, "scanSourceMatches": True, "scanMountReadOnly": True, "scanPermissionPolicyPersistent": True, "scanAccessForRuntimeIdentityReady": True, "controllerIntentDurable": True, "controllerIntentBindingSha256": intent_binding_sha256(intent), } def _ready_engine(): engine = create_engine("sqlite://") SQLModel.metadata.create_all(engine) with Session(engine) as session: session.add( AppSetting( key="deployment_enrollment:trust", is_secret=False, value_json={ "controller_identity": "unraid-controller", "bootstrap_secret_ref": "ENROLLMENT_BOOTSTRAP", "approved_networks": ["itworx-mcp-hub-npm"], "allowed_domain_suffixes": ["example.com"], "autonomous_profiles": ["repopulse-authenticated"], "repopulse_proxy_access_list_id": 1, }, ) ) set_secret( session, "ENROLL_REPOPULSE_TARGET_HMAC", "test-target-credential-only-for-fixture", commit=False, ) session.commit() return engine def _register_target( engine, *, enrollment_id: str | None = None ) -> tuple[dict, dict, int]: preflight = _preflight() with Session(engine) as session: reserved = journal.reserve_repopulse_adoption( session, preflight, "repopulse-adoption-2026" ) intent = journal.protocol.create_intent(preflight, "repopulse-adoption-2026") gates = _target_gates() with Session(engine) as session: journal.advance_repopulse_adoption( session, "target_gates", gates, expected_revision=reserved["revision"] ) intent = journal.protocol.advance_intent(intent, "target_gates", gates) quiescence = _quiescence(intent) with Session(engine) as session: journal.confirm_repopulse_controller_intent( session, quiescence, expected_revision=2 ) intent = journal.protocol.advance_intent(intent, "quiescence", quiescence) snapshot = _snapshot(intent) with Session(engine) as session: journal.advance_repopulse_adoption( session, "snapshot", snapshot, expected_revision=3 ) intent = journal.protocol.advance_intent(intent, "snapshot", snapshot) registration = _registration(intent, enrollment_id=enrollment_id) with Session(engine) as session: registered = journal.advance_repopulse_adoption( session, "controller_registration", registration, expected_revision=4, ) return intent, registration, registered["revision"] @pytest.fixture def engine(): db = _ready_engine() yield db db.dispose() def test_ownership_commit_is_atomic_fixed_and_replayable(engine) -> None: intent, registration, revision = _register_target(engine) with Session(engine) as session: result = ownership.commit_repopulse_adoption_ownership( session, registration, expected_revision=revision, ) assert result["phase"] == "appops_owned" assert result["revision"] == revision + 1 with Session(engine) as session: row = session.get(Enrollment, registration["enrollmentId"]) assert row is not None assert row.repository == "jens/repopulse" assert row.application_id == "repopulse" assert row.revision == "a" * 40 assert row.profile == "repopulse-authenticated" assert row.controller_identity == "unraid-controller" assert row.plan_sha256 == registration["planSha256"] assert row.target_secret_ref == "ENROLL_REPOPULSE_TARGET_HMAC" assert row.oidc_secret_ref is None assert row.plan_json == _plan_payload("a" * 40) assert row.provenance_json["createdBy"] == "repopulse-legacy-adoption/v1" assert ( row.provenance_json["controllerReceiptSha256"] == registration["receiptSha256"] ) policies = session.exec(select(Policy).order_by(Policy.id)).all() assert [policy.id for policy in policies] == [ "enrollment-repopulse-deployment-apply", "enrollment-repopulse-deployment-rollback", ] assert { (item.action, item.target, item.voice, item.manual, item.risk) for item in policies } == { ("deployment.apply", "repopulse", "blocked", "allowed", "high"), ("deployment.rollback", "repopulse", "blocked", "allowed", "high"), } assert all( item.provenance_json["adoptionIntentId"] == intent["intentId"] for item in policies ) stored_journal = session.get(RepoPulseAdoptionJournal, "repopulse") assert stored_journal.phase == "appops_owned" assert ( stored_journal.intent_json["evidence"]["appopsOwnership"][ "secretReferenceReady" ] is True ) with Session(engine) as session: replay = ownership.commit_repopulse_adoption_ownership( session, registration, expected_revision=revision + 1, ) assert replay["phase"] == "appops_owned" assert replay["revision"] == revision + 1 def test_ownership_commit_rejects_live_controller_receipt_drift(engine) -> None: _, registration, revision = _register_target(engine) changed = {**registration, "deploymentReceiptSha256": _sha("5")} with ( Session(engine) as session, pytest.raises(ownership.RepoPulseOwnershipError) as error, ): ownership.commit_repopulse_adoption_ownership( session, changed, expected_revision=revision ) assert error.value.code == "idempotency_conflict" with Session(engine) as session: assert session.exec(select(Enrollment)).all() == [] assert session.exec(select(Policy)).all() == [] def test_enrollment_identity_is_derived_from_persisted_intent(engine) -> None: _, registration, revision = _register_target( engine, enrollment_id="enr-0123456789abcdefabcd" ) with ( Session(engine) as session, pytest.raises(ownership.RepoPulseOwnershipError) as error, ): ownership.commit_repopulse_adoption_ownership( session, registration, expected_revision=revision ) assert error.value.code == "fixed_enrollment_id_mismatch" with Session(engine) as session: assert session.exec(select(Enrollment)).all() == [] assert session.exec(select(Policy)).all() == [] def test_preexisting_exact_policy_id_is_not_adopted(engine) -> None: _, registration, revision = _register_target(engine) with Session(engine) as session: session.add( Policy( id="enrollment-repopulse-deployment-apply", action="deployment.apply", target="repopulse", voice="blocked", manual="denied", risk="high", ) ) session.commit() with ( Session(engine) as session, pytest.raises(ownership.RepoPulseOwnershipError) as error, ): ownership.commit_repopulse_adoption_ownership( session, registration, expected_revision=revision ) assert error.value.code == "ownership_resource_conflict" with Session(engine) as session: assert session.exec(select(Enrollment)).all() == [] assert ( session.get(Policy, "enrollment-repopulse-deployment-apply").manual == "denied" ) assert session.get(Policy, "enrollment-repopulse-deployment-rollback") is None assert ( session.get(RepoPulseAdoptionJournal, "repopulse").phase == "target_registered" ) def test_ownership_commit_rejects_missing_fixed_secret_ref(engine) -> None: _, registration, revision = _register_target(engine) with Session(engine) as session: from backend.app.db.models import Secret session.delete(session.get(Secret, "ENROLL_REPOPULSE_TARGET_HMAC")) session.commit() with ( Session(engine) as session, pytest.raises(ownership.RepoPulseOwnershipError) as error, ): ownership.commit_repopulse_adoption_ownership( session, registration, expected_revision=revision ) assert error.value.code == "target_secret_reference_unavailable" with Session(engine) as session: assert session.exec(select(Enrollment)).all() == [] assert session.exec(select(Policy)).all() == [] def test_completion_is_a_separate_idempotent_step_bound_to_live_receipt(engine) -> None: _, registration, revision = _register_target(engine) with Session(engine) as session: owned = ownership.commit_repopulse_adoption_ownership( session, registration, expected_revision=revision ) with Session(engine) as session: completed = ownership.complete_repopulse_adoption( session, registration, expected_revision=owned["revision"], ) assert completed["phase"] == "completed" assert completed["status"] == "completed" assert completed["revision"] == owned["revision"] + 1 with Session(engine) as session: replay = ownership.complete_repopulse_adoption( session, registration, expected_revision=completed["revision"], ) assert replay["revision"] == completed["revision"] assert replay["phase"] == "completed" with Session(engine) as session: ownership_replay = ownership.commit_repopulse_adoption_ownership( session, registration, expected_revision=completed["revision"] ) assert ownership_replay["revision"] == completed["revision"] assert ownership_replay["phase"] == "completed" changed = {**registration, "receiptSha256": _sha("9")} with ( Session(engine) as session, pytest.raises(ownership.RepoPulseOwnershipError) as error, ): ownership.complete_repopulse_adoption( session, changed, expected_revision=completed["revision"] ) assert error.value.code == "idempotency_conflict" def test_completion_replay_rechecks_owned_policy_rows(engine) -> None: _, registration, revision = _register_target(engine) with Session(engine) as session: owned = ownership.commit_repopulse_adoption_ownership( session, registration, expected_revision=revision ) with Session(engine) as session: completed = ownership.complete_repopulse_adoption( session, registration, expected_revision=owned["revision"] ) with Session(engine) as session: policy = session.get(Policy, "enrollment-repopulse-deployment-apply") policy.manual = "denied" session.add(policy) session.commit() with ( Session(engine) as session, pytest.raises(ownership.RepoPulseOwnershipError) as error, ): ownership.complete_repopulse_adoption( session, registration, expected_revision=completed["revision"] ) assert error.value.code == "ownership_policy_readback_mismatch" def test_half_write_failure_rolls_back_all_ownership_rows_and_marks_recovery( engine, monkeypatch ) -> None: _, registration, revision = _register_target(engine) original_commit = Session.commit calls = 0 def fail_first_commit(session): nonlocal calls calls += 1 if calls == 1: raise RuntimeError("simulated commit interruption") return original_commit(session) monkeypatch.setattr(Session, "commit", fail_first_commit) with ( Session(engine) as session, pytest.raises(ownership.RepoPulseOwnershipError) as error, ): ownership.commit_repopulse_adoption_ownership( session, registration, expected_revision=revision ) assert error.value.code == "appops_commit_uncertain" with Session(engine) as session: assert session.exec(select(Enrollment)).all() == [] assert session.exec(select(Policy)).all() == [] stored = session.get(RepoPulseAdoptionJournal, "repopulse") assert stored.phase == "target_registered" assert stored.status == "recovery_required" assert stored.intent_json["recovery"]["reason"] == "appops_commit_uncertain" def test_commit_exception_after_durable_commit_is_resolved_by_readback( engine, monkeypatch ) -> None: _, registration, revision = _register_target(engine) original_commit = Session.commit calls = 0 def commit_then_lose_response(session): nonlocal calls calls += 1 if calls == 1: original_commit(session) raise RuntimeError("simulated lost commit response") return original_commit(session) monkeypatch.setattr(Session, "commit", commit_then_lose_response) with Session(engine) as session: result = ownership.commit_repopulse_adoption_ownership( session, registration, expected_revision=revision ) assert result["phase"] == "appops_owned" with Session(engine) as session: assert session.get(Enrollment, registration["enrollmentId"]) is not None stored = session.get(RepoPulseAdoptionJournal, "repopulse") assert stored.phase == "appops_owned" assert stored.status == "in_progress"