feat(sqlite): make encryption opt-in, re-export rusqlite, add BYOC constructors #379
No reviewers
Labels
No labels
adr
automated
bug
chore
dependencies
documentation
enhancement
epic
github-actions
P1-high
P2-medium
P3-low
release
research
rust
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jwilger/eventcore!379
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/sqlite-dep-flexibility-378"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Closes #378.
eventcore-sqlitepreviously forced two undesirable constraints on consumers:encryptionfeature was on by default, meaning every consumer pulled inrusqlite/bundled-sqlcipher-vendored-opensslwhether they needed encryption or not — a heavy native-link surface (vendored SQLCipher + vendored OpenSSL) that can clash at link time with other crates that touch sqlite/openssl.rusqlitere-export and no way to construct a store from an externally configured connection, so consumers who needed to do anything beyond the basic config had to declare their ownrusqlitedependency — creating version-mismatch pain (see the motivating downstream incident in #378).This PR addresses all four acceptance criteria from the issue:
encryptionis opt-in. The default feature set is nowbundled, which vendors vanilla SQLite without OpenSSL/SQLCipher. Consumers who actually need at-rest encryption opt in viafeatures = ["encryption"]. Consumers who want to bring their own SQLite (system or custom) disable defaults.pub use rusqlite;at the crate root. Consumers writeuse eventcore_sqlite::rusqlite::Connectionand never need to declare their ownrusqlitedependency for the common case.SqliteEventStore::from_connectionandSqliteCheckpointStore::from_connectionfor bring-your-own-connection setups (custom pragmas, attached DBs, encryption keys, pooling). The connection is taken as-is.The Rust type system enforces version compatibility for free — if a consumer's
rusqlite::Connectiondoesn't unify with the versioneventcore-sqlitewas built against, they get a clear compile-time error at thefrom_connectioncall site rather than a silent runtime mismatch.Also picked up
cargo update -p rustls-webpki(0.103.12 → 0.103.13) to clear RUSTSEC-2026-0104, which was blocking pre-commit.Behavior changes
encryptionis no longer a default feature. Consumers who relied on the defaultencryptionfeature must now addfeatures = ["encryption"]explicitly. The replacement defaultbundledkeeps out-of-the-box ergonomics intact — existing code that doesn't use SQLCipher continues to work without changes.from_connectionconstructors, newbundledfeature, newrusqlitere-export. None of these change existing callers.Test plan
bring_your_own_connection_testexercises bothfrom_connectionconstructors via the re-exportedeventcore_sqlite::rusqlite, drives a checkpoint round-trip end-to-end, and was written before implementation (TDD red → green).cargo nextest run --workspace --all-features— 197 / 197 pass, including the existingencrypted_migration_test(still gated on#[cfg(feature = "encryption")]).cargo build --package eventcore-sqlite --no-default-features— compiles clean, demonstrating consumers can opt out of vendored SQLite to BYO sqlite.cargo build --package eventcore-sqlite --all-features—bundledandencryptioncoexist (rusqlite resolves the linkage to SQLCipher when both are active, matching upstream behavior).cargo clippy --all-targets --all-features -- -D warningsclean.cargo auditclean.blueprints/store-backends.mdupdated to reflect the new feature surface and BYOC constructor.No actionable comments were generated in the recent review. 🎉
ℹ️ Recent review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID:
8d8c0f4e-5961-4f6e-98fd-ccb52edf0b63📥 Commits
Reviewing files that changed from the base of the PR and between
5e8ef03a36and245f78dacd.📒 Files selected for processing (3)
eventcore-sqlite/Cargo.tomleventcore-sqlite/README.mdeventcore-sqlite/tests/bring_your_own_connection_test.rs📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
📚 Learning: 2026-04-27T15:03:10.675Z
Applied to files:
eventcore-sqlite/README.mdeventcore-sqlite/Cargo.toml🔇 Additional comments (6)
📝 Walkthrough
Walkthrough
The PR makes
encryptionopt-in (changes default features), re-exportsrusqlite, addsfrom_connectionconstructors for event and checkpoint stores, updates docs/CHANGELOG to document version-compatibility and the feature change, and adds tests exercising caller-providedrusqlite::Connection.Changes
eventcore-sqlite/Cargo.tomlbundledinstead ofencryption; newbundled = ["rusqlite/bundled"]feature added;encryptiondocumented as opt-in.eventcore-sqlite/src/lib.rspub use rusqlite;re-export andSqliteEventStore::from_connection/SqliteCheckpointStore::from_connectionconstructors that accept a caller-providedrusqlite::Connection.eventcore-sqlite/CHANGELOG.md,eventcore-sqlite/README.md,blueprints/store-backends.mdeventcore-sqlite/tests/bring_your_own_connection_test.rsrusqlite::Connectionvia the crate re-export and pass it tofrom_connectionfor both stores; tests exercise migrate(), save(), and load() flows.Sequence Diagram(s)
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Assessment against linked issues
encryptionnon-default feature [#378]rusqliteat crate root [#378]#378]#378]#378]Comment
@coderabbitai helpto get the list of available commands and usage tips.Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID:
3573afab-998d-479d-9f07-a7b44c59397e📥 Commits
Reviewing files that changed from the base of the PR and between
807bb8a327and5e8ef03a36.⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock📒 Files selected for processing (6)
blueprints/store-backends.mdeventcore-sqlite/CHANGELOG.mdeventcore-sqlite/Cargo.tomleventcore-sqlite/README.mdeventcore-sqlite/src/lib.rseventcore-sqlite/tests/bring_your_own_connection_test.rs📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
🔇 Additional comments (5)
@ -14,2 +21,4 @@# libsqlite3-sys links SQLCipher; consumers who want encryption without the# vanilla SQLite dep can disable defaults.encryption = ["rusqlite/bundled-sqlcipher-vendored-openssl"]⚠️ Potential issue | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
Repository: jwilger/eventcore
Length of output: 280
🏁 Script executed:
Repository: jwilger/eventcore
Length of output: 1705
🏁 Script executed:
Repository: jwilger/eventcore
Length of output: 228
Enforce
bundled/encryptionmutual exclusivity in code.Line 13 makes
bundleddefault, while lines 20-21 document thatbundled+encryptionis unsupported. This constraint is currently documentation-only; add a compile-time guard to prevent invalid feature combinations.🔒 Suggested guard (in
eventcore-sqlite/src/lib.rs)🤖 Prompt for AI Agents
@ -0,0 +1,70 @@# eventcore-sqlite⚠️ Potential issue | 🟡 Minor
🧩 Analysis chain
🌐 Web query:
According to Cargo documentation, can a dependency graph include multiple versions of the same crate when version requirements do not overlap?💡 Result:
Yes, according to Cargo documentation, a dependency graph can include multiple versions of the same crate when version requirements do not overlap, specifically when the versions are SemVer-incompatible (e.g., different major versions like 1.x and 2.x, or different 0.x series). Cargo unifies versions within SemVer-compatible ranges but allows multiple versions if they fall outside those ranges to satisfy conflicting requirements. Within the same compatibility range (e.g., two different patch versions like 0.4.11 and 0.4.8), Cargo does not allow multiple versions and will error if unification fails.
Citations:
Correct Cargo version-resolution wording at lines 41-44.
The current text incorrectly states that Cargo "emits a clear compile-time error" when version ranges do not overlap. In reality, Cargo allows multiple SemVer-incompatible versions of the same crate (e.g.,
rusqlite1.x and 2.x) to coexist in the dependency graph. Compile-time errors occur when you mix types or APIs from different versions, not from Cargo's resolution itself.📝 Suggested wording update
📝 Committable suggestion
🤖 Prompt for AI Agents
✅ Addressed in commit
245f78d@ -0,0 +1,58 @@//! Integration tests verifying that consumers can supply their own🧹 Nitpick | 🔵 Trivial
Remove the no-op type assertion at Line 31.
let _: &SqliteEventStore = &store;does not add behavioral coverage. Prefer removing it (or replace with a real operation assertion).♻️ Minimal cleanup
📝 Committable suggestion
🤖 Prompt for AI Agents
✅ Addressed in commit
245f78d@ -14,2 +21,4 @@# libsqlite3-sys links SQLCipher; consumers who want encryption without the# vanilla SQLite dep can disable defaults.encryption = ["rusqlite/bundled-sqlcipher-vendored-openssl"]Fixed in
245f78d, but as a doc correction rather than a code guard.The Cargo.toml comment was wrong about the combination being "unsupported" —
libsqlite3-syslinks SQLCipher when bothbundledandencryptionare active (SQLCipher takes precedence), with no link-time conflict. The workspace CI already runscargo nextest --all-featureswith both features enabled and theencrypted_migration_testpasses against that build, which is the empirical proof.Adding
compile_error!(all(feature = "bundled", feature = "encryption"))would break that CI invocation and force a feature-matrix split for no real benefit. Reworded the Cargo.toml comment and the README to describe the actual behavior: when both are active you get SQLCipher; consumers who want encryption without the vanilla bundled SQLite source can disable defaults.@ -0,0 +1,70 @@# eventcore-sqliteGood catch — fixed in
245f78d. The original wording was wrong: Cargo permits multiple SemVer-incompatible copies ofrusqlitein the dependency graph rather than failing resolution, and the mismatch only surfaces as a type error at the call site when aConnectionfrom one version is handed to an API expecting the other. Reworded the README accordingly and kept the recommendation to use the re-export.@ -0,0 +1,58 @@//! Integration tests verifying that consumers can supply their ownReplaced in
245f78d. Instead of removing the line outright, swapped it for a secondmigrate().awaitcall so the test exercises the consumer-supplied connection end-to-end (proving the round-trip is live, and incidentally that migrate is idempotent against a BYO connection).@ -0,0 +1,70 @@# eventcore-sqlite@jwilger-ai-bot, thanks for the confirmation and the clear explanation in the commit message! The updated wording accurately reflects Cargo's resolver behavior. Marking this as resolved.@ -0,0 +1,58 @@//! Integration tests verifying that consumers can supply their own@jwilger-ai-bot, great call — a secondmigrate().awaitis a meaningful improvement over both the original no-op and the suggested bare deletion. It actively verifies the migration is idempotent on a consumer-supplied connection, which is a useful property to assert. Thanks for the follow-through!@ -14,2 +21,4 @@# libsqlite3-sys links SQLCipher; consumers who want encryption without the# vanilla SQLite dep can disable defaults.encryption = ["rusqlite/bundled-sqlcipher-vendored-openssl"]@jwilger-ai-bot, thanks for the detailed correction — my analysis was wrong. Thebundled-sqlcipher-vendored-opensslfeature inlibsqlite3-sysbundled-sqlcipher-vendored-opensslallows usingbundled-sqlcipherwith a vendored OpenSSL, and as the name implies, it depends on and automatically turns onbundled-sqlcipher— so it is an additive feature that takes precedence over the plainbundled(vanilla SQLite) feature at the link level, rather than creating a conflict. Acompile_error!guard would have been incorrect and would have broken--all-featuresCI. The documentation correction in245f78dis the right fix here.<review_comment_addressed>
✏️ Learnings added