mgmt: federated invite redelivery defeats the #97 in-flight dedupe — armed cooldown self-declines the just-joined DM #110

Closed
opened 2026-07-12 18:59:50 +00:00 by robocub · 1 comment
Collaborator

Found by the #109 fixture on its first live run — fed_dm_invite_lifecycle, deterministic (2/2).

With mgmt_cooldown_secs armed (prod posture), a FEDERATED DM invite to the mgmt bot is double-processed and self-declined — the #97 join-then-leave mode, alive over federation:

18:44:42.386 INFO accepting DM invite from self-serve user   room=!vSjng…:nether.autos inviter=@autotest_remote:nether.autos
18:44:42.854 INFO accepting DM invite from self-serve user   (same room, same inviter — 468 ms later)
18:44:42.855 INFO invite cooldown — declining self-serve invite (#89a)
18:44:56.861 WARN room never reached joined state — skipping greeting

Second run identical (18:47:28.354 / 28.900 / 28.900 / 42.904). Artifacts: staging e2e-artifacts/109-live{,2}/fed_dm_invite_lifecycle/.

Mechanism: the alpha.25 dedupe is the invite_inflight set (mgmt.rs:1215-1231) — it guards only CONCURRENT handling and is removed when the handler finishes. Same-server Tuwunel never redelivers an invite, so that sufficed locally (exactly why 23 scenarios stayed green). Over federation the invite is redelivered ~0.5 s later (after the accept-join federates back); the in-flight entry is already gone, the second pass runs the full gate, hits the cooldown the FIRST pass just stamped, and declines → the bot leaves the room it joined half a second ago. Unarmed cooldown hides it (second pass re-accepts idempotently) — which is also why fed_link_v12's unarmed DM survived.

Fix (in flight on feat/109-federation-fixture, per operator go): idempotence, not more dedupe — before gating an invite, check the bot's CURRENT membership; if already Joined, the invite is stale/redelivered: ignore, never decline. Guarded by fed_dm_invite_lifecycle (Pass) on the fixture.

**Found by the #109 fixture on its first live run — `fed_dm_invite_lifecycle`, deterministic (2/2).** With `mgmt_cooldown_secs` armed (prod posture), a FEDERATED DM invite to the mgmt bot is double-processed and self-declined — the #97 join-then-leave mode, alive over federation: ``` 18:44:42.386 INFO accepting DM invite from self-serve user room=!vSjng…:nether.autos inviter=@autotest_remote:nether.autos 18:44:42.854 INFO accepting DM invite from self-serve user (same room, same inviter — 468 ms later) 18:44:42.855 INFO invite cooldown — declining self-serve invite (#89a) 18:44:56.861 WARN room never reached joined state — skipping greeting ``` Second run identical (18:47:28.354 / 28.900 / 28.900 / 42.904). Artifacts: staging `e2e-artifacts/109-live{,2}/fed_dm_invite_lifecycle/`. **Mechanism:** the alpha.25 dedupe is the `invite_inflight` set (mgmt.rs:1215-1231) — it guards only CONCURRENT handling and is removed when the handler finishes. Same-server Tuwunel never redelivers an invite, so that sufficed locally (exactly why 23 scenarios stayed green). Over federation the invite is redelivered ~0.5 s later (after the accept-join federates back); the in-flight entry is already gone, the second pass runs the full gate, hits the cooldown the FIRST pass just stamped, and declines → the bot leaves the room it joined half a second ago. Unarmed cooldown hides it (second pass re-accepts idempotently) — which is also why `fed_link_v12`'s unarmed DM survived. **Fix (in flight on `feat/109-federation-fixture`, per operator go):** idempotence, not more dedupe — before gating an invite, check the bot's CURRENT membership; if already Joined, the invite is stale/redelivered: ignore, never decline. Guarded by `fed_dm_invite_lifecycle` (Pass) on the fixture.
Author
Collaborator

Fixed in master 172f55f (merged 25138b1). Guard 3 in handle_invite: before gating, ask the SERVER (GET /joined_rooms — ground truth, cannot lag the raw appservice join) whether the bot is already a member; if so the "invite" is a redelivery — ignored, never declined. Fail-open on a read error (a genuinely new invite must never be silently dropped; worst case is an idempotent re-accept).

Live proof on the #109 fixture: pre-fix fed_dm_invite_lifecycle failed 2/2 with the exact double-accept→cooldown-decline signature; post-fix it passed 4/4 tonight (incl. once against a freshly-wiped fixture and once via the CI dispatch leg). Prod corroboration: the 14:45 journal shows the same double-accept ~500 ms apart from @dau's federated invite (both passes died at the tinier.town join bug, so the decline never surfaced there — but prod was exposed). Ships to prod with the next release.

✅ **Fixed in master `172f55f`** (merged `25138b1`). Guard 3 in `handle_invite`: before gating, ask the SERVER (`GET /joined_rooms` — ground truth, cannot lag the raw appservice join) whether the bot is already a member; if so the "invite" is a redelivery — ignored, never declined. Fail-open on a read error (a genuinely new invite must never be silently dropped; worst case is an idempotent re-accept). **Live proof on the #109 fixture:** pre-fix `fed_dm_invite_lifecycle` failed 2/2 with the exact double-accept→cooldown-decline signature; post-fix it passed 4/4 tonight (incl. once against a freshly-wiped fixture and once via the CI dispatch leg). Prod corroboration: the 14:45 journal shows the same double-accept ~500 ms apart from @dau's federated invite (both passes died at the tinier.town join bug, so the decline never surfaced there — but prod was exposed). Ships to prod with the next release.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
dark/nether-voicebridge#110
No description provided.