fix(config_generator): gate routing_preferences migration on version < v0.4.0

Short-circuit the migration when the config already declares v0.4.0
or newer. Anything at v0.4.0+ is assumed to be on the canonical
top-level shape and is passed through untouched, including stray
inline preferences (which are the author's bug to fix). Only v0.3.0
and older configs are rewritten and bumped.
This commit is contained in:
Spherrrical 2026-04-24 11:45:47 -07:00
parent 7e99368c1d
commit 2619150028
2 changed files with 46 additions and 13 deletions

View file

@ -63,22 +63,26 @@ def migrate_inline_routing_preferences(config_yaml):
``model_providers`` entry to the v0.4.0 top-level ``routing_preferences``
list with ``models: [...]``.
Preferences with the same ``name`` across multiple providers are merged
into a single top-level entry whose ``models`` list contains every
provider's full ``<provider>/<model>`` string in declaration order. The
first ``description`` encountered wins; conflicts are warned, not
errored, so existing v0.3.0 configs keep compiling. Any top-level
preference already defined by the user is preserved as-is.
This function is a no-op for configs whose ``version`` is already
``v0.4.0`` or newer those are assumed to be on the canonical
top-level shape and are passed through untouched.
Also bumps ``version`` to ``v0.4.0`` up front (when the config is
``v0.3.0`` or older) so brightstaff's v0.4.0 gate for top-level
``routing_preferences`` accepts the rendered config, including configs
that already declare top-level ``routing_preferences`` on a v0.3.0
version string.
For older configs, the version is bumped to ``v0.4.0`` up front so
brightstaff's v0.4.0 gate for top-level ``routing_preferences``
accepts the rendered config, then inline preferences under each
provider are lifted into the top-level list. Preferences with the
same ``name`` across multiple providers are merged into a single
top-level entry whose ``models`` list contains every provider's
full ``<provider>/<model>`` string in declaration order. The first
``description`` encountered wins; conflicts are warned, not errored,
so existing v0.3.0 configs keep compiling. Any top-level preference
already defined by the user is preserved as-is.
"""
current_version = str(config_yaml.get("version", ""))
if _version_tuple(current_version) < (0, 4, 0):
config_yaml["version"] = "v0.4.0"
if _version_tuple(current_version) >= (0, 4, 0):
return
config_yaml["version"] = "v0.4.0"
model_providers = config_yaml.get("model_providers") or []
if not model_providers:

View file

@ -692,6 +692,35 @@ model_providers:
assert config_yaml["version"] == "v0.4.0"
def test_migration_is_noop_on_v040_config_with_stray_inline_preferences():
# v0.4.0 configs are assumed to be on the canonical top-level shape.
# The migration intentionally does not rescue stray inline preferences
# at v0.4.0+ so that the deprecation boundary is a clean version gate.
plano_config = """
version: v0.4.0
listeners:
- type: model
name: model_listener
port: 12000
model_providers:
- model: openai/gpt-4o
access_key: $OPENAI_API_KEY
routing_preferences:
- name: code generation
description: generating new code
"""
config_yaml = yaml.safe_load(plano_config)
migrate_inline_routing_preferences(config_yaml)
assert config_yaml["version"] == "v0.4.0"
assert "routing_preferences" not in config_yaml
assert config_yaml["model_providers"][0]["routing_preferences"] == [
{"name": "code generation", "description": "generating new code"}
]
def test_migration_does_not_downgrade_newer_versions():
plano_config = """
version: v0.5.0