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

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.