# `mix ggen_igniter.plan`
[🔗](https://github.com/seanchatmangpt/ggen_igniter/blob/v26.9.8/lib/mix/tasks/ggen_igniter.plan.ex#L1)

Read-only admission preview: `mix ggen_igniter.plan --template path.eex [--pack NAME |
--pack-dir DIR] --query name=path.rq (repeatable) [--engine oxigraph|sparql|qlever]
[--store-id ID] [--json]`.

Runs the SAME observe -> load ontology -> resolve pack -> run queries -> render ->
admit sequence `GgenIgniter.Reactors.ReconcileReactor.run/1` runs for a real `mix
ggen_igniter.sync` (with `use_reactor: true`), via
`GgenIgniter.Reactors.ReconcileReactor.plan/1` -- but STOPS before `:actuate`. Every
`%GgenIgniter.PendingActuation{}` the admitted plan would write is reported; nothing
is ever written to disk, and no compensation/rollback machinery is ever invoked,
because nothing this task runs can fail an already-applied actuation.

## Read-only, no lock (FR-5)

This task performs NO filesystem mutation of the target project and therefore does
**not** acquire `GgenIgniter.Lock` -- `doctor` and a read-only `plan` inspection may
both run concurrently with an in-flight `sync`/lock holder, per the PRD's FR-5. Do
not add a `Lock.acquire!/1` call here; `mix ggen_igniter.sync`/`mix
ggen_igniter.replay` are the only verbs that mutate and therefore lock.

## Output

Human-readable (default) or `--json` (a stable, script-parseable rendering of the
same real plan data -- no field present in one is silently dropped from the other):

  * **inputs + hashes** -- resolved `--ontology`/`--pack` root, every resolved
    `--query name=path` (or pack-discovered gate query) with its source path,
    each `%PendingActuation{}`'s `previous_hash`/`desired_hash`.
  * **query names/sources** -- every named query actually run, and whether it came
    from an explicit `--query`, a pack's `gates/*.rq`, or template frontmatter.
  * **engine** -- the resolved `--engine` (default `oxigraph`) and, for `qlever`,
    the resolved `--store-id`.
  * **bindings** -- the semantic-source binding keys visible to each pending item
    (`semantic_source` on `%PendingActuation{}` -- ontology/query/template
    identity, never the full rendered content in the human-readable form; `--json`
    includes `desired_hash` but never raw `desired_content`, since a plan preview
    is not a content dump).
  * **output paths** -- each `%PendingActuation{}.target` (or `nil` for
    `operation: :eval`, per that struct's own documented convention).
  * **existing-file decisions** -- `operation` (`:create`/`:replace`/`:delete`/
    `:eval`) plus `GgenIgniter.PendingActuation.plan_unchanged?/1` per item, so a
    plan run can distinguish "would create", "would replace with different bytes",
    "would replace with identical bytes (no-op)", and "would delete" without
    running `:actuate` to find out.
  * **skip conditions** -- `--unless-exists`/`--skip-if` guards that would cause
    `:actuate` to skip a target, surfaced here as a real, computed decision (not
    re-derived ad hoc by this task -- sourced from whatever
    `ReconcileReactor.plan/1` itself reports for each item, since admission-time
    skip evaluation is that function's responsibility, not this CLI wrapper's).
  * **unsupported features** -- anything this plan run could not admit because the
    resolved template/run uses a capability outside `GgenIgniter.Reconcile.run/1`'s
    bounded reactor scope (frontmatter `inject: true`, `--for-each` fan-out, etc.)
    is reported as exit code 3 (`:unsupported_capability`), never silently ignored
    or silently downgraded to a partial plan.
  * **intended mutations** -- one line per `%PendingActuation{}` in the
    human-readable form (`operation target (unchanged?)`), or the full list under
    `plan.pending_actuations` in `--json`.

## Dependency on `ReconcileReactor.plan/1`

This task calls `GgenIgniter.Reactors.ReconcileReactor.plan/1`, a read-only
admission-only entry point (observe -> load -> resolve -> run_queries -> render ->
admit, returning the admitted `[%PendingActuation{}]` without ever reaching
`:actuate`) that this Mix task was written against by intended signature but that
does **not yet exist** in this working tree as of this file's authorship -- see
`docs/architecture/adr/` and the plan doc this task was scoped from
(`prd-ard-wiggly-creek.md`, "2. `mix ggen_igniter.plan`") for the extraction this
depends on. Until `ReconcileReactor.plan/1` lands, `mix ggen_igniter.plan` will fail
to compile/run with an `UndefinedFunctionError`; this is expected and disclosed,
not a bug in this file. Do not add a compatibility shim here that re-derives a
parallel plan-only pipeline out of `run_pipeline!/3`-style logic -- the whole point
of this task is to share the SAME admission logic `sync` uses, not to duplicate it.

## Exit codes

  * `0` -- plan computed successfully (admitted cleanly, whether or not any item
    would actually change anything -- an all-unchanged plan is still exit 0).
  * `2` -- invalid invocation (missing required input resolution: no
    `--template` and no `--pack`/`--pack-dir` with a discoverable template; no
    queries resolvable; `--engine qlever` without `--store-id`; etc.) -- the same
    `ArgumentError` vocabulary `mix ggen_igniter.sync` already raises for these,
    caught here and turned into a clean exit instead of a raw stack trace.
  * `3` -- unsupported capability for the read-only plan path specifically (the
    resolved template/run needs `:actuate`-adjacent behavior `plan/1` cannot admit
    without executing it, or uses a feature outside `GgenIgniter.Reconcile.run/1`'s
    bounded reactor scope -- frontmatter `inject: true`, `--for-each` fan-out).

## Example

    mix ggen_igniter.plan --pack ash-lifecycle-pack:resource \
      --query resource=priv/ggen/ash-lifecycle-pack/gates/resource.rq

    mix ggen_igniter.plan --template test/fixtures/extension.ex.eex \
      --query spec=test/fixtures/spec.rq --json

---

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