Skip to content

Reconciliation

CrossXCloud treats your canvas as the desired state. To plan a change, it compares that desired state against what actually exists and works out the operations to close the gap. This is the reconciliation loop, and it is the “why” behind the Plan and Apply guide.

flowchart LR
    subgraph Edit
        A[Canvas] --> B[Desired state]
    end
    subgraph Plan
        C[Read live state] --> D[Diff]
        B --> D
        D --> E[Ordered ops]
    end
    subgraph Apply
        E --> F[Execute sequentially]
        F --> G[Commit]
    end

The desired state is what you have drawn on the canvas. It is a graph of resources and the edges between them, serialized from the editor’s node and edge stores.

The live state is what actually exists in your cloud account right now, read fresh each time you Plan. CrossXCloud only reads resources it manages, by filtering on the managed-by=crossxcloud label that every created resource gets. Each managed resource also carries a crossxcloud-node label that maps it back to the canvas node that produced it.

The diff between the two is the plan.

Desired state Live state
Source The canvas (nodes and edges) Your cloud account, read via provider APIs
Keyed by Editor node ID crossxcloud-node label on the provider resource
Includes Every node on the canvas Only resources tagged managed-by=crossxcloud
When read Continuously as you edit Freshly on every Plan

The diff produces five kinds of operations:

Operation When it fires Example
Create A node exists on the canvas but not in your account. You dragged a new compute node onto the canvas.
Update A resource exists in both, but a mutable field changed. You renamed a compute node or changed a private network’s IP range.
Delete A managed resource exists in your account but not on the canvas. You deleted a node that had already been applied.
Attach An edge was added between two existing resources. You wired a compute node into a private network.
Detach An edge was removed between two existing resources. You disconnected a floating IP from a compute node.

Some fields are immutable on the provider. Changing one of those on an existing node does not produce an in-place update. It produces a labeled delete-plus-create, so you can see that you are replacing the resource, not silently mutating it.

Operations run in dependency order so that every operation is valid against the state the previous one produced. The ordering is:

flowchart TD
    A[Create networks] --> B[Create servers]
    B --> C[Attach memberships]
    C --> D[Update in place]
    D --> E[Detach memberships]
    E --> F[Delete servers]
    F --> G[Delete networks]

Deletions run in reverse. A network is not deleted until every server in it has been detached and destroyed first.

CrossXCloud updates mutable fields in place where the provider allows it. The fields that support in-place update today:

Resource Mutable fields
Compute node Name
Private network Name, IP range

Everything else is a replace (delete-plus-create). This keeps apply small and quick when you only changed something cosmetic, and it makes destructive changes visible in the plan.

When you Execute, every operation in the plan runs sequentially without further confirmation. A summary card shows live progress as each operation completes.

sequenceDiagram
    participant User
    participant Canvas
    participant Reconcile as Reconcile Engine
    participant Provider as Cloud Provider
    participant History

    User->>Canvas: Click Plan
    Canvas->>Reconcile: Diff(desired, live)
    Reconcile->>Provider: Read current state
    Provider-->>Reconcile: Live resources
    Reconcile-->>Canvas: Ordered ops
    Canvas->>User: Show plan with diff badges
    User->>Canvas: Click Execute
    loop Each operation
        Canvas->>Reconcile: ExecuteOp(op)
        Reconcile->>Provider: Create / Update / Delete / Attach / Detach
        Provider-->>Reconcile: Result
        Reconcile-->>Canvas: Op status
    end
    Canvas->>History: Write commit + reconciliation record
    Canvas->>User: Show completion

There is no resume-from-failed-op. After a failure, the canvas is unchanged, and you Plan again. The new plan is computed against the partially applied live state, so it picks up exactly where things left off, like a rebase replaying onto a new base.

Attach and detach operations are routed to the correct provider API based on the target resource:

Target kind Attach action Detach action
Private network Attach server to network Detach server from network
Floating IP Assign floating IP to server Unassign floating IP from server

This routing happens at execute time. The resource IDs for membership operations are resolved at diff time, so the executor does not need to look them up again.

A canvas can mix providers. When you Plan, the diff produces executable operations for live providers (Hetzner, Google Cloud) and notices for scaffolded providers (AWS, Azure). Notices appear in the plan so you can see what would happen if those providers were live, rather than having those nodes silently ignored.

When you open a project canvas for the first time and the persisted graph is empty, CrossXCloud runs an initial reconciliation. It reads the live state from your connected provider and lays it out as nodes on the canvas. This is how existing infrastructure becomes visible.

After that, the canvas is the source of truth. Live state is only read again when you Plan.

A commit is only written at Execute time, never at edit or Plan time, and never for edits that did not actually hit the provider. The history is a faithful record of what actually changed in your cloud. Every Execute writes a commit, whether the apply was fully successful, partially applied, or failed.

Event Commit written?
Edit the canvas No
Run Plan No
Execute (all ops succeed) Yes, outcome: applied
Execute (some ops succeed) Yes, outcome: partial
Execute (all ops fail) Yes, outcome: failed

See Version history for how commits are stored and browsed.