fix(graph): reject as == output_key on map nodes

A map node whose item binding shares its name with output_key would have
the binding collected as the chain's result whenever the chain never
wrote it, while the missing-writer warning stayed silent. The collision
is now a validator error attributed to the map node and, with validation
off, a runtime error in run_map before any item forks.
This commit is contained in:
Dark-Alex-17 committed 2026-09-11 10:56:28 -06:00
1 parent cce8381962
commit ccec38c5b9
2 files changed
+116

No files matched your search

+60
View File
@@ -643,6 +643,16 @@ impl GraphValidator {
let NodeType::Map(m) = &node.node_type else {
continue;
};
if m.as_name == m.output_key {
result.error(ValidationError::with_node(
map_id,
format!(
"map node '{map_id}': `as` and `output_key` are both '{}'; the item \
binding would be collected as the chain's result",
m.as_name
),
));
}
// A missing entry is reported by `validate_node_references`.
let mut members: Vec<String> = branch_subgraph(graph, &m.branch).into_iter().collect();
if members.is_empty() {
@@ -3505,6 +3515,56 @@ mod tests {
map
}
fn map_with_as(id: &str, branch: &str, next: Option<&str>, as_name: &str) -> Node {
let mut map = map_node_basic(id, branch, next);
if let NodeType::Map(ref mut mm) = map.node_type {
mm.as_name = as_name.into();
}
map
}
#[test]
fn map_as_equal_output_key_errors() {
let map = map_with_as("m", "br", Some("end"), "output");
let branch = llm_with_state_updates("br", &[("output", "{{output}}")], None);
let graph = graph_with(
vec![("m", map), ("br", branch), ("end", end_node("end"))],
"m",
);
let result = validator().validate(&graph);
assert!(
result.errors.iter().any(|e| e.message
== "map node 'm': `as` and `output_key` are both 'output'; the item binding \
would be collected as the chain's result"
&& e.node_id.as_deref() == Some("m")),
"expected as/output_key collision error: {:?}",
result.errors
);
}
#[test]
fn map_as_distinct_from_output_key_passes() {
let map = map_with_as("m", "br", Some("end"), "item");
let branch = llm_with_state_updates("br", &[("output", "{{output}}")], None);
let graph = graph_with(
vec![("m", map), ("br", branch), ("end", end_node("end"))],
"m",
);
let result = validator().validate(&graph);
assert!(
!result
.errors
.iter()
.any(|e| e.message.contains("`as` and `output_key`")),
"distinct as/output_key must not error: {:?}",
result.errors
);
}
#[test]
fn map_max_concurrency_non_template_string_errors() {
let map = map_with_cap("m", "br", Some("end"), "4");