fabrica

hanna/fabrica

docs(decisions): record store, sqlx TLS, and build-source decisions

c231ea9 · hanna committed on 2026-07-25

Log the phase-3 choices: `sqlx::Any` over per-dialect code paths, building
sqlx without a TLS backend, and widening the Nix build source to keep `.sql`
migration files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
1 files changed · +52 −0UnifiedSplit
docs/decisions.md +52 −0
@@ -118,3 +118,55 @@ exist; create directories during validation.
118a single-operator forge. Tolerating missing files lets one binary serve both a118a single-operator forge. Tolerating missing files lets one binary serve both a
119bare first-run machine and a fully-configured one. Side-effect-free validation119bare first-run machine and a fully-configured one. Side-effect-free validation
120keeps `config check` safe to run anywhere.120keeps `config check` safe to run anywhere.
121
122## 2026-07-24 — Store runs over `sqlx::Any`, not per-dialect code paths
123
124**Decision:** The `store` crate uses a single `sqlx::Any` pool and writes each
125statement once, rather than a `Store` trait with separate `SqlitePool` and
126`PgPool` implementations. Three dialect differences are mediated explicitly:
127placeholders use the `$1,$2` syntax both SQLite and Postgres accept (only MySQL
128needs `?`); booleans bind as Rust `bool` (encodes correctly for both) and are
129read back through `Store::get_bool`, which decodes SQLite's `INTEGER` storage and
130Postgres's native `BOOLEAN`; timestamps and ids are already portable (`BIGINT`
131ms, `TEXT` ULID).
132
133**Alternatives:** A `Store` trait with two concrete pool implementations (the
134spec's other blessed option), duplicating every query per dialect.
135
136**Rationale:** The spec explicitly permits "runtime queries over `AnyPool`". One
137code path is far less surface to keep in sync than two, and the portability rules
138(portable SQL subset, no compile-time-checked macros) already constrain us to the
139intersection where `Any` is safe. The single genuinely divergent read — booleans
140— is funnelled through one helper, which is exactly the "the trait maps them"
141seam the spec calls for. Migrations remain two dialect-specific `.sql` sets,
142embedded via `sqlx::migrate!` and selected at runtime by backend.
143
144## 2026-07-24 — sqlx built without a TLS backend (for now)
145
146**Decision:** `sqlx` is pulled with `default-features = false` and no
147`tls-*` feature; Postgres connections are plaintext.
148
149**Alternatives:** Enable `tls-rustls-ring` (or native-tls) so Postgres-over-TLS
150works out of the box.
151
152**Rationale:** TLS is only relevant to a remote Postgres, which is a phase-17
153deployment concern; SQLite (the default) and local/socket Postgres need none.
154Omitting it keeps the dependency tree small and, notably, keeps `ring` out of the
155graph so `cargo-deny`'s license set stays clean without special-casing. To be
156revisited when deployment against a managed Postgres is implemented; the change
157is a one-line feature addition plus a `deny.toml` license allowance.
158
159## 2026-07-24 — Nix build source keeps `.sql` migration files
160
161**Decision:** `flake.nix` replaces `craneLib.cleanCargoSource` with a
162`cleanSourceWith` filter that additionally keeps `*.sql`, because
163`sqlx::migrate!` embeds the migration files at compile time (and a store test
164`include_str!`s them for the schema-equivalence check).
165
166**Alternatives:** Move migrations under a path crane already keeps; generate them
167into `OUT_DIR`.
168
169**Rationale:** Crane's cargo-source filter strips non-Rust files, which would
170break every derivation that compiles `store`. Widening the filter is the minimal,
171explicit fix and leaves room to add further asset globs (themes, vendored htmx)
172the same way in later phases.