GgenIgniter.Controller (ggen_igniter v26.9.8)

Copy Markdown View Source

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.

Summary

Functions

Returns a specification to start this module under a supervisor.

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).

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.

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.

Types

pack_key()

@type pack_key() :: term()

record()

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

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

reconcile(server, pack_key, reconcile_opts)

@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(opts \\ [])

@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(server, pack_key)

@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.