Found and driven by the Clause-② contract review of PR #15609 (card #14456) at CONTRACT_REVIEW_TIER, one layer past that PR's surface. Filed by the domain:services execution seat (session 03324ae2-0f5b-5ad2-8a2e-cf4aaff5a909). ⛔ domain:*, type and priority are triage's — this seat does not produce them.
⛔ Pre-existing. Not introduced by PR #15609 and outside its diff.
The defect
A map node in a loop body executes its collection on the first iteration only. Every later iteration runs nothing, the map step still reports success, and the run finishes completed.
Measured on the real engine: 5 iterations × 2 items ⇒ 2 child runs instead of 10; the map step is success on all five iterations; run completed; and — the part that matters for the card this was found under — failed=0.
Mechanism
map-node.ts stores ${node.id}.$mapState in the shared scope and never removes it once the collection is exhausted (variables.set(stateKey, state), ~line 212). The scope survives across loop iterations, so iterations 2..n read back a state whose started === collection.length and correctly conclude there is nothing left to start.
Control, in the same reading: the sibling key $mapItemDone is deleted (map-node.ts:132-133), while variables.delete(stateKey) returns zero hits. So the absence is a reading, not a broken grep — one key in the pair is cleaned up and the other is not.
Why this is worth a card rather than a note
Silent partial work is exactly the class #14456 exists to expose — and this instance is invisible even to the new counter. #14456 (landing as PR #15609) makes a caught per-iteration failure visible at run level via FlowRunSummary.failed. This defect produces no failure at all: nothing throws, nothing is caught, failures stays 0, and the fold that sums it reports failed=0. An operator reading the new counter is told the run was clean. It did 1/n of its work.
⇒ The two are complementary, not overlapping. Closing this one does not need the counter, and the counter cannot detect this one.
⚠️ Scope notes for whoever takes it
Refs: PR #15609 / #14456 (the review that found it) · #14414 (the adjacent parallel-in-loop attribution decision) · #13681 (the visibility rider this class sits under).
Found and driven by the Clause-② contract review of PR #15609 (card #14456) at
CONTRACT_REVIEW_TIER, one layer past that PR's surface. Filed by thedomain:servicesexecution seat (session03324ae2-0f5b-5ad2-8a2e-cf4aaff5a909). ⛔domain:*, type and priority are triage's — this seat does not produce them.⛔ Pre-existing. Not introduced by PR #15609 and outside its diff.
The defect
A
mapnode in aloopbody executes its collection on the first iteration only. Every later iteration runs nothing, the map step still reportssuccess, and the run finishescompleted.Measured on the real engine: 5 iterations × 2 items ⇒ 2 child runs instead of 10; the map step is
successon all five iterations; runcompleted; and — the part that matters for the card this was found under —failed=0.Mechanism
map-node.tsstores${node.id}.$mapStatein the shared scope and never removes it once the collection is exhausted (variables.set(stateKey, state), ~line 212). The scope survives across loop iterations, so iterations 2..n read back a state whosestarted === collection.lengthand correctly conclude there is nothing left to start.Control, in the same reading: the sibling key
$mapItemDoneis deleted (map-node.ts:132-133), whilevariables.delete(stateKey)returns zero hits. So the absence is a reading, not a broken grep — one key in the pair is cleaned up and the other is not.Why this is worth a card rather than a note
Silent partial work is exactly the class #14456 exists to expose — and this instance is invisible even to the new counter. #14456 (landing as PR #15609) makes a caught per-iteration failure visible at run level via
FlowRunSummary.failed. This defect produces no failure at all: nothing throws, nothing is caught,failuresstays 0, and the fold that sums it reportsfailed=0. An operator reading the new counter is told the run was clean. It did 1/n of its work.⇒ The two are complementary, not overlapping. Closing this one does not need the counter, and the counter cannot detect this one.
FlowRunSummary.failedfold, loop iteration throughtry_catch→runRegion,$error.iteration/$error.item,failed=on the summary line (engine half of #13681) #14456 / PR service-automation: populate the contained-failure visibility contract —FlowRunSummary.failedfold, loop iteration throughtry_catch,$error.iteration/$error.item,failed=on the summary line #15609: different mechanism, and that card is ruled and under review.Refs: PR #15609 / #14456 (the review that found it) · #14414 (the adjacent
parallel-in-loopattribution decision) · #13681 (the visibility rider this class sits under).