Loading README.md +41 −0 Changes for README.md: 41 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -261,6 +261,47 @@ credentials. Keep runtime config out of normalized responses and persisted topology. The deployment owns mounts; the SDK does not parse a secret URI. See `tests/contract/bootstrap_example.yaml` for an explicit file-mode example. ## OAI QoD provider profiles Optionally restrict profile identifiers in an OAI control path's runtime infrastructure. For example, when the NEF offers these names: ```yaml control_paths: - ref: oai-nef-lab adapter_family: network adapter_type: oai adapter_contract_version: v1 infrastructure: base_url: http://nef.example.test:8080 scs_as_id: openop notification_url: http://notifications.example.test/nef qos_profiles: [qos-e, qos2, qos3, qos4] ``` The existing bootstrap runtime accessor and SRM registry pass this configuration to the SDK factory. The adapter snapshots the list for its own client. Profiles are opaque provider names: they reach the NEF unchanged, with no inferred aliases or QoS policy mapping. Configure the actual names and policies offered by that NEF; the example does not assert a universal OAI default. Omitting qos_profiles applies no local allowlist: syntactically valid profile identifiers reach the NEF unchanged. The NEF decides whether a profile exists and can be applied. There are no built-in provider profile names. An explicit null, empty, duplicate or malformed list fails adapter construction with ValidationError. Requests outside a configured list fail before HTTP. Each control path keeps its own optional restriction. Backend refusals remain typed execution failures even for profiles allowed locally. The existing notification destination requirement still applies; configuring profiles does not implement a receiver or establish data-plane QoS guarantees. Network DomainExecutionFailed errors include the existing allowlisted backend status/reason diagnostics in details when available. HTTP400 carries `{"backend_status": 400, "reason": "bad_request"}`; transport errors carry a safe reason. Raw response bodies, URLs and exception text are not copied. Error classes, retryable flags and wrapped HTTP404 classification are unchanged. SRM's existing bridge can preserve these details without a new contract field. ## Helm workload endpoints The Kubernetes adapter can report external NodePort endpoints after activating Loading ci/README.md +4 −0 Changes for ci/README.md: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -54,3 +54,7 @@ The downstream trigger disables inheritance of SDK default variables and passes only the explicit SDK artifact coordinates. Job-only paths such as TMPDIR must be established by the owning project. Candidate rc1 exposed this boundary issue and was not published; rc2 retains a new immutable tag instead of moving rc1. Packaged container chart checks render canonical CPU millicores and decimal MB requests, including omission and absence of invented limits. Provider regressions exercise the same mapping through activation and update. ci/verify.py +17 −5 Changes for ci/verify.py: 17 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -182,13 +182,23 @@ def charts(): chart = Path(openop_tfsdk.__file__).parent / "contract/adapters/charts/container" helm = helm_binary() image = "example.invalid/fixture@sha256:" + "a" * 64 for name, service_type, ports in [ ("none", "ClusterIP", []), ("internal", "ClusterIP", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}]), ("external", "NodePort", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}]), for name, service_type, ports, requests in [ ("none", "ClusterIP", [], {}), ("internal", "ClusterIP", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}], {}), ("external", "NodePort", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}], {}), ("requests", "ClusterIP", [], {"cpu": "250m", "memory": "257M"}), ]: values = OUT / f"chart-{name}.json" write_json(values, {"image": image, "serviceType": service_type, "ports": ports}) expected_resources = {"requests": requests} if requests else {} write_json( values, { "image": image, "serviceType": service_type, "ports": ports, "resources": expected_resources, }, ) run(helm, "lint", chart, "--strict", "-f", values) raw = subprocess.check_output( [ Loading @@ -208,6 +218,8 @@ def charts(): deployment = objects["Deployment"] spec = deployment["spec"]["template"]["spec"]["containers"][0] assert spec["image"] == image assert spec.get("resources", {}) == expected_resources assert "limits" not in spec.get("resources", {}) assert spec.get("ports", []) == [ {"name": p["name"], "containerPort": p["port"], "protocol": p["protocol"]} for p in ports Loading src/openop_tfsdk/_diagnostics.py +1 −1 Changes for src/openop_tfsdk/_diagnostics.py: 1 added line, 1 removed line. Original line number Diff line number Diff line """Small log-only summaries; never copy backend text, payloads or input values.""" """Allowlisted diagnostic summaries; never copy backend text, payloads or input values.""" from typing import get_args Loading src/openop_tfsdk/contract/adapters/charts/container/templates/deployment.yaml +4 −0 Changes for src/openop_tfsdk/contract/adapters/charts/container/templates/deployment.yaml: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -25,3 +25,7 @@ spec: protocol: {{ .protocol }} {{- end }} {{- end }} {{- with .Values.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }} Loading
README.md +41 −0 Changes for README.md: 41 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -261,6 +261,47 @@ credentials. Keep runtime config out of normalized responses and persisted topology. The deployment owns mounts; the SDK does not parse a secret URI. See `tests/contract/bootstrap_example.yaml` for an explicit file-mode example. ## OAI QoD provider profiles Optionally restrict profile identifiers in an OAI control path's runtime infrastructure. For example, when the NEF offers these names: ```yaml control_paths: - ref: oai-nef-lab adapter_family: network adapter_type: oai adapter_contract_version: v1 infrastructure: base_url: http://nef.example.test:8080 scs_as_id: openop notification_url: http://notifications.example.test/nef qos_profiles: [qos-e, qos2, qos3, qos4] ``` The existing bootstrap runtime accessor and SRM registry pass this configuration to the SDK factory. The adapter snapshots the list for its own client. Profiles are opaque provider names: they reach the NEF unchanged, with no inferred aliases or QoS policy mapping. Configure the actual names and policies offered by that NEF; the example does not assert a universal OAI default. Omitting qos_profiles applies no local allowlist: syntactically valid profile identifiers reach the NEF unchanged. The NEF decides whether a profile exists and can be applied. There are no built-in provider profile names. An explicit null, empty, duplicate or malformed list fails adapter construction with ValidationError. Requests outside a configured list fail before HTTP. Each control path keeps its own optional restriction. Backend refusals remain typed execution failures even for profiles allowed locally. The existing notification destination requirement still applies; configuring profiles does not implement a receiver or establish data-plane QoS guarantees. Network DomainExecutionFailed errors include the existing allowlisted backend status/reason diagnostics in details when available. HTTP400 carries `{"backend_status": 400, "reason": "bad_request"}`; transport errors carry a safe reason. Raw response bodies, URLs and exception text are not copied. Error classes, retryable flags and wrapped HTTP404 classification are unchanged. SRM's existing bridge can preserve these details without a new contract field. ## Helm workload endpoints The Kubernetes adapter can report external NodePort endpoints after activating Loading
ci/README.md +4 −0 Changes for ci/README.md: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -54,3 +54,7 @@ The downstream trigger disables inheritance of SDK default variables and passes only the explicit SDK artifact coordinates. Job-only paths such as TMPDIR must be established by the owning project. Candidate rc1 exposed this boundary issue and was not published; rc2 retains a new immutable tag instead of moving rc1. Packaged container chart checks render canonical CPU millicores and decimal MB requests, including omission and absence of invented limits. Provider regressions exercise the same mapping through activation and update.
ci/verify.py +17 −5 Changes for ci/verify.py: 17 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -182,13 +182,23 @@ def charts(): chart = Path(openop_tfsdk.__file__).parent / "contract/adapters/charts/container" helm = helm_binary() image = "example.invalid/fixture@sha256:" + "a" * 64 for name, service_type, ports in [ ("none", "ClusterIP", []), ("internal", "ClusterIP", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}]), ("external", "NodePort", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}]), for name, service_type, ports, requests in [ ("none", "ClusterIP", [], {}), ("internal", "ClusterIP", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}], {}), ("external", "NodePort", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}], {}), ("requests", "ClusterIP", [], {"cpu": "250m", "memory": "257M"}), ]: values = OUT / f"chart-{name}.json" write_json(values, {"image": image, "serviceType": service_type, "ports": ports}) expected_resources = {"requests": requests} if requests else {} write_json( values, { "image": image, "serviceType": service_type, "ports": ports, "resources": expected_resources, }, ) run(helm, "lint", chart, "--strict", "-f", values) raw = subprocess.check_output( [ Loading @@ -208,6 +218,8 @@ def charts(): deployment = objects["Deployment"] spec = deployment["spec"]["template"]["spec"]["containers"][0] assert spec["image"] == image assert spec.get("resources", {}) == expected_resources assert "limits" not in spec.get("resources", {}) assert spec.get("ports", []) == [ {"name": p["name"], "containerPort": p["port"], "protocol": p["protocol"]} for p in ports Loading
src/openop_tfsdk/_diagnostics.py +1 −1 Changes for src/openop_tfsdk/_diagnostics.py: 1 added line, 1 removed line. Original line number Diff line number Diff line """Small log-only summaries; never copy backend text, payloads or input values.""" """Allowlisted diagnostic summaries; never copy backend text, payloads or input values.""" from typing import get_args Loading
src/openop_tfsdk/contract/adapters/charts/container/templates/deployment.yaml +4 −0 Changes for src/openop_tfsdk/contract/adapters/charts/container/templates/deployment.yaml: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -25,3 +25,7 @@ spec: protocol: {{ .protocol }} {{- end }} {{- end }} {{- with .Values.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }}