Date: 2026-07-13 Status: Accepted Target: v0.29.0
Storage today is hard-wired to SQLite via Rust (per ADR-013). Every code path that touches attestation storage assumes the SqliteStore concrete type. There is no notion of "backend" — there is only "the store."
The existing [database] config block conflates two things: which backend is used (implicit: SQLite) and how that backend is configured (path, backup_interval_seconds, bounded_storage). Introducing a separate [storage] block for backend selection alongside [database] for SQLite settings is confusing — two blocks doing storage-adjacent things.
Backend becomes a chosen thing. [storage] backend = "sqlite" selects the concrete store at startup. Accepted values are sqlite (this ADR) and parquet (ADR-024). Additional backends are added by subsequent ADRs.
Backend-specific configuration lives under [storage.<backend>]. SQLite's settings — path, backup_interval_seconds, bounded_storage — move from [database] to [storage.sqlite]. [database] is removed.
[storage]
backend = "sqlite"
[storage.sqlite]
path = "qntx.db"
backup_interval_seconds = 3600
[storage.sqlite.bounded_storage]
actor_context_limit = 32
actor_contexts_limit = 64
entity_actors_limit = 64
Backend implementations are Rust crates at crates/qntx-<name> exposed to Go via CGO/FFI, following the ADR-013 ownership pattern. The Go side lives at ats/storage/<name>cgo. Adding a new backend means adding a new crate and its FFI surface, not modifying an existing one.
A running QNTX has exactly one backend. No dual-backend operation, no runtime swap.
DatabaseConfig in internal/config/am.go is renamed and moved under StorageConfig. Every cfg.Database.* reader becomes cfg.Storage.Sqlite.* (~15 call sites).am.toml files with [database] no longer parse. Pre-release, no migration required.QNTX_DATABASE_PATH environment variable is renamed to QNTX_STORAGE_SQLITE_PATH to match the new config key.[storage.<name>] block.