# `Linx.Reconcile`
[🔗](https://github.com/oshlabs/linx/blob/v0.2.0/lib/linx/reconcile.ex#L1)

An opt-in, level-triggered reconcile loop over a single subsystem.

This is the long-lived control loop that turns Linx's single-shot
`reconcile` mechanism into continuous convergence: it re-observes and
re-diffs on a timer (the *correctness* mechanism — manual drift, crashes, and
reboots are corrected on the next pass), and reacts within milliseconds to a
subsystem Monitor when one is available (a *latency* layer over the timer).
Events are hints; resync is truth.

It is deliberately **thin and genuinely opt-in** — a primitives library that
refuses to become a runtime offers this as a separable convenience, never a
default:

  * **Zero footprint if absent.** Nothing here runs unless you start it. There
    is no `Application` boot side effect and no auto-supervised process; the
    `reconcile` and `Monitor` primitives work fully standalone.
  * **Your tree, not ours.** You add `{Linx.Reconcile, opts}` to *your* own
    supervision tree. Linx ships no supervisor that bundles it in.
  * **No hidden global state.** It holds only the desired state and
    `last_applied` you give it; there is no singleton or registry.
  * **Single-subsystem by construction.** It drives one
    `Linx.Reconcile.Source` in one namespace. The cross-subsystem composite
    ("this container, with this network, in this cgroup") is the consumer's —
    it is not, and cannot be, expressed here.
  * **Separable.** It is implemented entirely on the public `Source` contract
    (single-shot `reconcile` + an optional Monitor `subscribe`). Deleting it
    would cost a consumer only a ~15-line timer loop, nothing load-bearing.

It is especially handy for the simplest consumer — a plain host-network or
sysctl config app with no supervision tree of its own to roll a loop from —
for which it may be the *recommended* easy path, while still never automatic.

## Usage

Add it to your own supervision tree, naming the `Linx.Reconcile.Source`
adapter for the subsystem, the `scope` (the namespace, in that subsystem's
shape), and the desired state:

    children = [
      {Linx.Reconcile,
       source: Linx.Sysctl.Reconcile.Source,
       scope: :self,
       desired: %{"net.ipv4.ip_forward" => 1},
       name: :host_sysctls}
    ]
    Supervisor.start_link(children, strategy: :one_for_one)

or for rtnl, monitoring a network namespace by pid:

    {Linx.Reconcile,
     source: Linx.Netlink.Rtnl.Reconcile.Source,
     scope: {:pid, container_pid},
     desired: %{addresses: [{"eth0", "10.0.0.2", 24}], routes: [{:default, "10.0.0.1"}]},
     interval: :timer.seconds(30)}

## Options

  * `:source` (required) — a module implementing `Linx.Reconcile.Source`.
  * `:scope` (required) — the namespace handle, in the source's shape.
  * `:desired` (required) — the desired state, in the source's shape.
  * `:reconcile_opts` — keyword forwarded verbatim to the source's
    `reconcile/4` (e.g. rtnl's `:protocol`, sysctl's `:revert_on_release`).
  * `:last_applied` — initial ownership state (default `%{}`).
  * `:interval` — timer-resync period in ms, or `:infinity` to disable the
    timer and rely solely on the Monitor. Default `5000`.
  * `:monitor` — whether to subscribe to the source's Monitor for low-latency
    wakeups (default `true`; a source that returns `:unsupported` falls back
    to timer-only transparently).
  * `:debounce` — ms to coalesce a burst of Monitor hints (or a `put_desired`)
    into one pass. Default `100`.
  * `:owner` — a pid to notify after each pass with
    `{:linx_reconcile, :report, report}` / `{:linx_reconcile, :error, reason}`
    (default: no notifications).
  * `:name` — a `GenServer` name to register under.

## Failure model

When `monitor: true`, the loop **links** to the Monitor it starts: if the
Monitor dies the loop dies, and a supervisor restart resubscribes and does a
full resync from scratch (which is correct — resync is truth). A reconcile
pass that returns `{:error, _}` does not crash the loop; it keeps the previous
`last_applied` and retries on the next tick.

# `option`

```elixir
@type option() ::
  {:source, module()}
  | {:scope, Linx.Reconcile.Source.scope()}
  | {:desired, Linx.Reconcile.Source.desired()}
  | {:reconcile_opts, keyword()}
  | {:last_applied, Linx.Reconcile.Source.last_applied()}
  | {:interval, pos_integer() | :infinity}
  | {:monitor, boolean()}
  | {:debounce, non_neg_integer()}
  | {:owner, pid()}
  | {:name, GenServer.name()}
```

# `last_report`

```elixir
@spec last_report(GenServer.server()) :: Linx.Reconcile.Source.report() | nil
```

Returns the most recent reconcile report, or `nil` if no pass has completed yet.

# `put_desired`

```elixir
@spec put_desired(GenServer.server(), Linx.Reconcile.Source.desired()) :: :ok
```

Replaces the desired state and schedules a (debounced) reconcile to converge
on it. Returns `:ok` immediately.

# `reconcile`

```elixir
@spec reconcile(GenServer.server(), timeout()) ::
  {:ok, Linx.Reconcile.Source.report()} | {:error, term()}
```

Forces one reconcile pass now, synchronously, and returns its result
(`{:ok, report}` or `{:error, reason}`). Useful in tests and for a consumer
that wants to converge on demand without waiting for the next tick.

# `start_link`

```elixir
@spec start_link([option()]) :: GenServer.on_start()
```

Starts the loop. See the moduledoc for options.

# `stop`

```elixir
@spec stop(GenServer.server()) :: :ok
```

Stops the loop (and, via the link, its Monitor).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
