77from typing import Any , Mapping
88from zoneinfo import ZoneInfo , ZoneInfoNotFoundError
99
10+ from quant_platform_kit .data .decision_data_binding import DecisionDataBinding
11+
1012from .live_continuity import LiveContinuity , build_live_continuity
1113from .strategy_release import StrategyReleaseIdentity , build_strategy_release_identity
1214
@@ -77,6 +79,7 @@ class RuntimeTarget:
7779 account_identity : dict [str , Any ] | None = None
7880 live_continuity : LiveContinuity | None = None
7981 execution_environment : RuntimeExecutionEnvironment | str | None = None
82+ decision_data : DecisionDataBinding | None = None
8083
8184 def __post_init__ (self ) -> None :
8285 object .__setattr__ (
@@ -102,6 +105,7 @@ def to_dict(self) -> dict[str, object]:
102105 "strategy_release" ,
103106 "account_identity" ,
104107 "live_continuity" ,
108+ "decision_data" ,
105109 ):
106110 if payload .get (field ) is None :
107111 payload .pop (field , None )
@@ -128,6 +132,25 @@ def _normalize_optional_string(value: str | None) -> str | None:
128132 return text or None
129133
130134
135+ def _build_decision_data_binding (
136+ value : DecisionDataBinding | Mapping [str , object ] | None ,
137+ ) -> DecisionDataBinding | None :
138+ """Verify the public control-plane binding before exposing it to a runtime."""
139+
140+ if value is None or isinstance (value , DecisionDataBinding ):
141+ return value
142+ if not isinstance (value , Mapping ):
143+ raise ValueError ("decision_data must be a DecisionDataBinding or object" )
144+ payload = dict (value )
145+ claimed_digest = str (payload .pop ("binding_sha256" , "" ) or "" ).strip ().lower ()
146+ if claimed_digest .startswith ("sha256:" ):
147+ claimed_digest = claimed_digest .removeprefix ("sha256:" )
148+ binding = DecisionDataBinding .from_dict (payload )
149+ if claimed_digest and claimed_digest != binding .binding_sha256 :
150+ raise ValueError ("decision_data.binding_sha256 does not match the binding" )
151+ return binding
152+
153+
131154def _normalize_account_selector (value : Iterable [str ] | str | None ) -> tuple [str , ...]:
132155 if value is None :
133156 return ()
@@ -183,6 +206,7 @@ def build_runtime_target(
183206 account_identity : Mapping [str , Any ] | None = None ,
184207 execution_environment : RuntimeExecutionEnvironment | str | None = None ,
185208 live_continuity : LiveContinuity | Mapping [str , object ] | None = None ,
209+ decision_data : DecisionDataBinding | Mapping [str , object ] | None = None ,
186210 continuity_fingerprint_payload : Mapping [str , Any ] | None = None ,
187211) -> RuntimeTarget :
188212 normalized_market , normalized_calendar , normalized_timezone = _normalize_market_metadata (
@@ -210,6 +234,7 @@ def build_runtime_target(
210234 ),
211235 account_identity = dict (account_identity ) if account_identity is not None else None ,
212236 execution_environment = execution_environment ,
237+ decision_data = _build_decision_data_binding (decision_data ),
213238 )
214239 if live_continuity is None :
215240 return target
@@ -295,6 +320,9 @@ def resolve_runtime_target_from_env(
295320 live_continuity = payload .get ("live_continuity" )
296321 if live_continuity is not None and not isinstance (live_continuity , dict ):
297322 raise ValueError ("RUNTIME_TARGET_JSON.live_continuity must be an object when present" )
323+ decision_data = payload .get ("decision_data" )
324+ if decision_data is not None and not isinstance (decision_data , dict ):
325+ raise ValueError ("RUNTIME_TARGET_JSON.decision_data must be an object when present" )
298326
299327 return build_runtime_target (
300328 platform_id = resolved_platform_id ,
@@ -313,5 +341,6 @@ def resolve_runtime_target_from_env(
313341 account_identity = account_identity ,
314342 execution_environment = payload .get ("execution_environment" ),
315343 live_continuity = live_continuity ,
344+ decision_data = decision_data ,
316345 continuity_fingerprint_payload = payload ,
317346 )
0 commit comments