Skip to content

Commit 047e784

Browse files
committed
fix(sdk): prevent ORB scheduler config dict from being absorbed as string override
SDKConfig.from_dict maps known dataclass field names from the input dict. The top-level "scheduler" key in an ORB config.json is a nested config object ({"type": "hostfactory", "config_root": "..."}), not the plain string that SDKConfig.scheduler expects (e.g. "hostfactory"). When from_file() parsed a real config.json the dict was stored in SDKConfig.scheduler, the truthy dict triggered the override branch in ORBClient.initialize(), and ConfigurationManager.override_scheduler_strategy stored the dict. Downstream, _create_scheduler_strategy() retrieved the dict as scheduler_type and passed it to SchedulerRegistry.create_strategy() which attempted to use it as a dict key, raising "unhashable type: 'dict'", surfacing as FactoryError on TemplateConfigurationPort in all 32 test_sdk_full_cycle_default and 7 test_cleanup_e2e live tests. Fix: when from_dict encounters a "scheduler" value that is a dict, treat it as an opaque ORB config object (move to custom_config) rather than setting the string-override field. Plain string values ("default", "hostfactory") continue to work unchanged. Adds two regression tests to test_sdk_config.py.
1 parent ff9cb25 commit 047e784

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

src/orb/sdk/config.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,19 @@ def from_dict(cls, config: dict[str, Any]) -> "SDKConfig":
7070

7171
for key, value in config.items():
7272
if key in known_fields:
73-
sdk_config[key] = value
73+
# ``scheduler`` is a plain string override in SDKConfig (e.g. "default"
74+
# or "hostfactory"). When loading from an ORB config.json the top-level
75+
# ``scheduler`` key is the full scheduler sub-config dict
76+
# ({"type": "hostfactory", "config_root": "..."}). Ingesting that dict
77+
# as the string override causes ConfigurationManager.override_scheduler_strategy
78+
# to store a dict, which then propagates as the scheduler_type into the
79+
# registry lookup and fails with "unhashable type: 'dict'".
80+
if key == "scheduler" and isinstance(value, dict):
81+
# The ORB config scheduler object is not an SDK string override;
82+
# skip it so the scheduler type is resolved from the config file.
83+
custom_config[key] = value
84+
else:
85+
sdk_config[key] = value
7486
else:
7587
custom_config[key] = value
7688

tests/unit/sdk/test_sdk_config.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,30 @@ def test_empty_dict_uses_defaults(self):
102102
config = SDKConfig.from_dict({})
103103
assert config.provider == "aws"
104104

105+
def test_scheduler_dict_not_absorbed_as_string_override(self):
106+
# Regression: when loading from an ORB config.json the top-level "scheduler" key
107+
# is a nested config dict {"type": "hostfactory", "config_root": "..."}.
108+
# from_dict must NOT ingest that dict as the SDKConfig.scheduler string override
109+
# because that propagates a dict into ConfigurationManager.override_scheduler_strategy
110+
# which then surfaces as "unhashable type: 'dict'" deep in the DI factory chain.
111+
orb_config = {
112+
"provider": {"type": "aws"},
113+
"scheduler": {"type": "hostfactory", "config_root": "$ORB_CONFIG_DIR"},
114+
}
115+
config = SDKConfig.from_dict(orb_config)
116+
# scheduler field must remain None (no string override set)
117+
assert config.scheduler is None
118+
# the original dict is preserved in custom_config so nothing is silently dropped
119+
assert config.custom_config.get("scheduler") == {
120+
"type": "hostfactory",
121+
"config_root": "$ORB_CONFIG_DIR",
122+
}
123+
124+
def test_scheduler_string_override_is_still_accepted(self):
125+
# Explicit string scheduler overrides (e.g. passed programmatically) must still work.
126+
config = SDKConfig.from_dict({"provider": "aws", "scheduler": "default"})
127+
assert config.scheduler == "default"
128+
105129

106130
class TestSDKConfigFromFile:
107131
def test_loads_json_file(self, tmp_path):

0 commit comments

Comments
 (0)