Chronicle.CheckpointStore behaviour (chronicle v0.1.2)

Copy Markdown

Persistence boundary for independently anchored verification checkpoints.

Production implementations should write to a different trust boundary from the audit database. Returning :not_found is valid only before the first successful verification.

What "separate trust boundary" has to mean

Separate credentials, separate administrators, separate failure domain. A file written by the same host, with the same credentials, as the database it is meant to police is not an independent witness — it falls to whoever the database falls to, in the same breath, and its presence makes the property look available when it is not.

What is anchored here is only a position and a digest. Nothing about a checkpoint proves the ledger it came from was correct at that point; it proves the ledger has not been shortened or rewritten since. The chain supplies the rest.

Checkpoints come back as untrusted input

decode/1 raises on anything malformed rather than working around it, because a checkpoint arrives from storage this library does not control and cannot vouch for. It insists that a checkpoint's own ledger matches the key it was filed under — otherwise one ledger's position could be presented as another's, and a rollback would be compared against a boundary from an entirely different chain, which it would clear easily.

Digests must be exactly 64 lowercase hex characters, matching what Chronicle.Digest produces. A sequence of 0 paired with no digest is the one legitimate degenerate case: a ledger verified while still empty.

The integrity of the checkpoint storage itself is assumed, not checked. An attacker who can write there can retire the anchor, and nothing in this module will notice.

Summary

Callbacks

load(store)

@callback load(store :: atom()) ::
  {:ok, %{optional(String.t()) => Chronicle.Integrity.Checkpoint.t() | map()}}
  | :not_found
  | {:error, term()}

save(store, checkpoints)

@callback save(
  store :: atom(),
  checkpoints :: %{required(String.t()) => Chronicle.Integrity.Checkpoint.t()}
) :: :ok | {:error, term()}

Functions

decode(checkpoints)

@spec decode(map()) :: map()

encode(checkpoints)

@spec encode(map()) :: map()