Changelog
Copy MarkdownThis 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/2anddelete/2mirror theEcto.Repowrite 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/3for facts that are not database mutations — a denied authorization, an export, a payment provider's response.Chronicle.transaction/3,group/3,span/3andrun/3gather related work into one signed unit carrying its own outcome, duration and child count. Each accepts adoblock or an explicit zero-arity function.Chronicle.Multiadds audited steps to an ordinaryEcto.Multi, which stays anEcto.Multi.- Ambient actor, tenant, subject and correlation data through
Chronicle.Context, with explicit capture forTaskand 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/2recomputes every content digest, chain link and signature, and checks sequence gaps, the mutable ledger head, unsigned rows, duplicate entries and orphaned group children.Chronicle.CheckpointStoreanchors 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/2returns record-local versions numbered in committed ledger order, computed in the same statement that returns the page.Chronicle.at/2reconstructs a past state from a single stored version rather than replaying deltas.Chronicle.revert/2returns a changeset without persisting it.Chronicle.Querytimeline 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,hashandomitpolicies, plusChronicle.secret/1,hash/1andomit/0for 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.installgenerates the Ecto migration and a configuration example.mix chronicle.doctorchecks configuration, storage, signing, protected fields per audited schema, and ledger integrity.mix chronicle.keys.rotateplans a rotation, without mutating by default.Chronicle.health/2performs bounded reads only, and reports healthy only when the next write could actually be signed.- Structured
Chronicle.Errorvalues 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/2is 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.