mix ggen_igniter.plan (ggen_igniter v26.9.8)

Copy Markdown View Source

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