JSONL Ledger
Overview
The JSONL ledger is the authoritative audit trail for all mechanical and translation events in PipeWorks. Every chat interaction that involves the axis engine or translation layer produces at least one ledger event, written before any database update.
Key properties:
Append-only — events are never modified or deleted.
Self-verifying — each line carries a SHA-256 checksum over all other fields.
Per-world files —
data/ledger/<world_id>.jsonl.Not committed to git — ledger files are runtime data, git-ignored alongside
data/*.db.Non-fatal writes — a ledger write failure logs a WARNING and allows the interaction to continue; only the audit record is lost.
File Location
data/ledger/
├── daily_undertaking.jsonl
└── pipeworks_web.jsonl
One file per world. The directory is created automatically by the
first append_event call for that world.
Startup Integrity Check
World._init_axis_engine calls
verify_world_ledger() at startup.
result: LedgerVerifyResult = verify_world_ledger(world_id)
LedgerVerifyResult has three possible statuses:
|
|
Meaning |
|---|---|---|
|
hex string |
Last line is valid JSON with a matching checksum. |
|
|
File does not exist or is empty. Normal for a fresh world. |
|
|
Last line fails checksum verification or is not valid JSON. A CRITICAL log is emitted. The server continues; full replay from ledger is future scope. |
Envelope Format
Every JSONL line is a self-contained JSON object:
{
"event_id": "a3f91c9e2d4b5e6f...",
"timestamp": "2026-02-27T14:23:01.452Z",
"world_id": "daily_undertaking",
"event_type": "chat.mechanical_resolution",
"schema_version": "1.0",
"ipc_hash": "a3f91c9e...",
"meta": {},
"data": { ... event-specific payload ... },
"_checksum": "sha256:b94f3e..."
}
Field reference:
Field |
Description |
|---|---|
|
32-character hex string. Globally unique identifier for this event. |
|
ISO 8601 UTC timestamp. |
|
World this event belongs to. |
|
Dotted string identifying the event category. See Event Types below. |
|
Envelope schema version. Currently |
|
Deterministic fingerprint of the mechanical state at the time of
this event. Computed by |
|
Optional context dict. Empty |
|
Event-specific payload. Schema varies by |
|
|
Checksum Verification
The checksum covers all fields except _checksum itself:
import hashlib, json
def verify_line(line: str) -> bool:
event = json.loads(line)
stored = event.pop("_checksum") # remove before hashing
payload = json.dumps(event, sort_keys=True)
expected = "sha256:" + hashlib.sha256(payload.encode()).hexdigest()
return stored == expected
File Locking
append_event acquires an exclusive POSIX fcntl.flock(LOCK_EX)
before writing and releases it after. This serialises concurrent writes
from multiple threads within the same process and from separate processes
sharing the filesystem.
Note
fcntl is a POSIX API. Ledger writes are supported on Linux and
macOS; Windows is not a supported deployment target.
Event Types
chat.mechanical_resolution
Written by resolve_chat_interaction()
after computing axis deltas, before DB materialisation.
{
"event_type": "chat.mechanical_resolution",
"ipc_hash": "a3f91c9e...",
"data": {
"channel": "say",
"speaker": {
"character_id": 7,
"character_name": "Mira Voss",
"axis_deltas": {"demeanor": 0.011, "health": -0.01}
},
"listener": {
"character_id": 12,
"character_name": "Kael Rhys",
"axis_deltas": {"demeanor": -0.011, "health": -0.01}
},
"axis_snapshot_before": {
"7": {"demeanor": 0.87, "health": 0.72},
"12": {"demeanor": 0.51, "health": 0.44}
},
"grammar_version": "1.0"
}
}
axis_snapshot_before includes only axes with non-no_effect
resolvers to bound storage cost.
chat.translation
Written by
translate()
on every translate call — success and failure.
{
"event_type": "chat.translation",
"ipc_hash": "a3f91c9e...",
"meta": {},
"data": {
"status": "success",
"character_name": "Mira Voss",
"channel": "say",
"ooc_input": "give me the ledger",
"ic_output": "Hand it over — now.",
"axis_snapshot": {
"demeanor": {"score": 0.87, "label": "proud"},
"health": {"score": 0.72, "label": "hale"}
}
}
}
status values:
|
Meaning |
|---|---|
|
Ollama returned output and validation passed. |
|
Ollama was unavailable or returned an error. |
|
Ollama returned output but validation rejected it (PASSTHROUGH
sentinel, empty string, or exceeded |
ic_output is null on fallback paths. The raw (unvalidated)
Ollama output is intentionally not stored.
ipc_hash Linkage
Both event types carry the same ipc_hash for a given turn. This
links the mechanical resolution event to the translation event that
consumed it:
{"event_type": "chat.mechanical_resolution", "ipc_hash": "a3f91c9e...", ...}
{"event_type": "chat.translation", "ipc_hash": "a3f91c9e...", ...}
This allows replay tooling to reconstruct the full decision context for any translation: which axis scores were in play, what the grammar said, and what the model produced.
Pre-Axis-Engine Era
Before the axis engine was wired to the chat pipeline (Phase 4 of the
implementation plan), chat.translation events were emitted with
ipc_hash: null and meta: {"phase": "pre_axis_engine"}. These
events are valid and verifiable; they simply lack a mechanical resolution
counterpart.
Replay tooling can distinguish the eras by inspecting ipc_hash and
meta.phase:
is_pre_axis = event["ipc_hash"] is None
# or equivalently:
is_pre_axis = event.get("meta", {}).get("phase") == "pre_axis_engine"
Python API
append_event()
from mud_server.ledger import append_event
event_id: str = append_event(
world_id="daily_undertaking",
event_type="chat.translation",
data={"status": "success", ...},
ipc_hash="a3f91c9e...", # optional, default None
meta={"phase": "..."}, # optional, default None
)
Returns the event_id hex string. Raises
LedgerWriteError on filesystem
failure. Callers should catch and log; do not let ledger failures
propagate to the user.
verify_world_ledger()
from mud_server.ledger import verify_world_ledger
result = verify_world_ledger("daily_undertaking")
print(result.status) # "ok" | "empty" | "corrupt"
print(result.last_event_id) # hex string or None
print(result.error_detail) # None if ok
Hardening Notes
The current implementation follows PoC trade-offs:
Ledger write failure is non-fatal. An audit record may be lost.
No write-ahead buffer or retry queue.
No automatic replay-from-ledger on DB/ledger mismatch.
File-based locking only (no distributed lock).
Each of these is marked TODO(ledger-hardening) in the source code.