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.
- constellation_node_sdk-1.0.1/PKG-INFO +141 -0
- constellation_node_sdk-1.0.1/README.md +118 -0
- constellation_node_sdk-1.0.1/pyproject.toml +56 -0
- constellation_node_sdk-1.0.1/setup.cfg +4 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/__init__.py +123 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/__init__.py +47 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/client.py +108 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/config.py +153 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/errors.py +17 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/policy.py +70 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/gate/registration.py +144 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/__init__.py +3 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/base.py +57 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/merge.py +47 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/packet_builder.py +49 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/retry.py +61 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/state.py +78 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/orchestrator/step_executor.py +87 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/py.typed +0 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/__init__.py +52 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/app.py +274 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/config.py +307 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/errors.py +112 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/execution.py +249 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/handlers.py +45 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/inbound_policy.py +86 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/lifecycle.py +29 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/observability.py +72 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/runtime/preflight.py +122 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/__init__.py +36 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/delegation.py +57 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/errors.py +25 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/signing.py +121 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/validation.py +230 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/security/verification.py +107 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/__init__.py +88 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/codec.py +21 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/errors.py +41 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/hashing.py +73 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/hop_trace.py +378 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/lineage.py +28 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/models.py +447 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/packet.py +331 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/provenance.py +53 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk/transport/tenant.py +76 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk.egg-info/PKG-INFO +141 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk.egg-info/SOURCES.txt +48 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk.egg-info/dependency_links.txt +1 -0
- constellation_node_sdk-1.0.1/src/constellation_node_sdk.egg-info/requires.txt +16 -0
- 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,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
|