The two free-text ilike legs of gnubok_query_journal were the last users of
the journal_entries!inner embed. PostgREST compiles that embed to a
correlated LATERAL join that walks every tenant's journal_entry_lines, and
prod logs showed it as the query behind the daily 8 s statement timeouts
(SQLSTATE 57014), which reached agents as a generic UNKNOWN_ERROR.
- Both legs now run fetchEntryLines (lib/bookkeeping/entry-lines.ts) over the
same entry/line filter set as the plain query: leg A ilikes
journal_entries.description on the entry side, leg B ilikes
line_description on the line side. Leg B fetches the entry ids the plain
query already fetches, but only the matching lines, so it is never more
expensive than the same query without text.
- Each leg pulls its full match set, so text queries now report exact
totals/total_lines/truncated; legLimit/legCapHit and
totals_scope='returned_slice' are gone. The totals_scope field stays,
always 'full_match'.
- The amount filter runs before the display slice on every path, so limit=N
returns N matching lines instead of N minus what the filter removed.
- DB failures are still sanitised, but a transient one (statement timeout,
connection drop) now carries code TRANSIENT_ERROR plus a hint to retry or
narrow with date_from/date_to, so the structured-error layer returns the
retryable envelope instead of "Något gick fel".
Tests: text suite rewritten on a filter-aware two-step fake (no
.from() call-order pinning), plus text+date-range, no-embed, amount-before-
slice, and 57014 -> TRANSIENT_ERROR cases.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>