Diagnostic task: mix ggen_igniter.replay <receipt_file> [--verify-only] [--json] [--manifest-dir DIR].
Loads ONE real GgenIgniter.Receipt (see that module's moduledoc for the
on-disk format) and recomputes real, CURRENT hashes of the same inputs the
receipt recorded, to answer: "has anything this receipt depended on
drifted since it was written?"
receipt_file accepts either on-disk shape a receipt genuinely exists in:
- A date-partitioned
.jsonlpartition (<base_dir>/.ggen_igniter/receipts/<yyyy-mm-dd>.jsonl,GgenIgniter.Receipt.path/2's real format) -- the LAST line (most recent attempt in that partition) is replayed. - A single JSON object file -- one receipt's
GgenIgniter.Receipt.to_json_map/1shape, decoded directly viaJason.decode!/1(no receipt-specific wrapper). This is the shape a caller gets by extracting one line out of a.jsonlpartition into its own file.
Exits 2 (invalid invocation) if the file does not exist or does not parse as either shape -- never guesses at a receipt's content.
What real drift this can and cannot detect
GgenIgniter.Receipt's on-disk schema (see that module's moduledoc)
records, per attempt: recipe_key ("<template_path>=><out_template>"),
files (the real output paths this attempt touched),
pre_run_hash/post_run_hash (real content digests over exactly those
files, via GgenIgniter.Receipt.hash_files/1), and a free-form
metadata map -- on an :alive receipt written by
GgenIgniter.Reactors.ReconcileReactor, metadata["graph_hash"] is a real
"sha256:" <> hex digest of the ontology file that produced this run (see
that module's finalize_evidence/1).
This task recomputes and compares exactly what the schema makes recomputable, and reports each as its own category rather than a single pass/fail:
- "output state changed" --
GgenIgniter.Receipt.hash_files/1is re-run over the receipt's ownfileslist, RIGHT NOW, and compared to the receipt's recordedpost_run_hash. A mismatch means at least one output this recipe wrote has been edited (or deleted) since this receipt was recorded. - "ontology changed" -- only checked when the receipt's
recipe_keyresolves to aGgenIgniter.Manifestentry recording apack_dir(--manifest-dir's manifest, defaultFile.cwd!()) AND the receipt'smetadata["graph_hash"]is present.<pack_dir>/ontology.ttl(the--packconvention's fixed subpath -- seepriv/ggen/CLAUDE.md) is re-read and re-hashed with the SAME"sha256:" <> hexalgorithm the reactor used, and compared tograph_hash.
GgenIgniter.Receipt's schema does NOT record a standalone baseline hash
of the template file, the gate query, the pack directory as a whole, the
query engine's version string, or arbitrary run config -- there is no
recorded value for this task to compare a recomputed hash AGAINST for
those categories, so this task never fabricates a comparison for them.
When the receipt's recipe_key names a template path, its CURRENT hash is
still reported (--json's "template_path"/"template_current_hash"
fields, or the human line template: <path> (no baseline recorded -- informational only)) so a human has the value on hand, but it is never
counted as drift. If GgenIgniter.Receipt's schema grows recorded
baselines for these categories, extend detect_drift/2 to compare them for
real rather than widen this task's guesses.
Exit codes
0-- receipt loaded and compared; no real drift detected.1-- receipt loaded and compared; at least one real drift category found.2-- invalid invocation (missing/unreadable/unparsable receipt file, or a bad flag).
--verify-only performs only this read-only comparison (the only mode
implemented so far; reserved for a future re-actuation mode, never implied
as already present). --json prints the machine-readable report
(%{"drift" => [...], "categories" => %{...}, "receipt" => %{...}})
instead of the human-readable lines.
Examples
mix ggen_igniter.replay .ggen_igniter/receipts/2026-08-27.jsonl
mix ggen_igniter.replay tmp/one_receipt.json --json --manifest-dir priv/ggen/audit-trail-pack