---
summary: "Dialects declare known-dead POST /search routes — raw_search and find_genotype_calls refuse with a typed error before hitting the upstream. Orientation envelope surfaces the active dialect so agents can plan around quirks."
breaking: false
---

# 0.4.1 — 2026-04-30

Follow-on patch to 0.4.0 that closes two real workflow gaps the initial dialect adapter left open. CassavaBase advertises POST `/search/{noun}` for most read endpoints in `/calls`, but those routes are unresponsive in practice — agents calling them through `brapi_raw_search` or `brapi_find_genotype_calls` would hang or get malformed envelopes. And although the dialect was active under the hood, agents had no visibility into which dialect was selected or what quirks it was routing around — they only learned via after-the-fact warnings. Both are addressed; the orientation envelope now carries a `dialect` block, and the disabled-search nouns are refused before the upstream call.

## Added

- **`disabledSearchEndpoints?: ReadonlySet<string>` on `BrapiDialect`** — declarative list of POST `/search/{noun}` routes the dialect knows are dead. CassavaBase populates 11 entries (`germplasm`, `studies`, `observations`, `observationunits`, `locations`, `variables`, `images`, `variants`, `variantsets`, `samples`, `callsets`); `calls` is intentionally absent because async POST is the only realistic delivery for bulk genotype data and we have no contrary evidence that route is dead. Spec dialect omits the field entirely.
- **`brapi_raw_search` and `brapi_find_genotype_calls` consult the dialect's disabled-search set** — both throw a typed `search_endpoint_disabled` `ValidationError` before issuing the POST when the noun is declared dead. `raw_search` includes the curated-tool nudge in the recovery hint (e.g. "Consider `brapi_find_germplasm`"); `find_genotype_calls` directs the agent to bypass via `BRAPI_<ALIAS>_DIALECT=spec`. Avoids hangs and silent garbage from advertised-but-broken upstream routes.
- **`dialect` block on the orientation envelope** — `brapi_connect`, `brapi_server_info`, and the `brapi://server/info` resource now surface the active dialect: `{ id, source: 'env-override' | 'server-name' | 'organization-name' | 'fallback', envVar, disabledSearchEndpoints[] }`. The `source` field tells the agent how detection landed; the `envVar` field tells the operator which knob to pin if detection misfires. Rendered in the formatter under a new `## Dialect` block.
- **Tests** — 24 new cases. `tests/services/brapi-dialect/cassavabase-dialect.test.ts` covers the disabled-endpoint declaration. `tests/tools/brapi-raw-search.tool.test.ts` covers refusal on disabled nouns and pass-through on enabled ones. `tests/tools/brapi-find-genotype-calls.tool.test.ts` covers refusal via a custom test dialect that disables `calls`. `tests/tools/brapi-connect.tool.test.ts` covers the dialect block on the spec / cassavabase / env-override paths. `tests/services/brapi-dialect/detect.test.ts` covers the new `{ id, source }` return shape.

## Changed

- **`detectDialectId` and `detectDialectFromName` return `{ id, source }`** instead of a bare string — internal API change so the orientation envelope can report whether the dialect was env-pinned, name-matched, organization-matched, or a fallback. The single existing internal caller (`resolveDialect`) was updated; no public-API consequences.
- **`applyDialectFilters(dialect, endpoint, filters, warnings) → filters` helper added to `find-helpers`** — collapses the `adapted = dialect.adaptGetFilters(...); warnings.push(...adapted.warnings); const filters = adapted.filters;` 3-liner repeated across all seven `find_*` tools into a single line. Mirrors the existing `mergeFilters(named, extra, warnings)` shape; reduces the chance of forgetting the warnings spread in future tool additions.
