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

Модель данных

Зачем нужно

Документ собирает канонические сущности identity в одну модель, чтобы разработчик видел связи между users, auth, RBAC, family, organizations, OAuth, sessions, device bindings и actor context.

Правила

  • Каждая таблица имеет id, created_at, updated_at, если явно не указано иначе.
  • Все изменения доступа, ролей, сессий, OAuth clients, device bindings и security settings пишутся в audit.
  • Soft delete допустим для пользовательских и административных сущностей, но security-сущности должны сохранять историю.
  • Внешние идентификаторы хранятся с provider/source и не становятся внутренним id.
  • PII хранится минимально, редактируется в логах и не уходит в публичные события.
  • educator_profile и trust status не являются источником прав; доступ проверяется через roles, memberships, assignments, grants и product permissions.

Пользователь

users

Канонический аккаунт для входа.

ПолеНазначение
idвнутренний идентификатор пользователя
statusactive, blocked, pending, deleted
display_nameотображаемое имя
avatar_urlссылка на аватар
localeязык интерфейса
timezoneчасовой пояс
last_login_atпоследний успешный вход

user_emails

ПолеНазначение
user_idвладелец контакта
emailemail в нормализованном виде
is_primaryосновной email
is_verifiedподтверждён
verified_atвремя подтверждения

user_phones

Аналогично user_emails, но для телефонов в E.164.

user_field_definition / user_field_value

Кастомные поля профиля. Используются для расширения без миграции основной таблицы users.

educator_profiles

Анкета пользователя как преподавателя. Заполняется самим пользователем или администратором, используется для trust checks, контактов и подбора рабочих сценариев, но не выдаёт прав.

ПолеНазначение
user_idвладелец анкеты
display_name, contact_email, contact_phoneрабочие контактные данные
subjects, grade_rangesпредметы и классы, с которыми работает преподаватель
declared_organization_idnullable ссылка на organization, если пользователь указал организацию
declared_organization_nameтекстовая заявленная организация, если рабочей organization ещё нет
trust_statusself_declared, under_review, verified, rejected, suspended
trust_checksstructured JSON с результатами проверок
reviewed_by_user_id, reviewed_atкто и когда проверил trust profile

Правила:

  • наличие educator_profile не создаёт role assignment;
  • trust_status = verified не даёт доступ к чужим группам, официальной статистике организации или административным действиям product domain без отдельных permissions;
  • самозаявленный преподаватель может использовать анкету в Learning Workspace, но его рабочие ученики остаются learning_group_participant в LMS.

Auth

user_credentials

Хранит password hash и служебные параметры пароля.

ПолеНазначение
user_idпользователь
password_hashhash пароля
password_changed_atдата смены
must_change_passwordпринудительная смена

user_auth_methods

Привязанные методы входа.

ПолеНазначение
user_idпользователь
typepassword, totp, sms_otp, email_otp, oauth_provider
providerprovider key, если нужен
external_subjectвнешний subject/provider id
settingsmethod-specific encrypted/config data
is_enabledметод активен
is_verifiedметод подтверждён
last_used_atпоследнее использование

auth_flow_sessions

Временная серверная сессия auth flow.

ПолеНазначение
idидентификатор flow
identifieremail/phone/login
resolved_user_idнайденный пользователь
stateтекущий шаг
expires_atсрок жизни
metadataбезопасный контекст flow

verification_codes

Коды для email/phone verification, reset password, OTP.

Правила:

  • хранить hash кода, не сам код;
  • иметь purpose, transport, expires_at, attempts_count;
  • после успешного использования помечать как consumed;
  • лимитировать отправку и проверку.

user_sessions

Серверная запись пользовательской сессии.

ПолеНазначение
user_idвладелец
refresh_token_hashhash текущего refresh token
refresh_token_family_idсемейство ротации
device_idустройство
ip_address, user_agentконтекст
expires_atсрок жизни
revoked_atотзыв

saved_accounts

Локальная запись сохранённого аккаунта на устройстве. Не даёт права входа без нового auth flow.

Поля: user_id, device_fingerprint_hash, display_name_snapshot, last_used_at, expires_at, revoked_at.

device_binding

Привязка доверенного устройства к пользователю или сохранённому аккаунту.

Поля: user_id, saved_account_id, device_fingerprint_hash, status, trusted_until, last_seen_at, revoked_at.

refresh_tokens

Hash refresh token, семейство ротации и связь с user_session.

Поля: user_id, session_id, token_hash, token_family_id, rotated_from_token_id, expires_at, revoked_at.

RBAC

roles

ПолеНазначение
keyсистемный ключ роли
nameотображаемое имя
scopeglobal, organization, team
orderпорядок/уровень
is_systemсистемная роль

permissions

