Changelog

Copy Markdown

This project follows Semantic Versioning. Until 1.0, a minor version may change anything: names, options, struct fields, database columns, and the signed content format. No compatibility layers are kept during that period.

0.1.0

First release.

Chronicle records application facts and Ecto record versions in one append-only, hash-chained ledger authenticated with HMAC-SHA-256. Both go through the same model, so an authorization denial and a database update occupy the same ordered history and are verified by the same walk.

Recording

  • Chronicle.insert/2, update/2 and delete/2 mirror the Ecto.Repo write surface. The audit record commits in the same transaction as the change it describes, and a write that cannot be signed fails the operation. There is no unsigned mode.
  • Chronicle.record/3 for facts that are not database mutations — a denied authorization, an export, a payment provider's response.
  • Chronicle.transaction/3, group/3, span/3 and run/3 gather related work into one signed unit carrying its own outcome, duration and child count. Each accepts a do block or an explicit zero-arity function.
  • Chronicle.Multi adds audited steps to an ordinary Ecto.Multi, which stays an Ecto.Multi.
  • Ambient actor, tenant, subject and correlation data through Chronicle.Context, with explicit capture for Task and Oban, and request context for Phoenix.

Integrity

  • A SHA-256 hash chain over a versioned canonical binary encoding, independent of Erlang's External Term Format, with fixed resource limits and rejection of terms that cannot survive a restart.
  • HMAC-SHA-256 signatures over domain-separated digests for content, chain position and authentication, so a digest computed for one purpose can never verify as another.
  • Total order established by a ledger-head row lock, so concurrent writers cannot fork the chain.
  • Sequence-bounded key epochs, expressing rotation boundaries as ledger positions rather than timestamps, with dual-key transition proofs and pluggable keyrings for KMS, Vault or PKCS#11.
  • Chronicle.verify_all/2 recomputes every content digest, chain link and signature, and checks sequence gaps, the mutable ledger head, unsigned rows, duplicate entries and orphaned group children.
  • Chronicle.CheckpointStore anchors verified positions outside the audit database, which is what makes a whole-ledger rewind detectable.
  • Chronicle.Verifier, a supervised process that verifies on a schedule and advances a checkpoint only after a store verifies completely.

Reading

  • Chronicle.history/2 returns record-local versions numbered in committed ledger order, computed in the same statement that returns the page.
  • Chronicle.at/2 reconstructs a past state from a single stored version rather than replaying deltas. Chronicle.revert/2 returns a changeset without persisting it.
  • Chronicle.Query timeline and group queries, paginated by immutable ledger position rather than by timestamp or offset.

Protection

  • Credential-shaped field names are protected by default, matched by substring, underscore-separated segment, and exact name. Configured fields extend the built-in list rather than replacing it.
  • Per-schema redact, hash and omit policies, plus Chronicle.secret/1, hash/1 and omit/0 for values whose sensitivity is in their content rather than their name.
  • A version containing a withheld field reports itself incomplete, and reconstruction fails rather than inventing a value.

Operations

  • mix chronicle.install generates the Ecto migration and a configuration example.
  • mix chronicle.doctor checks configuration, storage, signing, protected fields per audited schema, and ledger integrity.
  • mix chronicle.keys.rotate plans a rotation, without mutating by default.
  • Chronicle.health/2 performs bounded reads only, and reports healthy only when the next write could actually be signed.
  • Structured Chronicle.Error values carrying a matchable reason alongside a human-readable message, and telemetry for verification runs.

Supported

  • Elixir 1.17 or later. That is the first release requiring Erlang/OTP 25, where :crypto.hash_equals/2 is available.
  • PostgreSQL and SQLite. SQLite cannot execute SELECT ... FOR UPDATE, so it does not exercise the lock that establishes total order.

Chronicle is tamper-evident and rollback-resistant, not tamper-proof, and it records only what is written through it. The README states the boundaries in full; read them before relying on this for anything that matters.