chronogrid/docs/ARCHITECTURE/ADR/0003-core-sqlcipher-db-init.md

42 lines
2.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-0003: chronogrid_core – SQLCipher 4.x Database Initializer
- **Date**: 2026-08-19
- **Status**: Accepted
- **Task**: 1.3
## Context
ChronoGrid requires an encrypted local-first database for storing namespaces, projects, tasks, notes, links, and ingestion logs. The schema is defined in `docs/API_CONTRACTS/schema.sql` and mandates SQLCipher 4.x (AES-256-CBC) per `docs/ARCHITECTURE/02-security-and-crypto.md`.
## Decision
### Dependency Choice
Adopted `rusqlite` v0.31 with the `bundled-sqlcipher-vendored-openssl` feature. This bundles both the SQLCipher C library and OpenSSL, producing a hermetic build that requires no external system libraries beyond a C compiler and Perl (for the OpenSSL build step).
### Key Handling
The 32-byte master key derived by `chronogrid_crypto::kdf::derive_master_key` (Argon2id) is passed as a **raw hex key** via `PRAGMA key = "x'…'"`. This instructs SQLCipher to use the key material directly, bypassing its internal PBKDF2-HMAC-SHA512 derivation. Rationale: Argon2id is a strictly stronger KDF than PBKDF2; double-derivation would add latency without meaningful security improvement.
### PRAGMA Configuration
| PRAGMA | Value | Purpose |
|----------------------------|-------|-------------------------------------------|
| `key` | Raw hex (32 bytes) | Encrypt/decrypt the database |
| `cipher_compatibility` | `4` | Enforce SQLCipher 4.x defaults |
| `foreign_keys` | `ON` | Required by schema FK constraints |
### Schema Embedding
The canonical schema SQL is embedded at compile time via `include_str!("../../../docs/API_CONTRACTS/schema.sql")`, ensuring the crate always stays in sync with the single source of truth without runtime file I/O.
### Post-Init Validation
After running the DDL, the initializer queries `sqlite_master` to verify all 6 expected tables (`namespaces`, `projects`, `tasks`, `notes`, `task_note_links`, `ingestion_logs`) and 4 indexes exist. A quick `SELECT count(*) FROM sqlite_master` immediately after keying detects wrong-key or corrupt-file conditions early.
## Consequences
- **Positive**: Fully encrypted at-rest storage with zero cloud dependencies. Schema changes in `schema.sql` are automatically picked up on recompilation. 11 unit tests validate correctness.
- **Negative**: First build requires Perl (Strawberry Perl on Windows) for OpenSSL compilation. Build time is ~10 minutes on first compile due to OpenSSL/SQLCipher C compilation (subsequent builds are cached).
- **Trade-off**: Using raw key (bypassing SQLCipher PBKDF2) means the security of the database encryption relies entirely on the Argon2id KDF strength, which is acceptable given our pinned parameters (64 MiB, 3 iterations, 4 threads).