985 lines
38 KiB
Python
985 lines
38 KiB
Python
"""Pure state and evidence rules for the fixed RepoPulse legacy adoption.
|
|
|
|
This module deliberately has no Portainer, filesystem, Docker, database, or
|
|
controller client. The integrated operator runner uses these rules only after
|
|
the fixed managed profile and controller-owned durable intent are verified.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from copy import deepcopy
|
|
from typing import Any
|
|
|
|
from backend.app.services.enrollment_profiles import PROFILE_VERSION
|
|
|
|
PROTOCOL_VERSION = "itworx.repopulse-legacy-adoption/v1"
|
|
INTENT_VERSION = "itworx.repopulse-legacy-adoption-intent/v1"
|
|
CONTROLLER_RECEIPT_VERSION = "itworx.repopulse-legacy-adoption-receipt/v1"
|
|
REPOSITORY = "jens/repopulse"
|
|
APPLICATION_ID = "repopulse"
|
|
LEGACY_VOLUME = "repopulse_data"
|
|
EXECUTION_ENABLED = True
|
|
|
|
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
_SHA1 = re.compile(r"^[0-9a-f]{40}$")
|
|
_IDEMPOTENCY_KEY = re.compile(r"^[A-Za-z0-9._:-]{8,128}$")
|
|
_PROFILE_ID = "repopulse-authenticated"
|
|
_ENROLLMENT_ID = re.compile(r"^enr-[0-9a-f]{20}$")
|
|
_INTENT_ID = re.compile(r"^rpa-[0-9a-f]{20}$")
|
|
|
|
_LEGACY_CHECKS = {
|
|
"legacy_registry",
|
|
"controller_state",
|
|
"gitea_ancestry",
|
|
"runtime_identity_and_health",
|
|
"legacy_storage_topology",
|
|
"legacy_appdata",
|
|
"product_api",
|
|
"database_health",
|
|
"queue_quiescence",
|
|
"database_baseline",
|
|
}
|
|
_PHASES = (
|
|
"reserved",
|
|
"target_gates_ready",
|
|
"quiesced",
|
|
"snapshot_verified",
|
|
"target_registered",
|
|
"appops_owned",
|
|
"completed",
|
|
)
|
|
_STEP_CONTRACTS: dict[str, tuple[str, str]] = {
|
|
"target_gates": ("reserved", "target_gates_ready"),
|
|
"quiescence": ("target_gates_ready", "quiesced"),
|
|
"snapshot": ("quiesced", "snapshot_verified"),
|
|
"controller_registration": ("snapshot_verified", "target_registered"),
|
|
"appops_ownership": ("target_registered", "appops_owned"),
|
|
}
|
|
_RECOVERY_REASONS = {
|
|
"controller_unavailable",
|
|
"database_backup_failed",
|
|
"registry_receipt_uncertain",
|
|
"target_health_failed",
|
|
"appops_commit_uncertain",
|
|
"preflight_drift",
|
|
"operator_interruption",
|
|
"unknown_external_effect",
|
|
}
|
|
_STEP_EVIDENCE_KEYS = {
|
|
"target_gates": "targetGates",
|
|
"quiescence": "quiescence",
|
|
"snapshot": "snapshot",
|
|
"controller_registration": "controllerRegistration",
|
|
"appops_ownership": "appopsOwnership",
|
|
"completion": "completion",
|
|
}
|
|
|
|
|
|
class AdoptionProtocolError(ValueError):
|
|
"""A fail-closed protocol or evidence validation error."""
|
|
|
|
def __init__(self, code: str):
|
|
super().__init__(code)
|
|
self.code = code
|
|
|
|
|
|
def _canonical(value: object) -> bytes:
|
|
try:
|
|
return json.dumps(
|
|
value,
|
|
sort_keys=True,
|
|
separators=(",", ":"),
|
|
ensure_ascii=True,
|
|
allow_nan=False,
|
|
).encode("ascii")
|
|
except (TypeError, ValueError, UnicodeEncodeError) as exc:
|
|
raise AdoptionProtocolError("invalid_evidence_encoding") from exc
|
|
|
|
|
|
def _digest(value: object) -> str:
|
|
return hashlib.sha256(_canonical(value)).hexdigest()
|
|
|
|
|
|
def _is_count(value: object) -> bool:
|
|
return isinstance(value, int) and not isinstance(value, bool) and value >= 0
|
|
|
|
|
|
def _validate_preflight_projection(value: object) -> dict[str, Any]:
|
|
required = {
|
|
"repository",
|
|
"main_revision",
|
|
"live_revision",
|
|
"container_id_sha256",
|
|
"container_image_sha256",
|
|
"queue",
|
|
"database",
|
|
"container_state",
|
|
"container_health",
|
|
}
|
|
_need(isinstance(value, dict) and set(value) == required, "intent_preflight_invalid")
|
|
projection = value
|
|
_need(
|
|
projection.get("repository") == REPOSITORY
|
|
and isinstance(projection.get("main_revision"), str)
|
|
and _SHA1.fullmatch(projection["main_revision"]) is not None
|
|
and isinstance(projection.get("live_revision"), str)
|
|
and _SHA1.fullmatch(projection["live_revision"]) is not None
|
|
and isinstance(projection.get("container_id_sha256"), str)
|
|
and _SHA256.fullmatch(projection["container_id_sha256"]) is not None
|
|
and isinstance(projection.get("container_image_sha256"), str)
|
|
and _SHA256.fullmatch(projection["container_image_sha256"]) is not None
|
|
and projection.get("container_state") == "running"
|
|
and projection.get("container_health") == "healthy",
|
|
"intent_preflight_invalid",
|
|
)
|
|
queue = projection.get("queue")
|
|
database = projection.get("database")
|
|
_need(
|
|
isinstance(queue, dict)
|
|
and set(queue) == {"queued", "active", "completed", "failed"}
|
|
and all(_is_count(queue.get(key)) for key in queue)
|
|
and queue.get("queued") == 0
|
|
and queue.get("active") == 0,
|
|
"intent_preflight_queue_invalid",
|
|
)
|
|
_need(
|
|
isinstance(database, dict)
|
|
and set(database)
|
|
== {"database_bytes", "journal_bytes", "scan_count", "compacted_scan_count"}
|
|
and all(_is_count(database.get(key)) for key in database)
|
|
and database.get("database_bytes", 0) > 0
|
|
and database.get("scan_count", -1) >= database.get("compacted_scan_count", 0),
|
|
"intent_preflight_database_invalid",
|
|
)
|
|
return projection
|
|
|
|
|
|
def _need(condition: bool, code: str) -> None:
|
|
if not condition:
|
|
raise AdoptionProtocolError(code)
|
|
|
|
|
|
def _safe_preflight_projection(report: object) -> dict[str, Any]:
|
|
"""Accept only the existing read-only preflight's allowlisted identity facts."""
|
|
_need(isinstance(report, dict), "legacy_preflight_missing")
|
|
source = report
|
|
_need(
|
|
source.get("apiVersion") == "itworx.repopulse-legacy-adoption-preflight/v1"
|
|
and source.get("repository") == REPOSITORY
|
|
and source.get("status") == "preflight_passed"
|
|
and source.get("readOnly") is True
|
|
and source.get("adoptionAuthorized") is False,
|
|
"legacy_preflight_not_compatible",
|
|
)
|
|
compatibility = source.get("legacyCompatibility")
|
|
_need(
|
|
isinstance(compatibility, dict)
|
|
and compatibility.get("status") == "passed",
|
|
"legacy_preflight_not_compatible",
|
|
)
|
|
checks = compatibility.get("checks")
|
|
_need(isinstance(checks, list), "legacy_preflight_checks_missing")
|
|
observed: dict[str, str] = {}
|
|
for check in checks:
|
|
_need(isinstance(check, dict), "legacy_preflight_check_invalid")
|
|
code = check.get("code")
|
|
_need(
|
|
isinstance(code, str)
|
|
and code in _LEGACY_CHECKS
|
|
and check.get("gate") == "legacy_compatibility"
|
|
and check.get("status") == "passed"
|
|
and code not in observed,
|
|
"legacy_preflight_check_failed",
|
|
)
|
|
observed[code] = "passed"
|
|
_need(set(observed) == _LEGACY_CHECKS, "legacy_preflight_check_incomplete")
|
|
|
|
evidence = source.get("evidence")
|
|
_need(isinstance(evidence, dict), "legacy_preflight_evidence_missing")
|
|
main_revision = evidence.get("main_revision")
|
|
live_revision = evidence.get("live_revision")
|
|
queue = evidence.get("queue")
|
|
database = evidence.get("database")
|
|
_need(
|
|
isinstance(main_revision, str)
|
|
and _SHA1.fullmatch(main_revision) is not None
|
|
and isinstance(live_revision, str)
|
|
and _SHA1.fullmatch(live_revision) is not None,
|
|
"legacy_preflight_revision_invalid",
|
|
)
|
|
_need(
|
|
isinstance(queue, dict)
|
|
and set(queue) == {"queued", "active", "completed", "failed"}
|
|
and all(_is_count(queue.get(key)) for key in queue)
|
|
and queue.get("queued") == 0
|
|
and queue.get("active") == 0,
|
|
"legacy_preflight_queue_not_quiescent",
|
|
)
|
|
_need(
|
|
isinstance(database, dict)
|
|
and all(
|
|
_is_count(database.get(key))
|
|
for key in (
|
|
"database_bytes",
|
|
"journal_bytes",
|
|
"scan_count",
|
|
"compacted_scan_count",
|
|
)
|
|
)
|
|
and database.get("database_bytes", 0) > 0
|
|
and database.get("scan_count", -1)
|
|
>= database.get("compacted_scan_count", 0),
|
|
"legacy_preflight_database_facts_invalid",
|
|
)
|
|
_need(
|
|
evidence.get("container_state") == "running"
|
|
and evidence.get("container_health") == "healthy",
|
|
"legacy_preflight_runtime_invalid",
|
|
)
|
|
return {
|
|
"repository": REPOSITORY,
|
|
"main_revision": main_revision,
|
|
"live_revision": live_revision,
|
|
"container_id_sha256": evidence.get("container_id_sha256"),
|
|
"container_image_sha256": evidence.get("container_image_sha256"),
|
|
"queue": {
|
|
key: queue[key]
|
|
for key in ("queued", "active", "completed", "failed")
|
|
},
|
|
"database": {
|
|
key: database[key]
|
|
for key in (
|
|
"database_bytes",
|
|
"journal_bytes",
|
|
"scan_count",
|
|
"compacted_scan_count",
|
|
)
|
|
},
|
|
"container_state": "running",
|
|
"container_health": "healthy",
|
|
}
|
|
|
|
|
|
def create_intent(report: object, idempotency_key: str) -> dict[str, Any]:
|
|
"""Create deterministic fixed-identity intent data from a passing preflight.
|
|
|
|
Persistence is deliberately left to the future AppOps/controller adapters;
|
|
this pure constructor never writes a database or controller state file.
|
|
"""
|
|
_need(
|
|
isinstance(idempotency_key, str)
|
|
and _IDEMPOTENCY_KEY.fullmatch(idempotency_key) is not None,
|
|
"idempotency_key_invalid",
|
|
)
|
|
projection = _safe_preflight_projection(report)
|
|
idempotency_sha256 = hashlib.sha256(
|
|
f"{PROTOCOL_VERSION}:{REPOSITORY}:{idempotency_key}".encode("ascii")
|
|
).hexdigest()
|
|
intent_id = f"rpa-{idempotency_sha256[:20]}"
|
|
intent = {
|
|
"apiVersion": INTENT_VERSION,
|
|
"protocolVersion": PROTOCOL_VERSION,
|
|
"intentId": intent_id,
|
|
"idempotencySha256": idempotency_sha256,
|
|
"repository": REPOSITORY,
|
|
"applicationId": APPLICATION_ID,
|
|
"phase": "reserved",
|
|
"status": "in_progress",
|
|
"preflightSha256": _digest(projection),
|
|
"legacyVolume": LEGACY_VOLUME,
|
|
"legacyVolumePreserved": True,
|
|
"evidence": {"preflight": projection},
|
|
"receipts": {},
|
|
"recovery": None,
|
|
}
|
|
return _validate_intent(intent)
|
|
|
|
|
|
def reserve_or_replay(
|
|
existing_intent: object | None, report: object, idempotency_key: str
|
|
) -> dict[str, Any]:
|
|
"""Enforce the single fixed adoption slot and exact retry identity."""
|
|
candidate = create_intent(report, idempotency_key)
|
|
if existing_intent is None:
|
|
return candidate
|
|
existing = _validate_intent(existing_intent)
|
|
_need(
|
|
existing["idempotencySha256"] == candidate["idempotencySha256"],
|
|
"one_time_adoption_already_reserved",
|
|
)
|
|
_need(
|
|
existing["preflightSha256"] == candidate["preflightSha256"],
|
|
"idempotency_conflict",
|
|
)
|
|
return deepcopy(existing)
|
|
|
|
|
|
def _validate_intent(intent: object) -> dict[str, Any]:
|
|
required = {
|
|
"apiVersion",
|
|
"protocolVersion",
|
|
"intentId",
|
|
"idempotencySha256",
|
|
"repository",
|
|
"applicationId",
|
|
"phase",
|
|
"status",
|
|
"preflightSha256",
|
|
"legacyVolume",
|
|
"legacyVolumePreserved",
|
|
"evidence",
|
|
"receipts",
|
|
"recovery",
|
|
}
|
|
_need(isinstance(intent, dict) and set(intent) == required, "intent_shape_invalid")
|
|
value = intent
|
|
_need(
|
|
value.get("apiVersion") == INTENT_VERSION
|
|
and value.get("protocolVersion") == PROTOCOL_VERSION
|
|
and value.get("repository") == REPOSITORY
|
|
and value.get("applicationId") == APPLICATION_ID
|
|
and value.get("legacyVolume") == LEGACY_VOLUME
|
|
and value.get("legacyVolumePreserved") is True
|
|
and isinstance(value.get("intentId"), str)
|
|
and _INTENT_ID.fullmatch(value["intentId"]) is not None
|
|
and isinstance(value.get("idempotencySha256"), str)
|
|
and _SHA256.fullmatch(value["idempotencySha256"]) is not None
|
|
and value["intentId"] == f"rpa-{value['idempotencySha256'][:20]}"
|
|
and isinstance(value.get("preflightSha256"), str)
|
|
and _SHA256.fullmatch(value["preflightSha256"]) is not None
|
|
and value.get("phase") in _PHASES
|
|
and value.get("status") in {"in_progress", "recovery_required", "completed"}
|
|
and isinstance(value.get("evidence"), dict)
|
|
and isinstance(value.get("receipts"), dict),
|
|
"intent_identity_invalid",
|
|
)
|
|
_need(
|
|
value.get("status") != "completed" or value.get("phase") == "completed",
|
|
"intent_terminal_state_invalid",
|
|
)
|
|
evidence = value["evidence"]
|
|
_need(
|
|
set(evidence) <= {"preflight", *_STEP_EVIDENCE_KEYS.values()}
|
|
and isinstance(evidence.get("preflight"), dict)
|
|
and _digest(evidence["preflight"]) == value["preflightSha256"],
|
|
"intent_evidence_invalid",
|
|
)
|
|
_validate_preflight_projection(evidence["preflight"])
|
|
receipts = value["receipts"]
|
|
phase = value["phase"]
|
|
expected_steps = {
|
|
"reserved": (),
|
|
"target_gates_ready": ("target_gates",),
|
|
"quiesced": ("target_gates", "quiescence"),
|
|
"snapshot_verified": ("target_gates", "quiescence", "snapshot"),
|
|
"target_registered": ("target_gates", "quiescence", "snapshot", "controller_registration"),
|
|
"appops_owned": tuple(key for key in _STEP_EVIDENCE_KEYS if key != "completion"),
|
|
"completed": tuple(_STEP_EVIDENCE_KEYS),
|
|
}[phase]
|
|
_need(set(receipts) == set(expected_steps), "intent_receipts_incomplete")
|
|
for step, record in receipts.items():
|
|
_need(
|
|
isinstance(record, dict)
|
|
and set(record) == {"evidenceSha256"}
|
|
and isinstance(record.get("evidenceSha256"), str)
|
|
and _SHA256.fullmatch(record["evidenceSha256"]) is not None,
|
|
"intent_receipt_invalid",
|
|
)
|
|
evidence_key = _STEP_EVIDENCE_KEYS[step]
|
|
_need(
|
|
evidence_key in evidence
|
|
and _digest(evidence[evidence_key]) == record["evidenceSha256"],
|
|
"intent_receipt_evidence_mismatch",
|
|
)
|
|
if value["status"] == "recovery_required":
|
|
recovery = value.get("recovery")
|
|
_need(
|
|
isinstance(recovery, dict)
|
|
and set(recovery)
|
|
== {
|
|
"reason",
|
|
"afterPhase",
|
|
"failureEvidenceSha256",
|
|
"controllerReconciliationRequired",
|
|
"legacyVolumePreserved",
|
|
}
|
|
and recovery.get("reason") in _RECOVERY_REASONS
|
|
and recovery.get("afterPhase") == phase
|
|
and isinstance(recovery.get("failureEvidenceSha256"), str)
|
|
and _SHA256.fullmatch(recovery["failureEvidenceSha256"]) is not None
|
|
and recovery.get("controllerReconciliationRequired") is True
|
|
and recovery.get("legacyVolumePreserved") is True,
|
|
"intent_recovery_marker_invalid",
|
|
)
|
|
else:
|
|
_need(value.get("recovery") is None, "intent_recovery_marker_unexpected")
|
|
return value
|
|
|
|
|
|
def _intent_binding(intent: dict[str, Any]) -> str:
|
|
return _digest(
|
|
{
|
|
"intentId": intent["intentId"],
|
|
"repository": REPOSITORY,
|
|
"applicationId": APPLICATION_ID,
|
|
"preflightSha256": intent["preflightSha256"],
|
|
"idempotencySha256": intent["idempotencySha256"],
|
|
}
|
|
)
|
|
|
|
|
|
def _target_gates(evidence: object) -> dict[str, Any]:
|
|
required = {
|
|
"profileId",
|
|
"profileVersion",
|
|
"planSha256",
|
|
"profileReady",
|
|
"controllerTrustReady",
|
|
"controllerBootstrapSecretImported",
|
|
"typedEnrollmentTrustReady",
|
|
"targetSecretReady",
|
|
"exactPolicyReady",
|
|
"authenticatedRouteReady",
|
|
"accessListReady",
|
|
"mutationBoundaryReady",
|
|
"managedRuntimeReady",
|
|
"scanSourceMatches",
|
|
"scanMountReadOnly",
|
|
"scanPermissionPolicyPersistent",
|
|
"scanAccessForRuntimeIdentityReady",
|
|
"ownedDataTargetReady",
|
|
"managedEnvironmentReady",
|
|
"legacyEnvironmentRetained",
|
|
"maintenanceAuthReady",
|
|
"controllerAdoptionCapabilityReady",
|
|
"controllerRegistryWriterReady",
|
|
"controllerSecretWriterReady",
|
|
}
|
|
_need(isinstance(evidence, dict) and set(evidence) == required, "target_gates_invalid")
|
|
value = evidence
|
|
_need(
|
|
value.get("profileId") == _PROFILE_ID
|
|
and value.get("profileVersion") == PROFILE_VERSION
|
|
and isinstance(value.get("planSha256"), str)
|
|
and _SHA256.fullmatch(value["planSha256"]) is not None
|
|
and all(value.get(key) is True for key in required - {"profileId", "profileVersion", "planSha256"}),
|
|
"target_gates_not_ready",
|
|
)
|
|
return {key: value[key] for key in sorted(required)}
|
|
|
|
|
|
def _quiescence_evidence(evidence: object, intent: dict[str, Any]) -> dict[str, Any]:
|
|
required = {
|
|
"intentId",
|
|
"globalDeployLockHeld",
|
|
"projectLockHeld",
|
|
"maintenanceModeActive",
|
|
"queueFrozen",
|
|
"queuedJobs",
|
|
"activeJobs",
|
|
"maintenanceModeAuthenticated",
|
|
"containerName",
|
|
"containerIdSha256",
|
|
"containerImageSha256",
|
|
"containerState",
|
|
"legacyVolume",
|
|
"controllerIntentDurable",
|
|
"controllerIntentBindingSha256",
|
|
}
|
|
_need(isinstance(evidence, dict) and set(evidence) == required, "quiescence_evidence_invalid")
|
|
value = evidence
|
|
_need(
|
|
value.get("intentId") == intent["intentId"]
|
|
and value.get("globalDeployLockHeld") is True
|
|
and value.get("projectLockHeld") is True
|
|
and value.get("maintenanceModeActive") is True
|
|
and value.get("queueFrozen") is True
|
|
and value.get("maintenanceModeAuthenticated") is True
|
|
and _is_count(value.get("queuedJobs"))
|
|
and value.get("queuedJobs") == 0
|
|
and _is_count(value.get("activeJobs"))
|
|
and value.get("activeJobs") == 0
|
|
and value.get("containerName") == "repopulse"
|
|
and isinstance(value.get("containerIdSha256"), str)
|
|
and _SHA256.fullmatch(value["containerIdSha256"]) is not None
|
|
and value.get("containerIdSha256") == intent["evidence"]["preflight"]["container_id_sha256"]
|
|
and isinstance(value.get("containerImageSha256"), str)
|
|
and _SHA256.fullmatch(value["containerImageSha256"]) is not None
|
|
and value.get("containerImageSha256") == intent["evidence"]["preflight"]["container_image_sha256"]
|
|
and value.get("containerState") == "exited"
|
|
and value.get("legacyVolume") == LEGACY_VOLUME
|
|
and value.get("controllerIntentDurable") is True,
|
|
"quiescence_not_proven",
|
|
)
|
|
_need(
|
|
isinstance(value.get("controllerIntentBindingSha256"), str)
|
|
and _SHA256.fullmatch(value["controllerIntentBindingSha256"]) is not None
|
|
and value["controllerIntentBindingSha256"] == _intent_binding(intent),
|
|
"controller_intent_binding_mismatch",
|
|
)
|
|
return {key: value[key] for key in sorted(required)}
|
|
|
|
|
|
def _snapshot_evidence(evidence: object, intent: dict[str, Any]) -> dict[str, Any]:
|
|
required = {
|
|
"intentId",
|
|
"globalDeployLockHeld",
|
|
"projectLockHeld",
|
|
"legacyContainerStopped",
|
|
"legacyVolume",
|
|
"legacyVolumeRetained",
|
|
"sqliteBackupMethod",
|
|
"quickCheck",
|
|
"integrityCheck",
|
|
"sourceLogicalSha256",
|
|
"snapshotLogicalSha256",
|
|
"snapshotFileSha256",
|
|
"sourceScanCount",
|
|
"snapshotScanCount",
|
|
"sourceCompactedScanCount",
|
|
"snapshotCompactedScanCount",
|
|
"sourceQueueSha256",
|
|
"snapshotQueueSha256",
|
|
"sourceAuxiliaryTreeSha256",
|
|
"sourceAuxiliaryFileCount",
|
|
"targetDataPathWasAbsent",
|
|
"snapshotReceiptDurable",
|
|
"legacyEnvironmentRetained",
|
|
"managedEnvironmentFilePrivate",
|
|
"controllerIntentBindingSha256",
|
|
}
|
|
_need(isinstance(evidence, dict) and set(evidence) == required, "snapshot_evidence_invalid")
|
|
value = evidence
|
|
_need(
|
|
value.get("intentId") == intent["intentId"]
|
|
and value.get("globalDeployLockHeld") is True
|
|
and value.get("projectLockHeld") is True
|
|
and value.get("legacyContainerStopped") is True
|
|
and value.get("legacyVolume") == LEGACY_VOLUME
|
|
and value.get("legacyVolumeRetained") is True
|
|
and value.get("sqliteBackupMethod") == "sqlite_online_backup"
|
|
and value.get("quickCheck") == "ok"
|
|
and value.get("integrityCheck") == "ok"
|
|
and isinstance(value.get("sourceLogicalSha256"), str)
|
|
and _SHA256.fullmatch(value["sourceLogicalSha256"]) is not None
|
|
and value.get("sourceLogicalSha256") == value.get("snapshotLogicalSha256")
|
|
and isinstance(value.get("snapshotFileSha256"), str)
|
|
and _SHA256.fullmatch(value["snapshotFileSha256"]) is not None
|
|
and _is_count(value.get("sourceScanCount"))
|
|
and value.get("sourceScanCount") == value.get("snapshotScanCount")
|
|
and _is_count(value.get("sourceCompactedScanCount"))
|
|
and value.get("sourceCompactedScanCount") == value.get("snapshotCompactedScanCount")
|
|
and isinstance(value.get("sourceQueueSha256"), str)
|
|
and _SHA256.fullmatch(value["sourceQueueSha256"]) is not None
|
|
and value.get("sourceQueueSha256") == value.get("snapshotQueueSha256")
|
|
and isinstance(value.get("sourceAuxiliaryTreeSha256"), str)
|
|
and _SHA256.fullmatch(value["sourceAuxiliaryTreeSha256"]) is not None
|
|
and _is_count(value.get("sourceAuxiliaryFileCount"))
|
|
and value.get("targetDataPathWasAbsent") is True
|
|
and value.get("snapshotReceiptDurable") is True
|
|
and value.get("legacyEnvironmentRetained") is True
|
|
and value.get("managedEnvironmentFilePrivate") is True,
|
|
"sqlite_snapshot_not_verified",
|
|
)
|
|
_need(
|
|
isinstance(value.get("controllerIntentBindingSha256"), str)
|
|
and _SHA256.fullmatch(value["controllerIntentBindingSha256"]) is not None
|
|
and value["controllerIntentBindingSha256"] == _intent_binding(intent),
|
|
"controller_intent_binding_mismatch",
|
|
)
|
|
return {key: value[key] for key in sorted(required)}
|
|
|
|
|
|
def _controller_registration_evidence(
|
|
evidence: object, intent: dict[str, Any]
|
|
) -> dict[str, Any]:
|
|
required = {
|
|
"receiptVersion",
|
|
"intentId",
|
|
"receiptSha256",
|
|
"repository",
|
|
"applicationId",
|
|
"enrollmentId",
|
|
"profileId",
|
|
"planSha256",
|
|
"snapshotFileSha256",
|
|
"targetLogicalSha256",
|
|
"targetScanCount",
|
|
"targetCompactedScanCount",
|
|
"targetQueueSha256",
|
|
"targetAuxiliaryTreeSha256",
|
|
"targetAuxiliaryFileCount",
|
|
"targetQueuedJobs",
|
|
"targetActiveJobs",
|
|
"targetQuickCheck",
|
|
"targetIntegrityCheck",
|
|
"targetRecoveryStatus",
|
|
"targetDataUid",
|
|
"targetDataGid",
|
|
"managedDataMountMatches",
|
|
"copySourceIsVerifiedSnapshot",
|
|
"registryTargetMatches",
|
|
"controllerStateReceiptVerified",
|
|
"deploymentReceiptSchema",
|
|
"deploymentReceiptSha256",
|
|
"deploymentReceiptVerified",
|
|
"runtimeHealthy",
|
|
"publishedHostPorts",
|
|
"globalDeployLockHeld",
|
|
"projectLockHeld",
|
|
"legacyVolume",
|
|
"legacyVolumeRetained",
|
|
"legacyEnvironmentRetained",
|
|
"managedEnvironmentFilePrivate",
|
|
"scanSourceMatches",
|
|
"scanMountReadOnly",
|
|
"scanPermissionPolicyPersistent",
|
|
"scanAccessForRuntimeIdentityReady",
|
|
"controllerIntentDurable",
|
|
"controllerIntentBindingSha256",
|
|
}
|
|
_need(
|
|
isinstance(evidence, dict) and set(evidence) == required,
|
|
"controller_receipt_evidence_invalid",
|
|
)
|
|
value = evidence
|
|
gates = intent["evidence"].get("targetGates")
|
|
snapshot = intent["evidence"].get("snapshot")
|
|
_need(isinstance(gates, dict) and isinstance(snapshot, dict), "adoption_prerequisite_missing")
|
|
_need(
|
|
value.get("receiptVersion") == CONTROLLER_RECEIPT_VERSION
|
|
and value.get("intentId") == intent["intentId"]
|
|
and isinstance(value.get("receiptSha256"), str)
|
|
and _SHA256.fullmatch(value["receiptSha256"]) is not None
|
|
and value.get("repository") == REPOSITORY
|
|
and value.get("applicationId") == APPLICATION_ID
|
|
and isinstance(value.get("enrollmentId"), str)
|
|
and _ENROLLMENT_ID.fullmatch(value["enrollmentId"]) is not None
|
|
and value.get("profileId") == gates["profileId"]
|
|
and value.get("planSha256") == gates["planSha256"]
|
|
and value.get("snapshotFileSha256") == snapshot["snapshotFileSha256"]
|
|
and isinstance(value.get("targetLogicalSha256"), str)
|
|
and value.get("targetLogicalSha256") == snapshot["snapshotLogicalSha256"]
|
|
and _is_count(value.get("targetScanCount"))
|
|
and value.get("targetScanCount") == snapshot["snapshotScanCount"]
|
|
and _is_count(value.get("targetCompactedScanCount"))
|
|
and value.get("targetCompactedScanCount") == snapshot["snapshotCompactedScanCount"]
|
|
and value.get("targetQueueSha256") == snapshot["snapshotQueueSha256"]
|
|
and value.get("targetAuxiliaryTreeSha256") == snapshot["sourceAuxiliaryTreeSha256"]
|
|
and value.get("targetAuxiliaryFileCount") == snapshot["sourceAuxiliaryFileCount"]
|
|
and _is_count(value.get("targetQueuedJobs"))
|
|
and value.get("targetQueuedJobs") == 0
|
|
and _is_count(value.get("targetActiveJobs"))
|
|
and value.get("targetActiveJobs") == 0
|
|
and value.get("targetQuickCheck") == "ok"
|
|
and value.get("targetIntegrityCheck") == "ok"
|
|
and value.get("targetRecoveryStatus") in {"healthy", "acknowledged"}
|
|
and value.get("targetDataUid") == 10001
|
|
and value.get("targetDataGid") == 10001
|
|
and value.get("managedDataMountMatches") is True
|
|
and value.get("copySourceIsVerifiedSnapshot") is True
|
|
and value.get("registryTargetMatches") is True
|
|
and value.get("controllerStateReceiptVerified") is True
|
|
and value.get("deploymentReceiptSchema") == "gitea-deploy-receipt.v1"
|
|
and isinstance(value.get("deploymentReceiptSha256"), str)
|
|
and _SHA256.fullmatch(value["deploymentReceiptSha256"]) is not None
|
|
and value.get("deploymentReceiptVerified") is True
|
|
and value.get("runtimeHealthy") is True
|
|
and value.get("publishedHostPorts") == []
|
|
and value.get("globalDeployLockHeld") is True
|
|
and value.get("projectLockHeld") is True
|
|
and value.get("legacyVolume") == LEGACY_VOLUME
|
|
and value.get("legacyVolumeRetained") is True
|
|
and value.get("legacyEnvironmentRetained") is True
|
|
and value.get("managedEnvironmentFilePrivate") is True
|
|
and value.get("scanSourceMatches") is True
|
|
and value.get("scanMountReadOnly") is True
|
|
and value.get("scanPermissionPolicyPersistent") is True
|
|
and value.get("scanAccessForRuntimeIdentityReady") is True
|
|
and value.get("controllerIntentDurable") is True
|
|
and value.get("controllerIntentBindingSha256") == _intent_binding(intent),
|
|
"controller_adoption_receipt_not_verified",
|
|
)
|
|
return {key: value[key] for key in sorted(required)}
|
|
|
|
|
|
def validate_controller_registration_evidence(
|
|
intent: object, evidence: object
|
|
) -> dict[str, Any]:
|
|
"""Validate fresh controller registration evidence against the intent.
|
|
|
|
This pure projection can validate receipt replays after AppOps reaches its
|
|
terminal `completed` phase. Controller response authentication remains the
|
|
caller's responsibility.
|
|
"""
|
|
current = _validate_intent(intent)
|
|
return _controller_registration_evidence(evidence, current)
|
|
|
|
|
|
def _appops_ownership_evidence(
|
|
evidence: object, intent: dict[str, Any]
|
|
) -> dict[str, Any]:
|
|
required = {
|
|
"repository",
|
|
"applicationId",
|
|
"enrollmentId",
|
|
"profileId",
|
|
"planSha256",
|
|
"controllerReceiptSha256",
|
|
"enrollmentPersisted",
|
|
"exactApplyPolicyPersisted",
|
|
"exactRollbackPolicyPersisted",
|
|
"secretReferenceReady",
|
|
}
|
|
_need(isinstance(evidence, dict) and set(evidence) == required, "appops_ownership_evidence_invalid")
|
|
value = evidence
|
|
registration = intent["evidence"].get("controllerRegistration")
|
|
_need(isinstance(registration, dict), "controller_registration_missing")
|
|
gates = intent["evidence"].get("targetGates")
|
|
_need(isinstance(gates, dict), "target_gates_missing")
|
|
_need(
|
|
value.get("repository") == REPOSITORY
|
|
and value.get("applicationId") == APPLICATION_ID
|
|
and value.get("enrollmentId") == registration["enrollmentId"]
|
|
and value.get("profileId") == registration["profileId"]
|
|
and value.get("planSha256") == gates["planSha256"]
|
|
and value.get("controllerReceiptSha256") == registration["receiptSha256"]
|
|
and all(
|
|
value.get(key) is True
|
|
for key in (
|
|
"enrollmentPersisted",
|
|
"exactApplyPolicyPersisted",
|
|
"exactRollbackPolicyPersisted",
|
|
"secretReferenceReady",
|
|
)
|
|
),
|
|
"appops_ownership_not_verified",
|
|
)
|
|
return {key: value[key] for key in sorted(required)}
|
|
|
|
|
|
def validate_appops_ownership_evidence(
|
|
intent: object, evidence: object
|
|
) -> dict[str, Any]:
|
|
"""Validate fixed ownership evidence for an idempotent completed replay."""
|
|
current = _validate_intent(intent)
|
|
return _appops_ownership_evidence(evidence, current)
|
|
|
|
|
|
def advance_intent(
|
|
intent: object, step: str, evidence: object
|
|
) -> dict[str, Any]:
|
|
"""Purely validate and record one forward protocol checkpoint.
|
|
|
|
The exact evidence digest makes retries idempotent. Replaying a step with
|
|
different evidence is a conflict and must enter recovery review.
|
|
"""
|
|
current = _validate_intent(intent)
|
|
_need(current.get("status") == "in_progress", "intent_requires_recovery")
|
|
_need(step in _STEP_CONTRACTS, "adoption_step_unknown")
|
|
source_phase, destination_phase = _STEP_CONTRACTS[step]
|
|
checked = {
|
|
"target_gates": _target_gates,
|
|
"quiescence": lambda value: _quiescence_evidence(value, current),
|
|
"snapshot": lambda value: _snapshot_evidence(value, current),
|
|
"controller_registration": lambda value: _controller_registration_evidence(value, current),
|
|
"appops_ownership": lambda value: _appops_ownership_evidence(value, current),
|
|
}[step](evidence)
|
|
digest = _digest(checked)
|
|
receipts = current["receipts"]
|
|
previous = receipts.get(step)
|
|
if previous is not None:
|
|
_need(
|
|
current.get("phase") in _PHASES[_PHASES.index(destination_phase):]
|
|
and isinstance(previous, dict)
|
|
and previous.get("evidenceSha256") == digest,
|
|
"idempotency_conflict",
|
|
)
|
|
return deepcopy(current)
|
|
_need(current.get("phase") == source_phase, "adoption_step_out_of_order")
|
|
|
|
updated = deepcopy(current)
|
|
updated["phase"] = destination_phase
|
|
updated["recovery"] = None
|
|
updated["receipts"][step] = {"evidenceSha256": digest}
|
|
evidence_key = _STEP_EVIDENCE_KEYS[step]
|
|
updated["evidence"][evidence_key] = checked
|
|
_validate_intent(updated)
|
|
return updated
|
|
|
|
|
|
def complete_intent(intent: object, evidence: object) -> dict[str, Any]:
|
|
"""Finalize only after re-reading both AppOps ownership and controller receipt."""
|
|
current = _validate_intent(intent)
|
|
_need(
|
|
current.get("status") == "in_progress"
|
|
and current.get("phase") == "appops_owned",
|
|
"adoption_not_ready_to_complete",
|
|
)
|
|
required = {
|
|
"intentId",
|
|
"repository",
|
|
"applicationId",
|
|
"enrollmentId",
|
|
"controllerReceiptSha256",
|
|
"enrollmentStillPersisted",
|
|
"exactApplyPolicyStillPersisted",
|
|
"exactRollbackPolicyStillPersisted",
|
|
"controllerReceiptStillMatches",
|
|
"legacyVolumeRetained",
|
|
}
|
|
_need(isinstance(evidence, dict) and set(evidence) == required, "completion_evidence_invalid")
|
|
value = evidence
|
|
ownership = current["evidence"].get("appopsOwnership")
|
|
registration = current["evidence"].get("controllerRegistration")
|
|
_need(isinstance(ownership, dict) and isinstance(registration, dict), "adoption_prerequisite_missing")
|
|
_need(
|
|
value.get("intentId") == current["intentId"]
|
|
and value.get("repository") == REPOSITORY
|
|
and value.get("applicationId") == APPLICATION_ID
|
|
and value.get("enrollmentId") == registration["enrollmentId"]
|
|
and value.get("controllerReceiptSha256") == registration["receiptSha256"]
|
|
and value.get("controllerReceiptSha256") == ownership["controllerReceiptSha256"]
|
|
and all(
|
|
value.get(key) is True
|
|
for key in (
|
|
"enrollmentStillPersisted",
|
|
"exactApplyPolicyStillPersisted",
|
|
"exactRollbackPolicyStillPersisted",
|
|
"controllerReceiptStillMatches",
|
|
"legacyVolumeRetained",
|
|
)
|
|
),
|
|
"completion_not_verified",
|
|
)
|
|
checked = {key: value[key] for key in sorted(required)}
|
|
updated = deepcopy(current)
|
|
updated["phase"] = "completed"
|
|
updated["status"] = "completed"
|
|
updated["evidence"]["completion"] = checked
|
|
updated["receipts"]["completion"] = {"evidenceSha256": _digest(checked)}
|
|
return _validate_intent(updated)
|
|
|
|
|
|
def mark_recovery_required(
|
|
intent: object, reason: str, failure_evidence: object
|
|
) -> dict[str, Any]:
|
|
"""Persist a sanitized recovery marker without compensating external state."""
|
|
current = _validate_intent(intent)
|
|
_need(current.get("status") != "completed", "intent_already_completed")
|
|
_need(reason in _RECOVERY_REASONS, "recovery_reason_invalid")
|
|
_need(isinstance(failure_evidence, dict), "recovery_evidence_invalid")
|
|
# Only bind the evidence; arbitrary external messages/paths/secret values
|
|
# are never copied into the journal by this pure protocol helper.
|
|
updated = deepcopy(current)
|
|
updated["status"] = "recovery_required"
|
|
updated["recovery"] = {
|
|
"reason": reason,
|
|
"afterPhase": current["phase"],
|
|
"failureEvidenceSha256": _digest(failure_evidence),
|
|
"controllerReconciliationRequired": True,
|
|
"legacyVolumePreserved": True,
|
|
}
|
|
return updated
|
|
|
|
|
|
def resume_intent(intent: object, controller_receipt: object) -> dict[str, Any]:
|
|
"""Resume only from an authenticated controller receipt bound to this intent.
|
|
|
|
The adapter that calls this function must authenticate the controller
|
|
response. This pure helper validates its exact content and only reconciles
|
|
the single ambiguous boundary: controller registration committed while the
|
|
AppOps intent checkpoint was interrupted.
|
|
"""
|
|
current = _validate_intent(intent)
|
|
_need(current.get("status") == "recovery_required", "intent_not_in_recovery")
|
|
required = {
|
|
"apiVersion",
|
|
"intentId",
|
|
"phase",
|
|
"receiptSha256",
|
|
"repository",
|
|
"applicationId",
|
|
"legacyVolumeRetained",
|
|
"intentDurable",
|
|
"intentBindingSha256",
|
|
"registrationEvidence",
|
|
}
|
|
_need(
|
|
isinstance(controller_receipt, dict)
|
|
and set(controller_receipt) == required,
|
|
"controller_recovery_receipt_invalid",
|
|
)
|
|
receipt = controller_receipt
|
|
_need(
|
|
receipt.get("apiVersion") == CONTROLLER_RECEIPT_VERSION
|
|
and receipt.get("intentId") == current["intentId"]
|
|
and receipt.get("repository") == REPOSITORY
|
|
and receipt.get("applicationId") == APPLICATION_ID
|
|
and receipt.get("legacyVolumeRetained") is True
|
|
and receipt.get("intentDurable") is True
|
|
and receipt.get("intentBindingSha256") == _intent_binding(current)
|
|
and isinstance(receipt.get("receiptSha256"), str)
|
|
and _SHA256.fullmatch(receipt["receiptSha256"]) is not None,
|
|
"controller_recovery_receipt_unbound",
|
|
)
|
|
phase = receipt.get("phase")
|
|
if phase == current["phase"]:
|
|
_need(
|
|
current["phase"] in {"reserved", "target_gates_ready", "quiesced", "snapshot_verified", "target_registered", "appops_owned"},
|
|
"controller_recovery_phase_invalid",
|
|
)
|
|
if phase in {"target_registered", "appops_owned"}:
|
|
registration = _controller_registration_evidence(
|
|
receipt.get("registrationEvidence"), current
|
|
)
|
|
_need(
|
|
receipt.get("receiptSha256") == registration["receiptSha256"]
|
|
and registration == current["evidence"].get("controllerRegistration"),
|
|
"controller_recovery_receipt_conflict",
|
|
)
|
|
else:
|
|
_need(
|
|
receipt.get("registrationEvidence") is None,
|
|
"controller_recovery_receipt_conflict",
|
|
)
|
|
updated = deepcopy(current)
|
|
updated["status"] = "in_progress"
|
|
updated["recovery"] = None
|
|
return updated
|
|
_need(
|
|
current.get("phase") == "snapshot_verified" and phase == "target_registered",
|
|
"controller_recovery_phase_conflict",
|
|
)
|
|
registration = _controller_registration_evidence(receipt.get("registrationEvidence"), current)
|
|
registration_digest = _digest(registration)
|
|
_need(receipt.get("receiptSha256") == registration["receiptSha256"], "controller_recovery_receipt_digest_mismatch")
|
|
updated = deepcopy(current)
|
|
updated["phase"] = "target_registered"
|
|
updated["status"] = "in_progress"
|
|
updated["recovery"] = None
|
|
updated["receipts"]["controller_registration"] = {"evidenceSha256": registration_digest}
|
|
updated["evidence"]["controllerRegistration"] = registration
|
|
_validate_intent(updated)
|
|
return updated
|
|
|
|
|
|
def intent_binding_sha256(intent: object) -> str:
|
|
"""Return the immutable root-controller journal binding for an intent."""
|
|
current = _validate_intent(intent)
|
|
return _intent_binding(current)
|
|
|
|
|
|
def execution_status() -> dict[str, Any]:
|
|
"""Report the code gate without claiming live target readiness."""
|
|
return {
|
|
"apiVersion": PROTOCOL_VERSION,
|
|
"repository": REPOSITORY,
|
|
"status": "enabled" if EXECUTION_ENABLED is True else "disabled",
|
|
"reason": None if EXECUTION_ENABLED is True else "controller_adoption_execution_disabled",
|
|
"executionEnabled": EXECUTION_ENABLED,
|
|
}
|