constellation-node-sdk 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. constellation_node_sdk-1.0.1/PKG-INFO +141 -0
  2. constellation_node_sdk-1.0.1/README.md +118 -0
  3. constellation_node_sdk-1.0.1/pyproject.toml +56 -0
  4. constellation_node_sdk-1.0.1/setup.cfg +4 -0
  5. constellation_node_sdk-1.0.1/src/constellation_node_sdk/__init__.py +123 -0
  6. constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/__init__.py +47 -0
  7. constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/client.py +108 -0
  8. constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/config.py +153 -0
  9. constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/errors.py +17 -0
  10. constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/policy.py +70 -0
  11. constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/registration.py +144 -0
  12. constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/__init__.py +3 -0
  13. constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/base.py +57 -0
  14. constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/merge.py +47 -0
  15. constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/packet_builder.py +49 -0
  16. constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/retry.py +61 -0
  17. constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/state.py +78 -0
  18. constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/step_executor.py +87 -0
  19. constellation_node_sdk-1.0.1/src/constellation_node_sdk/py.typed +0 -0
  20. constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/__init__.py +52 -0
  21. constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/app.py +274 -0
  22. constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/config.py +307 -0
  23. constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/errors.py +112 -0
  24. constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/execution.py +249 -0
  25. constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/handlers.py +45 -0
  26. constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/inbound_policy.py +86 -0
  27. constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/lifecycle.py +29 -0
  28. constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/observability.py +72 -0
  29. constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/preflight.py +122 -0
  30. constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/__init__.py +36 -0
  31. constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/delegation.py +57 -0
  32. constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/errors.py +25 -0
  33. constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/signing.py +121 -0
  34. constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/validation.py +230 -0
  35. constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/verification.py +107 -0
  36. constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/__init__.py +88 -0
  37. constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/codec.py +21 -0
  38. constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/errors.py +41 -0
  39. constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/hashing.py +73 -0
  40. constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/hop_trace.py +378 -0
  41. constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/lineage.py +28 -0
  42. constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/models.py +447 -0
  43. constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/packet.py +331 -0
  44. constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/provenance.py +53 -0
  45. constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/tenant.py +76 -0
  46. constellation_node_sdk-1.0.1/src/constellation_node_sdk.egg-info/PKG-INFO +141 -0
  47. constellation_node_sdk-1.0.1/src/constellation_node_sdk.egg-info/SOURCES.txt +48 -0
  48. constellation_node_sdk-1.0.1/src/constellation_node_sdk.egg-info/dependency_links.txt +1 -0
  49. constellation_node_sdk-1.0.1/src/constellation_node_sdk.egg-info/requires.txt +16 -0
  50. constellation_node_sdk-1.0.1/src/constellation_node_sdk.egg-info/top_level.txt +1 -0
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: constellation-node-sdk
3
+ Version: 1.0.1
4
+ Summary: L9 Constellation Node SDK — TransportPacket runtime, Gate client, and node factory
5
+ License: Proprietary
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: fastapi>=0.115.0
9
+ Requires-Dist: uvicorn[standard]>=0.32.0
10
+ Requires-Dist: pydantic>=2.9.0
11
+ Requires-Dist: httpx>=0.27.0
12
+ Requires-Dist: cryptography>=43.0.0
13
+ Requires-Dist: prometheus-client>=0.21.0
14
+ Requires-Dist: python-json-logger>=3.2.0
15
+ Requires-Dist: pyyaml>=6.0.2
16
+ Provides-Extra: dev
17
+ Requires-Dist: mypy>=1.11.0; extra == "dev"
18
+ Requires-Dist: pytest>=8.3.0; extra == "dev"
19
+ Requires-Dist: pytest-asyncio>=0.23.8; extra == "dev"
20
+ Requires-Dist: ruff>=0.6.0; extra == "dev"
21
+ Requires-Dist: types-PyYAML>=6.0.12; extra == "dev"
22
+ Requires-Dist: types-requests; extra == "dev"
23
+
24
+ # Constellation Node SDK
25
+
26
+ `constellation-node-sdk` is the canonical Python SDK for Constellation worker and orchestrator nodes.
27
+
28
+ It defines the packet-native transport contract used by the Constellation system:
29
+
30
+ - `TransportPacket` is the single canonical wire format
31
+ - all node-originated follow-up work goes back through **Gate**
32
+ - hop trace is append-only and tamper-evident
33
+ - transport integrity, lineage, provenance, and delegation are first-class protocol concepts
34
+
35
+ ## What this SDK provides
36
+
37
+ - canonical `TransportPacket` models and helpers
38
+ - transport hashing, signing, verification, and validation
39
+ - Gate-only outbound client
40
+ - reusable node runtime with `/v1/execute` and `/v1/health`
41
+ - orchestrator helpers for workflow composition through Gate
42
+ - tests and examples for worker and orchestrator nodes
43
+
44
+ ## Architectural rules
45
+
46
+ 1. Nodes **must not** know peer node URLs.
47
+ 2. Nodes **must only** send follow-up work to `GATE_URL`.
48
+ 3. Gate is the sole routing authority.
49
+ 4. `TransportPacket` is the only supported transport format.
50
+ 5. Semantic packet changes create child packets via `derive()`.
51
+ 6. Observational movement appends hop trace entries without changing `transport_hash`.
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ pip install -e .
57
+ For development:
58
+
59
+ pip install -e ".[dev]"
60
+ Quick start
61
+ Worker node
62
+ from constellation_node_sdk import create_node_app, register_handler
63
+
64
+ @register_handler("score")
65
+ async def handle_score(_tenant: str, payload: dict) -> dict:
66
+ return {
67
+ "status": "completed",
68
+ "score": 91,
69
+ "entity_id": payload["entity_id"],
70
+ }
71
+
72
+ app = create_node_app(
73
+ service_name="score-node",
74
+ version="1.0.0",
75
+ )
76
+ Orchestrator node
77
+ from constellation_node_sdk import create_node_app, register_handler
78
+ from constellation_node_sdk.gate import GateClient, get_gate_client_config_from_env
79
+ from constellation_node_sdk.orchestrator.step_executor import StepExecutor
80
+
81
+ gate_client = GateClient(get_gate_client_config_from_env())
82
+ step_executor = StepExecutor(gate_client=gate_client, source_node="orchestrator")
83
+
84
+ @register_handler("full_pipeline")
85
+ async def handle_full_pipeline(_tenant: str, payload: dict, packet):
86
+ enrich = await step_executor.execute_step(
87
+ parent=packet,
88
+ action="enrich",
89
+ payload={"entity_id": payload["entity_id"]},
90
+ )
91
+ score = await step_executor.execute_step(
92
+ parent=packet,
93
+ action="score",
94
+ payload={**payload, **enrich.payload},
95
+ )
96
+ return {
97
+ "status": "completed",
98
+ "entity_id": payload["entity_id"],
99
+ "enrich": enrich.payload,
100
+ "score": score.payload,
101
+ }
102
+
103
+ app = create_node_app(
104
+ service_name="orchestrator",
105
+ version="1.0.0",
106
+ )
107
+ Environment
108
+ See .env.example for runtime configuration.
109
+
110
+ Repo structure
111
+ src/constellation_node_sdk/transport/ — transport contract and hashing
112
+
113
+ src/constellation_node_sdk/security/ — signing, verification, validation
114
+
115
+ src/constellation_node_sdk/gate/ — Gate-only client and registration
116
+
117
+ src/constellation_node_sdk/runtime/ — reusable node runtime
118
+
119
+ src/constellation_node_sdk/orchestrator/ — workflow composition helpers
120
+
121
+ contracts/ — formal contract specs
122
+
123
+ examples/ — runnable worker and orchestrator examples
124
+
125
+ tests/ — unit and integration coverage
126
+
127
+ Status
128
+ This repo is intended to be:
129
+
130
+ packet-native
131
+
132
+ Gate-routed
133
+
134
+ composable
135
+
136
+ production-oriented
137
+
138
+ strict about protocol correctness
139
+
140
+
141
+ ```markdown
@@ -0,0 +1,118 @@
1
+ # Constellation Node SDK
2
+
3
+ `constellation-node-sdk` is the canonical Python SDK for Constellation worker and orchestrator nodes.
4
+
5
+ It defines the packet-native transport contract used by the Constellation system:
6
+
7
+ - `TransportPacket` is the single canonical wire format
8
+ - all node-originated follow-up work goes back through **Gate**
9
+ - hop trace is append-only and tamper-evident
10
+ - transport integrity, lineage, provenance, and delegation are first-class protocol concepts
11
+
12
+ ## What this SDK provides
13
+
14
+ - canonical `TransportPacket` models and helpers
15
+ - transport hashing, signing, verification, and validation
16
+ - Gate-only outbound client
17
+ - reusable node runtime with `/v1/execute` and `/v1/health`
18
+ - orchestrator helpers for workflow composition through Gate
19
+ - tests and examples for worker and orchestrator nodes
20
+
21
+ ## Architectural rules
22
+
23
+ 1. Nodes **must not** know peer node URLs.
24
+ 2. Nodes **must only** send follow-up work to `GATE_URL`.
25
+ 3. Gate is the sole routing authority.
26
+ 4. `TransportPacket` is the only supported transport format.
27
+ 5. Semantic packet changes create child packets via `derive()`.
28
+ 6. Observational movement appends hop trace entries without changing `transport_hash`.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ pip install -e .
34
+ For development:
35
+
36
+ pip install -e ".[dev]"
37
+ Quick start
38
+ Worker node
39
+ from constellation_node_sdk import create_node_app, register_handler
40
+
41
+ @register_handler("score")
42
+ async def handle_score(_tenant: str, payload: dict) -> dict:
43
+ return {
44
+ "status": "completed",
45
+ "score": 91,
46
+ "entity_id": payload["entity_id"],
47
+ }
48
+
49
+ app = create_node_app(
50
+ service_name="score-node",
51
+ version="1.0.0",
52
+ )
53
+ Orchestrator node
54
+ from constellation_node_sdk import create_node_app, register_handler
55
+ from constellation_node_sdk.gate import GateClient, get_gate_client_config_from_env
56
+ from constellation_node_sdk.orchestrator.step_executor import StepExecutor
57
+
58
+ gate_client = GateClient(get_gate_client_config_from_env())
59
+ step_executor = StepExecutor(gate_client=gate_client, source_node="orchestrator")
60
+
61
+ @register_handler("full_pipeline")
62
+ async def handle_full_pipeline(_tenant: str, payload: dict, packet):
63
+ enrich = await step_executor.execute_step(
64
+ parent=packet,
65
+ action="enrich",
66
+ payload={"entity_id": payload["entity_id"]},
67
+ )
68
+ score = await step_executor.execute_step(
69
+ parent=packet,
70
+ action="score",
71
+ payload={**payload, **enrich.payload},
72
+ )
73
+ return {
74
+ "status": "completed",
75
+ "entity_id": payload["entity_id"],
76
+ "enrich": enrich.payload,
77
+ "score": score.payload,
78
+ }
79
+
80
+ app = create_node_app(
81
+ service_name="orchestrator",
82
+ version="1.0.0",
83
+ )
84
+ Environment
85
+ See .env.example for runtime configuration.
86
+
87
+ Repo structure
88
+ src/constellation_node_sdk/transport/ — transport contract and hashing
89
+
90
+ src/constellation_node_sdk/security/ — signing, verification, validation
91
+
92
+ src/constellation_node_sdk/gate/ — Gate-only client and registration
93
+
94
+ src/constellation_node_sdk/runtime/ — reusable node runtime
95
+
96
+ src/constellation_node_sdk/orchestrator/ — workflow composition helpers
97
+
98
+ contracts/ — formal contract specs
99
+
100
+ examples/ — runnable worker and orchestrator examples
101
+
102
+ tests/ — unit and integration coverage
103
+
104
+ Status
105
+ This repo is intended to be:
106
+
107
+ packet-native
108
+
109
+ Gate-routed
110
+
111
+ composable
112
+
113
+ production-oriented
114
+
115
+ strict about protocol correctness
116
+
117
+
118
+ ```markdown
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["setuptools>=75.0.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "constellation-node-sdk"
7
+ version = "1.0.1"
8
+ description = "L9 Constellation Node SDK — TransportPacket runtime, Gate client, and node factory"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = { text = "Proprietary" }
12
+
13
+ dependencies = [
14
+ "fastapi>=0.115.0",
15
+ "uvicorn[standard]>=0.32.0",
16
+ "pydantic>=2.9.0",
17
+ "httpx>=0.27.0",
18
+ "cryptography>=43.0.0",
19
+ "prometheus-client>=0.21.0",
20
+ "python-json-logger>=3.2.0",
21
+ "pyyaml>=6.0.2",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "mypy>=1.11.0",
27
+ "pytest>=8.3.0",
28
+ "pytest-asyncio>=0.23.8",
29
+ "ruff>=0.6.0",
30
+ "types-PyYAML>=6.0.12",
31
+ "types-requests",
32
+ ]
33
+
34
+ [tool.setuptools]
35
+ package-dir = {"" = "src"}
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.ruff]
41
+ line-length = 100
42
+ target-version = "py312"
43
+
44
+ [tool.ruff.lint]
45
+ select = ["E", "F", "B", "I", "UP"]
46
+ ignore = ["E501"]
47
+
48
+ [tool.mypy]
49
+ python_version = "3.12"
50
+ strict = true
51
+ warn_return_any = true
52
+ warn_unused_configs = true
53
+
54
+ [tool.pytest.ini_options]
55
+ asyncio_mode = "auto"
56
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,123 @@
1
+ from __future__ import annotations
2
+
3
+ from constellation_node_sdk.gate import (
4
+ GateClient,
5
+ GateClientConfig,
6
+ GateRegistrationConfig,
7
+ assert_gate_only_destination,
8
+ assert_local_node_identity,
9
+ assert_node_origin_packet,
10
+ build_registration_payload,
11
+ get_gate_client_config_from_env,
12
+ get_gate_registration_config_from_env,
13
+ load_node_spec,
14
+ register_from_env,
15
+ register_with_gate,
16
+ validate_outbound_gate_packet,
17
+ )
18
+ from constellation_node_sdk.runtime import (
19
+ LifecycleHook,
20
+ NodeRuntimeConfig,
21
+ NoOpLifecycle,
22
+ PreflightFailure,
23
+ clear_handlers,
24
+ create_error_transport_packet,
25
+ create_node_app,
26
+ execute_transport_packet,
27
+ get_handler,
28
+ get_runtime_config,
29
+ register_handler,
30
+ registered_actions,
31
+ run_preflight,
32
+ )
33
+ from constellation_node_sdk.security import (
34
+ recompute_transport_core,
35
+ sign_transport_packet,
36
+ validate_derived_transport_packet,
37
+ validate_transport_packet,
38
+ verify_transport_packet_signature,
39
+ )
40
+ from constellation_node_sdk.transport import (
41
+ DelegationLink,
42
+ RoutingProvenance,
43
+ TenantContext,
44
+ TransportAddress,
45
+ TransportAttachment,
46
+ TransportAuthenticationError,
47
+ TransportAuthorizationError,
48
+ TransportError,
49
+ TransportExpiredError,
50
+ TransportGovernance,
51
+ TransportHeader,
52
+ TransportHop,
53
+ TransportIntegrityError,
54
+ TransportLineage,
55
+ TransportNotYetValidError,
56
+ TransportPacket,
57
+ TransportSecurity,
58
+ TransportValidationError,
59
+ canonical_json,
60
+ compute_payload_hash,
61
+ compute_transport_hash,
62
+ create_transport_packet,
63
+ ensure_tenant_context,
64
+ utc_now,
65
+ )
66
+
67
+ __all__ = [
68
+ "DelegationLink",
69
+ "GateClient",
70
+ "GateClientConfig",
71
+ "GateRegistrationConfig",
72
+ "LifecycleHook",
73
+ "NoOpLifecycle",
74
+ "NodeRuntimeConfig",
75
+ "PreflightFailure",
76
+ "RoutingProvenance",
77
+ "TenantContext",
78
+ "TransportAddress",
79
+ "TransportAttachment",
80
+ "TransportAuthenticationError",
81
+ "TransportAuthorizationError",
82
+ "TransportError",
83
+ "TransportExpiredError",
84
+ "TransportGovernance",
85
+ "TransportHeader",
86
+ "TransportHop",
87
+ "TransportIntegrityError",
88
+ "TransportLineage",
89
+ "TransportNotYetValidError",
90
+ "TransportPacket",
91
+ "TransportSecurity",
92
+ "TransportValidationError",
93
+ "assert_gate_only_destination",
94
+ "assert_local_node_identity",
95
+ "assert_node_origin_packet",
96
+ "build_registration_payload",
97
+ "canonical_json",
98
+ "clear_handlers",
99
+ "compute_payload_hash",
100
+ "compute_transport_hash",
101
+ "create_error_transport_packet",
102
+ "create_node_app",
103
+ "create_transport_packet",
104
+ "ensure_tenant_context",
105
+ "execute_transport_packet",
106
+ "get_gate_client_config_from_env",
107
+ "get_gate_registration_config_from_env",
108
+ "get_handler",
109
+ "get_runtime_config",
110
+ "load_node_spec",
111
+ "recompute_transport_core",
112
+ "register_from_env",
113
+ "register_handler",
114
+ "register_with_gate",
115
+ "registered_actions",
116
+ "run_preflight",
117
+ "sign_transport_packet",
118
+ "utc_now",
119
+ "validate_derived_transport_packet",
120
+ "validate_outbound_gate_packet",
121
+ "validate_transport_packet",
122
+ "verify_transport_packet_signature",
123
+ ]
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from constellation_node_sdk.gate.client import GateClient
4
+ from constellation_node_sdk.gate.config import (
5
+ GateClientConfig,
6
+ GateRegistrationConfig,
7
+ get_gate_client_config_from_env,
8
+ get_gate_registration_config_from_env,
9
+ )
10
+ from constellation_node_sdk.gate.errors import (
11
+ GateClientError,
12
+ GatePolicyError,
13
+ GateRegistrationError,
14
+ GateResponseError,
15
+ )
16
+ from constellation_node_sdk.gate.policy import (
17
+ assert_gate_only_destination,
18
+ assert_local_node_identity,
19
+ assert_node_origin_packet,
20
+ validate_outbound_gate_packet,
21
+ )
22
+ from constellation_node_sdk.gate.registration import (
23
+ build_registration_payload,
24
+ load_node_spec,
25
+ register_from_env,
26
+ register_with_gate,
27
+ )
28
+
29
+ __all__ = [
30
+ "GateClient",
31
+ "GateClientConfig",
32
+ "GateClientError",
33
+ "GatePolicyError",
34
+ "GateRegistrationConfig",
35
+ "GateRegistrationError",
36
+ "GateResponseError",
37
+ "assert_gate_only_destination",
38
+ "assert_local_node_identity",
39
+ "assert_node_origin_packet",
40
+ "build_registration_payload",
41
+ "get_gate_client_config_from_env",
42
+ "get_gate_registration_config_from_env",
43
+ "load_node_spec",
44
+ "register_from_env",
45
+ "register_with_gate",
46
+ "validate_outbound_gate_packet",
47
+ ]
@@ -0,0 +1,108 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Mapping
4
+ from typing import Any
5
+
6
+ import httpx
7
+
8
+ from constellation_node_sdk.security.signing import sign_transport_packet
9
+ from constellation_node_sdk.security.validation import validate_transport_packet
10
+ from constellation_node_sdk.transport.packet import TransportPacket
11
+
12
+ from .config import GateClientConfig
13
+ from .policy import validate_outbound_gate_packet
14
+
15
+
16
+ class GateClient:
17
+ """
18
+ Canonical Gate-only transport client for nodes.
19
+
20
+ This client is the only allowed outbound inter-node transport surface.
21
+ It never accepts an arbitrary peer URL.
22
+ """
23
+
24
+ def __init__(self, config: GateClientConfig) -> None:
25
+ self._config = config
26
+
27
+ @property
28
+ def gate_url(self) -> str:
29
+ return self._config.gate_url
30
+
31
+ def _transport_key_resolver(
32
+ self,
33
+ ) -> Callable[[str | None], str | bytes | None] | Mapping[str, str | bytes] | None:
34
+ if not self._config.verifying_keys and self._config.signing_key is None:
35
+ return None
36
+ return self._config.resolve_verifying_key
37
+
38
+ def _maybe_sign(self, packet: TransportPacket) -> TransportPacket:
39
+ if self._config.signing_key is None:
40
+ return packet
41
+ if self._config.signing_key_id is None or self._config.signing_algorithm is None:
42
+ raise ValueError(
43
+ "signing_key_id and signing_algorithm are required when signing_key is configured"
44
+ )
45
+ return sign_transport_packet(
46
+ packet,
47
+ key=self._config.signing_key,
48
+ key_id=self._config.signing_key_id,
49
+ algorithm=self._config.signing_algorithm,
50
+ )
51
+
52
+ def _validate_outbound(self, packet: TransportPacket) -> None:
53
+ validate_outbound_gate_packet(
54
+ packet,
55
+ local_node=self._config.local_node,
56
+ gate_node_name=self._config.allowed_gate_destination,
57
+ )
58
+ validate_transport_packet(
59
+ packet,
60
+ key_resolver=self._transport_key_resolver(),
61
+ require_signature=self._config.require_signature,
62
+ dev_mode=not self._config.require_signature,
63
+ verify_hop_signatures=self._config.verify_hop_signatures,
64
+ )
65
+
66
+ def _validate_inbound_response(self, packet: TransportPacket) -> None:
67
+ validate_transport_packet(
68
+ packet,
69
+ key_resolver=self._transport_key_resolver(),
70
+ require_signature=self._config.verify_response_signatures,
71
+ dev_mode=not self._config.verify_response_signatures,
72
+ verify_hop_signatures=self._config.verify_hop_signatures,
73
+ )
74
+
75
+ async def send_to_gate(self, packet: TransportPacket) -> TransportPacket:
76
+ """
77
+ Send a canonical TransportPacket to Gate and decode a canonical TransportPacket response.
78
+ """
79
+ self._validate_outbound(packet)
80
+ signed_packet = self._maybe_sign(packet)
81
+
82
+ async with httpx.AsyncClient(timeout=self._config.timeout_seconds) as client:
83
+ response = await client.post(
84
+ f"{self._config.gate_url}/v1/execute",
85
+ json=signed_packet.model_dump_json_dict(),
86
+ headers={"Content-Type": "application/json"},
87
+ )
88
+
89
+ response.raise_for_status()
90
+ body = response.json()
91
+ if not isinstance(body, dict):
92
+ raise ValueError("Gate response body must be a JSON object")
93
+
94
+ response_packet = TransportPacket.model_validate(body)
95
+ self._validate_inbound_response(response_packet)
96
+ return response_packet
97
+
98
+ async def health(self) -> dict[str, Any]:
99
+ """
100
+ Query Gate health endpoint.
101
+ """
102
+ async with httpx.AsyncClient(timeout=self._config.timeout_seconds) as client:
103
+ response = await client.get(f"{self._config.gate_url}/v1/health")
104
+ response.raise_for_status()
105
+ body = response.json()
106
+ if not isinstance(body, dict):
107
+ raise ValueError("Gate health response must be a JSON object")
108
+ return body