Skip to main content

Module store

Module store 

Source
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§

StoreError
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 the deferred_verdicts table; 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 trace as 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, in seq order (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 into deferred — the view for display/inspection (ADR 0004 §D3). A trace owned by another tenant returns None, exactly like a missing one, so an inspecting agent can never read across tenants. This is deliberately separate from load_all_traces: merging deferred verdicts changes the record, so a merged trace must NOT be fed to verify_chain (chain verification always runs on the sealed bodies from load_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_id exists and is owned by tenant — 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 a 404 with no existence oracle.

Type Aliases§

SpillHandle
A shared, mutex-guarded handle to the spill file used in durable mode.
TraceSender
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.