Формат: <domain>.<resource>.<action>[.<scope>], например identity.users.read или identity.profile.read.own. Runtime-каталог синхронизируется с packages/permissions/catalog.ts.

role_permissions

Связь many-to-many между ролями и permissions.

user_role_assignments

ПолеНазначение
user_idкому назначена роль
role_idроль
assigned_byкто назначил
expires_atвременная роль

Таблица используется только для global roles. Organization/team roles назначаются через organization_memberships.role_id и organization_team_memberships.role_id.

Family

family_groups

Семейная группа. В MVP это access-контур взрослых пользователей и детских student_profile, а не ограничительный детский режим.

Правила:

  • взрослых в семье может быть несколько;
  • детей в семье может быть несколько;
  • любой active adult семьи имеет одинаковые семейные права по всем детям семьи;
  • adult membership не даёт доступа к прогрессу, календарю, профилю, уведомлениям, покупкам и учебному контексту другого взрослого;
  • взрослый пользователь может быть самостоятельным учащимся и сохраняет self-права над собственным учебным контуром;
  • семейная группа добавляет взрослым права над детьми, но не ограничивает linked user ребёнка в MVP.

student_profiles

Учебный субъект ребёнка. Может существовать без самостоятельного user и позже быть привязан к аккаунту.

student_profile является source of truth учебного субъекта ребёнка. Linked user — auth subject для самостоятельного входа ребёнка, но не замена student_profile.

ПолеНазначение
family_group_idсемья, которая управляет профилем
linked_user_idnullable ссылка на user, если ребёнок имеет самостоятельный вход
display_nameимя в учебных сценариях
birth_dateдата рождения, если нужна для возрастных правил
grade_levelтекущий класс/уровень
statusactive, archived
created_by_user_idвзрослый, создавший профиль

Если linked_user_id задан, linked user ребёнка в MVP сохраняет обычные self-права: входить, смотреть свой прогресс и календарь, редактировать свой профиль, покупать продукт для себя и учиться под своим аккаунтом. Family adult rights не отключают эти права.

student_protected_attributes

Защищённые official attributes учебного субъекта. Используются для сценариев, где данные нельзя хранить как обычные поля продуктового домена.

ПолеНазначение
student_profile_idребёнок, к которому относится protected attribute
attribute_keysnils, official_birth_date, другой разрешённый official key
encrypted_value_refссылка на зашифрованное значение/secret storage
statusactive, needs_verification, rejected, archived
sourceparent, student, teacher_official_flow, admin, import
collected_by_user_idкто инициировал сбор
verified_by_user_id, verified_atкто и когда проверил

Official data package для ГИР не принадлежит competitions. Competitions может хранить только requirement/status/ref на эти protected attributes.

family_contact_records

Контакты взрослого, которые могут существовать до подтверждённого аккаунта родителя.

Поля: student_profile_id, optional family_group_id, email, phone, source, status, created_by_user_id, confirmed_by_user_id, confirmed_at.

Если contact record создан учителем в разрешённом official flow, он не выдаёт учителю родительские права и не создаёт active family membership.

family_contact_records не являются способом входа ребёнка через контакт родителя и не заменяют child_device_authorization.

parent_account_invites

Приглашение родителя создать аккаунт или подтвердить связь с ребёнком.

Поля: student_profile_id, family_contact_record_id, email, phone, token_hash, status, expires_at, accepted_by_user_id, accepted_at, created_by_user_id.

Статусы: created, sent, accepted, expired, revoked.

После acceptance identity создаёт или связывает user/family access по правилам family/claim flow. Pending invite не является подтверждённым родительским доступом.

parent_account_invites не являются child login flow через parent contact. Для входа linked user ребёнка на отдельном устройстве используется только child_device_authorization.

user_family_groups

Связь взрослого пользователя и семьи. В MVP таблица описывает только взрослых участников семьи.

ПолеНазначение
family_group_idсемья
user_idпользователь
roleadult
statuspending, active, suspended, removed

Linked user ребёнка не добавляется в user_family_groups как child member. Если post-MVP потребуется child membership, это решение нужно описать отдельно; source of truth учебного субъекта всё равно останется student_profile.

student_service_settings / student_service_access_settings (post-MVP)

Не входит в обязательную MVP-модель Family Group. Если после MVP потребуется родительский контроль сервисов, настройка должна описывать доступность сервиса для самого student_profile и быть family-wide, без adult_user_id.

delegated_sessions

Временное действие взрослого в контексте student_profile.

Правила:

  • actor_user_id — всегда взрослый из JWT sub;
  • subject_student_profile_id — обязательный subject;
  • subject_user_id — optional linked user ребёнка;
  • delegated session не создаёт сессию ребёнка и не используется для авторизации ребёнка на другом устройстве;
  • service-level access settings не являются MVP-precondition для delegated session.

