Comparison — two arms, one harness
Two scheduler ticks are forced to overlap over one due-set: both observe it before either records completion, held by an explicit barrier rather than by sleeping and hoping. The same fixtures, the same fake carrier receiver, one business identity. The receiver counts what it saw.
Why the n8n arm duplicates
Its execution is four steps in a row: read the due-set, decide eligibility, send, then write the result back. Nothing holds a transaction across them, so the second execution reads the same due-set before the first has written anything to it. Both executions see an approach that looks eligible, both decide it is, and both send. The eligibility rule was correct — it was just evaluated against state that had already changed by the time the send happened. The email cannot be recalled, and the market is blocked.
What the Python arm does instead
It claims the approach inside one transaction, selecting the due row with SELECT … FOR UPDATE SKIP LOCKED so a second scheduler skips it rather than queuing behind it, and it evaluates every eligibility rule inside that transaction — already approached, declined within the cooling period, held by another broker, outside placing authority. A rule evaluated before the claim is a rule evaluated against state that may already have changed. A unique constraint on (placement, market, stage) is the backstop, not the plan.
The claim is at most one accepted business approach, not exactly-once delivery. Email is not exactly-once and this console never says it is: a send that fails is retried and a receiver that fails to acknowledge may see it twice. What is measured here is the number of approaches the receiver accepted for one identity.