---
summary: "Field-test follow-up: studyCount cross-check, find_germplasm text spillover guard, find_observations preflight on unscoped germplasm queries, honest zero-match distributions, prominent dataset expiry."
breaking: false
---

# 0.4.3 — 2026-04-30

Five field-test-driven correctness and DX gaps closed after live testing the 0.4.2 release against `https://cassavabase.org/brapi/v2`. Each fix is server-agnostic — no new dialect-specific code paths — and uses the dialect adapter as the front-line check with a generic safety net for unknown servers.

## Fixed

- **`brapi_get_germplasm` `studyCount` returned the global `/studies` total** when the upstream silently dropped the `germplasmDbIds` filter (observed: TMS30572 reported `studyCount: 8340`, the entire CassavaBase studies count). The probe now routes through the dialect adapter and runs a parallel unfiltered baseline; if the filtered total equals the unfiltered baseline, `studyCount` is omitted with a warning explaining why a per-germplasm count cannot be trusted. Catches silent filter drops on any server, not just the SGN family.
- **`brapi_find_germplasm` text spillover trap** — querying `text: "TMS30572"` against an 842k-row catalog returned a 50k-row dataset of unrelated germplasm under a tiny "matched 0 of 10" warning. Two fixes: (1) when text matches 0 first-page rows, the spillover is skipped entirely with a strong warning recommending exact filters (`names`, `accessionNumbers`, `germplasmDbIds`); (2) when text has matches and spillover happens, the persisted dataset only contains text-matched rows via a new `rowFilter` predicate on `maybeSpill` / `spillToDataset`. The 50k-row dataset of unrelated rows is gone.
- **`brapi_find_observations` preflight for unscoped germplasm queries** — `germplasmDbIds=X` with no `studies` / `trials` / `observationUnits` / `observations` anchor was issuing `?pageSize=loadLimit` directly, stalling past the request timeout on large servers (CassavaBase observations join across every study a germplasm appeared in). Now probes with `pageSize=1` first; when the upstream advertises more than `PREFLIGHT_BULK_THRESHOLD` (5,000) rows, the bulk pull is skipped and the agent receives a warning recommending a study or trial scope. Other unscoped patterns (variable-only, bare query) keep their single-call path — only the documented danger pattern gets the extra preflight.
- **Distribution block on zero-match queries was misleading** — `find_germplasm` computed distributions from the full upstream rows and rendered them alongside an empty result set. Distributions are now computed from the post-filter `fullRows`, so a zero-match query produces an empty (honest) block instead of a confusing "Cassava (500)" line that looks like signal.
- **Dataset handle expiry was buried in a bullet** at the bottom of the format output. New `formatExpiresIn` helper renders `expiresAt: <iso> (expires in 24h)` on every dataset handle and the find_* header line shows `N in dataset (expires in 24h) · M total`. Applies automatically to all 8 find_* tools without per-tool wiring.

## Added

- **`rowFilter?: (row: T) => boolean` on `maybeSpill` / `spillToDataset`** in `find-helpers` — generic client-side predicate applied to every row (first-page + spilled) before persistence. The persisted dataset only contains rows that pass; `fullRows` returns the post-filter set so distributions reflect the matched rows. Re-usable for any future find_* tool that needs predicate-based narrowing.
- **`formatExpiresIn(expiresAt, now?)` in `find-helpers`** — coarsens an absolute ISO timestamp to a compact relative label (`<1m`, `30m`, `24h`, `7d`) with `expires in` / `expired ... ago` framing.
- **Tests** — 5 new cases. `tests/tools/brapi-get-germplasm.tool.test.ts` covers the silent-filter cross-check (filtered total == unfiltered baseline → drop). `tests/tools/brapi-find-germplasm.tool.test.ts` covers the spillover skip on text-miss and the dataset row count after text filtering. `tests/tools/brapi-find-observations.tool.test.ts` covers preflight skip above threshold, preflight skip when scoped, and the proceeds-with-bulk-pull case below threshold.

## Changed

- **`renderFindHeader` accepts `dataset.expiresAt`** — optional field on the dataset summary; when present, the relative expiry label appears in the dataset segment of the headline.
- **`brapi_find_observations` preflight pull** uses a local `loadObservations(pageSize)` closure that consolidates three near-identical `loadInitialPage` call sites into one — saves ~14 lines and makes the preflight intent obvious at the call site.
