Skip to main content

from_jsonl

Function from_jsonl 

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

  • seq is 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.
  • epoch is 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. append takes 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.