trace: wire TraceEmitter into ingest (events, trace_ref, flip-panel provenance)#9
Merged
Conversation
…s, trace_ref, flip-panel provenance Closes the provenance loop from PR #5 (issue #7). Ingest now emits a hash-chained trace log per run and stamps each shard with a trace_ref pointing back at its creation event; the card flip-panel surfaces the signer + trace. - init_trace_emitter(store): one TraceEmitter per `zeitghost ingest` run, writing a JSONL under <store.root>/traces/<run_id>.jsonl. run_id is timestamped and embedded in each shard's trace_ref (chain:<run_id>#<hash>). - article_to_*_shard take an optional `emitter`; _trace_shard emits shard_created (+ shard_superseded when the write chains onto a parent) and sets shard.trace_ref BEFORE signing, so the signature covers the trace_ref. trace_ref isn't part of the content-address, so shard_id is unchanged. - ingest builds the emitter, threads it through the writers, and prints a trace summary with a verify_chain() check. Tracing is opt-in like signing: emitter=None is a no-op. - _shard_to_article surfaces created_by + trace_ref as AnalyzedArticle .signed_by / .trace_ref; _card.html flip-panel renders Signed (Ed25519 thumbprint) and Trace rows. - Bump spiritwriter-core>=0.8.0 (current release; tested against it). Tests: trace_ref stamped + chain verifies, shard_superseded on revision, signer+trace round-trip to the article, no trace_ref without an emitter. 55 passed; flip-panel render smoke-tested; clean-cwd import verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, richer trace row Addresses PR #9 review: - _trace_shard is now best-effort/fail-open: a trace emit failure (unwritable traces/, disk full) logs and leaves trace_ref unset rather than aborting the shard write. Signing is the load-bearing provenance half; tracing must not be able to lose a shard. New test asserts a raising emitter still persists the (untraced) shard. - CLI trace summary no longer claims "(chain verified)" — verifying a chain you just wrote is a self-test that always passes. Now prints "N events recorded" and only surfaces a warning if the self-check unexpectedly fails. The green "verified" wording is reserved for a future verify-trace <run_id> audit. - Type the emitter params (TYPE_CHECKING forward-ref "TraceEmitter | None") on _trace_shard / both writers / init_trace_emitter return, matching the rest of the module; import stays lazy at runtime. - Flip-panel Trace row splits chain:<run_id>#<hash> into the run_id (the traces/<run_id>.jsonl filename) + the event-hash prefix, instead of a blind [:24] that showed none of the hash. Full ref stays in title=. - Extend the shard_superseded test to both shard scopes (parametrized). 57 passed; Trace-row render re-verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #7. Wires
spiritwriter.fabric.emitter.TraceEmitterinto the ingest pipeline, completing the provenance story PR #5 began: shards are signed and the lineage loads correctly — now there's also a hash-chained trace per run, each shard points back at its creation event, and the card flip-panel surfaces it.What changed
Tracing (opt-in, like signing —
emitter=Noneis a no-op):init_trace_emitter(store)— oneTraceEmitterperzeitghost ingestrun, writing a JSONL to<store.root>/traces/<run_id>.jsonl.run_idis timestamped and embedded in each shard'strace_ref(chain:<run_id>#<event_hash>), so a shard resolves back to the exact run + event that produced it.article_to_internal_shard/article_to_sw_shardtake an optionalemitter._trace_shardemitsshard_created(andshard_supersededwhen the write chains onto aparent_shard_id), then setsshard.trace_refbefore_maybe_signso the signature covers the trace_ref.trace_refisn't part of the content-address, soshard_idis unchanged.ingestbuilds the emitter, threads it through the writers, and prints a trace summary with averify_chain()check.Flip-panel provenance:
_shard_to_articlesurfacesshard.created_by/shard.trace_refasAnalyzedArticle.signed_by/.trace_ref._card.htmlgains a Signed row (Ed25519 thumbprint, or "unsigned") and a Trace row (thechain:…ref).Dependency:
spiritwriter-core>=0.8.0(current release; the whole branch is tested against it).Not in scope
shard_supersededevents only fire on a real re-analysis, which needs thezeitghost reanalyzecommand (Addzeitghost reanalyzeto exercise lineage (re-score a window, form parent_shard_id revisions) #8). The emission path is wired and unit-tested here; Addzeitghost reanalyzeto exercise lineage (re-score a window, form parent_shard_id revisions) #8 is the workload that exercises it in production.Test plan
python -m pytest tests/— 55 passed (against spiritwriter-core 0.8.0)trace_refstamped +verify_chain()passes;shard_supersededemitted on a revision (new→old); signer + trace round-trip toAnalyzedArticle; notrace_refwithout an emitterchain:<run_id>#…python -c "import zeitghost"clean from an empty cwd (emitter/uuid/datetime imports are all lazy — invariant Replace coming-soon placeholder with real spiritwriter.ai landing page #2)🤖 Generated with Claude Code