constellation-node-sdk 1.0.1__py3-none-any.whl

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 (45) hide show
  1. constellation_node_sdk/__init__.py +123 -0
  2. constellation_node_sdk/gate/__init__.py +47 -0
  3. constellation_node_sdk/gate/client.py +108 -0
  4. constellation_node_sdk/gate/config.py +153 -0
  5. constellation_node_sdk/gate/errors.py +17 -0
  6. constellation_node_sdk/gate/policy.py +70 -0
  7. constellation_node_sdk/gate/registration.py +144 -0
  8. constellation_node_sdk/orchestrator/__init__.py +3 -0
  9. constellation_node_sdk/orchestrator/base.py +57 -0
  10. constellation_node_sdk/orchestrator/merge.py +47 -0
  11. constellation_node_sdk/orchestrator/packet_builder.py +49 -0
  12. constellation_node_sdk/orchestrator/retry.py +61 -0
  13. constellation_node_sdk/orchestrator/state.py +78 -0
  14. constellation_node_sdk/orchestrator/step_executor.py +87 -0
  15. constellation_node_sdk/py.typed +0 -0
  16. constellation_node_sdk/runtime/__init__.py +52 -0
  17. constellation_node_sdk/runtime/app.py +274 -0
  18. constellation_node_sdk/runtime/config.py +307 -0
  19. constellation_node_sdk/runtime/errors.py +112 -0
  20. constellation_node_sdk/runtime/execution.py +249 -0
  21. constellation_node_sdk/runtime/handlers.py +45 -0
  22. constellation_node_sdk/runtime/inbound_policy.py +86 -0
  23. constellation_node_sdk/runtime/lifecycle.py +29 -0
  24. constellation_node_sdk/runtime/observability.py +72 -0
  25. constellation_node_sdk/runtime/preflight.py +122 -0
  26. constellation_node_sdk/security/__init__.py +36 -0
  27. constellation_node_sdk/security/delegation.py +57 -0
  28. constellation_node_sdk/security/errors.py +25 -0
  29. constellation_node_sdk/security/signing.py +121 -0
  30. constellation_node_sdk/security/validation.py +230 -0
  31. constellation_node_sdk/security/verification.py +107 -0
  32. constellation_node_sdk/transport/__init__.py +88 -0
  33. constellation_node_sdk/transport/codec.py +21 -0
  34. constellation_node_sdk/transport/errors.py +41 -0
  35. constellation_node_sdk/transport/hashing.py +73 -0
  36. constellation_node_sdk/transport/hop_trace.py +378 -0
  37. constellation_node_sdk/transport/lineage.py +28 -0
  38. constellation_node_sdk/transport/models.py +447 -0
  39. constellation_node_sdk/transport/packet.py +331 -0
  40. constellation_node_sdk/transport/provenance.py +53 -0
  41. constellation_node_sdk/transport/tenant.py +76 -0
  42. constellation_node_sdk-1.0.1.dist-info/METADATA +141 -0
  43. constellation_node_sdk-1.0.1.dist-info/RECORD +45 -0
  44. constellation_node_sdk-1.0.1.dist-info/WHEEL +5 -0
  45. constellation_node_sdk-1.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,331 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+ from uuid import uuid4
