"""Product contracts for the model-aware Krea 2 SIGMAS scheduler node.""" from __future__ import annotations import importlib import json import sys from dataclasses import FrozenInstanceError from types import SimpleNamespace from typing import Any, cast import pytest from comfyui_sigmax.core import CompatibilityLevel, ScheduleContractError, SigmaDomain from comfyui_sigmax.nodes import model_aware_sigma_scheduler as model_aware_module from comfyui_sigmax.nodes.krea2_sigma_scheduler import build_krea2_sigma_schedule from comfyui_sigmax.nodes.model_aware_sigma_scheduler import ( MODEL_AWARE_SIGMA_NODE_ID, MODEL_AWARE_SIGMA_NODE_SCHEMA_ID, ModelAwareScheduleError, ModelAwareSigmaNodeResult, ModelAwareSigmaScheduler, ModelFamilyProbe, build_model_aware_sigma_schedule, probe_model_family, ) from comfyui_sigmax.profiles import HostCapabilities def _krea2_model() -> object: krea2 = type("Krea2", (), {}) return SimpleNamespace(model=krea2()) def _info(result: ModelAwareSigmaNodeResult) -> dict[str, Any]: decoded = json.loads(result.schedule_info_json) assert isinstance(decoded, dict) return decoded def _arguments(**changes: object) -> dict[str, object]: values: dict[str, object] = { "model": _krea2_model(), "variant": "Turbo", "steps": 8, "width": 1024, "height": 1024, "strict_official": True, "start_step": 0, "end_step": -1, } values.update(changes) return values def test_node_declares_model_aware_legacy_current_schema() -> None: inputs = ModelAwareSigmaScheduler.INPUT_TYPES() assert MODEL_AWARE_SIGMA_NODE_ID == "Sigmax.ModelAwareSigmaScheduler" assert MODEL_AWARE_SIGMA_NODE_SCHEMA_ID == "sigmax.model-aware-sigma-node/1" assert ModelAwareSigmaScheduler.RETURN_TYPES == ("SIGMAS", "STRING") assert ModelAwareSigmaScheduler.RETURN_NAMES == ("sigmas", "schedule_info") assert ModelAwareSigmaScheduler.FUNCTION == "build" assert ModelAwareSigmaScheduler.CATEGORY == "Sigmax/scheduling" assert ModelAwareSigmaScheduler.OUTPUT_NODE is False assert inputs["required"]["model"] == ("MODEL",) assert inputs["required"]["variant"][0] == ("Auto", "Turbo", "RAW") assert set(inputs["required"]) == { "model", "variant", "steps", "width", "height", "strict_official", "start_step", "end_step", } assert inputs == ModelAwareSigmaScheduler.INPUT_TYPES() assert inputs is not ModelAwareSigmaScheduler.INPUT_TYPES() @pytest.mark.parametrize( ("model", "expected_signals"), ( (_krea2_model(), ("model_class:krea2",)), ( SimpleNamespace( model=SimpleNamespace( model_config=type("Krea2", (), {"unet_config": {}})(), ) ), ("model_config_class:krea2",), ), ( SimpleNamespace( model=SimpleNamespace( model_config=SimpleNamespace(unet_config={"image_model": "krea2"}) ) ), ("unet_config.image_model:krea2",), ), ), ) def test_probe_accepts_only_bounded_public_krea2_family_signals( model: object, expected_signals: tuple[str, ...], ) -> None: probe = probe_model_family(model) assert probe == ModelFamilyProbe( schema_id="sigmax.model-family-probe/1", family="krea2", supported=True, signals=expected_signals, reason_code="model.family_krea2", ) def test_probe_does_not_invoke_foreign_methods_or_leak_object_representations() -> None: calls: list[str] = [] class Foreign: def get_model_object(self, name: str) -> object: calls.append(name) return object() def __repr__(self) -> str: raise AssertionError("foreign object representation must not be evaluated") probe = probe_model_family(SimpleNamespace(model=Foreign())) assert calls == [] assert probe.supported is False assert probe.signals == () assert "Foreign" not in json.dumps( { "family": probe.family, "reason_code": probe.reason_code, "signals": list(probe.signals), } ) @pytest.mark.parametrize("descriptor_kind", ("property", "method")) def test_probe_ignores_executable_model_descriptors(descriptor_kind: str) -> None: calls: list[str] = [] if descriptor_kind == "property": class PropertyDescriptorModel: @property def model(self) -> object: calls.append("property") return _krea2_model() descriptor_model: object = PropertyDescriptorModel() else: class MethodDescriptorModel: def model(self) -> object: calls.append("method") return _krea2_model() descriptor_model = MethodDescriptorModel() probe = probe_model_family(descriptor_model) assert calls == [] assert probe.reason_code == "model.family_unsupported" @pytest.mark.parametrize( "model", ( object(), SimpleNamespace(model=None), SimpleNamespace(model=SimpleNamespace(model_config=SimpleNamespace())), SimpleNamespace(model=SimpleNamespace(model_config=SimpleNamespace(unet_config=[]))), ), ) def test_malformed_or_unsupported_models_fail_closed(model: object) -> None: with pytest.raises(ModelAwareScheduleError) as captured: build_model_aware_sigma_schedule(**_arguments(model=model)) assert captured.value.reason_code in { "model.family_unsupported", "model.probe_invalid", } assert "C:\\" not in str(captured.value) def test_auto_mode_exposes_family_only_ambiguity_without_returning_sigmas() -> None: with pytest.raises(ModelAwareScheduleError) as captured: build_model_aware_sigma_schedule(**_arguments(variant="Auto")) error = captured.value decision = json.loads(error.decision_json) assert error.reason_code == "model.identity_ambiguous" assert decision["schema"] == MODEL_AWARE_SIGMA_NODE_SCHEMA_ID assert decision["model_probe"]["family"] == "krea2" assert decision["variant_resolution"]["status"] == "ambiguous" assert decision["variant_resolution"]["reason_codes"] == [ "insufficient_variant_evidence", "krea2_family_does_not_identify_variant", "model_class.krea2_family", ] assert "sigmas" not in decision assert "fallback" not in decision @pytest.mark.parametrize( ("variant", "steps"), (("Turbo", 8), ("RAW", 52)), ) def test_explicit_variant_uses_exact_profile_capability_gate_and_m4_01_schedule( variant: str, steps: int, ) -> None: result = build_model_aware_sigma_schedule(**_arguments(variant=variant, steps=steps)) baseline = build_krea2_sigma_schedule( variant=variant, steps=steps, width=1024, height=1024, strict_official=True, start_step=0, end_step=-1, ) info = _info(result) capability = info["capability_decision"] assert result.domain is SigmaDomain.UNIT_FLOW assert result.sigmas == baseline.sigmas assert result.selected_variant == variant.casefold() assert capability["level"] == CompatibilityLevel.ALLOW.value assert capability["reason_codes"] == ["core.compatible"] assert capability["model_identity"]["decisive_source"] == "explicit_selection" assert capability["model_identity"]["status"] == "confirmed" assert capability["profile_key"] == ( "krea2.turbo.official@1" if variant == "Turbo" else "krea2.raw.official@1" ) assert capability["profile_fingerprint"].startswith("sha256:") assert info["profile"]["evidence"] == "official" assert info["profile"]["key"] == capability["profile_key"] assert info["host_evidence"] == { "kind": "static_contract", "host_id": "comfyui", "host_version": "0.29.0", "host_revision": "e651b7bef55a5376343dcb1c0edb79f0142c985e", # pragma: allowlist secret } assert info["schedule"] == json.loads(baseline.schedule_info_json) def test_non_official_recipe_never_upgrades_profile_or_schedule_evidence() -> None: result = build_model_aware_sigma_schedule( **_arguments(variant="Turbo", steps=12, strict_official=False) ) info = _info(result) assert info["profile"]["evidence"] == "official" assert info["schedule"]["profile"]["evidence"] == "modified" assert info["schedule"]["profile"]["recipe"] == "krea2.turbo.modified-12" def test_model_aware_information_is_deterministic_and_result_is_immutable() -> None: first = build_model_aware_sigma_schedule(**_arguments()) second = build_model_aware_sigma_schedule(**_arguments()) assert first == second assert first.schedule_info_json == second.schedule_info_json with pytest.raises(FrozenInstanceError): first.sigmas = () # type: ignore[misc] @pytest.mark.parametrize( "changes", ( {"schema_id": "other"}, {"family": "other"}, {"supported": cast(Any, 1)}, {"family": None, "supported": True}, {"signals": cast(Any, [])}, {"signals": ("",)}, {"signals": ("z", "a")}, {"signals": ("a", "a")}, {"reason_code": "invalid"}, {"reason_code": cast(Any, 1)}, ), ) def test_model_family_probe_contract_rejects_invalid_values( changes: dict[str, object], ) -> None: arguments: dict[str, object] = { "schema_id": "sigmax.model-family-probe/1", "family": "krea2", "supported": True, "signals": ("model_class:krea2",), "reason_code": "model.family_krea2", } arguments.update(changes) with pytest.raises(ScheduleContractError): ModelFamilyProbe(**cast(Any, arguments)) @pytest.mark.parametrize( "arguments", ( {"reason_code": "invalid", "action": "fix", "decision_json": "{}"}, {"reason_code": cast(Any, 1), "action": "fix", "decision_json": "{}"}, {"reason_code": "model.test", "action": "", "decision_json": "{}"}, {"reason_code": "model.test", "action": cast(Any, 1), "decision_json": "{}"}, {"reason_code": "model.test", "action": "fix", "decision_json": ""}, {"reason_code": "model.test", "action": "fix", "decision_json": cast(Any, 1)}, ), ) def test_model_aware_error_contract_rejects_invalid_values( arguments: dict[str, object], ) -> None: with pytest.raises(ScheduleContractError): ModelAwareScheduleError(**cast(Any, arguments)) def test_information_projection_rejects_non_json_values() -> None: with pytest.raises(ScheduleContractError, match="canonical JSON"): model_aware_module._canonical_json({"invalid": object()}) def test_rejected_capability_decision_stops_before_schedule_construction( monkeypatch: pytest.MonkeyPatch, ) -> None: original_host = model_aware_module._static_host_capabilities() rejected_host = HostCapabilities( evidence_version=original_host.evidence_version, host_id=original_host.host_id, host_version=original_host.host_version, host_revision=original_host.host_revision, capabilities=(), ) monkeypatch.setattr( model_aware_module, "_static_host_capabilities", lambda: rejected_host, ) with pytest.raises(ModelAwareScheduleError) as captured: build_model_aware_sigma_schedule(**_arguments()) decision = json.loads(captured.value.decision_json) assert captured.value.reason_code == "host.capability_missing" assert decision["capability_decision"]["level"] == "reject" assert decision["capability_decision"]["reason_codes"] == ["host.capability_missing"] @pytest.mark.parametrize( "changes", ( {"variant": "unknown"}, {"variant": 1}, {"model": None}, ), ) def test_invalid_public_inputs_fail_before_tensor_conversion( changes: dict[str, object], ) -> None: with pytest.raises((ModelAwareScheduleError, ScheduleContractError)): build_model_aware_sigma_schedule(**_arguments(**changes)) def test_runtime_node_converts_only_after_model_and_capability_resolution( monkeypatch: pytest.MonkeyPatch, ) -> None: calls: list[tuple[float, ...]] = [] def float_tensor(values: tuple[float, ...]) -> object: calls.append(tuple(values)) return SimpleNamespace(values=tuple(values), device="cpu") monkeypatch.setitem(sys.modules, "torch", SimpleNamespace(FloatTensor=float_tensor)) output = ModelAwareSigmaScheduler().build(**_arguments()) tensor = cast(SimpleNamespace, output[0]) assert tensor.device == "cpu" assert calls == [tensor.values] assert json.loads(output[1])["schema"] == MODEL_AWARE_SIGMA_NODE_SCHEMA_ID def test_auto_rejection_occurs_before_torch_import( monkeypatch: pytest.MonkeyPatch, ) -> None: imports: list[str] = [] def forbidden_import(name: str) -> object: imports.append(name) raise AssertionError("Torch must not be imported for a rejected decision") monkeypatch.setattr(importlib, "import_module", forbidden_import) with pytest.raises(ModelAwareScheduleError): ModelAwareSigmaScheduler().build(**_arguments(variant="Auto")) assert imports == [] @pytest.mark.parametrize("failure", ("missing_module", "missing_float_tensor")) def test_runtime_node_fails_actionably_without_torch_contract( monkeypatch: pytest.MonkeyPatch, failure: str, ) -> None: if failure == "missing_module": def missing_module(name: str) -> object: raise ImportError(name) monkeypatch.setattr(importlib, "import_module", missing_module) else: monkeypatch.setattr(importlib, "import_module", lambda name: SimpleNamespace()) with pytest.raises(RuntimeError, match="requires Torch FloatTensor"): ModelAwareSigmaScheduler().build(**_arguments()) @pytest.mark.parametrize( "changes", ( {"selected_variant": "other"}, {"domain": SigmaDomain.DISCRETE_TRAINING_INDEX}, {"sigmas": cast(Any, [1.0, 0.0])}, {"sigmas": (0.0,)}, {"schedule_info_json": ""}, {"schedule_info_json": cast(Any, 1)}, ), ) def test_model_aware_result_contract_rejects_invalid_values( changes: dict[str, object], ) -> None: arguments: dict[str, object] = { "selected_variant": "turbo", "domain": SigmaDomain.UNIT_FLOW, "sigmas": (1.0, 0.0), "schedule_info_json": "{}", } arguments.update(changes) with pytest.raises(ScheduleContractError): ModelAwareSigmaNodeResult(**cast(Any, arguments))