pub async fn from_jsonl<R: BufRead>(
store: &Arc<dyn JournalStore>,
cases: Option<&Arc<dyn CaseStore>>,
input: R,
) -> Result<RestoreReport, StoreError>Expand description
Rebuild a store from an export, then prove it by its own checkpoint.
§Why this goes through append rather than writing rows
The obvious implementation inserts records verbatim and rebuilds each index
beside them. It is also the one that fails quietly: append maintains
several derived structures — the case index, the exactly-once index, the
outcome index and its ordering counter, the admission index, both halves of
the activity index — and a restore that reconstructed all but one of them
would produce a store that reads perfectly until somebody queries the one it
missed.
Deliberately not a count. A number here is a claim that has to be re-checked
on every edit and is not, so it goes stale silently and reads as coverage —
the shape this project catalogues and has been bitten by. Going through
append is what makes the list not need enumerating: whatever append
maintains, a restore maintains.
So this replays the ordinary write path, and every constraint the store
enforces is enforced here too. What travels through it is each record’s
written bytes with its body (Append::restored): the store indexes the
body, lifted through the upcaster if the record is from an older shape, and
stores the bytes as written, so a restore across a shape change rebuilds
the chain that was exported rather than a re-seal of it. Three properties
make each record land at the position its bytes name:
seqis re-derived and lands identically, because a run restored into an empty store starts from the same genesis and receives the same records in the same order.epochis carried, not re-derived. It is a field of the hashed body, so a run that ever changed hands — the ones a disaster is most likely to involve — would hash differently under a single fresh lease.appendtakes the epoch as a parameter and fences only when a lease row exists, so restoring into a store with no leases writes each record under its own original epoch. Records are grouped into runs of equal epoch for exactly this reason.- Runs are sealed in log-index order, so the Merkle log is rebuilt in the order the original recorded, which is what makes the roots comparable at all.
§What does not survive
Every loss is a sentence in RestoreReport::not_carried, because the
reader who needs it is holding the report rather than this page. One of
them costs work rather than metadata and has its own field:
RestoreReport::awaiting names the runs that came back waiting, and says
there why nothing will wake them and what does.
§Errors
If the export cannot be read, if the store seals payloads as it writes, or if the store already holds any of its runs — each refused before anything is written: this rebuilds a history, it does not merge one. Also if the store refuses a write, or if a rebuilt record’s hash is not the one the file claims; those land mid-restore and leave a partial store that a retry refuses, so discard it and restore into a fresh one.