`FanInStep.execute` already guards a non-list `wait_for` (#3482), and the
engine's load-time validation rejects non-string entries. But the engine does
not auto-validate step config, so on an unvalidated run `execute` iterated the
list's *elements* raw:
- An unhashable entry (a list/dict from a YAML indentation slip like
`wait_for: [[a, b]]`) crashed the whole run at `context.steps.get(entry, ...)`
with a raw `TypeError: cannot use 'list' as a dict key`.
- A hashable-but-non-string entry (`wait_for: [123]`) silently joined an empty
`{}` and still reported COMPLETED — the exact "silent empty result +
COMPLETED" wiring bug the whole-list guard and the engine's fan-in
validation both exist to prevent.
Extend the execute() guard to reject any non-string entry with the engine's
"entries must be step-id strings" phrasing, mirroring the sibling non-list
guard right above it. Adds regression coverage for unhashable and
hashable-non-string entries.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>