format_utils._to_json_schema silently returns None for bare dict / Optional[dict] (and bare list)
Summary
iii.format_utils._to_json_schema returns None for a bare dict annotation, and for Optional[dict]. Parameterized or typing-alias forms work. The failure is silent: the schema for the parameter comes out empty, so a capability/worker author typing a parameter Optional[dict] gets no schema for that parameter at all instead of {"type": ["object", "null"]}.
The same defect class also affects bare list and Optional[list] (see repro table).
Root cause
sdk/packages/python/iii/src/iii/format_utils.py, _to_json_schema (line 42):
origin = get_origin(annotation)
...
if origin is list: # line 66
...
if origin is dict: # line 76For a bare class (dict, list), typing.get_origin returns None — the class itself is not a parameterized alias — so neither branch fires and control falls through to return None. typing.Dict / typing.List work even unparameterized because they are generic aliases and carry origin is dict / origin is list.
I confirmed the current main branch carries the identical keying (if origin is dict: at line 76 of sdk/packages/python/iii/src/iii/format_utils.py as of 2026-09-17).
Repro
iii-sdk 0.20.0, Python 3.14.5:
import typing
from iii import format_utils as fu
for ann in (typing.Optional[dict], dict,
typing.Optional[dict[str, typing.Any]], typing.Optional[typing.Dict],
list, typing.Optional[list]):
print(repr(ann), '->', fu._to_json_schema(ann))Output:
dict | None -> None # WRONG — want {"type": ["object", "null"]}
<class 'dict'> -> None # WRONG — want {"type": "object"}
dict[str, typing.Any] | None -> {'type': ['object', 'null']}
typing.Dict | None -> {'type': ['object', 'null']}
<class 'list'> -> None # WRONG — want {"type": "array"}
list | None -> None # WRONG — want {"type": ["array", "null"]}Impact
Any module author who types a parameter Optional[dict] (the natural way to write "an optional object") gets a silently-empty output schema for that parameter instead of {type: [object, null]}. In the RapidEngine conductor's rapid packaging (which consumes _to_json_schema), _schema_for(Optional[dict]) emits {} — the parameter vanishes from the module descriptor with no warning at generation time.
Suggested fix
Key the branches on the class, not the alias origin — e.g.:
origin = get_origin(annotation)
if origin is list or annotation is list: ...
if origin is dict or annotation is dict: ...(or normalize bare list/dict to their parameterized typing aliases before the origin checks — that also preserves additionalProperties handling for parameterized forms).
Bug observed in iii-sdk 0.20.0; I verified format_utils.py is byte-identical in the 0.23.0 wheel (159 lines, both versions) and the current main branch still keys if origin is dict:.
Environment: iii-sdk 0.20.0 (byte-identical in 0.23.0 wheel and on main), Python 3.14.5, macOS (darwin 25.4).
Source: iii-hq/iii