Skip to content

service-automation: a map node inside a loop body runs its collection ONCE — iterations 2..n do nothing, report success, and the run completes green #15616

Description

@os-warren

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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions