Initial curated Unraid-AppOps-Gateway source from 3bd6f6fee310125bfbb4d4650389a817f498724d
This commit is contained in:
commit
1dfbca08a1
528 files changed
+131054
No files matched your search
@@ -0,0 +1,497 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import inspect
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlmodel import Session, SQLModel, create_engine
|
||||
|
||||
from backend.app.db.models import RepoPulseAdoptionJournal
|
||||
from backend.app.services import repopulse_adoption_journal as journal
|
||||
from backend.app.services.enrollment_profiles import PROFILE_VERSION
|
||||
from backend.app.services.repopulse_legacy_adoption_protocol import (
|
||||
APPLICATION_ID,
|
||||
CONTROLLER_RECEIPT_VERSION,
|
||||
LEGACY_VOLUME,
|
||||
PROTOCOL_VERSION,
|
||||
REPOSITORY,
|
||||
advance_intent,
|
||||
create_intent,
|
||||
intent_binding_sha256,
|
||||
)
|
||||
|
||||
|
||||
def _sha(char: str) -> str:
|
||||
return char * 64
|
||||
|
||||
|
||||
def _passing_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": PROFILE_VERSION,
|
||||
"planSha256": _sha("c"),
|
||||
**{
|
||||
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) -> dict:
|
||||
gates = intent["evidence"]["targetGates"]
|
||||
snapshot = intent["evidence"]["snapshot"]
|
||||
return {
|
||||
"receiptVersion": "itworx.repopulse-legacy-adoption-receipt/v1",
|
||||
"intentId": intent["intentId"],
|
||||
"receiptSha256": _sha("2"),
|
||||
"repository": REPOSITORY,
|
||||
"applicationId": APPLICATION_ID,
|
||||
"enrollmentId": "enr-0123456789abcdefabcd",
|
||||
"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 _ownership(intent: dict, registration: dict) -> dict:
|
||||
return {
|
||||
"repository": REPOSITORY,
|
||||
"applicationId": APPLICATION_ID,
|
||||
"enrollmentId": registration["enrollmentId"],
|
||||
"profileId": registration["profileId"],
|
||||
"planSha256": intent["evidence"]["targetGates"]["planSha256"],
|
||||
"controllerReceiptSha256": registration["receiptSha256"],
|
||||
"enrollmentPersisted": True,
|
||||
"exactApplyPolicyPersisted": True,
|
||||
"exactRollbackPolicyPersisted": True,
|
||||
"secretReferenceReady": True,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def engine(tmp_path):
|
||||
database_engine = create_engine(f"sqlite:///{tmp_path / 'appops.db'}")
|
||||
SQLModel.metadata.create_all(database_engine)
|
||||
yield database_engine
|
||||
database_engine.dispose()
|
||||
|
||||
|
||||
def _reserve(engine, key: str = "repopulse-adoption-2026") -> dict:
|
||||
with Session(engine) as session:
|
||||
return journal.reserve_repopulse_adoption(session, _passing_preflight(), key)
|
||||
|
||||
|
||||
def test_create_all_registers_singleton_table_and_reservation_survives_restart(engine) -> None:
|
||||
assert inspect(engine).has_table("repopulse_adoption_journal")
|
||||
|
||||
first = _reserve(engine)
|
||||
assert first["apiVersion"] == PROTOCOL_VERSION
|
||||
assert first["status"] == "in_progress"
|
||||
assert first["phase"] == "reserved"
|
||||
assert first["repository"] == REPOSITORY
|
||||
assert first["applicationId"] == APPLICATION_ID
|
||||
assert first["revision"] == 1
|
||||
|
||||
# A new Session models a process restart: the slot and idempotent replay
|
||||
# are read back from SQLite, not held in adapter memory.
|
||||
replay = _reserve(engine)
|
||||
assert replay == first
|
||||
|
||||
with Session(engine) as session:
|
||||
status = journal.get_repopulse_adoption_status(session)
|
||||
assert status == first
|
||||
|
||||
|
||||
def test_changed_preflight_and_different_key_conflict_without_replacing_slot(engine) -> None:
|
||||
first = _reserve(engine)
|
||||
changed = _passing_preflight()
|
||||
changed["evidence"]["live_revision"] = "c" * 40
|
||||
|
||||
with Session(engine) as session, pytest.raises(journal.RepoPulseAdoptionJournalError) as error:
|
||||
journal.reserve_repopulse_adoption(session, changed, "repopulse-adoption-2026")
|
||||
assert error.value.code == "idempotency_conflict"
|
||||
|
||||
with Session(engine) as session, pytest.raises(journal.RepoPulseAdoptionJournalError) as error:
|
||||
journal.reserve_repopulse_adoption(
|
||||
session, _passing_preflight(), "repopulse-adoption-2027"
|
||||
)
|
||||
assert error.value.code == "one_time_adoption_already_reserved"
|
||||
assert _reserve(engine) == first
|
||||
|
||||
|
||||
def test_reservation_rejects_another_repository_identity_without_creating_a_row(engine) -> None:
|
||||
other_repository = _passing_preflight()
|
||||
other_repository["repository"] = "someone-else/repopulse"
|
||||
|
||||
with Session(engine) as session, pytest.raises(journal.RepoPulseAdoptionJournalError) as error:
|
||||
journal.reserve_repopulse_adoption(
|
||||
session, other_repository, "repopulse-adoption-2026"
|
||||
)
|
||||
assert error.value.code == "legacy_preflight_not_compatible"
|
||||
|
||||
with Session(engine) as session:
|
||||
assert journal.get_repopulse_adoption_status(session)["status"] == "unreserved"
|
||||
|
||||
intent = create_intent(_passing_preflight(), "repopulse-adoption-2026")
|
||||
with Session(engine) as session, pytest.raises(IntegrityError):
|
||||
session.add(
|
||||
RepoPulseAdoptionJournal(
|
||||
slot="other",
|
||||
repository="someone-else/repopulse",
|
||||
application_id="other-app",
|
||||
intent_id=intent["intentId"],
|
||||
idempotency_sha256=intent["idempotencySha256"],
|
||||
preflight_sha256=intent["preflightSha256"],
|
||||
phase=intent["phase"],
|
||||
status=intent["status"],
|
||||
revision=1,
|
||||
intent_json=intent,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
|
||||
|
||||
def test_controller_intent_confirmation_requires_exact_binding_and_replays(engine) -> None:
|
||||
_reserve(engine)
|
||||
with Session(engine) as session:
|
||||
after_gates = journal.advance_repopulse_adoption(
|
||||
session, "target_gates", _target_gates(), expected_revision=1
|
||||
)
|
||||
assert after_gates["phase"] == "target_gates_ready"
|
||||
assert after_gates["revision"] == 2
|
||||
|
||||
current = create_intent(_passing_preflight(), "repopulse-adoption-2026")
|
||||
valid_confirmation = _quiescence(current)
|
||||
invalid_confirmation = {**valid_confirmation, "controllerIntentBindingSha256": _sha("0")}
|
||||
|
||||
with Session(engine) as session, pytest.raises(journal.RepoPulseAdoptionJournalError) as error:
|
||||
journal.confirm_repopulse_controller_intent(
|
||||
session, invalid_confirmation, expected_revision=2
|
||||
)
|
||||
assert error.value.code == "controller_intent_binding_mismatch"
|
||||
|
||||
with Session(engine) as session:
|
||||
confirmed = journal.confirm_repopulse_controller_intent(
|
||||
session, valid_confirmation, expected_revision=2
|
||||
)
|
||||
assert confirmed["phase"] == "quiesced"
|
||||
assert confirmed["revision"] == 3
|
||||
|
||||
with Session(engine) as session:
|
||||
replay = journal.confirm_repopulse_controller_intent(
|
||||
session, valid_confirmation, expected_revision=2
|
||||
)
|
||||
assert replay == confirmed
|
||||
|
||||
|
||||
def test_compare_and_swap_rejects_stale_checkpoint_without_overwriting_state(engine) -> None:
|
||||
first = _reserve(engine)
|
||||
with Session(engine) as session:
|
||||
updated = journal.advance_repopulse_adoption(
|
||||
session,
|
||||
"target_gates",
|
||||
_target_gates(),
|
||||
expected_revision=1,
|
||||
)
|
||||
assert updated["status"] == "in_progress"
|
||||
assert updated["revision"] == 2
|
||||
|
||||
candidate = create_intent(_passing_preflight(), "repopulse-adoption-2026")
|
||||
valid_confirmation = _quiescence(candidate)
|
||||
|
||||
with Session(engine) as session, pytest.raises(journal.RepoPulseAdoptionJournalError) as error:
|
||||
journal.confirm_repopulse_controller_intent(
|
||||
session,
|
||||
valid_confirmation,
|
||||
expected_revision=1,
|
||||
)
|
||||
assert error.value.code == "journal_revision_conflict"
|
||||
|
||||
with Session(engine) as session:
|
||||
assert journal.get_repopulse_adoption_status(session) == updated
|
||||
assert updated["intentId"] == first["intentId"]
|
||||
|
||||
|
||||
def test_recovery_resume_checks_controller_binding_and_persists_across_sessions(engine) -> None:
|
||||
_reserve(engine)
|
||||
with Session(engine) as session:
|
||||
recovery = journal.mark_repopulse_adoption_recovery(
|
||||
session,
|
||||
"operator_interruption",
|
||||
{"operatorNote": "resume review", "path": "private/path"},
|
||||
expected_revision=1,
|
||||
)
|
||||
assert "private/path" not in str(recovery)
|
||||
|
||||
receipt = {
|
||||
"apiVersion": CONTROLLER_RECEIPT_VERSION,
|
||||
"intentId": recovery["intentId"],
|
||||
"phase": "reserved",
|
||||
"receiptSha256": hashlib.sha256(b"durable controller journal row").hexdigest(),
|
||||
"repository": REPOSITORY,
|
||||
"applicationId": APPLICATION_ID,
|
||||
"legacyVolumeRetained": True,
|
||||
"intentDurable": True,
|
||||
"intentBindingSha256": recovery["intentBindingSha256"],
|
||||
"registrationEvidence": None,
|
||||
}
|
||||
invalid_receipt = {**receipt, "intentBindingSha256": _sha("0")}
|
||||
with Session(engine) as session, pytest.raises(journal.RepoPulseAdoptionJournalError) as error:
|
||||
journal.resume_repopulse_adoption(session, invalid_receipt, expected_revision=2)
|
||||
assert error.value.code == "controller_recovery_receipt_unbound"
|
||||
|
||||
with Session(engine) as session:
|
||||
resumed = journal.resume_repopulse_adoption(
|
||||
session, receipt, expected_revision=2
|
||||
)
|
||||
assert resumed["status"] == "in_progress"
|
||||
assert resumed["phase"] == "reserved"
|
||||
assert resumed["revision"] == 3
|
||||
|
||||
with Session(engine) as session:
|
||||
assert journal.get_repopulse_adoption_status(session) == resumed
|
||||
|
||||
|
||||
def test_controller_registration_reconciles_before_appops_ownership_can_advance(engine) -> None:
|
||||
_reserve(engine)
|
||||
candidate = create_intent(_passing_preflight(), "repopulse-adoption-2026")
|
||||
with Session(engine) as session:
|
||||
current = journal.advance_repopulse_adoption(
|
||||
session, "target_gates", _target_gates(), expected_revision=1
|
||||
)
|
||||
candidate = advance_intent(candidate, "target_gates", _target_gates())
|
||||
with Session(engine) as session:
|
||||
current = journal.confirm_repopulse_controller_intent(
|
||||
session, _quiescence(candidate), expected_revision=2
|
||||
)
|
||||
candidate = advance_intent(candidate, "quiescence", _quiescence(candidate))
|
||||
with Session(engine) as session:
|
||||
current = journal.advance_repopulse_adoption(
|
||||
session, "snapshot", _snapshot(candidate), expected_revision=3
|
||||
)
|
||||
candidate = advance_intent(candidate, "snapshot", _snapshot(candidate))
|
||||
|
||||
early_ownership = {
|
||||
"repository": REPOSITORY,
|
||||
"applicationId": APPLICATION_ID,
|
||||
"enrollmentId": "enr-0123456789abcdefabcd",
|
||||
"profileId": "repopulse-authenticated",
|
||||
"planSha256": _sha("c"),
|
||||
"controllerReceiptSha256": _sha("2"),
|
||||
"enrollmentPersisted": True,
|
||||
"exactApplyPolicyPersisted": True,
|
||||
"exactRollbackPolicyPersisted": True,
|
||||
"secretReferenceReady": True,
|
||||
}
|
||||
with Session(engine) as session, pytest.raises(journal.RepoPulseAdoptionJournalError) as error:
|
||||
journal.advance_repopulse_adoption(
|
||||
session, "appops_ownership", early_ownership, expected_revision=4
|
||||
)
|
||||
assert error.value.code == "controller_registration_missing"
|
||||
assert current["phase"] == "snapshot_verified"
|
||||
|
||||
registration = _registration(candidate)
|
||||
with Session(engine) as session:
|
||||
registered = journal.advance_repopulse_adoption(
|
||||
session,
|
||||
"controller_registration",
|
||||
registration,
|
||||
expected_revision=4,
|
||||
)
|
||||
candidate = advance_intent(candidate, "controller_registration", registration)
|
||||
assert registered["phase"] == "target_registered"
|
||||
assert registered["revision"] == 5
|
||||
|
||||
with Session(engine) as session:
|
||||
recovery = journal.mark_repopulse_adoption_recovery(
|
||||
session,
|
||||
"appops_commit_uncertain",
|
||||
{"uncertain": True, "path": "private/path"},
|
||||
expected_revision=5,
|
||||
)
|
||||
assert recovery["status"] == "recovery_required"
|
||||
assert recovery["phase"] == "target_registered"
|
||||
assert recovery["legacyVolumePreserved"] is True
|
||||
assert recovery["controllerReconciliationRequired"] is True
|
||||
assert "private/path" not in str(recovery)
|
||||
|
||||
controller_receipt = {
|
||||
"apiVersion": CONTROLLER_RECEIPT_VERSION,
|
||||
"intentId": candidate["intentId"],
|
||||
"phase": "target_registered",
|
||||
"receiptSha256": registration["receiptSha256"],
|
||||
"repository": REPOSITORY,
|
||||
"applicationId": APPLICATION_ID,
|
||||
"legacyVolumeRetained": True,
|
||||
"intentDurable": True,
|
||||
"intentBindingSha256": intent_binding_sha256(candidate),
|
||||
"registrationEvidence": registration,
|
||||
}
|
||||
with Session(engine) as session:
|
||||
resumed = journal.resume_repopulse_adoption(
|
||||
session, controller_receipt, expected_revision=6
|
||||
)
|
||||
assert resumed["status"] == "in_progress"
|
||||
assert resumed["phase"] == "target_registered"
|
||||
assert resumed["revision"] == 7
|
||||
|
||||
ownership = _ownership(candidate, registration)
|
||||
with Session(engine) as session:
|
||||
owned = journal.advance_repopulse_adoption(
|
||||
session, "appops_ownership", ownership, expected_revision=7
|
||||
)
|
||||
assert owned["phase"] == "appops_owned"
|
||||
assert owned["revision"] == 8
|
||||
assert owned["status"] != "completed"
|
||||
Reference in new issue
Block a user