child_device_authorizations

Отдельный flow, в котором взрослый подтверждает вход ребёнка на конкретном устройстве. Не является delegated session.

Поля: family_group_id, student_profile_id, child_user_id, approved_by_user_id, device_binding_id или device_fingerprint_hash, request_code_hash или qr_token_hash, status, requested_at, approved_at, completed_at, expires_at, revoked_at.

Статусы: created, approved, completed, expired, revoked.

Для student_profile без linked_user_id device authorization в MVP невозможна без предварительного создания или привязки user.

Organizations

Identity владеет организациями как access/context layer. Product domains могут ссылаться на организацию, команду или ученика организации, но не меняют identity-owned таблицы напрямую.

countries / regions

Справочники географии для первого этапа organization reference search.

Поля countries: code, name_ru, name_en, status.

Поля regions: country_id, name, type, status.

Правило: на первом этапе country/region находятся в identity, потому что используются только для organization reference. Если эти справочники станут общеплатформенными, перенос в platform оформляется отдельным ADR.

organization_references

Справочная организация для поиска, подсказок и снижения дублей. Не даёт прав.

ПолеНазначение
country_id, region_id, settlementгеография
name, normalized_nameотображаемое и нормализованное название
external_registry_idссылка на внешний справочник, если есть
statusactive, hidden, deprecated

organizations

Рабочая организация платформы.

ПолеНазначение
typesystematika, school, private_school, club, education_center, commercial_center, franchise, foreign_organization, other
country_id, region_id, settlementгеография
name, normalized_nameназвание
reference_idnullable связь со справочной организацией
statusdraft, active, unclaimed, disputed, archived, merged
owner_membership_idactive membership владельца
duplicate_statusnone, possible_duplicate, confirmed_duplicate, merged
merged_into_organization_idосновная организация после ручной склейки
created_by_user_idкто создал рабочий контур

Правила:

  • organization_reference не является источником прав;
  • active организация должна иметь владельца, но constraint лучше проверять на service layer из-за циклического создания organization + membership;
  • merged организация не удаляется физически и обязана ссылаться на основную организацию;
  • автоматическая склейка запрещена.

organization_roles

Локальные роли организации. permissions хранит только ключи общего catalog (packages/permissions/catalog.ts), локальные namespaces вроде org.* запрещены.

Базовые роли: owner, admin, member; viewer возможен как post-MVP/конфигурационный режим.

organization_memberships

Связь пользователя с организацией.

ПолеНазначение
organization_id, user_idорганизация и пользователь
statusrequested, invited, active, rejected, suspended, left
sourceself_request, invitation, admin_created, ownership_claim, system_migration, system_action
role_idлокальная роль организации
verified_levellegacy/coarse result проверок; не является единственным источником решения
verified_checksstructured JSON: profile completed, contacts confirmed, affiliation checked, manual moderation passed
invited_by_membership_id, approved_by_membership_idкто пригласил и подтвердил
approved_at, suspended_atlifecycle timestamps

У пользователя не может быть двух активных или pending-like членств в одной организации.

organization_invitations

Приглашение в организацию с TTL, delivery channel, optional token hash, proposed role и статусом.

Статусы: created, sent, accepted, expired, revoked, rejected.

Каноническая модель invitation для организаций — organization_invitations, а не generic invitations.

Delivery channels:

  • link: приглашение может быть создано без email/phone/user и принято по token;
  • email: email обязателен, token обязателен;
  • sms: phone обязателен, token обязателен;
  • cabinet: invited_user_id обязателен, token не обязателен.

organization_ownership_claims

Заявка на владение existing/unclaimed/disputed организацией. Проверяется только администратором «Систематики».

Поля: organization_id, requested_by_user_id, requested_membership_id, status, applicant_full_name, applicant_position, applicant_comment, evidence_links, reviewed_by_user_id, reviewed_at, decision_comment.

Статусы: submitted, needs_more_info, approved, rejected, cancelled, disputed.

organization_ownership_transfers

Штатная передача owner-прав.

Поля: organization_id, from_membership_id, to_membership_id, to_email, status, token_hash, old_owner_new_role_id, expires_at, accepted_at, completed_at.

Статусы: created, sent, accepted, expired, cancelled, completed.

organization_teams

Команда внутри организации. В UI и API может называться team, но canonical entity name — organization_team, чтобы не конфликтовать с командами management или публичными командами storefront.

Поля: organization_id, name, description, status, created_by_membership_id.

Naming rule:

  • entity/table: organization_team;
  • URL/UI label: team;
  • permission resource: teams.

team_roles

Локальные роли команды. Как и organization roles, хранят только catalog permissions.

organization_team_memberships

Связь organization_membership с organization_team.

