Skip to content

trace: wire TraceEmitter into ingest (events, trace_ref, flip-panel provenance)#9

Merged
aaronmarkham merged 2 commits into
mainfrom
claude/trace-emitter
May 31, 2026
Merged

trace: wire TraceEmitter into ingest (events, trace_ref, flip-panel provenance)#9
aaronmarkham merged 2 commits into
mainfrom
claude/trace-emitter

Conversation

@aaronmarkham

Copy link
Copy Markdown
Owner

Summary

Closes #7. Wires spiritwriter.fabric.emitter.TraceEmitter into 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=None is a no-op):

  • init_trace_emitter(store) — one TraceEmitter per zeitghost ingest run, writing a JSONL to <store.root>/traces/<run_id>.jsonl. run_id is timestamped and embedded in each shard's trace_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_shard take an optional emitter. _trace_shard emits shard_created (and shard_superseded when the write chains onto a parent_shard_id), then sets shard.trace_ref before _maybe_sign 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.

Flip-panel provenance:

  • _shard_to_article surfaces shard.created_by / shard.trace_ref as AnalyzedArticle.signed_by / .trace_ref.
  • _card.html gains a Signed row (Ed25519 thumbprint, or "unsigned") and a Trace row (the chain:… ref).

Dependency:

  • Bump spiritwriter-core>=0.8.0 (current release; the whole branch is tested against it).

Not in scope

Test plan

  • python -m pytest tests/55 passed (against spiritwriter-core 0.8.0)
  • New tests: trace_ref stamped + verify_chain() passes; shard_superseded emitted on a revision (new→old); signer + trace round-trip to AnalyzedArticle; no trace_ref without an emitter
  • End-to-end render: built a site from a signed+traced shard, confirmed the Signed + Trace rows render with the thumbprint and chain:<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

aaronmarkham and others added 2 commits May 30, 2026 22:15
…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>
@aaronmarkham aaronmarkham merged commit c39c37d into main May 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wire TraceEmitter into ingest: shard_created/superseded events, trace_ref, per-run run_id, flip-panel provenance

1 participant