diff --git a/EvoScientist/model_registry/http_api.py b/EvoScientist/model_registry/http_api.py index a8b3fef..cd54372 100644 --- a/EvoScientist/model_registry/http_api.py +++ b/EvoScientist/model_registry/http_api.py @@ -273,7 +273,20 @@ def _check_enabled_model( ) auth_spec = spec.auth_specs[provider.auth.mode] credential_revision = 0 - if auth_spec.credential_required and provider.auth.credential_id is not None: + if auth_spec.credential_required: + if provider.auth.credential_id is None: + # resolve_parameters already rejects this case; raise explicitly + # here so the five-tuple check never depends on call ordering. + raise ModelRegistryError( + CREDENTIAL_NOT_CONFIGURED, + f"Auth mode {provider.auth.mode!r} requires a credential reference.", + details=[ + { + "path": "auth.credential_id", + "code": CREDENTIAL_NOT_CONFIGURED, + } + ], + ) row = connection.execute( "SELECT current_revision FROM credential_pointers WHERE credential_id = ?", (provider.auth.credential_id,), diff --git a/tests/test_model_registry_http.py b/tests/test_model_registry_http.py index 46f1954..bedcdd9 100644 --- a/tests/test_model_registry_http.py +++ b/tests/test_model_registry_http.py @@ -443,6 +443,34 @@ def test_put_registry_rejects_unsupported_auth_mode(active_client): assert response.json()["code"] == "AUTH_MODE_UNSUPPORTED" +def test_put_registry_rejects_capability_unsupported_by_adapter(active_client): + # Rule 8: declared capabilities must not exceed the adapter contract's + # protocol capabilities — the glm-5.2 contract declares vision=False. + registry = _current_registry(active_client)["registry"] + registry["providers"][0]["models"][0]["runtime"]["declared_capabilities"][ + "vision" + ] = True + + response = _put(active_client, registry) + assert response.status_code == 422 + body = response.json() + assert body["code"] == "CAPABILITY_UNSUPPORTED_BY_ADAPTER" + assert "providers[0].models[0]" in body["details"][0]["path"] + + +def test_put_registry_rejects_missing_credential_reference(active_client): + # The api_key AuthSpec requires a credential: credential_id=None must + # fail with CREDENTIAL_NOT_CONFIGURED, not a verification-tuple miss. + registry = _current_registry(active_client)["registry"] + registry["providers"][0]["auth"] = {"mode": "api_key", "credential_id": None} + + response = _put(active_client, registry) + assert response.status_code == 422 + body = response.json() + assert body["code"] == "CREDENTIAL_NOT_CONFIGURED" + assert "auth.credential_id" in body["details"][0]["path"] + + def test_put_registry_rejects_defaults_to_disabled_model(active_client): # With a bootstrap-state payload the schema-level check does not fire, so # the store's rule-7 guard (enforced on every save) is what rejects it.