Expand description
Tamper-evident trace storage: a background SQLite writer fed by a bounded channel.
The hot request path never blocks on disk: it try_sends a Trace down a bounded channel
and returns immediately (dropping the trace if the writer has fallen far enough behind to fill
the buffer — bounded memory over a guaranteed write). A single background task owns the SQLite
connection, assigns each trace’s prev_hash from the current chain head, computes its hash,
and appends it (SPEC §9: the hash chain is only meaningful if every writer agrees on chain
order, which a single writer task guarantees for free).
In crate::config::ReceiptsMode::Durable mode the channel-full path spills traces to
<db_path>.spill.jsonl (one JSON line per trace, synced to disk) instead of dropping them.
The writer drains the spill file at startup and whenever the channel empties, inserting spilled
traces BEFORE new channel arrivals so the hash chain stays append-only and valid.
Enums§
- Store
Error - Errors from the trace store.
Constants§
- TRACE_
CHANNEL_ CAP - Trace buffer depth. Deep enough to absorb normal write bursts; bounded so a wedged writer (disk stall) can’t OOM the process — excess traces are dropped with a warning, not queued forever.
Functions§
- append_
deferred - Append a deferred verdict for
trace_id(a downstream outcome or async gate result). This writes ONLY to thedeferred_verdictstable; the sealed trace and its hash are untouched, so the audit chain remains verifiable (SPEC §8.3.4 — the outcome-feedback loop). - append_
to_ spill - Serialize
traceas one JSON line and append it to the spill file, flushing to disk. - load_
all_ traces - Load every trace from the database in insertion (chain) order — used by tests and
operators to verify the hash chain with
firstpass_core::verify_chain. - load_
deferred - Load the deferred verdicts recorded for
trace_id, oldest first. Malformed stored rows are skipped (logged), never fatal — a corrupt late outcome must not break reading the trace. - load_
tenant_ traces - Tenant-scoped read: only traces owned by
tenant, inseqorder (ADR 0004 §D3). Tenant A can never see tenant B’s traces through this path. - load_
trace_ view - Load a single trace by id scoped to
tenant, with its deferred verdicts merged intodeferred— the view for display/inspection (ADR 0004 §D3). A trace owned by another tenant returnsNone, exactly like a missing one, so an inspecting agent can never read across tenants. This is deliberately separate fromload_all_traces: merging deferred verdicts changes the record, so a merged trace must NOT be fed toverify_chain(chain verification always runs on the sealed bodies fromload_all_traces). - open
- Open (creating if needed) the SQLite trace database in best-effort mode, migrate its schema, and spawn the background writer task.
- open_
with_ receipts - Open the SQLite trace database with the given receipts mode, migrate its schema, and spawn the background writer task.
- trace_
exists - Whether a trace with
trace_idexists and is owned bytenant— used to reject feedback for unknown traces and, crucially, to deny cross-tenant feedback (ADR 0004 §D3/§D4). A trace owned by another tenant is indistinguishable from a non-existent one here, so the caller can return a404with no existence oracle.
Type Aliases§
- Spill
Handle - A shared, mutex-guarded handle to the spill file used in durable mode.
- Trace
Sender - Sending half of the trace channel; cheap to clone, safe to share across request handlers.
Sending is fire-and-forget via
try_send— the hot path never awaits the writer, and a bounded buffer means a stalled writer sheds load (drops traces) instead of growing memory without limit.