Proof-of-concept proving the SQLite-storage exit criterion from
docs/architecture/proof-of-concept-plan.md §2:
"Schema, migration, and repository pattern are demonstrated."
Also satisfies the SRS-089 acceptance criteria explicitly:
"Storage implementation uses an embedded local data store and
migration mechanism."
Implements:
- A forward-only Migrator over a fixed Migration list, tracking
the applied version via PRAGMA user_version. Each migration is
applied inside an IMMEDIATE transaction; rolled back on failure.
- Three canonical migrations (initial schema, add nickname,
add last_connected_at) demonstrating ALTER TABLE flows.
- A LocalDatabaseRepository implementing both BookmarkRepository
and SettingsRepository traits.
- Bookmark.identity_ref is a reference to a secret name, never
a secret value (cross-checked by the secure-storage spike's
SS-AUD-001/002 scans). This is the SAD-067 separation.
Test suite (11/11 PASS on 2026-05-13):
- migrator brings fresh DB to latest version
- migrator is idempotent (no-op when already current)
- migrator applies only pending versions (catch-up upgrade)
- migrator rejects out-of-order versions
- migrator rejects DB newer than known migrations (downgrade guard)
- failed migration rolls back atomically
- bookmark CRUD round-trip
- bookmark list ordered by recency
- bookmark UNIQUE(host, identity_ref) enforcement
- settings upsert + delete
- open creates file and persists across reopen
Surfaced finding for the decision register: DEC-013 does not pin a
SQLite crate. The PoC uses rusqlite with the bundled feature
(no system libsqlite3 dependency); production code needs an
owner ruling on rusqlite vs. sqlx vs. sea-orm.
Authority: PoC plan §2, SRS-089, SDD-077, SAD-067,
SysDes-033/036/049/091.
Not product code; not promoted into chanora_storage.
3.2 KiB
3.2 KiB
SQLite Storage Spike
Chanora proof-of-concept. Not product code.
| Field | Value |
|---|---|
| PoC name | sqlite-storage-spike |
| PoC plan | docs/architecture/proof-of-concept-plan.md §2 |
| Purpose | Prove embedded local non-secret persistence |
| Exit criterion | "Schema, migration, and repository pattern are demonstrated" |
| Authority | SRS-089 (acceptance criteria: embedded data store + migration mechanism), SDD-077, SAD-067 |
What it proves
- A schema managed entirely inside SQLite (
bookmarks,settings) with a UNIQUE index, secondary indices, andON CONFLICTupsert semantics for the settings table. - A forward-only migration mechanism keyed on
PRAGMA user_version. Migrations are applied atomically inside an IMMEDIATE transaction and rolled back on failure. - A repository pattern:
BookmarkRepositoryandSettingsRepositorytraits, implemented once byLocalDatabaseRepository. The rest of the Rust core can depend on the traits and avoid leaking SQLite-specific types upward (SAD-067, SDD-077). - Cross-spike contract:
Bookmark.identity_refis a name, not a secret. The secret behind that name lives inSecretStorageRepository(seesecure-storage-spike).
Layout
sqlite-storage-spike/
src/
lib.rs # crate root, re-exports
migrate.rs # Migrator + canonical migrations (v1..v3)
repo.rs # traits, DTOs, LocalDatabaseRepository
main.rs # sqlite-storage-cli driver
tests/
storage.rs # 11 tests — migration + repo + persistence
Cargo.toml
Canonical migrations
| Version | Description |
|---|---|
| 1 | Initial bookmarks table + UNIQUE index on (server_host, identity_ref); settings key/value table. |
| 2 | ALTER TABLE bookmarks ADD COLUMN nickname TEXT. |
| 3 | ALTER TABLE bookmarks ADD COLUMN last_connected_at INTEGER + descending index. |
Future migrations are appended only. The migrator refuses to start if
the DB's user_version exceeds the highest known migration (downgrade
guard).
Reproduce
Requires Rust stable (developed against 1.95). rusqlite is built with
the bundled feature, so no system libsqlite3 is needed.
cargo test
cargo run --bin sqlite-storage-cli
Scope boundaries
- Non-secret data only. Any secret material (identity private
keys, server passwords) belongs to
secure-storage-spike/SecretStorageRepository. This is the SAD-067 separation. - Single-process access. Connection pooling, multi-process contention, and concurrent transactions are not exercised here. Production code will likely run on a single Rust core worker.
- No encryption at rest. SQLCipher and similar are not in scope. Secrets must not land in this DB in the first place.
- No backup / restore.
- No reverse migrations. Forward-only is the documented policy; rollbacks require a fresh DB.
- The migration-engine choice (
refinery,sqlx::migrate!, etc.) remains open for the product crate. The PoC's ~50-line custom mechanism is intentional, to keep the audit surface explainable.
Verification log
See VERIFICATION.md in this directory.