diff --git a/packages/client/src/launchdarkly_ai_server/lifecycle.py b/packages/client/src/launchdarkly_ai_server/lifecycle.py index ed767400..59c6dc99 100644 --- a/packages/client/src/launchdarkly_ai_server/lifecycle.py +++ b/packages/client/src/launchdarkly_ai_server/lifecycle.py @@ -532,13 +532,13 @@ async def extract_variation( f"Variation '{config_key}' returned None (flag may be disabled)." ) - if isinstance(raw, dict) and raw.get("_ldMeta", {}).get("enabled") is False: + # Only an explicit ``_ldMeta.enabled`` counts as enabled (spec §3.10, as in JS and Go). + ld_meta = (raw.get("_ldMeta") or {}) if isinstance(raw, dict) else {} + if not isinstance(ld_meta, dict) or not ld_meta.get("enabled"): raise RuntimeError(f"Variation '{config_key}' is disabled.") - # Extract meta from _ldMeta if present - ld_meta = raw.get("_ldMeta", {}) if isinstance(raw, dict) else {} meta = { - "enabled": ld_meta.get("enabled", True), + "enabled": True, "variationKey": ld_meta.get("variationKey", ""), "version": ld_meta.get("version", 1), "mode": ld_meta.get("mode"), diff --git a/packages/client/tests/test_lifecycle.py b/packages/client/tests/test_lifecycle.py index 50a7e733..3c7dbda0 100644 --- a/packages/client/tests/test_lifecycle.py +++ b/packages/client/tests/test_lifecycle.py @@ -967,3 +967,35 @@ async def test_never_raises_when_variation_throws(self) -> None: assert result["enabled"] is False assert result["config"] is None assert result["meta"] is None + + +# --------------------------------------------------------------------------- +# extract_variation — missing enabled flag +# --------------------------------------------------------------------------- + + +class TestExtractVariationMissingEnabled: + # Spec §3.10: only ``_ldMeta.enabled: true`` is enabled, as in JS and Go. + @pytest.mark.parametrize("ld_meta", [None, {}], ids=["no_ldMeta", "empty_ldMeta"]) + async def test_raises_when_enabled_is_missing( + self, ld_meta: dict[str, Any] | None + ) -> None: + raw: dict[str, Any] = { + "model": {"name": "claude-3-5"}, + "provider": {"name": "Anthropic"}, + "instructions": "You are helpful.", + } + if ld_meta is not None: + raw["_ldMeta"] = ld_meta + stub = _make_stub_client() + stub.variation = AsyncMock(return_value=raw) + with patch.object(lifecycle_module, "_setup_telemetry", return_value=None): + await init_client(client=stub) + ctx = {"kind": "user", "key": "user-1"} + with patch( + "launchdarkly_ai_server.utils.to_ld_context", + side_effect=lambda _c, ctx: ctx, + ): + with pytest.raises(RuntimeError, match="'my-flag' is disabled"): + await lifecycle_module.extract_variation("my-flag", ctx) + assert (await inspect_config("my-flag", ctx))["enabled"] is False