v1.1.22
[1.1.22] — 2026-07-11
The closeout of the v1.1.x hardening release line — streaming lifecycle, configuration validation, persistence consolidation, and provider fixes. One behavior change to note: configuration files are now rejected when they contain unrecognized keys (see Changed). The public Go API is otherwise unchanged.
Added
- Config
apiVersionfield. An optional, advisoryapiVersion(currentlyv1) can be set at the top of a config file. It is informational: an omitted value defaults to the current version, and an unrecognized value is accepted with a warning for forward compatibility. - Qwen live model discovery. The Qwen provider can now refresh its model list from the provider's
/modelsendpoint, opt-in viaFERRO_MODEL_DISCOVERY_INTERVAL, like the other OpenAI-compatible providers.
Changed
- Configuration is now strictly validated: an unrecognized key fails the load instead of being silently ignored, so a typo like
strategy:is reported at startup rather than dropping a whole block. Free-form plugin and exporterconfigmaps still accept any key. JSON configs can no longer carry_-prefixed pseudo-comment keys; the field documentation lives in the YAML example instead. - All persistent stores share one schema-migration path. The API-key, config, and request-log stores now open, bind placeholders, and migrate through a single internal SQL layer, and each store's schema is versioned through the migration runner (baseline-adopted on databases from earlier releases). Behavior is unchanged; the consolidation removes the per-store SQL variations that previously drifted.
Fixed
- Streaming responses no longer leak a goroutine when a client disconnects mid-stream. Every provider's streaming producer now abandons a pending send once the request context is cancelled, so the producer returns and its upstream HTTP body is closed instead of blocking forever.
- Streaming requests probe a recovering provider's circuit breaker exactly once. The streaming path previously consumed the single half-open probe permit at provider-selection time and again at send time, so a recovering provider could never be retried by a streaming request.
- Streaming target selection now matches non-streaming. The load-balance, conditional, and content-based strategies select the same candidate targets on both paths, so a streaming request can no longer be weighted toward or routed to a target that does not serve the requested model.
- Gemini requests no longer emit a doubled
models/prefix ingenerateContentURLs when a model id already carries it. - Bedrock Cohere embedding requests now forward the
input_typeparameter, so embed calls that depend on it are handled correctly upstream. - Config history is persisted atomically with the active config. A save now writes the active config and its history record in one transaction, so a crash can no longer leave the stored config ahead of its audit trail (#292).
- A concurrent index build that fails transiently is retried on the next start rather than being recorded as applied and skipped permanently.
- Key-store file permissions are applied to the real file even for
file:URI DSNs. The SQLite path resolver now decodes percent-escapes and query suffixes the way SQLite opens the file, so the owner-only restriction always targets the actual database file. The API-key schema also migrates through its own dedicated ledger, keeping its version sequence independent of the other stores that may share a database.
Internal
- Consolidated gateway internals: extracted the plugin hook bus, added a shared target-selection seam between the streaming and non-streaming routers, routed plugin and tool spans through the observability seam, and removed redundant locking around
math/rand. - Enabled a stricter linter set and cleared the findings; added fuzz targets and goroutine-leak coverage for the streaming and parser paths; the release build now gates on the test suite and signs its artifacts.
Contributors
Thanks to everyone who shipped this release:
- v1.1.22 - v1.1.x Hardening Closeout (#340) — @MitulShah1
Full changelog: https://github.com/ferro-labs/ai-gateway/blob/v1.1.22/CHANGELOG.md