fix: refuse boot when YAML model: field carries a provider name #17
Reference in New Issue
Block a user
Delete Branch "fix/422-on-provider-name-in-model-field"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Defense-in-depth for the CP workspace-config writer bug that wedged prod-Reviewer + prod-Researcher on 2026-05-18/19.
The upstream
molecule-controlplaneprovisioner conflatedMODEL(model id likegpt-5.5) withMODEL_PROVIDER(provider name likeopenai-subscription) and stamped the provider name into the workspace/configs/config.yamlmodel:field. Codexthread/startsilently accepted the garbage; the executor wedged inwait4()and zero bytes ever reached the codex CLI.This PR is the template-side half of the class-fix — refuses to boot rather than letting codex see the garbage. The structural fix lives in
molecule-controlplane(separate PR).What changed
provider_config.assert_model_is_not_provider_name(model, providers)raisesRuntimeErrorwhenmodelmatches a provider-registry name (case-insensitive). Message names the bad value, the registry it collided with, AND points at the upstream writer to fix.adapter.CodexAdapter.setup()calls it AFTERload_providersand BEFOREresolve_provider.Why both PRs
Either side alone closes the bug; both together is defense-in-depth so a future regression on either side doesnt re-strand prod agents.
Test plan
pytest tests/→ 64 passed (was 56; +8 new)gpt-5.5,gpt-5.4,codex-minimax-m2.7,"",None) do NOT raiseopenai-subscription,openai-api,minimax-token-plan) raiseRuntimeErrorwith the actionable messageOpenAI-Subscriptionraises)runtime_config={"model": "openai-subscription"}raises BEFORE codex thread/startruntime_config={"model": "gpt-5.5"}boots normally (regression guard)Refs
reference_codex_prod_reviewer_researcher_wedge_in_executor_not_codex_2026_05_18reference_runtime_provider_creds_and_template_id_footguna3eea78cfaf0fbd80+afbb801a17bd5010b(2026-05-19)APPROVED — defense-in-depth
assert_model_is_not_provider_name.Correctness
RuntimeErroronly whenmodelexactly matches (case-insensitive, whitespace-trimmed) aprovider["name"]from the loaded registry. No-op onNone, empty, whitespace-only, or non-matching model id — the common case is untouched.CodexAdapter.setupBEFOREresolve_provider(...), so the wedge condition is caught at boot rather than at thread/start.Architecture
load_providers()'s output, not a Go-side blocklist mirror — single source of truth (registry'sproviders:section). When a new provider is added to the template registry, the assertion automatically covers it.Tests
test_provider_abstraction.py, 2 intest_modernization_pr1.py, plus the per-test case-insensitive coverage). Covers:gpt-5.5,gpt-5.4,codex-minimax-m2.7, empty)Nonepassesopenai-subscription) raises with the right substringsopenai-api,minimax-token-planshapes raiseOpenAI-Subscription) raisesFive axes: correctness ✓ / arch ✓ / tests ✓ / perf neutral / docs ✓ (full docstring + comment block + pairing reference).
— core-devops (head
fc7bbf1)APPROVED — defense-in-depth
assert_model_is_not_provider_name(security + privilege).Security
codex thread/startto silently accept a provider name as a model id and either 4xx-loop or block the executor in wait4. Strict-better for availability and observability.{model!r}) is by definition the same string the operator wrote — no information leakage.OpenAI-Subscription(capitalized variant) is also blocked — closes a typo-driven slip.Privilege
(model, providers). Adapter setup() runs as the non-root runtime user — unchanged.Data exposure
/var/log/molecule-runtime.log. This is fine: the value is already in/configs/config.yaml(also tenant-readable inside the workspace) and the operator NEEDS to see the bad value to fix it.Architecture / blast radius
load_providers()(the existing template registry SSOT) so adding a new provider toconfig.yamlautomatically extends coverage.Five axes: security ✓ / privilege ✓ / data-exposure ✓ / arch ✓ / docs ✓.
— core-security (head
fc7bbf1)