defmodule ArcadiaCloudWeb.DriftController do @moduledoc """ Drift inbox — operator surface for resources whose live state has diverged from the desired-state we provisioned. platform_admin only. accept: live value becomes the new desired-state. Revert (mutating live infra back to spec) is deferred — it requires a saga and lands with the droplet-resize work. """ use ArcadiaCloudWeb, :controller alias ArcadiaCloud.Drift def index(conn, _params) do with :ok <- require_platform_admin(conn) do drift = Drift.list_open_drift() |> Enum.map(&shape/1) json(conn, %{drift: drift, count: length(drift)}) end end def accept(conn, %{"id" => id}) do with :ok <- require_platform_admin(conn) do case Drift.get_drift(id) do nil -> conn |> put_status(:not_found) |> json(%{error: "not_found"}) %{status: "open"} = drift -> actor = conn.assigns.current_identity.email || "operator" {:ok, _} = Drift.accept_drift(drift, actor: actor) json(conn, %{status: "accepted", drift_id: id}) %{status: status} -> conn |> put_status(:conflict) |> json(%{error: "already_resolved", current_status: status}) end end end defp require_platform_admin(conn) do identity = conn.assigns.current_identity if is_list(identity.roles) and "platform_admin" in identity.roles do :ok else conn |> put_status(:forbidden) |> json(%{error: "platform_admin_required"}) |> halt() end end defp shape(d) do %{ id: d.id, resource_id: d.resource_id, resource_name: d.resource && d.resource.name, resource_kind: d.resource && d.resource.kind, field: d.field, expected: d.expected, actual: d.actual, status: d.status, detected_at: d.detected_at } end end