Ferro's transaction context manager (async with transaction():) gives us guaranteed connection affinity for ORM calls — but there is no public API to run an arbitrary SQL statement on that same connection. This blocks legitimate uses such as setting Postgres GUCs (SET LOCAL, set_config()), advisory locks, LISTEN/NOTIFY setup, or one-off statements that don't fit a Model.
Proposed API
async def execute(sql: str, *args, tx_id: str | None = None) -> None:
"""Run a raw statement on the current transaction's connection.
Uses positional asyncpg-style placeholders ($1, $2, ...).
Honors the active `transaction()` context if `tx_id` is omitted.
"""
Acceptance criteria
- When called inside
async with transaction():, runs on that transaction's connection.
- When called outside any transaction, runs on a one-off pool connection.
- Parameters bind as
$1, $2, ...; reject embedded interpolation.
- Errors surface as the same exception types Ferro's CRUD calls already raise.
Motivation
A downstream project (Blueberry) is implementing Postgres row-level security and needs to run select set_config('request.jwt.claims', $1, true) per request inside the request's Ferro transaction. This is the only Ferro gap blocking that work; full design at docs/superpowers/specs/2026-04-27-rls-security-design.md in the consumer repo.
Ferro's transaction context manager (
async with transaction():) gives us guaranteed connection affinity for ORM calls — but there is no public API to run an arbitrary SQL statement on that same connection. This blocks legitimate uses such as setting Postgres GUCs (SET LOCAL,set_config()), advisory locks,LISTEN/NOTIFYsetup, or one-off statements that don't fit a Model.Proposed API
Acceptance criteria
async with transaction():, runs on that transaction's connection.$1, $2, ...; reject embedded interpolation.Motivation
A downstream project (Blueberry) is implementing Postgres row-level security and needs to run
select set_config('request.jwt.claims', $1, true)per request inside the request's Ferro transaction. This is the only Ferro gap blocking that work; full design atdocs/superpowers/specs/2026-04-27-rls-security-design.mdin the consumer repo.