# cm_invite_pending — утечка указателей и corruption при destroy

## Корень проблемы

У `cm_invite_pending` три внешних держателя указателя в разных подсистемах:

  uasync heap (overall_timer) → node->arg = inv
  NCD (node_conn_direct)     → arg = inv в cm_invite_ncd_callback
  STCP (stcp_link)           → arg = inv в cm_tcp_ready_cb

Когда cm_invite_cancel делает u_free(inv), коллбэки от уже запланированных
событий приходят после освобождения → пишут в переиспользованную память →
corruption (float-значения в полях mgr/node_id).

## Решение

1. Поле uint8_t cancelled:1 в struct cm_invite_pending
   (рядом с tcp_ready:1 в conn_mgr_priv.h)

2. cm_invite_cancel:
   - inv->cancelled = 1 первой же строкой
   - отмена таймера, закрытие ncd/tcp handles
   - удаление из invite_list
   - uasync_post(ua, cm_invite_free_deferred, inv) вместо немедленного u_free

3. Все коллбэки — guard первой строкой:
   void cm_invite_ncd_callback(..., void* arg) {
       struct cm_invite_pending* inv = arg;
       if (inv->cancelled) return;
       ...
   }

4. cm_invite_fail:
   - if (inv->cancelled) return
   - сохранить cb/handle/node_id/group_id ДО вызова cm_invite_cancel
   - cm_invite_cancel(inv)  (cancelled=1, deferred free)
   - cb(handle, ...)        (коллбэк после cancel, inv ещё жив)
   - u_free(handle)

## Почему корректно

  cm_invite_cancel:
    inv->cancelled = 1       ← блокировка коллбэков (однопоточно, немедленно)
    uasync_cancel_timeout    ← таймер удалён из heap
    node_conn_direct_close   ← handle закрыт
    uasync_post(free, inv)   ← освобождение в конец очереди событий

  [уже запланированные коллбэки]:
    if (inv->cancelled) return  ← выход без действий

  [deferred free]:
    u_free(inv)                 ← все коллбэки уже отработали

## Краевые случаи

  conn_mgr_destroy → cm_invite_cancel:
    Отмена таймера, закрытие handles, deferred free. Ни один коллбэк
    не дёрнет освобождённую память.

  NCD/STCP коллбэк после cancel:
    cancelled=1 → мгновенный return.

  Таймаут приглашения после cancel:
    cancelled=1 → return, не вызывает cm_invite_fail повторно.

  uasync_destroy до deferred free:
    inv утекает при shutdown (приемлемо).
