Chronicle.Verifier (chronicle v0.1.0)

Copy Markdown

Periodically verifies a named store against independently persisted anchors.

A checkpoint is advanced only after the entire store verifies. Configure an Chronicle.CheckpointStore whose storage is outside the audit database's trust boundary.

Load, verify, then save — in that order and no other

Anchors are read before verification and fed into it as expectations; new checkpoints are written only once the whole store has verified. Advancing an anchor before or during the walk would permanently bless whatever history happened to be there, including history an attacker had just rewritten. The anchor is the only thing standing between this library and an undetectable rollback, so an anchor advanced over corrupt data is worse than no anchor at all — it converts a detectable attack into a signed-off one.

A failed run therefore leaves the previous checkpoints in place. It does not advance them partially, and it does not clear them.

Why a process, and why it does not crash

This is one of the few places in the library that genuinely needs its own lifecycle: it owns a timer, it carries the last result between runs, and it must serialise verification so two overlapping runs cannot race to advance the same anchor.

A verification failure is reported and scheduled again rather than raised. Crashing would hand a supervisor an invalid state to restart into, and the next run would fail identically — a restart loop rather than resilience. Misconfiguration is treated as the opposite case and raises in init/1: a verifier with no checkpoint store cannot do the one job it exists for, and discovering that at boot is far better than appearing healthy while anchoring nothing.

Summary

Functions

Returns a specification to start this module under a supervisor.

Runs a verification immediately and returns its result.

Types

status()

@type status() :: %{
  store: atom(),
  last_result: :not_run | :ok | {:error, Chronicle.Error.t()},
  last_checked_at: DateTime.t() | nil,
  checkpoints: map()
}

Functions

child_spec(init_arg)

@spec child_spec(keyword()) :: Supervisor.child_spec()

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

status(server \\ __MODULE__)

@spec status(GenServer.server()) :: status()

verify_now(server \\ __MODULE__)

@spec verify_now(GenServer.server()) :: {:ok, map()} | {:error, Chronicle.Error.t()}

Runs a verification immediately and returns its result.

The :infinity timeout is deliberate rather than inherited. Verification walks the entire ledger, so its duration grows with history and has no bound worth guessing at. Under the five-second default a caller would give up while the work continued behind it, and a retry would stack a second walk on top of the first.