Files
chanora/poc/sqlite-storage-spike/README.md
T
EdisonJwa 52e8d43f69 feat(poc/storage): add sqlite-storage spike
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.
2026-05-14 12:26:36 +08:00

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, 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

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.