Перейти к основному содержимому

Event Contract: pin.referral.bond-closed (ReferralBondClosedKafkaEvent)

Версионируемый контракт интеграционного Kafka-события «реферальная связь закрыта». Consumer-документы ссылаются сюда, не переописывая payload. Дом полей агрегата — referral-bond.md; здесь перечислено только то, что реально входит в событие. Пара к bond-created.md — второе produced-событие топика pin.referral.bonds.v1.

Назначение

Фиксирует закрытие действующей ReferralBondEntity admin-коррекцией: переход Active → Replaced (перепривязка, rebind-bond.md, правило R6) или Active → Revoked (отзыв, revoke-bond.md). Оба — терминальные состояния (INV-REF-B6); автоматическая перепривязка отключена (решение № 3 README.md) — связь закрывается только вручную через админ-панель (ADR-0053). Даёт downstream-контекстам сигнал закрытия без синхронного запроса в referralrewards прекращает создавать новые начисления по BondId, уже созданные сохраняются (consumer — referral-bond.md; решение № 7: referral владеет фактом связи, rewards — деньгами).

Metadata

ПолеЗначение
Event typepin.referral.bond-closed
C# typeReferralBondClosedKafkaEvent
Topicpin.referral.bonds.v1 (общий с ReferralBondCreatedKafkaEvent — общий key = строгий порядок created → closed per-приглашённый)
Partitions / key6 partitions (~1 млн связей на горизонте, ADR-0056) / key = ReferredUserId
ContractVersion1
Producerreferral: admin-rebind (rebind-bond.md, R6 → Replaced), admin-revoke (revoke-bond.mdRevoked) — админ-панель (ADR-0053)
Статусdraft

Метаданные события: идентификатор сообщения (Guid v7, дедупликация), время, версия (= ContractVersion), AggregateId = BondId, AggregateType = "ReferralBond". Корреляция трейса и actor admin-коррекции восстанавливаются из метаданных. Публикуется атомарно со сменой состояния агрегата.

Payload ReferralBondClosedKafkaEvent

Snapshot-поля ссылаются на дом ReferralBondEntity (раздел «Доменные и интеграционные события», enum ReferralBondStatus); здесь — только то, что реально в событии.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
BondIdGuidДаReferralBondEntity.IdImmutableИдентификатор закрываемой связи (= AggregateId); rewards останавливает начисления по нему.
ReferredUserIdGuidДаReferralBondEntity.ReferredUserIdImmutableПриглашённый пользователь («кого привели»); = Kafka key (упорядочивает created → closed per-приглашённый).
Statusstring (enum ReferralBondStatus)ДаReferralBondEntity.StatusAdditive only (новые значения)Терминальный статус закрытия: Replaced (admin-rebind) или Revoked (admin-revoke). Дом enum — referral-bond.md.
ReplacedByBondIdGuid?Да при Status = Replaced (иначе null)ReferralBondEntity.ReplacedByBondIdImmutableСвязь-замена (цепочка admin-коррекций); заполнен только при Replaced. null при Revoked.
RevokeReasonstring?Да при Status = Revoked (иначе null)ReferralBondEntity.RevokeReasonAdditive onlyПричина отзыва без PII (фрод / ошибка привязки / претензия), ≤ 512; заполнен только при Revoked. Правило PII — раздел «Безопасность».
VersionlongДаReferralBondEntity.VersionImmutable semanticsМонотонная доменная версия связи после закрытия; guard потребителей (per-BondId).

Вложенных моделей нет — все поля скалярные. ФИО/телефон участников, суммы вознаграждений и slug ссылки в payload не входят (PII-минимизация и границы: денег в referral нет — зона rewards).

Когда публикуется

УсловиеПереход состоянияСогласованность
Admin-rebind (rebind-bond.md, правило R6): закрытие старой связи одновременно с созданием заменыActive → Replaced (INV-REF-B4; ReplacedByBondId заполнен)замена старой связи + создание новой + журнал Rebind + оба события (closed + created) — в одном согласованном изменении
Admin-revoke (revoke-bond.md): отзыв связиActive → Revoked (INV-REF-B5; RevokeReason обязателен)отзыв + closed-событие — в одном согласованном изменении

Только переход из Active; Replaced/Revoked — терминальны (INV-REF-B6, обратных переходов нет). Восстановление связи — это новая связь admin-коррекцией (отдельное bond-created.md). При rebind старая и новая связь делят один key ReferredUserId, поэтому потребитель получает closed (остановка старого BondId) и created (запуск нового BondId) строго упорядоченно.

