Event Contract: pin.identity.company-upserted (CompanyUpsertedKafkaEvent)
Версионируемый контракт интеграционного события «компания создана/обновлена». Consumer-документы
ссылаются сюда, не переописывая payload. Покрывает оба минимальных события — company-created и
company-updated: создание отличается тем, что это первое событие агрегата (Version = 1,
потребитель видит CompanyId впервые); отдельного «created»-контракта нет, чтобы не иметь два
источника одинакового snapshot-а.
Назначение
Фиксирует создание CompanyEntity (через admin API PIN, SEED или синхронизацию из Talent) и каждое
значимое обновление её публичных полей (Name/Status/Requisites/LogoFileId/Slug). Событие
публичное: даёт downstream-контекстам snapshot компании без синхронного запроса в identity —
карточка застройщика в каталоге, компании участников сделок в CRM, владельцы правил вознаграждений,
авторы-компании постов.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.identity.company-upserted |
| Topic | pin.identity.companies.v1 — общий с company-suspended; порядок событий одной компании гарантирован общим ключом (CompanyId) |
| Partitions / key | 3 (компаний тысячи) / key = CompanyId |
| ContractVersion | 1 |
| Producer | identity: операции компаний в admin API, синхронизация из Talent (talent-identity-sync) |
| Статус | draft |
Событие публикуется атомарно со сменой состояния компании: до фиксации изменения событие не отправляется, после фиксации — доставляется гарантированно (at-least-once).
Payload CompanyUpsertedKafkaEvent
Полный snapshot публичных display-полей. Дом полей — entity: CompanyEntity;
здесь перечислено только то, что реально в событии.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
CompanyId | Guid | Да | CompanyEntity.Id | Immutable | Ключ агрегата (= Kafka key). Для Talent-реплик равен TenantId Talent. |
Type | string (enum CompanyType) | Да | CompanyEntity.Type | Immutable | Developer / Agency / Broker / Partner / Pin. Иммутабельно (INV-4, см. company.md). |
Status | string (enum CompanyStatus) | Да | CompanyEntity.Status | Additive only (новые значения enum) | Pending / Active / Suspended / Archived. |
Name | string | Да | CompanyEntity.Name | Additive only | Отображаемое название (≤ 256). |
Slug | string | Да | CompanyEntity.Slug | Additive only | Адрес workspace (^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$). |
LogoFileId | Guid? | Нет | CompanyEntity.LogoFileId | Additive only | Файл логотипа во внешнем хранилище (пресайн — граница storage/CDN). |
Requisites | CompanyRequisitesSnapshot? | Нет | CompanyEntity.Requisites | Additive only | Юридические реквизиты (см. вложенную модель). |
SyncSource | string (enum CompanySyncSource) | Да | CompanyEntity.SyncSource | Immutable | Pin / TalentIdentity — потребитель понимает, реплика это или локальная компания. |
Version | long | Да | CompanyEntity.Version | Immutable semantics | Доменная версия агрегата; монотонный guard у потребителей (событие с меньшей версией — skip). Version = 1 ⇔ создание компании. |
Вложенная модель CompanyRequisitesSnapshot
| Поле | Тип | Обязательность | Ограничения | Описание |
|---|---|---|---|---|
LegalName | string? | Нет | ≤ 512 | Юридическое наименование. |
Inn | string? | Нет | ^\d{10}(\d{2})?$ | ИНН (10 — юрлицо, 12 — ИП). |
Kpp | string? | Нет | ^\d{9}$ | КПП. |
Ogrn | string? | Нет | ^\d{13}(\d{2})?$ | ОГРН/ОГРНИП. |
LegalAddress | string? | Нет | ≤ 512 | Юридический адрес. |
Когда публикуется
| Условие | Переход состояния | Публикация |
|---|---|---|
Создание компании (admin API identity, SEED-миграция компании ПИН, синхронизация из Talent) | [*] → Pending → Active (или сразу Suspended/Archived по snapshot Talent) | атомарно со сменой состояния агрегата |
Значимое обновление: Name / LogoFileId / Requisites / Slug и другие поля snapshot-а | без перехода | та же |
Возобновление (Suspended → Active) и архивация (→ Archived) | Suspended → Active / `Active | Suspended → Archived` |
Потребители (consumers)
Сверено с service-boundaries.md («Владение данными»: catalog/crm/rewards/news читают identity).
| Consumer-контекст | Эффект | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
catalog | Проекция «Застройщик» витрины: название/лого/статус; Suspended/Archived скрывает бренд-страницу | MessageId + guard по Version | < 1 мин |
crm | Реплика компании (стороны сделки/фиксации): название, тип, статус | то же | < 1 мин |
rewards | Справочник компаний для правил вознаграждений и комиссионной политики | то же | < 5 мин |
news | Автор-компания постов: название/лого; доступные категории — по Type | то же | < 5 мин |
Порядок и согласованность
- Полный snapshot — замещение, не слияние. Событие несёт полный набор публичных display-полей;
потребитель замещает свою реплику целиком, а не мёржит дельты. Отсутствие optional-поля
(
LogoFileId,Requisites) в снапшоте означает «значение очищено», а не «оставить прежнее». - First-seen = создание. Upsert для ранее неизвестного
CompanyIdсоздаёт реплику, для известного — обновляет; поведение детерминированоCompanyId(idempotent upsert). - Порядок per-company. Ключ
CompanyIdв общем с company-suspended топике даёт строгий порядок; потребитель держитVersion-guard, событие с меньшей версией — skip. Повтор доставки (at-least-once) — идемпотентный no-op.
Версионирование и совместимость
- Версия — один
ContractVersion = 1; consumer при несоответствии версии события логирует warning и продолжает (без upcasting/миграции схемы). Топик кодирует версию конвенцией суффикса.v1. - Additive-only для minor: новые поля добавляются как optional с безопасным default — без bump.
- Breaking (удаление/переименование/смена типа/смысла поля) — новый
ContractVersion = 2+ новый топикpin.identity.companies.v2+ dual-publish на период миграции + deadline миграции consumers + sunset.v1. - Unknown enum (
Type/Status/SyncSource) на стороне потребителя — fallback: лог warning + сохранение строкового значения без изменений (не терять запись), поведение — как для наиболее ограничивающего статуса (Suspended). - Deprecated-версия — sunset через 2 релиза после миграции последнего потребителя.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
CompanyId, Type, Status, Slug, Version, SyncSource | Public | include |
Name, LogoFileId | Public | include |
Requisites.* | Internal (юрданные, не PII физлица) | include; в публичные проекции каталога потребителями не отдаются (правило company.md) |
Событие НЕ содержит secrets, tokens, PII физлиц, WorkspaceSettings (внутренние настройки
workspace — не публикуются).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (2): Entity (Replica): UserDisplayReplicaEntity, Entity / Aggregate: AuthorReplica + CompanyReplica
- События (3): События сервиса news: карта топиков и отклонённые события/консюмеры, Event Contract: pin.chessboard.developer-upserted (DeveloperUpsertedKafkaEvent), Event Contract: pin.identity.company-suspended (CompanySuspendedKafkaEvent)
- Консюмеры (4): Consumer: Identity Companies (pin-chessboard-identity-companies), Consumer: identity.companies.v1 → CompanyReplica, Consumer: Talent Identity Sync (pin-identity-talent-sync), Consumer: Wallet Identity Display Sync (wallet-identity-users)
- Фоновые задачи (1): Background Job: talent-initial-import (первичная загрузка / bootstrap из Talent Identity)
- index.md (2): Identity Service (Pin.Identity), Wallet Service
- registries (1): Реестр событий PIN
Связанные документы
- Источник: talent-identity-sync (синк) и admin API компаний (создание Agency/Broker/Partner — README identity, раздел API).
- Сущность: company.md; соседнее событие того же топика: company-suspended.md.