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.
118118 a single-operator forge. Tolerating missing files lets one binary serve both a
119119 bare first-run machine and a fully-configured one. Side-effect-free validation
120120 keeps `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
125+statement once, rather than a `Store` trait with separate `SqlitePool` and
126+`PgPool` implementations. Three dialect differences are mediated explicitly:
127+placeholders use the `$1,$2` syntax both SQLite and Postgres accept (only MySQL
128+needs `?`); booleans bind as Rust `bool` (encodes correctly for both) and are
129+read back through `Store::get_bool`, which decodes SQLite's `INTEGER` storage and
130+Postgres's native `BOOLEAN`; timestamps and ids are already portable (`BIGINT`
131+ms, `TEXT` ULID).
132+
133+**Alternatives:** A `Store` trait with two concrete pool implementations (the
134+spec's other blessed option), duplicating every query per dialect.
135+
136+**Rationale:** The spec explicitly permits "runtime queries over `AnyPool`". One
137+code 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
139+intersection where `Any` is safe. The single genuinely divergent read — booleans
140+— is funnelled through one helper, which is exactly the "the trait maps them"
141+seam the spec calls for. Migrations remain two dialect-specific `.sql` sets,
142+embedded 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
150+works out of the box.
151+
152+**Rationale:** TLS is only relevant to a remote Postgres, which is a phase-17
153+deployment concern; SQLite (the default) and local/socket Postgres need none.
154+Omitting it keeps the dependency tree small and, notably, keeps `ring` out of the
155+graph so `cargo-deny`'s license set stays clean without special-casing. To be
156+revisited when deployment against a managed Postgres is implemented; the change
157+is 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
167+into `OUT_DIR`.
168+
169+**Rationale:** Crane's cargo-source filter strips non-Rust files, which would
170+break every derivation that compiles `store`. Widening the filter is the minimal,
171+explicit fix and leaves room to add further asset globs (themes, vendored htmx)
172+the same way in later phases.