Severity: low
Found in: 2026-07-22 code review
Location
lib/dag/workflow/definition.rb:24-32 (and :119).
Failure mode
Workflow::Definition.new does not validate graph/step_types key alignment. initialize computes @hash = to_h.hash, and to_h does @step_types.fetch(id) for every graph node. A graph node missing from step_types raises a raw KeyError (not a DAG::Error) during construction; conversely, extra step_types keys for nonexistent nodes are silently dropped from to_h, so the definition fingerprint/serialization quietly ignores them. Internal producers (Builder, DefinitionEditor) always keep the two in sync, so this only bites consumers using the public constructor directly — but the failure surfaces far from the mistake, and the asymmetry (missing key = raw crash, extra key = silent ignore) is a real validation hole in a public API.
Minimal trigger (verified)
DAG::Workflow::Definition.new(graph: DAG::Graph.new.add_node(:ghost).freeze, step_types: {}) → KeyError: key not found: :ghost. And Definition.new(graph: Graph.new.freeze, step_types: {ghost: {type: :noop, config: {}}}) is accepted with to_h[:nodes] == [].
Fix direction
Validate key-set equality in initialize and raise ValidationError listing missing/extra node ids.
Severity: low
Found in: 2026-07-22 code review
Location
lib/dag/workflow/definition.rb:24-32(and:119).Failure mode
Workflow::Definition.newdoes not validate graph/step_types key alignment.initializecomputes@hash = to_h.hash, andto_hdoes@step_types.fetch(id)for every graph node. A graph node missing fromstep_typesraises a rawKeyError(not aDAG::Error) during construction; conversely, extrastep_typeskeys for nonexistent nodes are silently dropped fromto_h, so the definition fingerprint/serialization quietly ignores them. Internal producers (Builder,DefinitionEditor) always keep the two in sync, so this only bites consumers using the public constructor directly — but the failure surfaces far from the mistake, and the asymmetry (missing key = raw crash, extra key = silent ignore) is a real validation hole in a public API.Minimal trigger (verified)
DAG::Workflow::Definition.new(graph: DAG::Graph.new.add_node(:ghost).freeze, step_types: {})→KeyError: key not found: :ghost. AndDefinition.new(graph: Graph.new.freeze, step_types: {ghost: {type: :noop, config: {}}})is accepted withto_h[:nodes] == [].Fix direction
Validate key-set equality in
initializeand raiseValidationErrorlisting missing/extra node ids.