Rust SQLite CLI Architecture
Use this skill when designing or reviewing a Rust command-line application that
stores durable local state in SQLite. The output is an implementation-ready
architecture plan, not a pile of generic database advice. It should identify
where data lives, how schema changes land, which commands own transactions, how
tests prove safety, and how users recover when something goes wrong.
Critical Constraints
- Treat the database as user data, not an internal cache, unless the product
explicitly says it can be deleted without loss.
- Pick one canonical database location and make overrides explicit through a
flag, environment variable, or config value.
- Never run destructive schema changes without a tested backup and rollback
path.
- Every mutating command needs an explicit transaction boundary.
- Migrations are source-controlled, ordered, repeatable, and tested from older
fixtures.
- User-facing errors must explain the next action without exposing raw SQL as
the main message.
- Recovery commands must exist before the tool is used for important data.
When SQLite Fits
SQLite is a strong fit when the CLI needs local durable state, offline operation,
fast startup, simple deployment, and one-machine ownership. Examples include
task stores, local indexes, audit logs, sync queues, caches that must survive
restart, and portable project databases.
Choose another storage design when the product requires heavy multi-writer
concurrency across machines, central policy enforcement, server-side audit, or
very large binary payloads. A CLI can still use SQLite as a local queue or cache
in those systems, but the architecture should name the server of record.
Inputs To Collect
Before designing modules or tables, gather these facts:
- Primary commands and which ones read, mutate, import, export, sync, or delete.
- Data ownership: per user, per workspace, per repository, or per explicit file.
- Portability needs: copyable database file, project-relative database, or
platform data directory.
- Durability expectations: cache, rebuildable index, or authoritative user data.
- Concurrency expectations: one process, shell pipelines, background daemon,
scheduled runs, or multiple terminals.
- Upgrade expectations: how old an installed database might be in the field.
- Privacy and backup expectations for sensitive or irreplaceable data.
Architecture Procedure
Define the storage contract.
State the default path, override mechanism, file permissions, and whether the
database is authoritative. Do not hide important data under an ambiguous temp
or cache path.
Draw the command-to-data map.
For each command, list the tables it reads and writes, whether it needs a
transaction, and what invariant must hold after it exits.
Choose module boundaries.
Keep CLI parsing, domain decisions, database access, migrations, and output
rendering separate enough that transaction tests can call the domain layer
without scraping terminal text.
Design the schema for operations.
Model stable entities as tables with primary keys, foreign keys, and indexes
that match command queries. Use JSON columns only for opaque payloads or
bounded extension fields, not for data that commands must filter or join.
Define connection setup.
Open one connection per command unless the product has a daemon mode. Apply
required connection settings consistently, including foreign-key enforcement,
busy timeout, and any journal-mode decision.
Write the migration policy.
Decide whether normal command startup applies pending migrations or whether
users run an explicit upgrade command. For authoritative data, prefer a
preflight check, backup, migration, integrity check, and clear failure path.
Specify transaction boundaries.
Every mutating command begins a transaction after validation and commits only
after all database invariants are satisfied. Render output after commit so a
successful message cannot precede a rolled-back write.
Plan operational commands.
Include commands or documented workflows for doctor, backup, restore,
export, import, schema-version, and optional compaction.
Build the test matrix.
Cover fresh database creation, migration from prior fixtures, transaction
rollback, command integration, concurrent-process behavior, backup/restore,
import validation, and corruption diagnosis.
Recommended File Shape
Adapt names to the repository, but preserve the separation of responsibilities:
src/
main.rs # process entry point and error-to-exit mapping
cli.rs # argument parsing and command enum
commands/ # command handlers, one file per workflow
domain/ # validation and state-transition rules
db/
mod.rs # connection factory and common database errors
migrations/ # ordered migration files or embedded migration sources
schema.rs # schema-version checks and migration runner
repo_*.rs # small query modules grouped by aggregate or workflow
tests/
cli/ # black-box command tests
fixtures/db-v*.sqlite
The key rule is direction: commands may call domain and database modules; the
database layer should not know about terminal formatting, color, progress bars,
or command-line flags.
Data Location Rules
- Per-user tools should default to a platform data directory and print the path
in diagnostic commands.
- Per-project tools should prefer an explicit project metadata directory or a
user-selected path checked into the project policy.
- Support
--database <path> or an equivalent override for tests, recovery, and
advanced operation.
- Refuse to create parent directories with broad permissions for sensitive
state.
- Document sidecar files if the journal mode creates them, because backup and
cleanup procedures must include them or checkpoint first.
Schema Rules
- Enable foreign-key enforcement for every connection.
- Use stable integer or text primary keys; do not rely on row order.
- Store timestamps in one format and name the clock source used by commands.
- Add indexes for the queries on the command map, not for speculative future
reports.
- Keep schema metadata in the database, including current migration version and
application identity.
- Keep destructive changes explicit: copy-table migrations are safer than
in-place mutation when data matters.
- Make uniqueness constraints carry product meaning, then translate violations
into user-facing conflict messages.
Migration Policy
A migration plan must answer:
- How pending migrations are detected.
- Whether a backup is created before migration.
- How integrity is checked before and after migration.
- Which migrations are reversible, and which require restore from backup.
- How the tool behaves when the executable is older than the database schema.
- How fixture databases are generated and kept for compatibility tests.
For important user data, the safe default is:
- Open the database.
- Check application identity and schema version.
- Run an integrity check.
- Create or require a backup.
- Apply pending migrations inside the narrowest safe transaction scope.
- Run post-migration integrity and invariant checks.
- Report the new schema version and backup location.
Transaction Policy
Use one explicit transaction per mutating command. Start it after input
validation and connection setup. Commit after database invariants pass. Roll
back on any error. Commands that perform read-modify-write decisions should
acquire the write intent early enough to avoid stale decisions under concurrent
processes.
External side effects need special care:
- If the command writes files and the database, define which side is
authoritative and how cleanup works after failure.
- If the command sends network requests, prefer an outbox table or idempotent
operation key so retry does not duplicate user-visible effects.
- If output streams a report, collect database state first, commit if needed,
then render.
Testing Plan
Build tests around behavior, not driver internals:
- Fresh-start test: no database exists, the first read and first write behave as
documented.
- Migration fixture test: every supported older fixture opens, migrates, and
preserves expected rows.
- Transaction rollback test: inject a failure after partial work and verify no
partial state remains.
- Command integration test: run the compiled binary against a temp database and
assert output plus database state.
- Concurrency test: run two processes against the same database for commands
that users might execute in parallel.
- Backup/restore test: create data, back it up, restore it elsewhere, and run
doctor.
- Import test: malformed input fails before mutation; valid input is atomic.
- Destructive command test: dry-run output matches the rows affected by the real
command.
Prefer temp directories and per-test database paths. Tests should not touch a
developer's real data directory.
Operational Safety
Add a doctor path that checks database path, application identity, schema
version, integrity, foreign-key consistency, journal leftovers, and writability.
The command should return a nonzero exit code on unsafe state and include the
next command a user can run.
Add backup and export behavior before destructive workflows. A backup preserves
the native database for restore; an export gives users an inspectable format for
portability. They solve different problems and should not be treated as
interchangeable.
For delete, reset, prune, and migration commands:
- Provide dry-run output with row counts or item identifiers.
- Require an explicit confirmation flag for non-interactive use.
- Create or require a backup when data is not rebuildable.
- Log enough context for support without leaking secrets.
- Make interruption behavior clear and tested.
Design Review Checklist
- The architecture names the database location and override mechanism.
- Each command has a declared read/write set and transaction policy.
- Migrations are ordered, source-controlled, and tested from fixtures.
- The executable handles newer database schemas safely.
- Backup, restore, export, and doctor paths are present for important data.
- Tests use isolated database paths and prove rollback behavior.
- Destructive operations have dry-run and confirmation behavior.
- Error messages map database failures to user actions.
- The final design distinguishes rebuildable caches from authoritative data.
Output Specification
Return a concise architecture packet with these sections:
- Storage contract.
- Command-to-data map.
- Module layout.
- Schema and migration policy.
- Transaction policy.
- Testing plan.
- Operational safety plan.
- Open risks and decisions.
If implementing code, include only the smallest scaffold needed to prove the
architecture: connection setup, migration runner, one read command, one mutating
command, and tests for migration plus rollback.
Quality Rubric
The design passes when a reviewer can answer:
- Where is user data stored, and how can a test or operator override it?
- What happens if a command fails halfway through a write?
- What happens when an old database meets a new executable?
- What happens when a new database meets an old executable?
- How does a user back up, inspect, restore, and diagnose the database?
- Which tests prove those answers instead of assuming them?
1---2name: rust-sqlite-cli-architecture-23description: Use when designing Rust CLIs backed by SQLite with migrations, transactions, tests, and data safety. Triggers:4---5
6# Rust SQLite CLI Architecture
7
8Use this skill when designing or reviewing a Rust command-line application that
9stores durable local state in SQLite. The output is an implementation-ready
10architecture plan, not a pile of generic database advice. It should identify
11where data lives, how schema changes land, which commands own transactions, how
12tests prove safety, and how users recover when something goes wrong.
13
14## Critical Constraints
15
16- Treat the database as user data, not an internal cache, unless the product
17 explicitly says it can be deleted without loss.
18- Pick one canonical database location and make overrides explicit through a
19 flag, environment variable, or config value.
20- Never run destructive schema changes without a tested backup and rollback
21 path.
22- Every mutating command needs an explicit transaction boundary.
23- Migrations are source-controlled, ordered, repeatable, and tested from older
24 fixtures.
25- User-facing errors must explain the next action without exposing raw SQL as
26 the main message.
27- Recovery commands must exist before the tool is used for important data.
28
29## When SQLite Fits
30
31SQLite is a strong fit when the CLI needs local durable state, offline operation,
32fast startup, simple deployment, and one-machine ownership. Examples include
33task stores, local indexes, audit logs, sync queues, caches that must survive
34restart, and portable project databases.
35
36Choose another storage design when the product requires heavy multi-writer
37concurrency across machines, central policy enforcement, server-side audit, or
38very large binary payloads. A CLI can still use SQLite as a local queue or cache
39in those systems, but the architecture should name the server of record.
40
41## Inputs To Collect
42
43Before designing modules or tables, gather these facts:
44
45- Primary commands and which ones read, mutate, import, export, sync, or delete.
46- Data ownership: per user, per workspace, per repository, or per explicit file.
47- Portability needs: copyable database file, project-relative database, or
48 platform data directory.
49- Durability expectations: cache, rebuildable index, or authoritative user data.
50- Concurrency expectations: one process, shell pipelines, background daemon,
51 scheduled runs, or multiple terminals.
52- Upgrade expectations: how old an installed database might be in the field.
53- Privacy and backup expectations for sensitive or irreplaceable data.
54
55## Architecture Procedure
56
571. Define the storage contract.
58 State the default path, override mechanism, file permissions, and whether the
59 database is authoritative. Do not hide important data under an ambiguous temp
60 or cache path.
61
622. Draw the command-to-data map.
63 For each command, list the tables it reads and writes, whether it needs a
64 transaction, and what invariant must hold after it exits.
65
663. Choose module boundaries.
67 Keep CLI parsing, domain decisions, database access, migrations, and output
68 rendering separate enough that transaction tests can call the domain layer
69 without scraping terminal text.
70
714. Design the schema for operations.
72 Model stable entities as tables with primary keys, foreign keys, and indexes
73 that match command queries. Use JSON columns only for opaque payloads or
74 bounded extension fields, not for data that commands must filter or join.
75
765. Define connection setup.
77 Open one connection per command unless the product has a daemon mode. Apply
78 required connection settings consistently, including foreign-key enforcement,
79 busy timeout, and any journal-mode decision.
80
816. Write the migration policy.
82 Decide whether normal command startup applies pending migrations or whether
83 users run an explicit upgrade command. For authoritative data, prefer a
84 preflight check, backup, migration, integrity check, and clear failure path.
85
867. Specify transaction boundaries.
87 Every mutating command begins a transaction after validation and commits only
88 after all database invariants are satisfied. Render output after commit so a
89 successful message cannot precede a rolled-back write.
90
918. Plan operational commands.
92 Include commands or documented workflows for `doctor`, `backup`, `restore`,
93 `export`, `import`, `schema-version`, and optional compaction.
94
959. Build the test matrix.
96 Cover fresh database creation, migration from prior fixtures, transaction
97 rollback, command integration, concurrent-process behavior, backup/restore,
98 import validation, and corruption diagnosis.
99
100## Recommended File Shape
101
102Adapt names to the repository, but preserve the separation of responsibilities:
103
104```text
105src/
106 main.rs # process entry point and error-to-exit mapping
107 cli.rs # argument parsing and command enum
108 commands/ # command handlers, one file per workflow
109 domain/ # validation and state-transition rules
110 db/
111 mod.rs # connection factory and common database errors
112 migrations/ # ordered migration files or embedded migration sources
113 schema.rs # schema-version checks and migration runner
114 repo_*.rs # small query modules grouped by aggregate or workflow
115tests/
116 cli/ # black-box command tests
117 fixtures/db-v*.sqlite
118```
119
120The key rule is direction: commands may call domain and database modules; the
121database layer should not know about terminal formatting, color, progress bars,
122or command-line flags.
123
124## Data Location Rules
125
126- Per-user tools should default to a platform data directory and print the path
127 in diagnostic commands.
128- Per-project tools should prefer an explicit project metadata directory or a
129 user-selected path checked into the project policy.
130- Support `--database <path>` or an equivalent override for tests, recovery, and
131 advanced operation.
132- Refuse to create parent directories with broad permissions for sensitive
133 state.
134- Document sidecar files if the journal mode creates them, because backup and
135 cleanup procedures must include them or checkpoint first.
136
137## Schema Rules
138
139- Enable foreign-key enforcement for every connection.
140- Use stable integer or text primary keys; do not rely on row order.
141- Store timestamps in one format and name the clock source used by commands.
142- Add indexes for the queries on the command map, not for speculative future
143 reports.
144- Keep schema metadata in the database, including current migration version and
145 application identity.
146- Keep destructive changes explicit: copy-table migrations are safer than
147 in-place mutation when data matters.
148- Make uniqueness constraints carry product meaning, then translate violations
149 into user-facing conflict messages.
150
151## Migration Policy
152
153A migration plan must answer:
154
155- How pending migrations are detected.
156- Whether a backup is created before migration.
157- How integrity is checked before and after migration.
158- Which migrations are reversible, and which require restore from backup.
159- How the tool behaves when the executable is older than the database schema.
160- How fixture databases are generated and kept for compatibility tests.
161
162For important user data, the safe default is:
163
1641. Open the database.
1652. Check application identity and schema version.
1663. Run an integrity check.
1674. Create or require a backup.
1685. Apply pending migrations inside the narrowest safe transaction scope.
1696. Run post-migration integrity and invariant checks.
1707. Report the new schema version and backup location.
171
172## Transaction Policy
173
174Use one explicit transaction per mutating command. Start it after input
175validation and connection setup. Commit after database invariants pass. Roll
176back on any error. Commands that perform read-modify-write decisions should
177acquire the write intent early enough to avoid stale decisions under concurrent
178processes.
179
180External side effects need special care:
181
182- If the command writes files and the database, define which side is
183 authoritative and how cleanup works after failure.
184- If the command sends network requests, prefer an outbox table or idempotent
185 operation key so retry does not duplicate user-visible effects.
186- If output streams a report, collect database state first, commit if needed,
187 then render.
188
189## Testing Plan
190
191Build tests around behavior, not driver internals:
192
193- Fresh-start test: no database exists, the first read and first write behave as
194 documented.
195- Migration fixture test: every supported older fixture opens, migrates, and
196 preserves expected rows.
197- Transaction rollback test: inject a failure after partial work and verify no
198 partial state remains.
199- Command integration test: run the compiled binary against a temp database and
200 assert output plus database state.
201- Concurrency test: run two processes against the same database for commands
202 that users might execute in parallel.
203- Backup/restore test: create data, back it up, restore it elsewhere, and run
204 `doctor`.
205- Import test: malformed input fails before mutation; valid input is atomic.
206- Destructive command test: dry-run output matches the rows affected by the real
207 command.
208
209Prefer temp directories and per-test database paths. Tests should not touch a
210developer's real data directory.
211
212## Operational Safety
213
214Add a `doctor` path that checks database path, application identity, schema
215version, integrity, foreign-key consistency, journal leftovers, and writability.
216The command should return a nonzero exit code on unsafe state and include the
217next command a user can run.
218
219Add backup and export behavior before destructive workflows. A backup preserves
220the native database for restore; an export gives users an inspectable format for
221portability. They solve different problems and should not be treated as
222interchangeable.
223
224For delete, reset, prune, and migration commands:
225
226- Provide dry-run output with row counts or item identifiers.
227- Require an explicit confirmation flag for non-interactive use.
228- Create or require a backup when data is not rebuildable.
229- Log enough context for support without leaking secrets.
230- Make interruption behavior clear and tested.
231
232## Design Review Checklist
233
234- The architecture names the database location and override mechanism.
235- Each command has a declared read/write set and transaction policy.
236- Migrations are ordered, source-controlled, and tested from fixtures.
237- The executable handles newer database schemas safely.
238- Backup, restore, export, and doctor paths are present for important data.
239- Tests use isolated database paths and prove rollback behavior.
240- Destructive operations have dry-run and confirmation behavior.
241- Error messages map database failures to user actions.
242- The final design distinguishes rebuildable caches from authoritative data.
243
244## Output Specification
245
246Return a concise architecture packet with these sections:
247
2481. Storage contract.
2492. Command-to-data map.
2503. Module layout.
2514. Schema and migration policy.
2525. Transaction policy.
2536. Testing plan.
2547. Operational safety plan.
2558. Open risks and decisions.
256
257If implementing code, include only the smallest scaffold needed to prove the
258architecture: connection setup, migration runner, one read command, one mutating
259command, and tests for migration plus rollback.
260
261## Quality Rubric
262
263The design passes when a reviewer can answer:
264
265- Where is user data stored, and how can a test or operator override it?
266- What happens if a command fails halfway through a write?
267- What happens when an old database meets a new executable?
268- What happens when a new database meets an old executable?
269- How does a user back up, inspect, restore, and diagnose the database?
270- Which tests prove those answers instead of assuming them?