Потребители (consumers)

Сверено с referral-bond.md («Потребители pin.referral.bonds.v1») и service-boundaries.md.

Consumer-контекстЭффект (событие → что меняется)ИдемпотентностьЗадержка (SLA)
rewardsПомечает реплику связи закрытой (Status) — новые начисления по BondId не создаются; уже созданные начисления сохраняются (не отменяются и не пересчитываются; отзыв по фроду — только ручная отмена оператором ПИН). Consumer — referral-bond.mdидентификатор сообщения + BondId + Version-guard (per-BondId)< 5 мин
notifications (опц.)Внутренний аудит-канал admin-коррекций связей; пользовательских СМС на закрытие связи нетидентификатор сообщения (повтор — no-op)< 5 мин

crm/messenger этот контракт не потребляют: карточка лида и контакт не отзываются событием закрытия связи (их lifecycle — собственные контексты).

Окно согласованности: закрытие фиксируется в referral атомарно (переход состояния + событие), остановка новых начислений в rewards — в пределах SLA (< 5 мин). В интервале между коммитом и обработкой rewards может создать ещё одно начисление по старому BondId — оно, как и все ранее созданные, сохраняется (автоматической отмены по закрытию связи нет; отзыв по фроду — только явная ручная отмена оператором ПИН, регламент — referral-bond.md), referral его не откатывает. При rebind потребитель получает closed (старый BondId) и created (новый) строго упорядоченно (общий key ReferredUserId) — реплика rewards не окажется одновременно с двумя активными связями одного приглашённого.

Гарантии, на которые может опереться потребитель:

  • На один BondId приходит не более одного closed: Replaced/Revoked терминальны (INV-REF-B6), обратных переходов нет — событие можно трактовать как финальную остановку по BondId.
  • Конкурентные admin-коррекции одной связи сериализуются: закрытие проходит только из Active, проигравшая операция получает REFERRAL_BOND_INVALID_TRANSITION и события не публикует — потребитель не увидит двух closed по одному переходу.
  • При rebind closed (старый BondId) и created (новый) делят ключ ReferredUserId — потребитель никогда не окажется с двумя активными связями одного приглашённого.

Версионирование и совместимость

  • Версия — один ContractVersion = 1; при несовпадении версии потребитель логирует warning и продолжает (upcasting/schema-migration нет).
  • Additive-only для minor: новые optional-поля с безопасным default — без bump.
  • Breaking (удаление/переименование/смена типа) — ContractVersion = 2 + новый топик pin.referral.bonds.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1 (единая эволюция с ReferralBondCreatedKafkaEvent — общий топик).
  • Unknown Status → warning + fail-closed: closed-событие по определению означает, что связь больше не активна, поэтому безопасное поведение — остановить начисления по BondId.
  • Порядок per-ReferredUserId гарантирован key-ом; потребители обязаны держать Version-guard per-BondId — события created и closed одного топика упорядочены между собой.

Безопасность

ПолеКласс данныхПравило
BondId, ReferredUserId, ReplacedByBondId, VersionInternalinclude
StatusInternalinclude
RevokeReasonInternal (free-text; PII запрещён политикой)include с валидацией «без PII»; producer (админ-панель) не вносит ФИО/телефон/email — рекомендуется контролируемый справочник причин; в логах — короткая причина/код, при подозрении на PII маскируется

Событие НЕ содержит secrets, tokens, PII (ФИО/телефон/email), сумм вознаграждений (деньги — зона rewards), slug ссылки. Доступ к топику — m2m ACL Kafka (rewards, опц. notifications). Trusted/untrusted boundary: RevokeReason — единственное free-text поле; источник — доверенная админ-панель (ADR-0053), но валидация «без PII» обязательна на producer-стороне.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы

  • Источник: агрегат referral-bond.md (enum ReferralBondStatus, переходы Active → Replaced/Revoked, INV-REF-B4/B5/B6); producer-операции: rebind-bond.md (замена), revoke-bond.md (отзыв).
  • Соседний контракт топика: bond-created.md (ReferralBondCreatedKafkaEvent — создание связи, Active) — пара produced-событий pin.referral.bonds.v1.
  • Правила коррекций: решение № 3 README.md (авто-rebind отключён), ADR-0053 (админ-панель).
  • Потребители: rewards — consumer referral-bond.md (закрытие реплики: новые начисления прекращаются, созданные — referral-accrual.md — сохраняются); docs/06-services/notifications/README.md.