Files
Unraid-AppOps-Gateway-Public/backend/app/services/workmesh_promotion.py
T

723 lines
29 KiB
Python

"""Approval-bound promotion for the single registered WorkMesh product target."""
from __future__ import annotations
import hashlib
import json
import re
import uuid
from datetime import timedelta
from typing import Any
from sqlalchemy import update
from sqlmodel import Session, select
from ..core.time import utc_now
from ..db.models import Approval, WorkMeshPromotion
from .approval_service import approval_fingerprint
from .audit_logger import log_event
from .deployment_broker import DeploymentBrokerClient
from .secret_store import get_secret
_ACTION = "deployment.target.promote_workmesh"
_TARGET = "jens/workmesh"
_SECRET_REF = "WORKMESH_DEPLOYMENT_BROKER"
_API_VERSION = "itworx.workmesh-promotion/v2"
_REVISION = re.compile(r"^[0-9a-f]{40}$")
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
_IDEMPOTENCY = re.compile(r"^[A-Za-z0-9._:-]{8,128}$")
_CHECKS = {"exactMain", "pushValidation", "controllerPreflight", "dataBackup"}
_SAFE_PREFLIGHT_FIELDS = {
"apiVersion",
"status",
"repository",
"revision",
"preflightSha256",
"backupInputsSha256",
"checks",
}
_TERMINAL = {"succeeded", "failed", "preflight_changed"}
class WorkMeshPromotionError(ValueError):
"""A fail-closed error in the fixed-target promotion contract."""
def _idempotency_digest(value: str) -> str:
normalized = value.strip()
if not _IDEMPOTENCY.fullmatch(normalized):
raise WorkMeshPromotionError("Idempotency-Key must contain 8 to 128 safe characters.")
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
def _request_id(revision: str, idempotency_key: str) -> str:
value = json.dumps(
{
"repository": _TARGET,
"revision": revision,
"operation": "workmesh_promote",
"idempotency": idempotency_key,
},
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
return hashlib.sha256(value).hexdigest()
def _safe_preflight(result: dict[str, Any], revision: str) -> dict[str, Any] | None:
checks = result.get("checks")
digest = str(result.get("preflightSha256") or "")
if (
result.get("status") != "preflight_ok"
or result.get("apiVersion") != _API_VERSION
or result.get("repository") != _TARGET
or result.get("revision") != revision
or not _SHA256.fullmatch(digest)
or not _SHA256.fullmatch(str(result.get("backupInputsSha256") or ""))
or not isinstance(checks, dict)
or set(checks) != _CHECKS
or any(checks.get(key) != "passed" for key in _CHECKS)
):
return None
return {
key: result[key]
for key in _SAFE_PREFLIGHT_FIELDS
if key in result
}
def _serialize(
row: WorkMeshPromotion,
*,
idempotent_replay: bool = False,
approval_status: str | None = None,
) -> dict[str, Any]:
return {
"promotionId": row.id,
"jobId": row.id,
"revision": row.revision,
"status": row.status,
"stage": row.stage,
"preflight": dict(row.preflight_json or {}),
"approvalId": row.approval_id,
"approvalStatus": approval_status,
"request": row.broker_request_id,
"evidence": dict(row.evidence_json or {}),
"result": row.result_summary,
"auditId": row.audit_event_id,
"createdAt": row.created_at.isoformat(),
"updatedAt": row.updated_at.isoformat(),
"completedAt": row.completed_at.isoformat() if row.completed_at else None,
"idempotentReplay": idempotent_replay,
}
def _record_preflight_error(result: dict[str, Any]) -> str:
# Never surface arbitrary controller response text or its transport details.
status = str(result.get("status") or "unavailable")
if status in {"unreachable", "unavailable", "unknown", "missing_config", "missing_secret"}:
return "The fixed WorkMesh controller preflight is unavailable."
if status in {"preflight_failed", "preflight_changed", "deployment_policy_rejected"}:
return "The fixed WorkMesh controller rejected the requested revision preflight."
return "The controller response did not satisfy the WorkMesh promotion contract."
class WorkMeshPromotionService:
def __init__(self, *, broker: DeploymentBrokerClient | None = None):
self.broker = broker or DeploymentBrokerClient()
def preflight(
self,
session: Session,
*,
revision: str,
idempotency_key: str,
) -> dict[str, Any]:
if not _REVISION.fullmatch(revision):
raise WorkMeshPromotionError("revision must be a full lowercase 40-character Git SHA.")
key_digest = _idempotency_digest(idempotency_key)
existing = session.exec(
select(WorkMeshPromotion).where(
WorkMeshPromotion.idempotency_sha256 == key_digest
)
).first()
if existing:
if existing.revision != revision:
raise WorkMeshPromotionError(
"Idempotency-Key was already used for a different WorkMesh revision."
)
if existing.approval_id:
approval = session.get(Approval, existing.approval_id)
return _serialize(
existing,
idempotent_replay=True,
approval_status=approval.status if approval else None,
)
raise WorkMeshPromotionError(
"The original preflight request is incomplete and requires operator review."
)
try:
secret = get_secret(session, _SECRET_REF, touch=False)
except Exception:
secret = None
if not secret:
raise WorkMeshPromotionError(
"The fixed WorkMesh deployment-controller secret is not configured."
)
try:
result = self.broker.workmesh_promotion_preflight(revision, secret=secret)
except Exception as exc:
raise WorkMeshPromotionError(
"The fixed WorkMesh controller preflight is unavailable."
) from exc
if not isinstance(result, dict):
result = {"status": "invalid_response"}
safe = _safe_preflight(result, revision)
if safe is None:
raise WorkMeshPromotionError(_record_preflight_error(result))
promotion_id = f"workmesh-prom-{uuid.uuid4().hex}"
context = {
"apiVersion": _API_VERSION,
"promotionId": promotion_id,
"repository": _TARGET,
"revision": revision,
"preflightSha256": safe["preflightSha256"],
"backupInputsSha256": safe["backupInputsSha256"],
"preflight": safe,
}
approval = Approval(
id=f"appr-{uuid.uuid4().hex[:12]}",
action=_ACTION,
target=_TARGET,
source="manual",
risk="high",
status="pending",
requester="operator",
reason=(
f"Promote WorkMesh revision {revision} after preflight digest "
f"{safe['preflightSha256']}."
),
expires_at=utc_now() + timedelta(minutes=30),
request_context_json=context,
action_fingerprint=approval_fingerprint(
_ACTION, _TARGET, "manual", "high", context
),
)
row = WorkMeshPromotion(
id=promotion_id,
revision=revision,
preflight_sha256=safe["preflightSha256"],
idempotency_sha256=key_digest,
status="approval_required",
stage="preflight_accepted",
preflight_json=safe,
approval_id=approval.id,
)
# The approval and its immutable preflight binding become durable together.
session.add(row)
session.add(approval)
session.commit()
session.refresh(row)
audit = log_event(
session,
source="deployment",
identity="operator",
action_id=_ACTION,
target=_TARGET,
target_type="approval",
risk="high",
policy_decision="approval_required",
reason="WorkMesh promotion preflight passed for the exact revision.",
result="approval_requested",
execution_plan={
"steps": [
"Verify exact protected-main revision and push validation",
"Run fixed WorkMesh controller preflight",
"Bind operator approval to the immutable preflight digest",
"Verify a PostgreSQL and n8n backup with isolated restore evidence before apply",
],
"promotionId": row.id,
"revision": revision,
"preflightSha256": row.preflight_sha256,
"backupInputsSha256": safe["backupInputsSha256"],
"approvalId": approval.id,
},
redacted_metadata={"promotionId": row.id, "approvalId": approval.id},
)
row.audit_event_id = audit.id
row.updated_at = utc_now()
session.add(row)
session.commit()
session.refresh(row)
return _serialize(row, approval_status=approval.status)
def execute_approved(self, session: Session, approval: Approval) -> dict[str, Any]:
context = dict(approval.request_context_json or {})
promotion_id = str(context.get("promotionId") or "")
row = session.get(WorkMeshPromotion, promotion_id)
if (
approval.action != _ACTION
or approval.target != _TARGET
or approval.source != "manual"
or approval.risk != "high"
or approval.status != "approved"
or (approval.expires_at is not None and approval.expires_at <= utc_now())
or context.get("apiVersion") != _API_VERSION
or not row
or row.approval_id != approval.id
or row.revision != context.get("revision")
or row.preflight_sha256 != context.get("preflightSha256")
or dict(row.preflight_json or {}).get("backupInputsSha256")
!= context.get("backupInputsSha256")
):
return {
"success": False,
"status": "failed",
"result": "blocked",
"message": "Approval is not bound to a valid fixed WorkMesh promotion preflight.",
}
if row.status != "approval_required":
# The apply boundary is one-shot. This also closes the crash window
# after persisting `submitting` but before learning the HTTP outcome.
return self._approval_execution_result(row)
try:
secret = get_secret(session, _SECRET_REF, touch=False)
except Exception:
secret = None
if not secret:
return {
"success": False,
"status": "failed",
"result": "blocked",
"message": "The fixed WorkMesh deployment-controller secret is unavailable.",
}
try:
current = self.broker.workmesh_promotion_preflight(row.revision, secret=secret)
except Exception:
current = {"status": "unavailable"}
if not isinstance(current, dict):
current = {"status": "invalid_response"}
safe = _safe_preflight(current, row.revision)
if (
safe is None
or safe["preflightSha256"] != row.preflight_sha256
or safe["backupInputsSha256"]
!= dict(row.preflight_json or {}).get("backupInputsSha256")
):
now = utc_now()
claim = session.exec(
update(WorkMeshPromotion)
.where(
(WorkMeshPromotion.id == row.id)
& (WorkMeshPromotion.status == "approval_required")
)
.values(
status="preflight_changed",
stage="approval_invalidated",
evidence_json={"preflightChanged": True},
result_summary="The controller preflight changed after approval; no promotion was queued.",
completed_at=now,
updated_at=now,
)
)
if claim.rowcount != 1:
session.rollback()
session.refresh(row)
return self._approval_execution_result(row)
approval.status = "expired"
approval.decided_at = now
session.add(approval)
session.commit()
session.refresh(row)
audit = log_event(
session,
source="approval",
identity="operator",
action_id=_ACTION,
target=_TARGET,
target_type="approval",
risk="high",
policy_decision="approval_required",
reason="WorkMesh preflight changed after operator approval; promotion was not queued.",
result="blocked",
redacted_metadata={"promotionId": row.id, "revision": row.revision},
)
row.audit_event_id = audit.id
row.updated_at = utc_now()
session.add(row)
session.commit()
return {
"success": False,
"status": row.status,
"result": "preflight_changed",
"message": row.result_summary,
"jobId": row.id,
"auditId": audit.id,
}
idempotency_key = f"workmesh-promotion:{row.id}"
request_id = _request_id(row.revision, idempotency_key)
# This durable marker is committed before the only mutating controller
# request. Re-entry reconciles status and never repeats apply.
now = utc_now()
claim = session.exec(
update(WorkMeshPromotion)
.where(
(WorkMeshPromotion.id == row.id)
& (WorkMeshPromotion.status == "approval_required")
)
.values(
status="submitting",
stage="controller_request_submitted",
broker_request_id=request_id,
updated_at=now,
)
)
session.commit()
if claim.rowcount != 1:
session.refresh(row)
return self._approval_execution_result(row)
session.refresh(row)
audit = log_event(
session,
source="approval",
identity="operator",
action_id=_ACTION,
target=_TARGET,
target_type="approval",
risk="high",
policy_decision="approval_required",
reason="Submitting the exact approved WorkMesh revision to the fixed controller target.",
result="pending",
execution_plan={
"promotionId": row.id,
"revision": row.revision,
"preflightSha256": row.preflight_sha256,
"backupInputsSha256": dict(row.preflight_json or {}).get("backupInputsSha256"),
"controllerRequest": request_id,
},
redacted_metadata={"promotionId": row.id, "approvalId": approval.id},
)
row.audit_event_id = audit.id
session.add(row)
session.commit()
try:
result = self.broker.workmesh_promotion_apply(
row.revision,
secret=secret,
preflight_sha256=row.preflight_sha256,
idempotency_key=idempotency_key,
)
except Exception:
result = {"status": "unavailable"}
if not isinstance(result, dict):
result = {"status": "invalid_response"}
return self._accept_apply_result(session, row, result, request_id)
def _accept_apply_result(
self,
session: Session,
row: WorkMeshPromotion,
result: dict[str, Any],
request_id: str,
) -> dict[str, Any]:
identity_matches = (
result.get("request") == request_id
and result.get("repository") == _TARGET
and result.get("revision") == row.revision
and result.get("operation") == "workmesh_promote"
)
status = str(result.get("status") or "unknown")
if not identity_matches:
self._mark_outcome_unknown(session, row)
return self._approval_execution_result(row)
if (
status == "preflight_changed"
and result.get("reason") == "approved_preflight_no_longer_matches"
):
now = utc_now()
current_digest = str(result.get("preflightSha256") or "")
row.status = "preflight_changed"
row.stage = "approval_invalidated"
row.evidence_json = {
"preflightChanged": True,
**(
{"currentPreflightSha256": current_digest}
if _SHA256.fullmatch(current_digest)
else {}
),
}
row.result_summary = (
"Controller preflight changed before queueing; no deployment was started and approval expired."
)
row.completed_at = now
row.updated_at = now
approval = session.get(Approval, row.approval_id) if row.approval_id else None
if approval and approval.status == "approved":
approval.status = "expired"
approval.decided_at = now
session.add(approval)
session.add(row)
audit = log_event(
session,
source="approval",
identity="operator",
action_id=_ACTION,
target=_TARGET,
target_type="approval",
risk="high",
policy_decision="approval_required",
reason=(
"Controller preflight changed before queueing; no deployment was started "
"and the approval was expired."
),
result="blocked",
execution_plan={
"promotionId": row.id,
"revision": row.revision,
"approvedPreflightSha256": row.preflight_sha256,
"controllerRequest": request_id,
},
redacted_metadata={"promotionId": row.id, "approvalId": row.approval_id},
)
row.audit_event_id = audit.id
row.updated_at = utc_now()
session.add(row)
session.commit()
return self._approval_execution_result(row)
if (
status == "deployed"
and _SHA256.fullmatch(str(result.get("receiptSha256") or ""))
and _SHA256.fullmatch(str(result.get("backupReceiptSha256") or ""))
):
row.status = "succeeded"
row.stage = "receipt_verified"
row.evidence_json = {
"repository": _TARGET,
"revision": row.revision,
"operation": "workmesh_promote",
"receiptSha256": result["receiptSha256"],
"backupReceiptSha256": result["backupReceiptSha256"],
}
row.result_summary = "The exact WorkMesh revision has a verified controller receipt."
row.completed_at = utc_now()
elif status in {"accepted", "already_queued", "pending", "running"}:
row.status = "applying"
row.stage = "controller_accepted"
row.evidence_json = {
"repository": _TARGET,
"revision": row.revision,
"operation": "workmesh_promote",
"backupInputsSha256": dict(row.preflight_json or {}).get("backupInputsSha256"),
}
row.result_summary = "The fixed controller accepted the WorkMesh promotion; reconcile its receipt."
elif status == "failed" and result.get("reason") in {
"controller_rejected_before_mutation",
"workmesh_preflight_changed",
}:
row.status = "preflight_changed"
row.stage = "approval_invalidated"
row.evidence_json = {"preflightChanged": True, "noMutation": True}
row.result_summary = (
"The controller rejected the approved backup preflight before deployment; approval expired."
)
approval = session.get(Approval, row.approval_id) if row.approval_id else None
if approval and approval.status == "approved":
approval.status = "expired"
approval.decided_at = utc_now()
session.add(approval)
row.completed_at = utc_now()
audit = log_event(
session,
source="approval",
identity="operator",
action_id=_ACTION,
target=_TARGET,
target_type="approval",
risk="high",
policy_decision="approval_required",
reason="Controller rejected the approved WorkMesh backup preflight before mutation.",
result="blocked",
execution_plan={
"promotionId": row.id,
"revision": row.revision,
"preflightSha256": row.preflight_sha256,
"backupInputsSha256": dict(row.preflight_json or {}).get("backupInputsSha256"),
"controllerRequest": request_id,
},
redacted_metadata={"promotionId": row.id, "approvalId": row.approval_id},
)
row.audit_event_id = audit.id
elif status == "failed":
row.status = "failed"
row.stage = "controller_failed"
row.evidence_json = {"repository": _TARGET, "revision": row.revision}
row.result_summary = "The fixed controller recorded a failed WorkMesh promotion."
row.completed_at = utc_now()
else:
self._mark_outcome_unknown(session, row)
return self._approval_execution_result(row)
row.updated_at = utc_now()
session.add(row)
session.commit()
return self._approval_execution_result(row)
@staticmethod
def _mark_outcome_unknown(session: Session, row: WorkMeshPromotion) -> None:
row.status = "outcome_unknown"
row.stage = "reconciliation_required"
row.evidence_json = {"reconciliationRequired": True}
row.result_summary = (
"Controller acceptance is ambiguous. Reconcile the fixed request status; do not resubmit."
)
row.updated_at = utc_now()
session.add(row)
session.commit()
@staticmethod
def _approval_execution_result(row: WorkMeshPromotion) -> dict[str, Any]:
# Once apply might have crossed the network boundary, success here means
# the approval was consumed and cannot be executed a second time.
return {
"success": row.status != "preflight_changed",
"status": row.status,
"result": row.status,
"message": row.result_summary or "WorkMesh promotion request was recorded.",
"jobId": row.id,
"auditId": row.audit_event_id or "",
}
def refresh(self, session: Session, promotion_id: str) -> dict[str, Any] | None:
row = session.get(WorkMeshPromotion, promotion_id)
if not row:
return None
if row.status in _TERMINAL or not row.broker_request_id:
approval = session.get(Approval, row.approval_id) if row.approval_id else None
return _serialize(row, approval_status=approval.status if approval else None)
try:
secret = get_secret(session, _SECRET_REF, touch=False)
except Exception:
secret = None
if not secret:
approval = session.get(Approval, row.approval_id) if row.approval_id else None
return _serialize(row, approval_status=approval.status if approval else None)
try:
result = self.broker.workmesh_promotion_status(
row.broker_request_id, secret=secret
)
except Exception:
result = {"status": "unavailable"}
if not isinstance(result, dict):
result = {"status": "unavailable"}
matches = (
result.get("request") == row.broker_request_id
and result.get("repository") == _TARGET
and result.get("revision") == row.revision
and result.get("operation") == "workmesh_promote"
)
observed_status = str(result.get("status") or "unknown")
previous = row.status
if matches and observed_status == "deployed":
receipt_sha256 = str(result.get("receiptSha256") or "")
backup_receipt_sha256 = str(result.get("backupReceiptSha256") or "")
if _SHA256.fullmatch(receipt_sha256) and _SHA256.fullmatch(backup_receipt_sha256):
row.status = "succeeded"
row.stage = "receipt_verified"
row.evidence_json = {
"repository": _TARGET,
"revision": row.revision,
"operation": "workmesh_promote",
"receiptSha256": receipt_sha256,
"backupReceiptSha256": backup_receipt_sha256,
}
row.result_summary = "The exact WorkMesh revision has a verified controller receipt."
row.completed_at = utc_now()
elif matches and observed_status in {"pending", "running", "accepted", "already_queued"}:
row.status = "applying"
row.stage = "controller_running"
row.evidence_json = {
"repository": _TARGET,
"revision": row.revision,
"operation": "workmesh_promote",
"backupInputsSha256": dict(row.preflight_json or {}).get("backupInputsSha256"),
}
elif matches and observed_status == "failed" and result.get("reason") in {
"controller_rejected_before_mutation",
"workmesh_preflight_changed",
}:
row.status = "preflight_changed"
row.stage = "approval_invalidated"
row.evidence_json = {"preflightChanged": True, "noMutation": True}
row.result_summary = (
"The controller rejected the approved backup preflight before deployment; approval expired."
)
approval = session.get(Approval, row.approval_id) if row.approval_id else None
if approval and approval.status == "approved":
approval.status = "expired"
approval.decided_at = utc_now()
session.add(approval)
row.completed_at = utc_now()
elif matches and observed_status == "failed":
row.status = "failed"
row.stage = "controller_failed"
row.evidence_json = {"repository": _TARGET, "revision": row.revision}
row.result_summary = "The fixed controller recorded a failed WorkMesh promotion."
row.completed_at = utc_now()
else:
# Not found, transport failure, missing receipt, or identity drift
# cannot prove that it is safe to submit again.
row.status = "outcome_unknown"
row.stage = "reconciliation_required"
row.evidence_json = {"reconciliationRequired": True}
row.result_summary = (
"Controller acceptance is not verified. Keep reconciling this request; do not resubmit."
)
row.updated_at = utc_now()
session.add(row)
session.commit()
session.refresh(row)
if row.status != previous:
audit = log_event(
session,
source="deployment",
identity="operator",
action_id=_ACTION,
target=_TARGET,
target_type="deployment",
risk="high",
policy_decision="approval_required",
reason="Reconciled the fixed WorkMesh controller request without resubmission.",
result="success" if row.status == "succeeded" else row.status,
execution_plan={"promotionId": row.id, "revision": row.revision},
redacted_metadata={"promotionId": row.id, "controllerStatus": observed_status},
)
row.audit_event_id = audit.id
row.updated_at = utc_now()
session.add(row)
session.commit()
approval = session.get(Approval, row.approval_id) if row.approval_id else None
return _serialize(row, approval_status=approval.status if approval else None)
@staticmethod
def list_promotions(session: Session, *, limit: int = 20) -> list[dict[str, Any]]:
rows = session.exec(
select(WorkMeshPromotion)
.order_by(WorkMeshPromotion.created_at.desc())
.limit(max(1, min(limit, 50)))
).all()
# Listing is a durable local read. Reconciliation is an explicit GET by
# promotion id so page loads never create an unbounded controller poll.
result = []
for row in rows:
approval = session.get(Approval, row.approval_id) if row.approval_id else None
result.append(_serialize(row, approval_status=approval.status if approval else None))
return result