6
+
7
+ from pydantic import BaseModel, ConfigDict, field_validator, model_validator
8
+
9
+ from .errors import TransportIntegrityError
10
+ from .hashing import compute_payload_hash, compute_transport_hash
11
+ from .models import (
12
+ DelegationLink,
13
+ TransportAddress,
14
+ TransportAttachment,
15
+ TransportGovernance,
16
+ TransportHeader,
17
+ TransportHop,
18
+ TransportLineage,
19
+ TransportSecurity,
20
+ utc_now,
21
+ )
22
+ from .provenance import RoutingProvenance
23
+ from .tenant import TenantContext, assert_tenant_immutable, ensure_tenant_context
24
+
25
+
26
+ class TransportPacket(BaseModel):
27
+ model_config = ConfigDict(frozen=True, extra="forbid")
28
+
29
+ header: TransportHeader
30
+ address: TransportAddress
31
+ tenant: TenantContext
32
+ payload: dict[str, Any]
33
+ security: TransportSecurity
34
+ governance: TransportGovernance
35
+ provenance: RoutingProvenance
36
+ delegation_chain: tuple[DelegationLink, ...] = ()
37
+ hop_trace: tuple[TransportHop, ...] = ()
38
+ lineage: TransportLineage
39
+ attachments: tuple[TransportAttachment, ...] = ()
40
+
41
+ @field_validator("payload")
42
+ @classmethod
43
+ def validate_payload(cls, value: dict[str, Any]) -> dict[str, Any]:
44
+ if not isinstance(value, dict):
45
+ raise TypeError("payload must be a dict")
46
+ return value
47
+
48
+ @model_validator(mode="after")
49
+ def validate_integrity(self) -> TransportPacket:
50
+ expected_payload_hash = compute_payload_hash(self.payload)
51
+ if self.security.payload_hash != expected_payload_hash:
52
+ raise TransportIntegrityError("payload_hash does not match payload")
53
+
54
+ expected_transport_hash = compute_transport_hash(self)
55
+ if self.security.transport_hash != expected_transport_hash:
56
+ raise TransportIntegrityError("transport_hash does not match packet")
57
+
58
+ return self
59
+
60
+ def model_dump_json_dict(self) -> dict[str, Any]:
61
+ """Return a JSON-safe dict representation."""
62
+ return self.model_dump(mode="json", exclude_none=True)
63
+
64
+ def with_hop(self, hop: TransportHop) -> TransportPacket:
65
+ """
66
+ Append a hop without mutating the signed transport core.
67
+
68
+ hop_trace is intentionally excluded from transport_hash, so a hop append
69
+ must preserve payload_hash, transport_hash, and any existing transport
70
+ signature.
71
+ """
72
+ if hop.packet_id != self.header.packet_id:
73
+ raise ValueError("hop.packet_id must match packet.header.packet_id")
74
+
75
+ appended = self.model_copy(update={"hop_trace": self.hop_trace + (hop,)})
76
+ return TransportPacket(
77
+ header=appended.header,
78
+ address=appended.address,
79
+ tenant=appended.tenant,
80
+ payload=appended.payload,
81
+ security=appended.security,
82
+ governance=appended.governance,
83
+ provenance=appended.provenance,
84
+ delegation_chain=tuple(appended.delegation_chain),
85
+ hop_trace=tuple(appended.hop_trace),
86
+ lineage=appended.lineage,
87
+ attachments=tuple(appended.attachments),
88
+ )
89
+
90
+ def derive(
91
+ self,
92
+ *,
93
+ packet_type: str | None = None,
94
+ action: str | None = None,
95
+ source_node: str | None = None,
96
+ destination_node: str | None = None,
97
+ reply_to: str | None = None,
98
+ payload: dict[str, Any] | None = None,
99
+ provenance: RoutingProvenance | None = None,
100
+ hop: TransportHop | None = None,
101
+ delegation_link: DelegationLink | None = None,
102
+ governance: TransportGovernance | None = None,
103
+ priority: int | None = None,
104
+ expires_at: datetime | None = None,
105
+ timeout_ms: int | None = None,
106
+ not_before: datetime | None = None,
107
+ replay_mode: bool | None = None,
108
+ ) -> TransportPacket:
109
+ """Create an immutable child packet with correct lineage and refreshed hashes."""
110
+ new_header = TransportHeader(
111
+ packet_id=uuid4(),
112
+ packet_type=packet_type or self.header.packet_type,
113
+ action=action or self.header.action,
114
+ priority=self.header.priority if priority is None else priority,
115
+ created_at=utc_now(),
116
+ expires_at=self.header.expires_at if expires_at is None else expires_at,
117
+ timeout_ms=self.header.timeout_ms if timeout_ms is None else timeout_ms,
118
+ schema_version=self.header.schema_version,
119
+ idempotency_key=self.header.idempotency_key,
120
+ trace_id=self.header.trace_id,
121
+ correlation_id=self.header.correlation_id,
122
+ causation_id=self.header.packet_id,
123
+ retry_count=0,
124
+ replay_mode=self.header.replay_mode if replay_mode is None else replay_mode,
125
+ not_before=self.header.not_before if not_before is None else not_before,
126
+ )
127
+
128
+ new_address = TransportAddress(
129
+ source_node=(source_node or self.address.source_node).strip().lower(),
130
+ destination_node=(destination_node or self.address.destination_node).strip().lower(),
131
+ reply_to=(reply_to or self.address.reply_to).strip().lower(),
132
+ )
133
+
134
+ new_payload = dict(self.payload if payload is None else payload)
135
+ new_governance = governance or self.governance
136
+ new_provenance = provenance or self.provenance
137
+ new_delegation_chain = self.delegation_chain + (
138
+ (delegation_link,) if delegation_link is not None else ()
139
+ )
140
+ new_hop_trace = self.hop_trace + ((hop,) if hop is not None else ())
141
+ new_lineage = TransportLineage(
142
+ parent_id=self.header.packet_id,
143
+ root_id=self.lineage.root_id,
144
+ generation=self.lineage.generation + 1,
145
+ )
146
+
147
+ provisional = TransportPacket.model_construct(
148
+ header=new_header,
149
+ address=new_address,
150
+ tenant=self.tenant,
151
+ payload=new_payload,
152
+ security=TransportSecurity(
153
+ payload_hash="0" * 64,
154
+ transport_hash="0" * 64,
155
+ signature=None,
156
+ signature_algorithm=None,
157
+ signing_key_id=None,
158
+ classification=self.security.classification,
159
+ encryption_status=self.security.encryption_status,
160
+ pii_fields=self.security.pii_fields,
161
+ ),
162
+ governance=new_governance,
163
+ provenance=new_provenance,
164
+ delegation_chain=new_delegation_chain,
165
+ hop_trace=new_hop_trace,
166
+ lineage=new_lineage,
167
+ attachments=self.attachments,
168
+ )
169
+ child = _finalize_transport_packet(provisional, preserve_signature=False)
170
+ assert_tenant_immutable(self.tenant, child.tenant)
171
+ return child
172
+
173
+
174
+ def _default_provenance(*, source_node: str, action: str) -> RoutingProvenance:
175
+ source = source_node.strip().lower()
176
+ origin_kind = "client" if source == "client" else "gate" if source == "gate" else "node"
177
+ return RoutingProvenance(
178
+ origin_kind=origin_kind,
179
+ requested_action=action.strip().lower(),
180
+ resolved_by_gate=False,
181
+ original_source_node=None if origin_kind == "client" else source,
182
+ )
183
+
184
+
185
+ def _finalize_transport_packet(
186
+ packet: TransportPacket, *, preserve_signature: bool
187
+ ) -> TransportPacket:
188
+ payload_hash = compute_payload_hash(packet.payload)
189
+ provisional = packet.model_copy(
190
+ update={
191
+ "security": packet.security.model_copy(
192
+ update={
193
+ "payload_hash": payload_hash,
194
+ "transport_hash": "0" * 64,
195
+ "signature": packet.security.signature if preserve_signature else None,
196
+ "signature_algorithm": packet.security.signature_algorithm
197
+ if preserve_signature
198
+ else None,
199
+ "signing_key_id": packet.security.signing_key_id
200
+ if preserve_signature
201
+ else None,
202
+ }
203
+ )
204
+ }
205
+ )
206
+ transport_hash = compute_transport_hash(provisional)
207
+ signature_still_valid = (
208
+ preserve_signature
209
+ and packet.security.payload_hash == payload_hash
210
+ and packet.security.transport_hash == transport_hash
211
+ )
212
+
213
+ finalized = provisional.model_copy(
214
+ update={
215
+ "security": provisional.security.model_copy(
216
+ update={
217
+ "transport_hash": transport_hash,
218
+ "signature": packet.security.signature if signature_still_valid else None,
219
+ "signature_algorithm": packet.security.signature_algorithm
220
+ if signature_still_valid
221
+ else None,
222
+ "signing_key_id": packet.security.signing_key_id
223
+ if signature_still_valid
224
+ else None,
225
+ }
226
+ )
227
+ }
228
+ )
229
+ return TransportPacket(
230
+ header=finalized.header,
231
+ address=finalized.address,
232
+ tenant=finalized.tenant,
233
+ payload=finalized.payload,
234
+ security=finalized.security,
235
+ governance=finalized.governance,
236
+ provenance=finalized.provenance,
237
+ delegation_chain=tuple(finalized.delegation_chain),
238
+ hop_trace=tuple(finalized.hop_trace),
239
+ lineage=finalized.lineage,
240
+ attachments=tuple(finalized.attachments),
241
+ )
242
+
243
+
244
+ def create_transport_packet(
245
+ *,
246
+ action: str,
247
+ payload: dict[str, Any],
248
+ tenant: str | dict[str, Any] | TenantContext,
249
+ destination_node: str = "gate",
250
+ source_node: str = "client",
251
+ reply_to: str = "client",
252
+ priority: int = 2,
253
+ timeout_ms: int = 30_000,
254
+ classification: str = "internal",
255
+ compliance_tags: tuple[str, ...] = (),
256
+ retention_days: int = 90,
257
+ expires_at: datetime | None = None,
258
+ idempotency_key: str | None = None,
259
+ trace_id: str | None = None,
260
+ correlation_id: str | None = None,
261
+ provenance: RoutingProvenance | None = None,
262
+ ) -> TransportPacket:
263
+ """Build a canonical root transport packet with initialized lineage and hashes."""
264
+ packet_id = uuid4()
265
+ normalized_tenant = ensure_tenant_context(tenant)
266
+ normalized_action = action.strip().lower()
267
+ normalized_source = source_node.strip().lower()
268
+ normalized_destination = destination_node.strip().lower()
269
+ normalized_reply_to = reply_to.strip().lower()
270
+
271
+ header = TransportHeader(
272
+ packet_id=packet_id,
273
+ packet_type="request",
274
+ action=normalized_action,
275
+ priority=priority,
276
+ created_at=utc_now(),
277
+ expires_at=expires_at,
278
+ timeout_ms=timeout_ms,
279
+ schema_version="1.0",
280
+ idempotency_key=idempotency_key,
281
+ trace_id=trace_id or str(packet_id),
282
+ correlation_id=correlation_id or str(packet_id),
283
+ causation_id=None,
284
+ retry_count=0,
285
+ replay_mode=False,
286
+ not_before=None,
287
+ )
288
+
289
+ address = TransportAddress(
290
+ source_node=normalized_source,
291
+ destination_node=normalized_destination,
292
+ reply_to=normalized_reply_to,
293
+ )
294
+
295
+ governance = TransportGovernance(
296
+ intent=normalized_action,
297
+ compliance_tags=compliance_tags,
298
+ retention_days=retention_days,
299
+ redaction_applied=False,
300
+ audit_required=False,
301
+ data_subject_id=None,
302
+ )
303
+
304
+ packet = TransportPacket.model_construct(
305
+ header=header,
306
+ address=address,
307
+ tenant=normalized_tenant,
308
+ payload=dict(payload),
309
+ security=TransportSecurity(
310
+ payload_hash="0" * 64,
311
+ transport_hash="0" * 64,
312
+ signature=None,
313
+ signature_algorithm=None,
314
+ signing_key_id=None,
315
+ classification=classification,
316
+ encryption_status="plaintext",
317
+ pii_fields=(),
318
+ ),
319
+ governance=governance,
320
+ provenance=provenance
321
+ or _default_provenance(source_node=normalized_source, action=normalized_action),
322
+ delegation_chain=(),
323
+ hop_trace=(),
324
+ lineage=TransportLineage(
325
+ parent_id=None,
326
+ root_id=packet_id,
327
+ generation=0,
328
+ ),
329
+ attachments=(),
330
+ )
331
+ return _finalize_transport_packet(packet, preserve_signature=False)
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, ConfigDict, field_validator
4
+
5
+
6
+ class RoutingProvenance(BaseModel):
7
+ model_config = ConfigDict(frozen=True, extra="forbid")
8
+
9
+ origin_kind: str
10
+ requested_action: str
11
+ resolved_by_gate: bool = False
12
+ route_kind: str | None = None
13
+ original_source_node: str | None = None
14
+
15
+ @field_validator("origin_kind")
16
+ @classmethod
17
+ def validate_origin_kind(cls, value: str) -> str:
18
+ normalized = value.strip().lower()
19
+ allowed = {"client", "node", "gate"}
20
+ if normalized not in allowed:
21
+ msg = f"origin_kind must be one of {sorted(allowed)}"
22
+ raise ValueError(msg)
23
+ return normalized
24
+
25
+ @field_validator("requested_action")
26
+ @classmethod
27
+ def validate_requested_action(cls, value: str) -> str:
28
+ normalized = value.strip().lower()
29
+ if not normalized:
30
+ raise ValueError("requested_action must not be empty")
31
+ return normalized
32
+
33
+ @field_validator("route_kind")
34
+ @classmethod
35
+ def validate_route_kind(cls, value: str | None) -> str | None:
36
+ if value is None:
37
+ return None
38
+ normalized = value.strip().lower()
39
+ allowed = {"external_ingress", "gate_relay"}
40
+ if normalized not in allowed:
41
+ msg = f"route_kind must be one of {sorted(allowed)}"
42
+ raise ValueError(msg)
43
+ return normalized
44
+
45
+ @field_validator("original_source_node")
46
+ @classmethod
47
+ def validate_original_source_node(cls, value: str | None) -> str | None:
48
+ if value is None:
49
+ return None
50
+ normalized = value.strip().lower()
51
+ if not normalized:
52
+ raise ValueError("original_source_node must not be blank")
53
+ return normalized
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, ConfigDict, field_validator
6
+
7
+ from .errors import TenantMutationError
8
+
9
+
10
+ class TenantContext(BaseModel):
11
+ model_config = ConfigDict(frozen=True, extra="forbid")
12
+
13
+ actor: str
14
+ on_behalf_of: str
15
+ originator: str
16
+ org_id: str
17
+ user_id: str | None = None
18
+
19
+ @field_validator("actor", "on_behalf_of", "originator", "org_id")
20
+ @classmethod
21
+ def validate_required_strings(cls, value: str) -> str:
22
+ normalized = value.strip()
23
+ if not normalized:
24
+ raise ValueError("tenant fields must not be empty")
25
+ return normalized
26
+
27
+ @field_validator("user_id")
28
+ @classmethod
29
+ def validate_optional_string(cls, value: str | None) -> str | None:
30
+ if value is None:
31
+ return None
32
+ normalized = value.strip()
33
+ if not normalized:
34
+ raise ValueError("user_id must not be blank")
35
+ return normalized
36
+
37
+
38
+ def ensure_tenant_context(value: str | dict[str, Any] | TenantContext) -> TenantContext:
39
+ """Normalize a string, dict, or TenantContext into a canonical TenantContext."""
40
+ if isinstance(value, TenantContext):
41
+ return value
42
+
43
+ if isinstance(value, str):
44
+ normalized = value.strip()
45
+ if not normalized:
46
+ raise ValueError("tenant string must not be empty")
47
+ return TenantContext(
48
+ actor=normalized,
49
+ on_behalf_of=normalized,
50
+ originator=normalized,
51
+ org_id=normalized,
52
+ user_id=None,
53
+ )
54
+
55
+ payload = dict(value or {})
56
+ if "tenant_id" in payload and "org_id" not in payload:
57
+ payload["org_id"] = payload["tenant_id"]
58
+
59
+ org_id = str(payload.get("org_id") or payload.get("actor") or "default").strip()
60
+ actor = str(payload.get("actor") or org_id).strip()
61
+ on_behalf_of = str(payload.get("on_behalf_of") or actor).strip()
62
+ originator = str(payload.get("originator") or actor).strip()
63
+
64
+ return TenantContext(
65
+ actor=actor,
66
+ on_behalf_of=on_behalf_of,
67
+ originator=originator,
68
+ org_id=org_id,
69
+ user_id=payload.get("user_id"),
70
+ )
71
+
72
+
73
+ def assert_tenant_immutable(parent: TenantContext, child: TenantContext) -> None:
74
+ """Raise if a derived packet mutates tenant context."""
75
+ if parent != child:
76
+ raise TenantMutationError("tenant context is immutable across derived packets")
@@ -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,45 @@
1
+ constellation_node_sdk/__init__.py,sha256=7DIaR8opok0gPv4VUvorlrVEoEkJQKnPTX4tBR8VQHQ,3197
2
+ constellation_node_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ constellation_node_sdk/gate/__init__.py,sha256=0BmOmw0BGZnNIUJBaZmoaoy8tSaSZiE-p0bEapDbx5M,1275
4
+ constellation_node_sdk/gate/client.py,sha256=MzAreAblo2uQ2NPYbDIHh8fPtd6ZU-eadsBke6O_OFk,4070
5
+ constellation_node_sdk/gate/config.py,sha256=HPn3mIm6zZ_rNd_RpfJy7Xuav1eftxlBEA44CjjETvw,5726
6
+ constellation_node_sdk/gate/errors.py,sha256=-gQmOrRuFU3KoOBuuUPWAUHbU9dazk527kFctUMgydw,473
7
+ constellation_node_sdk/gate/policy.py,sha256=Qtwt8jR6nNnWfd0DajbRvIBAIQiyEWyXqu-E7bx7DTc,2592
8
+ constellation_node_sdk/gate/registration.py,sha256=9uymIBmBA6yfKAKYzDSoX79PDXyBAWpogYE1Uzqs1ng,4609
9
+ constellation_node_sdk/orchestrator/__init__.py,sha256=RmS0Ok-1-CwWOZL_socLWksEk9MfsshL9eQx879I4mU,60
10
+ constellation_node_sdk/orchestrator/base.py,sha256=2YjLWfoplVCu1EAECrKiFkPQluJCYHS9Y6hBbCjfkTo,1739
11
+ constellation_node_sdk/orchestrator/merge.py,sha256=k6PWq7eTacz_lmAdnmaSXbGhr-FOb3RUQ5ZoB0xMM_U,1344
12
+ constellation_node_sdk/orchestrator/packet_builder.py,sha256=e6gyeGwgRcLIFF1Q4Fjl7RPWORvFMevXOBvH8bpujSs,1472
13
+ constellation_node_sdk/orchestrator/retry.py,sha256=GMpWjnYP0QowL4ik2tjAjzkyQmqNnJcGZYkQkKxSjBA,2108
14
+ constellation_node_sdk/orchestrator/state.py,sha256=bh82_Jpq3UnegSrXZuogNVf4YtESVAfD8FNVXlbjjxk,2618
15
+ constellation_node_sdk/orchestrator/step_executor.py,sha256=q_TMyCichml0GpQWLEvpSEipYb2YjQFoMCkon78T5no,2902
16
+ constellation_node_sdk/runtime/__init__.py,sha256=EQ6fwCQRamm78Kq8UMVL2q5lDUIL9OfZKE-r4JDfpJg,1547
17
+ constellation_node_sdk/runtime/app.py,sha256=bcIvglSfRwGlQAQ_dgk6LmYbb-DgRbkMAM2OyNNYgxc,11127
18
+ constellation_node_sdk/runtime/config.py,sha256=pEhjf26Uy7tr1LjKpdKVmPos2EoouS1NSofcCaR68lk,12916
19
+ constellation_node_sdk/runtime/errors.py,sha256=TbQMtDi4yZL1WAqGcTMlpsHsBD1PfgwYkKU2FByB4i4,3014
20
+ constellation_node_sdk/runtime/execution.py,sha256=DFHxEPHgeOKUABkBZv9NXmjfS7SNMpZoQ4O8GwjHvhg,8436
21
+ constellation_node_sdk/runtime/handlers.py,sha256=VBU3kdon0gR3xsPDEh7V5IoP4i7wxfrEe2S_QWwmbcM,1223
22
+ constellation_node_sdk/runtime/inbound_policy.py,sha256=sqTJr6ur0n-_qmFSaTf7cLBDyfU-2K9Do4jNQkmy8xs,3140
23
+ constellation_node_sdk/runtime/lifecycle.py,sha256=17ZNWTRdg4rA8Bi7-Gh0CIunYdXlYgWCn1Gri33UvoQ,651
24
+ constellation_node_sdk/runtime/observability.py,sha256=JRLwQ_97_GnYTQSoqsGMRaacHMdnSgWaoJRlGtsPuK8,1799
25
+ constellation_node_sdk/runtime/preflight.py,sha256=5SHhoPJWomjJgUr6Ve9l4ZRF7h0CBoPMomxjkYjXeXc,4735
26
+ constellation_node_sdk/security/__init__.py,sha256=4JTsEHsO5d6tHo_H4yeqTo0rRTtlFlVkd3b7fVBJxn4,1202
27
+ constellation_node_sdk/security/delegation.py,sha256=MlT81UFxRhiNsi25r8GxKNlUXs0KuH2q2gWFR_iK-JM,1707
28
+ constellation_node_sdk/security/errors.py,sha256=l6Eel5pbr3meFib0qvDPdGXkMueVoqvLIjJhn1W6SOY,596
29
+ constellation_node_sdk/security/signing.py,sha256=ahniFXeXdV7EDhg1LxpPXVPpIX2_l5kHWgecTakj9QU,4122
30
+ constellation_node_sdk/security/validation.py,sha256=nktaWgSdleZvm5lxsKfrT6g8iDaUWj2039-Nwug3ACU,9453
31
+ constellation_node_sdk/security/verification.py,sha256=-JOavVW7CZHrq3fMolkDnv_g2UX08Whmv3RD6BWMpn0,3508
32
+ constellation_node_sdk/transport/__init__.py,sha256=AvwIqfzguRjCCsz7R3ASFKJF_uzZ5dPrVIHzcCh3E-0,2267
33
+ constellation_node_sdk/transport/codec.py,sha256=SFalmKhwxcv7V-USPxnmK1lKDHh3q-n-kgFtah6vZ98,618
34
+ constellation_node_sdk/transport/errors.py,sha256=ohgC0NoRYepEKeh3EkgZZCMZIJ5D8cQjJfxp-0ndg5k,1279
35
+ constellation_node_sdk/transport/hashing.py,sha256=Ds6Z109gCDUVLbLQO7-3fBZdFYqSUf5n--kjvb0H_KM,2477
36
+ constellation_node_sdk/transport/hop_trace.py,sha256=bgydTputZNimNJAVON89tLsvFfjJT6DGTYWEFdbixtU,11761
37
+ constellation_node_sdk/transport/lineage.py,sha256=xswFwlfELGZcgd-alncuMR6lOxFP-i4uv5awQ_TsPmE,1172
38
+ constellation_node_sdk/transport/models.py,sha256=-xc0KYLMrXSK_iLIKmMxx-vWI0MHbOq4TCKDLduQaLo,16174
39
+ constellation_node_sdk/transport/packet.py,sha256=aaqY_BZVWDT4zSpKPkqUeHu6bkbb0ZTVQFcjEUg-0f8,12295
40
+ constellation_node_sdk/transport/provenance.py,sha256=QwmAJvnxB7254nNkySzmkv93OHM_z-dsySyWFpmMJOw,1781
41
+ constellation_node_sdk/transport/tenant.py,sha256=YJxAZ6aLoXt7WHy4rV01VEaaon90FPEVSI9B6s7Vcpo,2427
42
+ constellation_node_sdk-1.0.1.dist-info/METADATA,sha256=obHcV6S2efypObELHH0nlj0jK2GzYcKMmlHjD2bo4yA,4220
43
+ constellation_node_sdk-1.0.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
44
+ constellation_node_sdk-1.0.1.dist-info/top_level.txt,sha256=JBNbpbcgca5h--BJwxqRpIXRW4VCWqufmj_djAZI6Fk,23
45
+ constellation_node_sdk-1.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ constellation_node_sdk