docs: Verify and document append-only immutability guarantees
Some checks failed
CI / build (pull_request) Successful in 22s
CI / build (push) Successful in 21s
CI / integration (pull_request) Failing after 1m59s
CI / integration (push) Failing after 2m0s

Document that EventStore interface has no Update/Delete methods, enforcing
append-only semantics by design. Events are immutable once persisted.

Changes:
- Update EventStore interface documentation in event.go to explicitly state
  immutability guarantee and explain why Update/Delete methods are absent
- Add detailed retention policy documentation to JetStreamConfig showing
  how MaxAge limits enforce automatic expiration without manual deletion
- Document JetStreamEventStore's immutability guarantee with storage-level
  explanation of file-based storage and limits-based retention
- Add comprehensive immutability tests verifying:
  - Events cannot be modified after persistence
  - No Update or Delete methods exist on EventStore interface
  - Versions are monotonically increasing
  - Events cannot be deleted through the API
- Update README with detailed immutability section explaining:
  - Interface-level append-only guarantee
  - Storage-level immutability through JetStream configuration
  - Audit trail reliability
  - Pattern for handling corrections (append new event)

Closes #60

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit was merged in pull request #136.
This commit is contained in:
Claude Code
2026-01-13 21:45:26 +01:00
committed by Hugo Nijhuis
parent 464fed67ec
commit 6549125f3d
4 changed files with 280 additions and 8 deletions

View File

@@ -20,7 +20,14 @@ const (
// JetStreamConfig holds configuration options for JetStreamEventStore
type JetStreamConfig struct {
// StreamRetention is how long to keep events (default: 1 year)
// StreamRetention is how long to keep events (default: 1 year).
// JetStream enforces this retention policy at the storage level using a limits-based policy:
// - MaxAge: Events older than this duration are automatically deleted
// - Storage is file-based (nats.FileStorage) for durability
// - Once the retention period expires, events are permanently removed from the stream
// This ensures that old events do not consume storage indefinitely.
// To keep events indefinitely, set StreamRetention to a very large value or configure
// a custom retention policy in the JetStream stream configuration.
StreamRetention time.Duration
// ReplicaCount is the number of replicas for high availability (default: 1)
ReplicaCount int
@@ -42,6 +49,21 @@ func DefaultJetStreamConfig() JetStreamConfig {
// JetStreamEventStore implements EventStore using NATS JetStream for persistence.
// It also implements EventStoreWithErrors to report malformed events during replay.
//
// ## Immutability Guarantee
//
// JetStreamEventStore is append-only. Events are stored in a JetStream stream that
// is configured with file-based storage (nats.FileStorage) and a retention policy
// (nats.LimitsPolicy). The configured MaxAge retention policy ensures that old events
// eventually expire, but during their lifetime, events are never modified or deleted
// through the EventStore API. Once an event is published to the stream:
// - It cannot be updated
// - It cannot be deleted before expiration
// - It can only be read
//
// This architectural guarantee, combined with the EventStore interface providing
// no Update or Delete methods, ensures events are immutable and suitable as an
// audit trail.
//
// ## Version Cache Invalidation Strategy
//
// JetStreamEventStore maintains an in-memory cache of actor versions for optimistic
@@ -72,12 +94,6 @@ type JetStreamEventStore struct {
// NewJetStreamEventStore creates a new JetStream-based event store with default configuration
func NewJetStreamEventStore(natsConn *nats.Conn, streamName string) (*JetStreamEventStore, error) {
return NewJetStreamEventStoreWithConfig(natsConn, streamName, DefaultJetStreamConfig())