Loading ci/sdk-consumer.md +7 −0 Changes for ci/sdk-consumer.md: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -9,6 +9,13 @@ dependencies. `uv pip check` must pass. Source imports use SRM's src directory. The normal lock pins the published 2.0.0rc2 candidate. New SDK candidates can be overlaid for qualification before their own registry publication. Registry construction passes each entry's control-path reference to its SDK instance on a copied configuration. Empty or duplicate references and conflicting config identities fail before constructing any adapter. Kubernetes fixtures declare their connection mode and namespace explicitly. SDK construction errors use the existing SRM error translation. These conventions work with the published rc2 baseline and the stricter candidate without changing the dependency lock. All tests/unit tests run, including the real SDK factory/bridge boundary. Missing configuration, skipped tests and failures block qualification. The receipt records both source revisions, the wheel checksum and CI pipeline URL. These offline Loading src/srm/adapters/transformation_functions/registry.py +15 −1 Changes for src/srm/adapters/transformation_functions/registry.py: 15 added lines, 1 removed line. Original line number Diff line number Diff line from collections.abc import Mapping from copy import deepcopy from srm.config import ControlPathSettings from srm.domain.ports.transformation_functions import ( Loading Loading @@ -38,9 +39,22 @@ def build_control_path_registry( "control paths are configured but no TF-SDK adapter factory is installed: " + ", ".join(entry.control_path_ref for entry in settings.entries) ) refs: set[str] = set() for entry in settings.entries: if not entry.control_path_ref.strip() or entry.control_path_ref in refs: raise ValueError("Control-path refs must be nonempty and unique") refs.add(entry.control_path_ref) if ( "control_path_ref" in entry.config and entry.config["control_path_ref"] != entry.control_path_ref ): raise ValueError("Control-path config identity conflicts with its registry key") return ConfiguredControlPathRegistry( { entry.control_path_ref: factory.create_adapter(entry.adapter, entry.config) entry.control_path_ref: factory.create_adapter( entry.adapter, {**deepcopy(entry.config), "control_path_ref": entry.control_path_ref}, ) for entry in settings.entries } ) src/srm/adapters/transformation_functions/tfsdk_execution.py +2 −0 Changes for src/srm/adapters/transformation_functions/tfsdk_execution.py: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -266,4 +266,6 @@ class OpenOpTfSdkAdapterFactory(TransformationFunctionAdapterFactory): adapter = build_adapter(kind, dict(config)) except TfSdkUnknownAdapterKind as exc: raise UnknownAdapterKind(kind) from exc except TfSdkTFSDKError as exc: raise _translate_tfsdk_error(exc) from exc return TfSdkExecutionBridge(adapter) tests/unit/test_tfsdk_registry_wiring.py +63 −3 Changes for tests/unit/test_tfsdk_registry_wiring.py: 63 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ from __future__ import annotations from datetime import UTC, datetime from pathlib import Path from typing import Any from unittest.mock import Mock import pytest from openop_tfsdk.contract.errors import DomainExecutionFailed as TfSdkDomainExecutionFailed Loading Loading @@ -363,7 +364,10 @@ def test_bridge_rejects_invalid_query_result() -> None: def test_factory_builds_kubernetes_adapter() -> None: factory = OpenOpTfSdkAdapterFactory() adapter = factory.create_adapter("kubernetes", {"namespace": "openop-services"}) adapter = factory.create_adapter( "kubernetes", {"in_cluster": True, "namespace": "openop-services", "control_path_ref": "k8s-athens-edge"}, ) assert isinstance(adapter, TfSdkExecutionBridge) assert not isinstance(adapter, TopologyDiscoveryPort) Loading @@ -382,12 +386,12 @@ def test_registry_resolves_multiple_entries() -> None: ControlPathEntry( control_path_ref="k8s-athens-edge", adapter="kubernetes", config={"namespace": "athens"}, config={"in_cluster": True, "namespace": "athens"}, ), ControlPathEntry( control_path_ref="k8s-other", adapter="kubernetes", config={"namespace": "other"}, config={"in_cluster": True, "namespace": "other"}, ), ] ) Loading @@ -400,6 +404,62 @@ def test_registry_resolves_multiple_entries() -> None: assert registry.control_path_refs() == ["k8s-athens-edge", "k8s-other"] def test_registry_derives_identity_without_mutating_input() -> None: settings = ControlPathSettings( entries=[ ControlPathEntry( control_path_ref="athens", adapter="opaque", config={"nested": {"value": "original"}}, ) ] ) factory = Mock() build_control_path_registry(settings, factory) kind, config = factory.create_adapter.call_args.args assert kind == "opaque" assert config == {"nested": {"value": "original"}, "control_path_ref": "athens"} config["nested"]["value"] = "changed" assert settings.entries[0].config == {"nested": {"value": "original"}} @pytest.mark.parametrize( "entries", [ [ ControlPathEntry(control_path_ref="same", adapter="opaque"), ControlPathEntry(control_path_ref="same", adapter="opaque"), ], [ ControlPathEntry( control_path_ref="athens", adapter="opaque", config={"control_path_ref": "berlin"} ) ], [ControlPathEntry(control_path_ref=" ", adapter="opaque")], ], ) def test_registry_rejects_ambiguous_identity_before_any_construction( entries: list[ControlPathEntry], ) -> None: factory = Mock() with pytest.raises(ValueError): build_control_path_registry(ControlPathSettings(entries=entries), factory) factory.create_adapter.assert_not_called() def test_factory_translates_sdk_construction_error(monkeypatch: pytest.MonkeyPatch) -> None: build = Mock(side_effect=TfSdkValidationError("invalid connection configuration")) monkeypatch.setattr( "srm.adapters.transformation_functions.tfsdk_execution.build_adapter", build ) with pytest.raises(ValidationError) as caught: OpenOpTfSdkAdapterFactory().create_adapter("kubernetes", {}) assert caught.value.error_code == TfSdkValidationError.error_code assert caught.value.message == "invalid connection configuration" def test_build_topology_discovery_none_without_path() -> None: assert build_topology_discovery(None) is None assert build_topology_discovery("") is None Loading Loading
ci/sdk-consumer.md +7 −0 Changes for ci/sdk-consumer.md: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -9,6 +9,13 @@ dependencies. `uv pip check` must pass. Source imports use SRM's src directory. The normal lock pins the published 2.0.0rc2 candidate. New SDK candidates can be overlaid for qualification before their own registry publication. Registry construction passes each entry's control-path reference to its SDK instance on a copied configuration. Empty or duplicate references and conflicting config identities fail before constructing any adapter. Kubernetes fixtures declare their connection mode and namespace explicitly. SDK construction errors use the existing SRM error translation. These conventions work with the published rc2 baseline and the stricter candidate without changing the dependency lock. All tests/unit tests run, including the real SDK factory/bridge boundary. Missing configuration, skipped tests and failures block qualification. The receipt records both source revisions, the wheel checksum and CI pipeline URL. These offline Loading
src/srm/adapters/transformation_functions/registry.py +15 −1 Changes for src/srm/adapters/transformation_functions/registry.py: 15 added lines, 1 removed line. Original line number Diff line number Diff line from collections.abc import Mapping from copy import deepcopy from srm.config import ControlPathSettings from srm.domain.ports.transformation_functions import ( Loading Loading @@ -38,9 +39,22 @@ def build_control_path_registry( "control paths are configured but no TF-SDK adapter factory is installed: " + ", ".join(entry.control_path_ref for entry in settings.entries) ) refs: set[str] = set() for entry in settings.entries: if not entry.control_path_ref.strip() or entry.control_path_ref in refs: raise ValueError("Control-path refs must be nonempty and unique") refs.add(entry.control_path_ref) if ( "control_path_ref" in entry.config and entry.config["control_path_ref"] != entry.control_path_ref ): raise ValueError("Control-path config identity conflicts with its registry key") return ConfiguredControlPathRegistry( { entry.control_path_ref: factory.create_adapter(entry.adapter, entry.config) entry.control_path_ref: factory.create_adapter( entry.adapter, {**deepcopy(entry.config), "control_path_ref": entry.control_path_ref}, ) for entry in settings.entries } )
src/srm/adapters/transformation_functions/tfsdk_execution.py +2 −0 Changes for src/srm/adapters/transformation_functions/tfsdk_execution.py: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -266,4 +266,6 @@ class OpenOpTfSdkAdapterFactory(TransformationFunctionAdapterFactory): adapter = build_adapter(kind, dict(config)) except TfSdkUnknownAdapterKind as exc: raise UnknownAdapterKind(kind) from exc except TfSdkTFSDKError as exc: raise _translate_tfsdk_error(exc) from exc return TfSdkExecutionBridge(adapter)
tests/unit/test_tfsdk_registry_wiring.py +63 −3 Changes for tests/unit/test_tfsdk_registry_wiring.py: 63 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ from __future__ import annotations from datetime import UTC, datetime from pathlib import Path from typing import Any from unittest.mock import Mock import pytest from openop_tfsdk.contract.errors import DomainExecutionFailed as TfSdkDomainExecutionFailed Loading Loading @@ -363,7 +364,10 @@ def test_bridge_rejects_invalid_query_result() -> None: def test_factory_builds_kubernetes_adapter() -> None: factory = OpenOpTfSdkAdapterFactory() adapter = factory.create_adapter("kubernetes", {"namespace": "openop-services"}) adapter = factory.create_adapter( "kubernetes", {"in_cluster": True, "namespace": "openop-services", "control_path_ref": "k8s-athens-edge"}, ) assert isinstance(adapter, TfSdkExecutionBridge) assert not isinstance(adapter, TopologyDiscoveryPort) Loading @@ -382,12 +386,12 @@ def test_registry_resolves_multiple_entries() -> None: ControlPathEntry( control_path_ref="k8s-athens-edge", adapter="kubernetes", config={"namespace": "athens"}, config={"in_cluster": True, "namespace": "athens"}, ), ControlPathEntry( control_path_ref="k8s-other", adapter="kubernetes", config={"namespace": "other"}, config={"in_cluster": True, "namespace": "other"}, ), ] ) Loading @@ -400,6 +404,62 @@ def test_registry_resolves_multiple_entries() -> None: assert registry.control_path_refs() == ["k8s-athens-edge", "k8s-other"] def test_registry_derives_identity_without_mutating_input() -> None: settings = ControlPathSettings( entries=[ ControlPathEntry( control_path_ref="athens", adapter="opaque", config={"nested": {"value": "original"}}, ) ] ) factory = Mock() build_control_path_registry(settings, factory) kind, config = factory.create_adapter.call_args.args assert kind == "opaque" assert config == {"nested": {"value": "original"}, "control_path_ref": "athens"} config["nested"]["value"] = "changed" assert settings.entries[0].config == {"nested": {"value": "original"}} @pytest.mark.parametrize( "entries", [ [ ControlPathEntry(control_path_ref="same", adapter="opaque"), ControlPathEntry(control_path_ref="same", adapter="opaque"), ], [ ControlPathEntry( control_path_ref="athens", adapter="opaque", config={"control_path_ref": "berlin"} ) ], [ControlPathEntry(control_path_ref=" ", adapter="opaque")], ], ) def test_registry_rejects_ambiguous_identity_before_any_construction( entries: list[ControlPathEntry], ) -> None: factory = Mock() with pytest.raises(ValueError): build_control_path_registry(ControlPathSettings(entries=entries), factory) factory.create_adapter.assert_not_called() def test_factory_translates_sdk_construction_error(monkeypatch: pytest.MonkeyPatch) -> None: build = Mock(side_effect=TfSdkValidationError("invalid connection configuration")) monkeypatch.setattr( "srm.adapters.transformation_functions.tfsdk_execution.build_adapter", build ) with pytest.raises(ValidationError) as caught: OpenOpTfSdkAdapterFactory().create_adapter("kubernetes", {}) assert caught.value.error_code == TfSdkValidationError.error_code assert caught.value.message == "invalid connection configuration" def test_build_topology_discovery_none_without_path() -> None: assert build_topology_discovery(None) is None assert build_topology_discovery("") is None Loading