Event Contract: pin.referral.bond-created (ReferralBondCreatedKafkaEvent)
Версионируемый контракт интеграционного Kafka-события «реферальная связь создана». Consumer-документы ссылаются сюда, не переописывая payload. Дом полей агрегата — referral-bond.md; здесь перечислено только то, что реально входит в событие.
Назначение
Фиксирует создание ReferralBondEntity ([*] → Active) — факта «кто
привёл кого, когда и через какую ссылку» по правилам привязки
(attach-rules.md, правила R1/R2/R6). Даёт downstream-контекстам основание
без синхронного запроса в referral: rewards строит реплику связи и будущие начисления
вознаграждений по BondId (решение № 7 README.md: referral — факт связи, rewards —
деньги), notifications отправляет рефереру СМС «по вашей ссылке зарегистрировался пользователь»,
messenger создаёт контакт «агент ↔ инвестор» (поток «Реферальная привязка»,
service-boundaries.md).
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.referral.bond-created |
| C# type | ReferralBondCreatedKafkaEvent |
| Topic | pin.referral.bonds.v1 (общий с ReferralBondClosedKafkaEvent — общий key = строгий порядок событий per-приглашённый) |
| Partitions / key | 6 partitions (~1 млн связей на горизонте, ADR-0056) / key = ReferredUserId |
| ContractVersion | 1 |
| Producer | referral: consumer T1 user-upserted.md (правило R1), consumer T2 user-signed-in.md (правило R2), admin-коррекция rebind-bond.md (правило R6) |
| Статус | draft |
Метаданные события: идентификатор сообщения (Guid v7, дедупликация), время, версия (= ContractVersion),
AggregateId = BondId, AggregateType = "ReferralBond". Корреляция трейса восстанавливается из
метаданных. Публикуется атомарно с созданием связи и журнальной записи.
Payload ReferralBondCreatedKafkaEvent
Snapshot-поля ссылаются на дом ReferralBondEntity (раздел «Доменные и
интеграционные события») и ReferralLinkEntity (Channel).
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
BondId | Guid | Да | ReferralBondEntity.Id | Immutable | Идентификатор связи; на него ссылаются начисления rewards (= AggregateId). |
ReferredUserId | Guid | Да | ReferralBondEntity.ReferredUserId | Immutable | Приглашённый пользователь («кого привели»); = Kafka key. |
ReferrerUserId | Guid | Да | ReferralBondEntity.ReferrerUserId | Immutable | Реферер («кто привёл») — владелец ссылки на момент привязки; получатель СМС notifications и участник контакта messenger. |
ReferrerCompanyId | Guid? | Нет | ReferralBondEntity.TenantId | Immutable | CompanyId реферера на момент привязки; null — реферер-инвестор (ADR-0052). |
ReferralLinkId | Guid | Да | ReferralBondEntity.ReferralLinkId | Immutable | Ссылка, через которую произошла привязка. |
Channel | string (enum ReferralChannel) | Да | ReferralLinkEntity.Channel на момент привязки | Additive only (новые значения) | Канал распространения ссылки (Direct/Telegram/…, дом enum — referral-link.md); для аналитики каналов у потребителей. |
BoundAt | DateTimeOffset | Да | ReferralBondEntity.BoundAt | Immutable | Момент привязки (= время триггер-события identity; для T3/R6 — момент операции). |
AppliedRule | string (enum ReferralAttachRule) | Да | ReferralBondEntity.AppliedRule | Additive only (новые значения) | Правило, создавшее связь: R1 (регистрация), R2 (авторизация в окне lazy-регистрации), R6 (admin-коррекция). Дом enum — attach-rules.md. |
Version | long | Да | ReferralBondEntity.Version | Immutable semantics | Монотонная доменная версия связи; guard потребителей (события created/closed одного топика упорядочены key-ом). |
Вложенных моделей нет — все поля скалярные. ФИО/телефон участников в payload не входят
(PII-минимизация): notifications и messenger берут отображаемые данные из собственных реплик
pin.identity.users.v1.
Когда публикуется
| Условие | Переход состояния | Согласованность |
|---|---|---|
| Правило R1 — регистрация с реф-ссылкой (consumer T1 user-upserted.md) | ReferralBondEntity: [*] → Active | связь + журнал Initial + событие — в одном согласованном изменении |
| Правило R2 — авторизация в окне lazy-регистрации (consumer T2 user-signed-in.md) | та же | та же |
| Правило R6 — admin-rebind (rebind-bond.md): создание связи-замены | [*] → Active (новая) одновременно со Active → Replaced (старая, INV-REF-B4) | новая связь + замена старой + журнал Rebind + оба события (created + closed) — в одном согласованном изменении |
Правила R3/R4 связь не создают — событие не публикуется (уходит только
lead-attach-requested.md). Закрытие связи (Replaced/Revoked) —
отдельный контракт ReferralBondClosedKafkaEvent того же топика (bond-closed.md).
Потребители (consumers)
Сверено с referral-bond.md («Потребители pin.referral.bonds.v1») и
service-boundaries.md.
| Consumer-контекст | Эффект (событие → что меняется) | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
rewards | Реплика связи — основание будущих реферальных начислений по BondId (consumer — referral-bond.md) | идентификатор сообщения + BondId (upsert реплики) + Version-guard | < 5 мин |
notifications | СМС/уведомление рефереру (ReferrerUserId) «по вашей ссылке зарегистрировался пользователь» | идентификатор сообщения (повтор — no-op, СМС не дублируется) | < 1 мин (SLO привязки, README.md) |
messenger | Контакт «реферер ↔ приглашённый» для будущего чата (ADR-0055) | идентификатор сообщения + business key (ReferrerUserId, ReferredUserId) | < 5 мин |
crm этот контракт не потребляет: карточка лида создаётся по отдельному событию
lead-attach-requested.md (лид возможен и без связи — правила R3/R4).
Межтопиковый порядок не гарантируется: ReferralBondCreatedKafkaEvent (pin.referral.bonds.v1) и
ReferralLeadAttachRequestedKafkaEvent (pin.referral.lead-attachments.v1) публикуются в одном коммите,
но в разные топики — взаимного порядка «связь vs лид» нет, потребители независимы. Окно согласованности:
связь существует в referral сразу (тот же коммит), поэтому getMyReferrer приглашённого возвращает
реферера немедленно после обработки триггер-события; downstream отстаёт — СМС рефереру < 1 мин, реплика
связи в rewards и контакт в messenger < 5 мин.
Гарантии, на которые может опереться потребитель:
ReferredUserId != ReferrerUserIdвсегда (INV-REF-B2): событие никогда не публикуется для самопривязки — отдельная проверка «сам себе» потребителю не нужна.- На один
BondIdприходит ровно одноcreated; последующее закрытие — отдельноеclosed(bond-closed.md) того же топика и ключа, строго послеcreated. - Потребители независимы и идемпотентны: сбой/повтор одного (
rewards/notifications/messenger) не влияет на других; дубль по идентификатору сообщения — no-op (СМС не дублируется, реплика связи и контакт апсертятся).
Версионирование и совместимость
- Версия — один
ContractVersion = 1; при несовпадении версии потребитель логирует warning и продолжает (upcasting/schema-migration нет). - Additive-only для minor: новые optional-поля с безопасным default — без bump (пример уже в
контракте:
Channel/AppliedRuleзаведены с первой версии, чтобы не расширять позже). - Breaking (удаление/переименование/смена типа) —
ContractVersion = 2+ новый топикpin.referral.bonds.v2+ dual-publish на период миграции + deadline миграции consumers + sunset.v1. - Unknown enum
Channel→ fallbackOther+ warning; unknownAppliedRule→ warning + обработка какR1(консервативно, правило attach-rules.md). - Порядок per-
ReferredUserIdгарантирован key-ом; потребители обязаны держатьVersion-guard — событияcreatedиclosedодного топика упорядочены между собой.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
BondId, ReferredUserId, ReferrerUserId, ReferrerCompanyId, ReferralLinkId, Version | Internal | include |
Channel, AppliedRule, BoundAt | Internal | include |
Событие НЕ содержит secrets, tokens, PII (ФИО/телефон/email), сумм вознаграждений (денег в referral
нет — зона rewards), slug ссылки (потребителям не нужен; резолв — по ReferralLinkId). Доступ к
топику — m2m ACL Kafka (rewards/notifications/messenger).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (4): Entity / Aggregate: NotificationEntity, Entity / Aggregate: ReferralAccrualEntity, Entity / Aggregate: ReferralBondEntity, Entity / Aggregate: ReferralRateRuleEntity
- API (1): POST /api/admin/referrals/bonds/{bondId}/rebind — коррекция связи (R6, Replace)
- Use Cases (3): UC-REF-003. Регистрация по реферальной ссылке → связь + лид (правило R1), UC-REF-004. Авторизация со ссылкой в окне lazy-регистрации → связь + лид (правило R2), UC-REF-006. Ссылка от инвестора (не агента/застройщика) → связь без лида (модификатор R5)
- События (4): Событие «Запрошена карточка лида»: pin.referral.lead-attach-requested, Event Contract: pin.referral.bond-closed (ReferralBondClosedKafkaEvent), Event Contract: pin.rewards.referral-accrual-created (ReferralAccrualCreatedKafkaEvent), Event Contract: pin.rewards.referral-accrual-status-changed (ReferralAccrualStatusChangedKafkaEvent)
- Консюмеры (4): Consumer: Referral Bond Sync (pin-rewards-referral-bonds), Consumer: Referral Events → Notifications (notifications-referral-events), Consumer: ReferralUserSignedInConsumer (pin.identity.sign-ins.v1), Consumer: ReferralUserUpsertedConsumer (pin.identity.users.v1)
- Саги (1): Saga «Commission Settlement» (финансовый контур вознаграждений)
- adr (1): ADR-0066 — удержание ПИН и реферальные ставки
- scenarios (1): UC-OVR-004. От реферальной ссылки до объекта у собственника
- registries (1): Реестр событий PIN
- index.md (1): Rewards Service
Связанные документы
- Источник: агрегат referral-bond.md (поля, INV-REF-001/B2…B6), процесс — attach-rules.md (шаги 6–7); producer-операции: user-upserted.md, user-signed-in.md, rebind-bond.md.
- Соседний контракт топика: bond-closed.md (
ReferralBondClosedKafkaEvent—Replaced/Revoked, остановка начисленийrewards). - Событие о лиде: lead-attach-requested.md.
- Потребители:
rewards— consumer referral-bond.md (реплика связей → начисления referral-accrual.md);docs/06-services/notifications/README.md,docs/06-services/messenger/README.md.