MESHFLOW_MOCK=1 python3 hands_on/11_ledger_audit.pyBy the end of this lesson, you should be able to:
Estimated time: 40 to 55 minutes.
An AI workflow can produce wrong, biased, or harmful output. When that happens, you need to answer three questions:
A standard application log answers the first two questions but not the third. Logs can be edited silently. In regulated environments — healthcare, finance, legal — silent editing is unacceptable. You need a ledger that makes tampering visible.
MeshFlow records every workflow step as a StepRecord. After writing each record, it computes a SHA-256 hash of the record content combined with the hash of the previous record. This creates a chain:
step_0: content_hash_0
step_1: SHA-256(content_1 + content_hash_0) = hash_1
step_2: SHA-256(content_2 + hash_1) = hash_2
step_N: SHA-256(content_N + hash_{N-1}) = hash_N
To verify the chain, the verifier recomputes every hash from scratch. If any record was modified — even a single character — the chain breaks at that step. The verifier reports exactly which step was tampered with.
What SHA-256 hash chaining proves:
What it does NOT prove:
from meshflow.core.ledger import ReplayLedger
ledger = ReplayLedger("my_pipeline.db")
# List all run IDs stored in this ledger
run_ids = ledger.list_runs()
# Read all step records for a run
steps = ledger.get_run(run_id)
# Aggregated metrics for a run
summary = ledger.run_summary(run_id)
# summary contains: total_cost_usd, total_tokens, total_carbon_g, duration_s, step_count
# Verify the hash chain — returns True or raises on tamper
ok = ledger.verify_chain(run_id)
# Export to JSON string
json_text = ledger.export_run(run_id)
# Export to CSV string
csv_text = ledger.export_run_csv(run_id)
The ledger stores every intermediate state. You can reconstruct the workflow context at any step — this is called time-travel:
# Load the state as it was after step 3
state_at_step_3 = ledger.load_state(run_id, step=3)
# Fork: start a new run from step 3 with a different policy
new_run_id = ledger.fork(run_id, step=3, new_run_id="fork_run_001")
Time-travel is useful for:
After changing a prompt or policy, you can compare two runs step by step:
delta = ledger.diff(run_id_before, run_id_after)
# Returns a list of per-step differences in output, cost, tokens, and carbon
This is particularly useful for regression testing: run a workflow before and after a change, then confirm the outputs changed only where expected.
Under GDPR's right to erasure, you may need to remove personally identifiable information (PII) from audit records without breaking the chain. MeshFlow provides anonymize_run():
ledger.anonymize_run(run_id)
# Overwrites PII fields with [REDACTED] markers
# Recomputes the chain hashes so verify_chain still passes
Important: anonymization is a destructive operation. The original PII cannot be recovered after anonymization. Run it only when legally required.
When you call verify_chain, the ledger walks every step in the run and recomputes each hash. If it finds a mismatch it raises an exception identifying the tampered step:
TamperDetectedError: step 4 hash mismatch
expected: a3f2b1...
found: d7e9c3...
In production, run verify_chain on a schedule (for example, after every batch completes) so you detect tampering quickly. Store the chain hash externally (for example, in a read-only object store) for an additional independent verification point.
Run the ledger audit demo:
MESHFLOW_MOCK=1 python3 hands_on/11_ledger_audit.py
Observe:
ledger.list_runs()run_summaryverify_chainexport_runThen open one of the audit JSON files in the repository root:
cat audit_run_480c.json | python3 -m json.tool | head -40
Identify the step_records array and read the hash field on each record.
The ReplayLedger records every workflow step with a SHA-256 hash chain that makes tampering visible. You can read, verify, export, diff, time-travel, fork, and anonymize runs. In regulated environments, the ledger is not optional — it is the proof that the system did what it claims to have done.
Key operations:
get_run → read all stepsverify_chain → detect tamperingload_state → reconstruct context at any stepfork → branch from a past statediff → compare two runsanonymize_run → GDPR-compliant redactionGoal: Familiarize yourself with the full output of the ledger audit hands-on script.
Instructions:
meshflow_tutorial project root. python hands_on/11_ledger_audit.py
- How many runs were created during the script execution? - What was the SHA-256 hash of the first step in the first run? (Look for a field like hash or step_hash in the printed output.) - Did verify_chain() return True or False on the first check? - Which run ID was used for the time-travel load_state demo?
Expected output: The script should print a chain-verification result of True, a run summary table, and at least one exported JSON block. No Python tracebacks should appear.
Goal: Understand the raw structure of a ledger record on disk.
Instructions:
Exported run to: ...).python -m json.tool <filename> for pretty-printing. - The top-level run_id field. - The steps array. How many steps are present? - The hash field on the first step. This is the SHA-256 of (previous_hash + step_payload). - The hash field on the second step. Notice that it incorporates the first step's hash.
Expected output: A clear view of the nested hash values and a conceptual understanding that each hash depends on all prior hashes.
Goal: Experience how hash chaining detects tampering.
Instructions:
run_id that verify_chain() confirms as valid (True).~/.meshflow/ledger.db by default). sqlite3 ~/.meshflow/ledger.db
SELECT * FROM steps LIMIT 5;
output column of step 1 for your chosen run: UPDATE steps SET output = '{"tampered": true}' WHERE run_id = '<your_run_id>' AND step_index = 1;
.quit) and run verify_chain(run_id) again in a Python script or REPL: from meshflow.ledger import ReplayLedger
ledger = ReplayLedger()
print(ledger.verify_chain("<your_run_id>"))
False. Record which step index is reported as the first tampered step.Expected output: verify_chain returns False and identifies step 1 (or the step you edited) as the integrity violation.
Clean-up: Restore the original value or re-run the hands-on script to generate a fresh run.
Goal: Use diff to understand how two runs diverged.
Instructions:
11_ledger_audit.py twice (or find two existing runs in your ledger with list_runs()).run_id values of both runs. They should be runs of the same workflow but may have different inputs or outputs. from meshflow.ledger import ReplayLedger
ledger = ReplayLedger()
runs = ledger.list_runs(limit=5)
run_a = runs[0]["run_id"]
run_b = runs[1]["run_id"]
delta = ledger.diff(run_a, run_b)
print(delta)
- Which steps are present in run A but not run B (or vice versa). - Which steps have the same name but different outputs. - Any changes in timing or token counts.
Expected output: A structured diff object (dict or dataclass) showing added, removed, and changed steps between the two runs.
Goal: Confirm that GDPR anonymization does not break ledger integrity.
Instructions:
list_runs() to find one).anonymize_run: from meshflow.ledger import ReplayLedger
ledger = ReplayLedger()
run_id = "<your_run_id>"
ledger.anonymize_run(run_id)
verify_chain on the same run: result = ledger.verify_chain(run_id)
print("Chain valid after anonymization:", result)
export_run(run_id) to confirm that PII fields (names, emails, IP addresses, or any fields marked as personal data) have been replaced with placeholder values (e.g., "[REDACTED]" or null).verify_chain once more after you manually edit a non-anonymized field (repeat the tamper test from Exercise 3) to confirm tampering is still detectable even after anonymization.Expected output: verify_chain returns True after anonymization and False after the manual tamper. The export shows redacted PII fields while preserving structural fields like step_index, agent_id, and timestamp.
Reflection question: Why does anonymizing PII not invalidate the hash chain? Think about which fields are included in the hash computation versus which are treated as metadata.