fix(v2): CRITICAL -- deterministic tie-break in Top10 selection (real duplicate-send root cause)

Found running the real double-send idempotency proof the mission
explicitly required: two executions seconds apart, essentially unchanged
GLPI data, produced TWO DIFFERENT Top10 compositions -- and since the
idempotency guard is keyed on the exact message hash, both were treated
as legitimately different digests and BOTH sent real Slack messages.

Root cause: logic/diversity.js's selectTop() sorted candidates by score
descending using Array.sort, which is stable but has no secondary key.
Many real candidates tie on score (e.g. several ACTIONABLE_NOW tickets
at 260 or 247.6 points), so ties were broken purely by whatever order
$input.all() happened to deliver items in -- and n8n does NOT guarantee
relative item order across parallel branches that re-merge (documented
elsewhere in this project's history: cache/ia/fallback classification
branches converge at Merge - Consolidar Classificacoes before reaching
here). Different arrival order -> different tie-break -> different
Top10 -> different message -> both "different enough" to bypass
idempotency, even though nothing meaningful changed in GLPI.

Fixed with a deterministic secondary sort key (ticket.id, ascending)
that only applies when scores are exactly equal -- doesn't change WHO
wins (still purely score-driven), only makes ties reproducible run to
run. This is a determinism fix, not a ranking/weight change (mission
section 21 constraint respected).

Added a regression test that reproduces the exact real-world scenario:
the same 12 candidates (all tied at score 200), fed in two different
orders, must produce the identical Top10.

171/171 tests passing.

Two real Slack messages were sent during this investigation (with the
user's standing test authorization) before the fix landed -- both
correctly formatted, no attribution footer, real diverse next_action
values, no literal backslash-n. Reverted SEND_ENABLED to false and
deactivated the workflow immediately after discovering the root cause.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Gustavo
2026-08-24 21:59:29 -03:00
co-authored by Claude Sonnet 5
parent 124516e64a
commit b11fcb8a7f
3 changed files with 36 additions and 2 deletions
File diff suppressed because one or more lines are too long