Packages

Tamper-evident audit facts and restorable Ecto record versions

Current section

Files

Jump to
chronicle CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
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.