"""Atomic AppOps ownership commit for the one RepoPulse adoption. This is a database-only protocol step. The caller must authenticate the live controller receipt before supplying the fixed registration evidence. No route, controller client, secret writer, or generic enrollment orchestration is used. """ from __future__ import annotations import hashlib import json from typing import Any from sqlalchemy import or_, update from sqlalchemy.exc import SQLAlchemyError from sqlmodel import Session, select from ..core.time import utc_now from ..db.models import AppSetting, Enrollment, Policy, RepoPulseAdoptionJournal from ..schemas.enrollment import EnrollmentPlanV1, EnrollmentTrustConfigV1 from . import repopulse_adoption_journal as journal from . import repopulse_legacy_adoption_protocol as protocol from .enrollment_profiles import PROFILE_VERSION from .secret_store import has_secret _SLOT = "repopulse" _TRUST_SETTING_KEY = "deployment_enrollment:trust" _CONTROLLER_IDENTITY = "unraid-controller" _TARGET_SECRET_REF = "ENROLL_REPOPULSE_TARGET_HMAC" _CREATED_BY = "repopulse-legacy-adoption/v1" _APPLY_POLICY_ID = "enrollment-repopulse-deployment-apply" _ROLLBACK_POLICY_ID = "enrollment-repopulse-deployment-rollback" _POLICY_REASON = "Enrollment-generated exact-target deployment policy." class RepoPulseOwnershipError(ValueError): """Sanitized fixed ownership precondition or persistence error.""" def __init__(self, code: str): super().__init__(code) self.code = code def _need(condition: bool, code: str) -> None: if not condition: raise RepoPulseOwnershipError(code) def _canonical_plan(plan: EnrollmentPlanV1) -> tuple[dict[str, Any], str]: payload = plan.model_dump(mode="json") encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") return payload, hashlib.sha256(encoded).hexdigest() def _fixed_plan( session: Session, intent: dict[str, Any] ) -> tuple[EnrollmentPlanV1, dict[str, Any], str]: preflight = intent["evidence"].get("preflight") gates = intent["evidence"].get("targetGates") registration = intent["evidence"].get("controllerRegistration") _need( isinstance(preflight, dict) and isinstance(gates, dict), "adoption_prerequisite_missing", ) _need(isinstance(registration, dict), "controller_registration_missing") trust_row = session.get(AppSetting, _TRUST_SETTING_KEY) _need( trust_row is not None and trust_row.is_secret is False, "enrollment_trust_unavailable", ) try: trust = EnrollmentTrustConfigV1.model_validate(trust_row.value_json) except (TypeError, ValueError): raise RepoPulseOwnershipError("enrollment_trust_invalid") from None _need( trust.controller_identity == _CONTROLLER_IDENTITY, "controller_identity_mismatch", ) _need( "repopulse-authenticated" in trust.autonomous_profiles and "itworx-mcp-hub-npm" in trust.approved_networks and "example.com" in trust.allowed_domain_suffixes and trust.repopulse_proxy_access_list_id is not None, "repopulse_trust_boundary_mismatch", ) revision = preflight.get("main_revision") _need( isinstance(revision, str) and len(revision) == 40, "preflight_revision_invalid" ) try: plan = EnrollmentPlanV1.model_validate( { "application_id": protocol.APPLICATION_ID, "display_name": "RepoPulse", "environment": "production", "repository": "Jens/RepoPulse", "revision": revision, "profile": "repopulse-authenticated", "target": { "target_identity": _CONTROLLER_IDENTITY, "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"]}, } ) except (TypeError, ValueError): raise RepoPulseOwnershipError("fixed_repopulse_plan_invalid") from None payload, plan_sha256 = _canonical_plan(plan) _need( gates.get("profileId") == "repopulse-authenticated" and gates.get("profileVersion") == PROFILE_VERSION and gates.get("planSha256") == plan_sha256 and registration.get("planSha256") == plan_sha256, "fixed_repopulse_plan_digest_mismatch", ) expected_enrollment_id = f"enr-{intent['intentId'][4:]}" _need( registration.get("enrollmentId") == expected_enrollment_id, "fixed_enrollment_id_mismatch", ) return plan, payload, plan_sha256 def _expected_provenance( intent: dict[str, Any], registration: dict[str, Any], plan_sha256: str ) -> dict[str, Any]: return { "createdBy": _CREATED_BY, "adoptionIntentId": intent["intentId"], "profileVersion": PROFILE_VERSION, "policyTemplateVersion": "exact-target/v1", "sourceRevision": intent["evidence"]["preflight"]["main_revision"], "planSha256": plan_sha256, "controllerReceiptSha256": registration["receiptSha256"], "deploymentReceiptSha256": registration["deploymentReceiptSha256"], } def _expected_resources(registration: dict[str, Any]) -> dict[str, Any]: return { "appdataPath": "/srv/example/appdata/repopulse", "dataPath": "/srv/example/appdata/repopulse/data", "network": "itworx-mcp-hub-npm", "hostname": "repopulse.example.com", "controllerReceiptSha256": registration["receiptSha256"], "deploymentReceiptSha256": registration["deploymentReceiptSha256"], } def _expected_policy_rows( intent: dict[str, Any], registration: dict[str, Any], plan_sha256: str ) -> tuple[Policy, Policy]: provenance = _expected_provenance(intent, registration, plan_sha256) return ( Policy( id=_APPLY_POLICY_ID, action="deployment.apply", target=protocol.APPLICATION_ID, voice="blocked", manual="allowed", risk="high", reason=_POLICY_REASON, provenance_json=provenance, ), Policy( id=_ROLLBACK_POLICY_ID, action="deployment.rollback", target=protocol.APPLICATION_ID, voice="blocked", manual="allowed", risk="high", reason=_POLICY_REASON, provenance_json=provenance, ), ) def _policy_matches(actual: Policy | None, expected: Policy) -> bool: return bool( actual is not None and actual.id == expected.id and actual.action == expected.action and actual.target == expected.target and actual.voice == expected.voice and actual.manual == expected.manual and actual.risk == expected.risk and actual.reason == expected.reason and actual.provenance_json == expected.provenance_json ) def _expected_enrollment( intent: dict[str, Any], registration: dict[str, Any], plan: EnrollmentPlanV1, plan_json: dict[str, Any], plan_sha256: str, ) -> Enrollment: return Enrollment( id=registration["enrollmentId"], application_id=protocol.APPLICATION_ID, repository=protocol.REPOSITORY, revision=plan.revision, profile="repopulse-authenticated", controller_identity=_CONTROLLER_IDENTITY, plan_sha256=plan_sha256, status="ready", target_secret_ref=_TARGET_SECRET_REF, oidc_secret_ref=None, plan_json=plan_json, resources_json=_expected_resources(registration), provenance_json=_expected_provenance(intent, registration, plan_sha256), ) def _enrollment_matches(actual: Enrollment | None, expected: Enrollment) -> bool: return bool( actual is not None and actual.id == expected.id and actual.application_id == expected.application_id and actual.repository == expected.repository and actual.revision == expected.revision and actual.profile == expected.profile and actual.controller_identity == expected.controller_identity and actual.plan_sha256 == expected.plan_sha256 and actual.status == expected.status and actual.target_secret_ref == expected.target_secret_ref and actual.oidc_secret_ref is None and actual.plan_json == expected.plan_json and actual.resources_json == expected.resources_json and actual.provenance_json == expected.provenance_json and actual.last_verified_at is not None and actual.completed_at is not None ) def _verify_owned_rows( session: Session, intent: dict[str, Any], registration: dict[str, Any], plan: EnrollmentPlanV1, plan_json: dict[str, Any], plan_sha256: str, ) -> tuple[Enrollment, tuple[Policy, Policy]]: expected_enrollment = _expected_enrollment( intent, registration, plan, plan_json, plan_sha256 ) matching_enrollments = session.exec( select(Enrollment).where( or_( Enrollment.application_id == protocol.APPLICATION_ID, Enrollment.repository == protocol.REPOSITORY, ) ) ).all() _need( len(matching_enrollments) == 1 and _enrollment_matches(matching_enrollments[0], expected_enrollment), "ownership_enrollment_readback_mismatch", ) expected_policies = _expected_policy_rows(intent, registration, plan_sha256) _need( all( _policy_matches(session.get(Policy, expected.id), expected) for expected in expected_policies ), "ownership_policy_readback_mismatch", ) _need( has_secret(session, _TARGET_SECRET_REF), "target_secret_reference_unavailable" ) return matching_enrollments[0], expected_policies def _verify_live_registration( intent: dict[str, Any], live_registration_evidence: object ) -> dict[str, Any]: try: if intent.get("status") == "completed": checked = protocol.validate_controller_registration_evidence( intent, live_registration_evidence ) replayed_registration = checked else: replayed = protocol.advance_intent( intent, "controller_registration", live_registration_evidence ) replayed_registration = replayed["evidence"].get("controllerRegistration") except protocol.AdoptionProtocolError as error: raise RepoPulseOwnershipError(error.code) from None _need( replayed_registration == intent["evidence"].get("controllerRegistration"), "idempotency_conflict", ) return replayed_registration def _ownership_evidence( registration: dict[str, Any], plan_sha256: str ) -> dict[str, Any]: return { "repository": protocol.REPOSITORY, "applicationId": protocol.APPLICATION_ID, "enrollmentId": registration["enrollmentId"], "profileId": registration["profileId"], "planSha256": plan_sha256, "controllerReceiptSha256": registration["receiptSha256"], "enrollmentPersisted": True, "exactApplyPolicyPersisted": True, "exactRollbackPolicyPersisted": True, "secretReferenceReady": True, } def _advance_or_verify_ownership( intent: dict[str, Any], evidence: dict[str, Any] ) -> dict[str, Any]: try: if intent.get("status") == "completed": checked = protocol.validate_appops_ownership_evidence(intent, evidence) _need( checked == intent["evidence"].get("appopsOwnership"), "idempotency_conflict", ) return intent return protocol.advance_intent(intent, "appops_ownership", evidence) except protocol.AdoptionProtocolError as error: raise RepoPulseOwnershipError(error.code) from None def _write_journal_update( session: Session, row: RepoPulseAdoptionJournal, current: dict[str, Any], updated: dict[str, Any], expected_revision: int, ) -> int: result = session.execute( update(RepoPulseAdoptionJournal) .where( RepoPulseAdoptionJournal.slot == _SLOT, RepoPulseAdoptionJournal.revision == expected_revision, RepoPulseAdoptionJournal.intent_id == current["intentId"], ) .values( idempotency_sha256=updated["idempotencySha256"], preflight_sha256=updated["preflightSha256"], phase=updated["phase"], status=updated["status"], intent_json=updated, revision=expected_revision + 1, updated_at=utc_now(), ) .execution_options(synchronize_session=False) ) _need(result.rowcount == 1, "journal_revision_conflict") return expected_revision + 1 def _readback_owned( session: Session, registration: dict[str, Any] ) -> dict[str, Any] | None: session.rollback() session.expire_all() try: row, intent = journal._load(session) if row is None or intent is None: return None live = _verify_live_registration(intent, registration) plan, plan_json, plan_sha256 = _fixed_plan(session, intent) _verify_owned_rows(session, intent, live, plan, plan_json, plan_sha256) _advance_or_verify_ownership(intent, _ownership_evidence(live, plan_sha256)) if intent["phase"] not in {"appops_owned", "completed"}: return None if intent["phase"] == "completed": _assert_completion_binding(intent, live, registration) return journal.get_repopulse_adoption_status(session) except ( RepoPulseOwnershipError, journal.RepoPulseAdoptionJournalError, protocol.AdoptionProtocolError, SQLAlchemyError, ): session.rollback() return None def _record_uncertain_commit( session: Session, expected_revision: int, receipt_sha256: str ) -> None: session.rollback() session.expire_all() try: row, current = journal._load(session) if ( row is None or current is None or row.revision != expected_revision or current.get("phase") != "target_registered" or current.get("status") != "in_progress" ): return journal.mark_repopulse_adoption_recovery( session, "appops_commit_uncertain", {"controllerReceiptSha256": receipt_sha256}, expected_revision=expected_revision, ) except ( RepoPulseOwnershipError, journal.RepoPulseAdoptionJournalError, protocol.AdoptionProtocolError, SQLAlchemyError, ): session.rollback() def _assert_completion_binding( intent: dict[str, Any], registration: dict[str, Any], live_registration: dict[str, Any], ) -> None: stored = intent.get("evidence", {}).get("completion") _need(isinstance(stored, dict), "completion_evidence_missing") expected = { "intentId": intent["intentId"], "repository": protocol.REPOSITORY, "applicationId": protocol.APPLICATION_ID, "enrollmentId": registration["enrollmentId"], "controllerReceiptSha256": registration["receiptSha256"], "enrollmentStillPersisted": True, "exactApplyPolicyStillPersisted": True, "exactRollbackPolicyStillPersisted": True, "controllerReceiptStillMatches": ( live_registration == registration and live_registration["receiptSha256"] == registration["receiptSha256"] ), "legacyVolumeRetained": live_registration["legacyVolumeRetained"], } _need(stored == expected, "completion_binding_mismatch") def commit_repopulse_adoption_ownership( session: Session, authenticated_controller_registration: object, *, expected_revision: int, ) -> dict[str, Any]: """Atomically install fixed enrollment/policies and advance to `appops_owned`. The caller is responsible for authenticating the live controller response. The protocol validator then binds every field to the persisted fixed intent. Exact replay reads back the DB rows and validates the fresh receipt again. """ try: row, current = journal._load(session) _need(row is not None and current is not None, "adoption_intent_not_reserved") live = _verify_live_registration(current, authenticated_controller_registration) plan, plan_json, plan_sha256 = _fixed_plan(session, current) _need( has_secret(session, _TARGET_SECRET_REF), "target_secret_reference_unavailable", ) if current["phase"] in {"appops_owned", "completed"}: _verify_owned_rows(session, current, live, plan, plan_json, plan_sha256) _advance_or_verify_ownership( current, _ownership_evidence(live, plan_sha256) ) if current["phase"] == "completed": _assert_completion_binding(current, live, live) return journal.get_repopulse_adoption_status(session) _need( current["phase"] == "target_registered" and current["status"] == "in_progress", "adoption_not_ready_for_ownership", ) _need( isinstance(expected_revision, int) and not isinstance(expected_revision, bool) and expected_revision == row.revision, "journal_revision_conflict", ) conflicting_enrollments = session.exec( select(Enrollment).where( or_( Enrollment.application_id == protocol.APPLICATION_ID, Enrollment.repository == protocol.REPOSITORY, ) ) ).all() _need(not conflicting_enrollments, "ownership_resource_conflict") expected_enrollment = _expected_enrollment( current, live, plan, plan_json, plan_sha256 ) expected_policies = _expected_policy_rows(current, live, plan_sha256) _need( all(session.get(Policy, item.id) is None for item in expected_policies), "ownership_resource_conflict", ) now = utc_now() expected_enrollment.created_at = now expected_enrollment.updated_at = now expected_enrollment.last_verified_at = now expected_enrollment.completed_at = now for policy in expected_policies: session.add(policy) session.add(expected_enrollment) session.flush() updated = _advance_or_verify_ownership( current, _ownership_evidence(live, plan_sha256) ) next_revision = _write_journal_update( session, row, current, updated, expected_revision ) try: session.commit() except (SQLAlchemyError, RuntimeError, OSError): replay = _readback_owned(session, live) if replay is not None: return replay _record_uncertain_commit(session, expected_revision, live["receiptSha256"]) raise RepoPulseOwnershipError("appops_commit_uncertain") from None return journal._status(updated, next_revision) except RepoPulseOwnershipError: session.rollback() raise except journal.RepoPulseAdoptionJournalError as error: session.rollback() raise RepoPulseOwnershipError(error.code) from None except protocol.AdoptionProtocolError as error: session.rollback() raise RepoPulseOwnershipError(error.code) from None except SQLAlchemyError: session.rollback() raise RepoPulseOwnershipError("ownership_storage_unavailable") from None def complete_repopulse_adoption( session: Session, authenticated_controller_registration: object, *, expected_revision: int, ) -> dict[str, Any]: """Persist AppOps completion after ownership, with fresh receipt readback. Controller authentication and its completed adoption/deployment receipt verification remain caller responsibilities. This step independently rechecks the receipt binding and exact owned rows on every replay. """ try: row, current = journal._load(session) _need(row is not None and current is not None, "adoption_intent_not_reserved") live = _verify_live_registration(current, authenticated_controller_registration) plan, plan_json, plan_sha256 = _fixed_plan(session, current) _verify_owned_rows(session, current, live, plan, plan_json, plan_sha256) _advance_or_verify_ownership(current, _ownership_evidence(live, plan_sha256)) _need( (current["phase"] == "appops_owned" and current["status"] == "in_progress") or (current["phase"] == "completed" and current["status"] == "completed"), "adoption_not_ready_to_complete", ) completion_evidence = { "intentId": current["intentId"], "repository": protocol.REPOSITORY, "applicationId": protocol.APPLICATION_ID, "enrollmentId": live["enrollmentId"], "controllerReceiptSha256": live["receiptSha256"], "enrollmentStillPersisted": True, "exactApplyPolicyStillPersisted": True, "exactRollbackPolicyStillPersisted": True, "controllerReceiptStillMatches": True, "legacyVolumeRetained": live["legacyVolumeRetained"], } if current["phase"] == "completed": _assert_completion_binding(current, live, live) _need( current["evidence"].get("completion") == completion_evidence, "completion_binding_mismatch", ) return journal.get_repopulse_adoption_status(session) _need( isinstance(expected_revision, int) and not isinstance(expected_revision, bool) and expected_revision == row.revision, "journal_revision_conflict", ) try: updated = protocol.complete_intent(current, completion_evidence) except protocol.AdoptionProtocolError as error: raise RepoPulseOwnershipError(error.code) from None return journal._persist_updated_intent( session, row, current, updated, expected_revision ) except RepoPulseOwnershipError: session.rollback() raise except journal.RepoPulseAdoptionJournalError as error: session.rollback() raise RepoPulseOwnershipError(error.code) from None except protocol.AdoptionProtocolError as error: session.rollback() raise RepoPulseOwnershipError(error.code) from None except SQLAlchemyError: session.rollback() raise RepoPulseOwnershipError("ownership_storage_unavailable") from None