# `GgenIgniter.Controller`
[🔗](https://github.com/seanchatmangpt/ggen_igniter/blob/v26.9.8/lib/ggen_igniter/controller.ex#L1)

A persistent, BEAM-native reconciliation controller: a real `GenServer`
that holds live reconciliation state IN THIS PROCESS across multiple
`reconcile/2` calls -- in contrast to `mix ggen_igniter.sync`'s
per-invocation model, where every run is a fresh OS process with zero
in-memory continuity to the previous run (any "what did the last run do"
question can only be answered by re-reading disk, because there is no
process to ask).

Wraps `GgenIgniter.Reconcile.run/1` (the one real reconciliation pipeline
implementation, shared with `Mix.Tasks.GgenIgniter.Sync`) -- this module
adds no new reconciliation LOGIC of its own, only in-process state plus a
small public API around that one shared pipeline call.

## State shape

    %{
      last_reconciliations: %{
        pack_key => %{
          ontology_path: String.t(),
          manifest: %{out_path :: String.t() => outcome :: atom()},
          reconciliation_count: pos_integer(),
          last_run_at: DateTime.t(),
          receipt: map()
        }
      }
    }

`reconciliation_count` is real, process-only knowledge: it is NOT
recoverable by reading the output file alone (the file's content is
byte-identical after reconciliation #1 and #2 once nothing has changed --
that is the whole point of `Actuate.write_file!/3`'s idempotency guard),
yet this controller can still answer "how many times has this key actually
been reconciled, in this process" without touching disk. That is the
concrete, testable economic claim this architecture makes over the CLI's
model (see `test/ggen_igniter_controller_test.exs`).

## Fault isolation

A real failure inside `GgenIgniter.Reconcile.run/1` (bad ontology path,
missing template, engine error, ...) is caught in `handle_call/3` and
turned into a clean `{:error, reason}` reply -- it does NOT crash this
GenServer, and it does NOT touch any other `pack_key`'s already-stored good
record. This is a real, tested guarantee, not an assumption.

## Scope

This is a bounded, real proof-of-concept slice: no Reactor integration, no
distributed/multi-node topology, no crash-SUPERVISION-policy design (this
module is about fault ISOLATION between keys within one already-alive
GenServer -- a different concern from a Supervisor's restart strategy for
the GenServer itself, which this pass does not attempt to design).
Legitimate future directions, out of scope here.

## Wiring

Started as an OPT-IN child of `GgenIgniter.Application`'s supervision
tree, gated behind `Application.get_env(:ggen_igniter, :start_controller,
false)` (default `false`) -- a consuming application that wants it sets
`config :ggen_igniter, start_controller: true` explicitly. In a test (or
any other caller that wants its own instance), call `start_link/1`
directly instead of relying on the application's supervision tree.

# `pack_key`

```elixir
@type pack_key() :: term()
```

# `record`

```elixir
@type record() :: %{
  ontology_path: String.t(),
  manifest: %{optional(String.t()) =&gt; atom()},
  reconciliation_count: pos_integer(),
  last_run_at: DateTime.t(),
  receipt: map()
}
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `reconcile`

```elixir
@spec reconcile(GenServer.server(), pack_key(), keyword()) ::
  {:ok, record()} | {:error, term()}
```

Runs the real reconciliation pipeline (`GgenIgniter.Reconcile.run/1`) for
`pack_key`, using `reconcile_opts` (the same option shape
`Mix.Tasks.GgenIgniter.Sync` accepts, within `GgenIgniter.Reconcile.run/1`'s
own bounded scope -- see that module's moduledoc: `:ontology`/`:query`/
`:template`/`:pack`/`:pack_dir`/`:engine`/`:mode`/`:out`/`:unless_exists`/
`:skip_if`/`:dry_run`).

On success, updates this server's in-process state for `pack_key` and
returns `{:ok, record()}`. On a real pipeline failure, returns
`{:error, reason}` -- this server's state for `pack_key` (and every other
key) is left completely unchanged, and the server itself does not crash.

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

Starts the controller `GenServer`. `opts` are plain
`GenServer.start_link/3` options (`:name`, etc.) -- pass `name:
GgenIgniter.Controller` to start the singleton the application's
supervision tree expects; omit `:name` (as tests do) to start an
independent, unnamed instance addressed by its returned pid.

# `status`

```elixir
@spec status(GenServer.server(), pack_key()) :: {:ok, record()} | :never_reconciled
```

Returns the real, current in-memory record for `pack_key` -- `{:ok,
record()}` -- or `:never_reconciled` if this server has never
successfully reconciled that key. A pure in-process state read: this call
never touches disk.

---

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