[1.70.0] - 2026-07-16 — Albireo
Theme: blob-policy composition + two long-standing database fixes — a registry seam that
lets multiple extensions contribute blob access policies simultaneously, plus whereIn() on
write operations and plain create-table indexes on SQLite/PostgreSQL now actually working.
Additive API; no new env vars, no migrations, no default changes. One operational note for
pre-existing dev databases (see Upgrade Notes).
Added
BlobAccessPolicyRegistry+CompositeBlobAccessPolicy— a composition seam over the
existingBlobAccessPolicyextension point. Applications and extensions register named
contributors into the sharedBlobAccessPolicyRegistry(a normal DI service bound by
StorageProvider; no static accessor, no process-global fallback);UploadControllernow
always receives aCompositeBlobAccessPolicywrapping today's primary policy (bound
BlobAccessPolicy, or theNullfallback) AND-composed with every current registry
contributor (veto semantics — any denial denies, short-circuiting in primary-then-insertion
order). The composite holds the registry object itself, not a snapshot, so a contributor
registered during a later extension'sboot()is enforced immediately, even though the
controller was already constructed. With zero contributors, behavior is byte-identical to
the previous unwrapped primary-or-Null policy.
Fixed
whereIn()/whereNotIn()now work onupdate()/delete(). The write path's condition
reparser only recognized single-placeholder raw conditions, sowhereIn's multi-placeholder
col IN (?, ?, …)was rejected with "Complex WHERE conditions … not yet supported" — even
though the UPDATE/DELETE builders already emitIN/NOT INfor array-valued conditions.
The reparser now recovers the column, operator, and bound values (binding offsets preserved
when composed with other predicates).whereIn(col, [])on a write still throws, as before
(the always-false raw condition has no simple-map representation).createTable()no longer silently discards plain (non-unique) indexes on SQLite and
PostgreSQL. Inline->index(...)definitions in a create-table callback were only ever
emitted by the MySQL generator (as inlineKEYclauses); the SQLite/PostgreSQL generators
inlined UNIQUE constraints only, so plain indexes vanished without error.TableBuilder::create()
now emits every plain index as a follow-upCREATE INDEXstatement uniformly across drivers
(the same artifact kind thealterTable()path produces — real and droppable), and the MySQL
generator no longer inlines plain indexes (unique/fulltext stay inline) so nothing is created
twice. Existing SQLite/PostgreSQL databases migrated before this fix are missing those indexes;
re-running the relevantCREATE INDEXstatements (or re-migrating dev databases) restores them.
Performance-only: query results were never affected.
Upgrade Notes
- SQLite/PostgreSQL databases migrated before 1.70.0 are missing every plain index declared
inline in acreateTable()callback (they were silently discarded — see Fixed). Fresh
migrations are correct automatically. For existing databases, re-run the relevant
CREATE INDEXstatements or re-migrate dev databases. Performance-only; no data or query
results were ever affected. - No other action required: the registry seam is additive (zero contributors is byte-identical
to the previous unwrapped policy), and thewhereIn()write fix turns a previously throwing
call into the behavior its builders already advertised.