Packages

BEAM-native coordinate event store. Append-only segment files, ETS indexes, BLAKE3 hash chains.

Current section

Files

Jump to
event_cell CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.6.1] - 2026-03-15
### Fixed
- **Corrupt-tail recovery**: Writer now scans for last valid frame offset on startup instead of using raw file size; truncates corrupt tail bytes so new appends are always reachable on next cold start
- **Malformed subscription patterns**: `subscribe/3` now validates pattern keys (`:entity`, `:fact`, `:scope` only); unknown keys are stripped with a warning instead of crashing the writer on next `notify/3`
- **Metadata durability**: `put_meta/3` now increments sync counter and triggers `maybe_sync`/`maybe_rotate`, matching the durability contract of regular appends
- **v1/v2 hash verification**: `Entry.verify_hash/1` now dispatches SHA-256 for `:v1`/`:v2` entries and BLAKE3 for `:v3`; added `format_version` field to Entry struct, set during deserialization
- **Legacy-format compaction**: `Segment.encode_entry/1` now preserves the entry's `format_version` tag (`:v1`/`:v2`/`:v3`) instead of always emitting `:v3`; compaction of sealed v1/v2 segments no longer fails verification
- **Subscriber death cleanup**: Subscriptions routed through Writer GenServer, which holds the process monitor and handles `:DOWN` immediately; dead subscribers are cleaned up on death, not deferred to next `notify/3`. Lazy cleanup in `notify/3` remains as a secondary safety net.
- **Unsafe ETF parsing**: All `binary_to_term` call sites now use `[:safe]` option (segment, hint file, and snapshot loaders) to prevent hostile atom creation from untrusted files
- **`limit: 0` crash**: `Index.query/2` no longer raises `CaseClauseError` on `limit: 0`; returns `[]`
- **Scope wildcard excludes root**: `scope: "org:acme/*"` now matches both `"org:acme"` and `"org:acme/..."` (same fix applied to entity prefix queries)
## [0.6.0] - 2026-03-15
### Added
- Segment compaction: `Store.compact/1` rewrites sealed segments in entity-sorted order
- Integrity auditor: periodic GenServer sampling CRC and hash chain integrity
- Compaction verification: full chain + hash recomputation before atomic cutover
- Chaos tests: writer kill recovery, corruption detection, concurrent stress (tagged `:chaos`)
### Changed
- Hash algorithm: BLAKE3 replaces SHA-256 (1.5-2x faster, still in `:crypto` stdlib)
- `:v3` serialization format with BLAKE3 hashes (`:v1`/`:v2` with SHA-256 remain readable)
- `datasync` replaces `sync` for durability flushes (skips inode metadata, halves latency)
- `Registry.lock/3` replaces ETS spin lock for per-entity serialization (Elixir 1.18)
- ETS tables use `write_concurrency: :auto` (OTP 25+) for adaptive lock granularity
- Index scanning uses `:ets.next_lookup/2` (OTP 27) — single atomic ETS call per step
- `:file.advise/4` hints OS about sequential vs random access patterns
- `:file.allocate/3` for segment pre-allocation where supported (POSIX `fallocate`)
- `proc_lib:set_label/1` on writer/reader processes for Observer visibility (OTP 27)
### Fixed
- Compaction invalidates Segment.Reader cached FDs after file replacement
- Compaction verifies hash chains (not just entry count) before cutover
## [0.5.0] - 2026-03-15
### Added
- ExDoc guide configuration with module groupings
- HexDocs extras: architecture, mental model, quick start, format versions, operations
## [0.4.0] - 2026-03-15
### Added
- Live subscriptions: `Store.subscribe/2`, `Store.unsubscribe/2`
- Pattern-matched event delivery with entity/fact/scope filters
- Process monitoring with auto-unsubscribe on subscriber death
- Backpressure: drops notifications when subscriber mailbox exceeds 10K messages
## [0.3.0] - 2026-03-15
### Added
- `Store.count/2`: count entries matching filters without materializing payloads
- `Store.has_fact?/3`: check if fact type exists for entity
### Changed
- `Index.stream/3` uses `:ets.next` prefix scan on entity ordered_set (O(log N))
- `Index.count/1` uses `:ets.select_count` instead of fold
- `Index.query/2` uses dimension tables for exact entity/fact/scope filters
## [0.2.0] - 2026-03-15
### Added
- `Entry.Ref`: lightweight index entry (no payload in ETS memory)
- `Segment.Reader`: read-only FD manager with LRU eviction for sealed segments
- Optional telemetry integration (conditional on `:telemetry` dep)
### Changed
- **Breaking:** `Store.stream/3` returns `Enumerable.t()` (lazy Stream), not `{:ok, [Entry.t()]}`
- Index stores `Entry.Ref` instead of full `Entry` (10x memory reduction at scale)
- Store hydrates Refs to Entries via Segment.Reader on demand
## [0.1.1] - 2026-03-15
### Added
- Golden fixtures in `test/fixtures/format/v0.1.0/` with backwards compatibility tests
- Test infrastructure: `EventCell.Case`, `Factory`, `Assertions`, `Generators`
- Protocol implementations: `String.Chars` for Coordinate, `Inspect` for Coordinate/Entry/Store
- `EventCell.child_spec/1` for supervision tree integration
- `Store.open/2`: idempotent open-or-create with shared state
- `Entry.content_address/1` promoted to public API
- Format contract documentation and ADR-001
### Fixed
- Segment.Writer `maybe_rotate/1` handles open failure gracefully
- Snapshot `replay_from/5` logs deserialization errors instead of swallowing
- `require Logger` at module level in Segment.Writer (not inside functions)
## [0.1.0] - 2026-03-14
### Added
- Core storage engine: append-only segment files with CRC32 integrity
- 4D coordinate system: entity/fact/scope/clock with validation
- SHA-256 hash chains per entity stream (append order, not clock order)
- ETS SoA indexes with collision-safe keys (hash discriminator)
- Single segment writer GenServer (crash-safe sequential appends)
- Configurable fsync (event count threshold + periodic timer)
- Segment rotation on size threshold (default 256MB)
- Snapshot infrastructure (take/load/replay via `:ets.tab2file`)
- Cold start recovery: snapshot + incremental replay, or full replay
- Symbols: ETS-backed string interning with v2 self-describing format
- Durable metadata side channel (`put_meta`/`get_meta`)
- OTP supervision: automatic writer restart on crash
- Registry-based naming: zero atom leaking, any term as store name
- Multi-dimension queries: filter by entity, fact, scope, clock range
- Scope prefix matching for hierarchical isolation
- Single runtime dependency (`blake3` — Rust NIF for BLAKE3 hashing)