feat(ledger): LedgerEmitter wrapper with write+fsync+verify-readback [W2.C.3]#58
Draft
TimothyVang wants to merge 3 commits into
Draft
feat(ledger): LedgerEmitter wrapper with write+fsync+verify-readback [W2.C.3]#58TimothyVang wants to merge 3 commits into
TimothyVang wants to merge 3 commits into
Conversation
added 3 commits
May 2, 2026 08:42
Failing test: tests/graph/wrappers/test_ledger_emitter.py — import fails because verdict/graph/wrappers/ledger_emitter.py does not exist yet. Also adds prerequisite modules (not yet on W2.C.3 branch): - verdict/schemas/ledger.py — LedgerEntry (W1.B.11 backport) - verdict/schemas/mode.py — Mode enum - verdict/ledger/hmac_key.py — TPM-backed / gpg-encrypted HMAC (W1.G.6) - verdict/ledger/redaction.py — auth-field stripping before hash/sign - verdict/ledger/writer.py — write + fsync + verify-readback (W2.G.1) Assertions: - ledger.jsonl grows by one line per run(). - run() returns (ToolOutput, LedgerEntry) tuple. - LedgerEntry carries NIST SP 800-86 metadata fields. - prev_entry_hash of N+1 == blake3(line N + newline). - HMAC sig present and verifiable with same key; fails with different key. - langfuse_trace_id bidirectional cross-link in entry + payload. - mode_at_case_init locked to construction value. - Callable interface writes ledger and returns ToolOutput.
…[W2.C.3]
LedgerEmitter is the third wrapper in the DenyRuleWrapper → ToolExecutor →
LedgerEmitter composition (ARCHITECTURE.md §2).
Durability contract (CLAUDE.md §9):
- write() + fsync() + verify-readback in LedgerWriter; no buffered writes.
- Verify-readback re-reads last line, JSON-parses it, spot-checks
entry_id + hmac_sig before advancing prev_entry_hash.
Chain integrity:
- prev_entry_hash = blake3(prev line bytes including newline).
- GENESIS_HASH ("0"×64) for the first entry.
- LedgerWriter.build_entry() computes HMAC and sets prev_entry_hash.
- Two sequential run() calls produce two chained JSONL lines.
HMAC (W1.G.6):
- HMACKeyProvider Protocol: sign(bytes) → hex, verify(bytes, sig) → bool.
- _TPMHMACProvider: /dev/tpmrm0 + tpm2-pytss (optional dep).
- _GpgFileHMACProvider: ~/.verdict/key.gpg via system gpg.
- _SoftwareHMACProvider: in-memory HMAC-SHA256 (gpg path + tests).
- get_hmac_key_provider(): auto-selects TPM → gpg → error.
- get_hmac_key_provider_from_bytes(): in-process key for tests.
Redaction (CLAUDE.md §3.9):
- redact_payload() strips authorization, auth_user, api_key (9 fields)
before hash/sign. Order: strip → hash → sign.
Langfuse cross-link:
- LedgerEntry.langfuse_trace_id set from construction arg.
- payload["langfuse_trace_id"] for Langfuse → ledger direction.
- langfuse_session_id == case_id (one Langfuse session per case).
LedgerEmitter.run() returns (ToolOutput, LedgerEntry).
LedgerEmitter.__call__() returns ToolOutput only (executor contract).
- 20 tests GREEN; ruff clean.
Remove unused imports: hashlib, datetime, timezone, Path from ledger_emitter.py; struct from hmac_key.py; unused re-export from writer.py. All 20 tests still pass.
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
verdict/graph/wrappers/ledger_emitter.py— third wrapper in executor_work composition.write() + fsync() + verify-readback; no buffered writes (CLAUDE.md §9).prev_entry_hash = blake3(prev line bytes + newline).langfuse_trace_idin bothLedgerEntryfields and payload.microsandbox_version,rootfs_sha256,tool_version,kernel_version.redact_payload()inverdict/ledger/redaction.py.verdict/schemas/ledger.py(W1.B.11 backport),verdict/schemas/mode.py,verdict/ledger/hmac_key.py(W1.G.6: TPM-backed + gpg-encrypted + in-process),verdict/ledger/writer.py(W2.G.1).TDD trace
89d49ee— test + prerequisite modules (no ledger_emitter yet)6c09ff5— LedgerEmitter implementation3721741— ruff unused import cleanupTest plan
pytest tests/graph/wrappers/test_ledger_emitter.py -v→ 20 passedruff check verdict/graph/wrappers/ledger_emitter.py verdict/ledger/ verdict/schemas/ledger.py→ All checks passed