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.
86 lines
3.2 KiB
Markdown
86 lines
3.2 KiB
Markdown
# 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`](../../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, and `ON CONFLICT` upsert
|
|
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**: `BookmarkRepository` and
|
|
`SettingsRepository` traits, implemented once by
|
|
`LocalDatabaseRepository`. 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_ref` is a *name*, not a
|
|
secret. The secret behind that name lives in
|
|
`SecretStorageRepository` (see `secure-storage-spike`).
|
|
|
|
## Layout
|
|
|
|
```text
|
|
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.
|
|
|
|
```bash
|
|
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.
|