mirror of
https://github.com/github/spec-kit.git
synced 2026-07-03 12:28:06 +08:00
fix(workflows): reject a fan-in wait_for that names an unknown step at validation (#3225)
* fix(workflows): reject a fan-in wait_for that names an unknown step at validation * fix(workflows): reject fan-in wait_for self-reference and non-string entries Address review feedback on the fan-in wait_for validator: - A fan-in's own id is added to seen_ids before the wait_for check, so `wait_for: [<self>]` passed validation while producing a silent empty join at runtime. Reject self-references explicitly. - Non-string entries (e.g. YAML `wait_for: [123]`) were skipped by the isinstance(str) guard and validated even though they can never match a real step id. Flag them as wiring errors. Add coverage for both cases.
This commit is contained in:
@@ -296,6 +296,40 @@ def _validate_steps(
|
||||
f"boolean, got {type(coe).__name__}."
|
||||
)
|
||||
|
||||
# Fan-in: every wait_for id must reference a step declared at or before
|
||||
# this point. An id not yet seen is either a typo (unknown step) or a
|
||||
# forward reference (the target runs after this fan-in, so its results
|
||||
# cannot exist yet) — both are wiring errors that previously surfaced as
|
||||
# a silent empty result + COMPLETED. A step that is declared but only
|
||||
# conditionally executed (e.g. inside an if/switch branch) is still
|
||||
# "seen" here, so a legitimately-empty result at runtime stays valid.
|
||||
if step_type == "fan-in":
|
||||
wait_for = step_config.get("wait_for")
|
||||
if isinstance(wait_for, list):
|
||||
for wid in wait_for:
|
||||
if not isinstance(wid, str):
|
||||
# A non-string entry (e.g. YAML `wait_for: [123]`) can
|
||||
# never match a real step id, so the join is silently
|
||||
# empty at runtime — surface it as a wiring error.
|
||||
errors.append(
|
||||
f"Fan-in step {step_id!r}: 'wait_for' entries must "
|
||||
f"be step-id strings, got {type(wid).__name__} "
|
||||
f"({wid!r})."
|
||||
)
|
||||
elif wid == step_id:
|
||||
# The fan-in's own id is already in seen_ids by now, so
|
||||
# a self-reference would pass the membership check below
|
||||
# while still producing an empty join at runtime.
|
||||
errors.append(
|
||||
f"Fan-in step {step_id!r}: 'wait_for' references "
|
||||
f"itself; a fan-in cannot wait for its own results."
|
||||
)
|
||||
elif wid not in seen_ids:
|
||||
errors.append(
|
||||
f"Fan-in step {step_id!r}: 'wait_for' references "
|
||||
f"unknown or not-yet-declared step id {wid!r}."
|
||||
)
|
||||
|
||||
# Recursively validate nested steps
|
||||
for nested_key in ("then", "else", "steps"):
|
||||
nested = step_config.get(nested_key)
|
||||
|
||||
Reference in New Issue
Block a user