Поля: team_id, membership_id, role_id, status, added_by_membership_id.

organization_permission_grants

Точечная выдача или запрет catalog permission для membership/team/role с optional resource scope.

ПолеНазначение
subject_typemembership, team, role
subject_idid получателя
permissionключ из общего permissions catalog
resource_type, resource_idoptional ограничение на конкретный ресурс
effectallow или deny
starts_at, expires_atвременное действие

Resolution algorithm:

  1. Источники permissions собираются из global permissions, organization role permissions, team role permissions и explicit organization_permission_grants.
  2. Expired или ещё не начавшиеся grants игнорируются.
  3. subject_type = membership должен ссылаться на organization_memberships.id в этой же организации.
  4. subject_type = team должен ссылаться на organization_teams.id в этой же организации.
  5. subject_type = role должен ссылаться на organization_roles.id или team_roles.id, применимый к actor context.
  6. Более узкий resource grant (resource_type + resource_id) применяется только к этому ресурсу и не расширяет права на всю организацию.
  7. deny overrides allow для той же permission и применимого resource scope.
  8. Если после resolution нет allow, действие запрещено.

organization_students

Официальная или подтверждённая запись ученика внутри организации. Не обязан иметь user_id и не заменяет семейный student_profile или рабочий learning_group_participant.

ПолеНазначение
organization_idорганизация
last_name, first_name, middle_nameФИО
grade, class_letterкласс и буква
normalized_full_nameнормализованное ФИО для поиска дублей
statusactive, archived, possible_duplicate
duplicate_noteкомментарий для полных тёзок
created_by_membership_id, updated_by_membership_idкто создал/обновил

Связь с competitions: competition_participant может хранить external ref на organization_student_id, но владеет участием сам. Для обычных учеников самозаявленного преподавателя используется learning_group_participant, а не organization_student.

organization_merges

Ручная склейка организаций.

Поля: primary_organization_id, duplicate_organization_id, status, initiated_by_user_id, reason, impact_summary, completed_at.

Статусы: planned, confirmed, completed, cancelled, failed.

audit_logs

Общий identity audit фиксирует organization actions: заявки, invites, role/grant changes, ownership transfer, duplicate marking, merge, student create/update/archive и break-glass admin access.

OAuth/OIDC

oauth_clients

Клиенты экосистемы и внешние приложения.

ПолеНазначение
client_idпубличный id
client_secret_hashhash секрета для confidential clients
redirect_urisразрешённые callback URL
allowed_scopesscopes
is_first_partyдоверенный сервис экосистемы

oauth_authorization_codes

Одноразовые authorization codes с PKCE.

oauth_refresh_tokens

Refresh tokens OAuth-клиентов. Не смешиваются с main auth sessions.

oauth_consents

Согласие пользователя на scopes конкретного client.

Actor context

actor_contexts

Проверяемый access context: личный режим, действие взрослого в детском режиме, организационный scope внутри Learning Workspace/admin или service/plugin context. Он не подменяет users.id и хранится отдельно от audit logs.

Поля: actor_user_id, subject_student_profile_id, optional subject_user_id, kind, family_group_id, organization_id, team_id, role_assignment_id, oauth_client_id, delegated_session_id, issued_at, expires_at.

Internal message history and audit

message_logs

Internal diagnostic/domain-history logs. This is not the durable event bus retention store.

audit_logs

Security/business история. Не заменяет application logs.

Platform-hosted capabilities

Notifications, plugin registry, system_settings, pages_config, navigation_menus and translations are platform-owned capabilities. During bootstrap they may be exposed by identity-api, but they are not canonical identity entities and are not listed in identity/database-schema.md.

Связи

User
-> user_emails / user_phones / user_field_values
-> educator_profiles
-> user_credentials / user_auth_methods / user_sessions -> refresh_tokens
-> saved_accounts -> device_bindings
-> user_role_assignments -> roles -> permissions
-> user_family_groups -> family_groups
FamilyGroup
-> student_profiles
-> child_device_authorizations
-> organization_memberships -> organizations
-> organization_team_memberships -> organization_teams
-> oauth_consents -> oauth_clients
-> actor_contexts

OAuth client
-> authorization codes
-> oauth refresh tokens
-> scopes / consent

Message
-> message_logs
-> audit_logs, если событие security/business

Готовность

  • все сущности имеют владельца и документ;
  • auth tokens, OAuth tokens и sessions не смешиваются;
  • семейный и организационный доступ представлены разными связями;
  • educator_profile отделён от RBAC и не выдаёт права;
  • organization_student отделён от рабочих learning_group_participant;
  • audit/event/logging разделены;
  • platform-owned runtime settings не требуют изменения core identity-таблиц;
  • модель позволяет реализовать MVP без неописанных таблиц.