> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Владение данными

> Какая система авторитетна для каждой категории данных, как эти две системы соединены и что ломается при нарушении границы

Omniflow и Remnawave обе хранят состояние об одних и тех же клиентах, и ровно одна из них права по каждому
вопросу. Remnawave авторитетна для пользователей VPN, счётчиков трафика, подключённых устройств, ссылок
доступа, узлов, хостов и Internal Squads. Omniflow авторитетна для идентичности, биллинга, тарифов, заказов,
платежей, возвратов, проводок кошелька, прав на услуги, поддержки, маркетинга, реферальной программы,
лояльности, RBAC и истории аудита.

Причина провести эту черту и держаться её в том, что две системы, каждая из которых считает состояние VPN
своим, порождают класс ошибок, который никто не может разобрать: клиент, чья подписка активна в одном месте и
отключена в другом, и нет способа понять, что правда. Граница делает вопрос разрешимым. Ответ Remnawave —
правда о доступе; ответ Omniflow — правда о деньгах и намерении. Это также означает, что оператор может
обновить Remnawave, а у Omniflow не будет мнения о её схеме.

Граница структурная, а не декларативная. Omniflow никогда не пишет в базу данных Remnawave и никогда не
управляет Xray. Каждый вызов идёт через HTTP-адаптер в `internal/remnawave`, который предоставляет ровно
тринадцать операций, а всё остальное в кодовой базе принимает `remnawave.Service`, `remnawave.Provisioner` или
`remnawave.Importer` как интерфейс.

## Чем владеет Remnawave

| Данные | Где хранятся | Как их видит Omniflow |
| - | - | - |
| Пользователь VPN и его технический статус | Remnawave | Читается обратно после каждой записи и сохраняется как `entitlements.observed_state`; отображается в `active`, `limited`, `disabled`, `expired` или `failed` |
| Актуальные счётчики трафика | Remnawave | Запрашиваются на каждый запрос для счётчика трафика; страница помечает подписку `live: false`, а полезную нагрузку — `degraded`, когда Remnawave не ответила |
| Подключённые устройства | Remnawave | Перечисляются вживую через `GET /api/hwid/devices/{id}`; никогда не зеркалируются |
| Ссылка доступа (подписки) | Remnawave | Запрашивается вживую на каждый запрос; столбца для неё нет нигде в схеме Omniflow |
| Узлы, хосты и Internal Squads | Remnawave | Версия тарифа хранит UUID сквадов и больше ничего о них |
| Конфигурация Xray | Remnawave | Не трогается, не читается и не представлена |

## Чем владеет Omniflow

| Данные | Таблица или запись | Примечание |
| - | - | - |
| Идентичность клиента, контакты, согласия, сессии | `users`, записи контактов и согласий | Значения контактов запечатаны ключом `APP_DATA_ENCRYPTION_KEY` |
| Тарифы, неизменяемые версии тарифов, цены, локализации, промоакции | таблицы каталога | Цена версии фиксируется снимком в строке заказа, поэтому вывод версии из обращения никогда не переоценивает прошлый заказ |
| Заказы, платёжные намерения, возвраты, приём вебхуков провайдеров | коммерческие таблицы | Только на добавление, если задокументированный жизненный цикл не разрешает изменение |
| Балансы кошельков и каждое движение | двойная запись в книге проводок | См. [Кошелёк и книга проводок](https://omniflow-vpn.mintlify.site/ru/commerce/wallet-ledger.md) |
| Подписки — видимая клиенту ручка для одного пользователя Remnawave | `subscriptions` | Слот, метка и `remnawave_user_id` |
| Права на услуги — то, что купил оплаченный заказ | `entitlements` | `UNIQUE (order_id)`, `CHECK (ends_at > starts_at)` |
| Намерение выдать доступ и его история | `fulfillment_operations`, `fulfillment_history` | Ключ идемпотентности — `UNIQUE` |
| Наблюдаемое расхождение с Remnawave | `entitlement_drifts` | JSON `expected` и `observed` |
| Обращения в поддержку, ответы, вложения | таблицы поддержки | |
| Маркетинг, новости, кампании, сегменты аудитории, исключения | таблицы маркетинга | |
| Реферальная программа, уровни лояльности, подарки, заказы цифровых товаров | коммерческие таблицы | |
| Операторы, роли, права доступа, сессии, журнал аудита | административные таблицы | Сам каталог прав доступа вкомпилирован, а не хранится в базе |

## Единственная связь между системами

Системы соединяет один столбец: `subscriptions.remnawave_user_id`, обнуляемый `bigint` под уникальным
частичным индексом, поэтому две подписки Omniflow никогда не смогут претендовать на одного пользователя
Remnawave. Больше ничего об удалённом пользователе не дублируется.

Удалённое имя пользователя вычисляется, а не хранится: `omniflow_` плюс первые шестнадцать шестнадцатеричных
символов UUID подписки. Поскольку оно детерминированное, операция `create`, выполняемая после сбоя, находит уже
созданного пользователя вместо того, чтобы создать второго: воркер ищет кандидата по имени перед созданием, а
установки, обновлённые с более ранней версии, дополнительно пробуют старое имя, производное от клиента, чтобы
существующий пользователь Remnawave был усыновлён, а не продублирован.

Каждая запись в Remnawave — это сначала запись в Omniflow: строка `fulfillment_operations` с желаемым
состоянием, ключом идемпотентности и идентификатором корреляции, применяемая надёжной задачей. Клик в панели,
который сам записал бы в Remnawave, был бы тем единственным изменением, которое сверка не смогла бы объяснить.

## Что Omniflow намеренно не хранит

* **Ссылку доступа.** Столбца для неё в `database/schema.sql` нет. Она запрашивается из Remnawave на каждый
  запрос, а ответы, которые её несут, отправляются с `Cache-Control: no-store` и
  `Referrer-Policy: no-referrer`, потому что ссылка — это предъявительский ключ доступа: у кого она есть, у
  того и доступ клиента. Единственное средство после утечки — ротация, и она закрыта явным `confirm: true`
  плюс недавним входом в систему.
* **Идентификаторы устройств.** Ни одна панель никогда не получает HWID. Устройство адресуется дайджестом —
  панель оператора использует `sha256(hwid)`, усечённый до шестнадцати шестнадцатеричных символов, панель
  клиента — собственную ручку, — а при удалении дайджест разрешается обратно на сервере: перечисляются
  устройства клиента и ищется совпадение. Ценой одного дополнительного вызова Remnawave покупается то, что
  идентификатор никогда не попадает в браузер.
* **Идентификаторы сквадов в истории.** Строка `fulfillment_history` записывает `endsAt`, факт настройки
  лимита трафика и лимита устройств и *количество* сквадов — никогда идентификаторы сквадов, ссылки или тела
  ответов провайдера.
* **Полезные нагрузки провайдеров в операторских представлениях.** Сырое тело вебхука не возвращается ни
  `GET /v1/admin/webhooks`, ни экраном вебхуков в панели: это собственная полезная нагрузка провайдера, и она
  может содержать платёжные данные. Списки задач резюмируют аргументы и редактируют последнюю ошибку.
* **Ничего идентифицирующего в телеметрии.** Анонимный heartbeat несёт номер схемы, UUID установки, имя
  события, версию сборки, имя сервиса, `GOOS`, `GOARCH` и метку времени. Идентификаторы клиентов,
  Telegram ID, IP-адреса, имена хостов, домены, URL, суммы, валюты, показатели трафика, ссылки доступа,
  токены, содержимое сообщений, названия тарифов и произвольный текст не собираются никогда. См.
  [Телеметрия](https://omniflow-vpn.mintlify.site/ru/operations/telemetry.md).

## Что ломается при нарушении границы

**Правка Remnawave напрямую.** Ничто не мешает оператору открыть панель Remnawave и вручную изменить срок
действия, лимит устройств или набор сквадов. Omniflow не считает это авторитетным: следующий проход
`reconcile` читает удалённого пользователя, сравнивает его с правом на услугу, записывает разницу и возвращает
намерение Omniflow. Ручная правка откатывается, и причина в том, что право на услугу — это то, за что заплатил
клиент.

**Удаление пользователя Remnawave.** Проход сверки записывает расхождение `missing_remote`, а не молча
воссоздаёт состояние, и последующее применение создаёт пользователя заново под детерминированным именем. Ничто
не тарифицируется дважды и ничто не теряется.

**Считать Omniflow авторитетным по актуальному потреблению.** Трафик и количество устройств читаются вживую.
Когда Remnawave не отвечает, обзор для клиента деградирует — каждая подписка помечается `live: false`, а
полезная нагрузка `degraded`, — вместо того чтобы показывать устаревшую цифру как актуальную.

**Доверять статусу одной системы применительно к другой.** Блокировка клиента в панели оператора записывает
событие жизненного цикла Omniflow и строку аудита; она не ставит в очередь изменение выдачи доступа. Панель
объясняет почему: блокировка — это не удаление, а жизненный цикл права на услугу принадлежит конвейеру
исполнения, а не клику в панели. Чтобы прекратить доступ сейчас, выполните операцию подписки `disable`, которая
проходит через тот же конвейер, что и всё остальное.

## Расхождения: запись о несогласии

Операция `reconcile` сравнивает пользователя Remnawave с правом на услугу по сроку действия, лимиту трафика,
лимиту устройств, набору Internal Squads и статусу и записывает каждое несовпадение как строку
`entitlement_drifts` с JSON `expected` и `observed`. На практике встречаются виды `missing_remote`, `expiry`,
`traffic`, `device_limit`, `squads` и `status`. Расхождение всегда оценивается относительно того пользователя
Remnawave, которым владеет данная подписка, поэтому вторая подписка клиента никогда не оценивается по
удалённому состоянию первой.

Операторы смотрят их в **System → Drift** в `/admin` (`GET /v1/panel/system/drift`, право доступа
`system.read`) или через `GET /v1/admin/fulfillment/drifts?limit=<1..500>` на токенном интерфейсе.

> [!NOTE]
>
> Обнаружение и исправление происходят в одной транзакции, поэтому расхождение, открытое проходом сверки,
> который затем завершился успешно, закрывается через микросекунды. Поэтому экран расхождений — это вид на те
> несогласия, которые корректирующая работа сейчас не может устранить, а не общий журнал всего, что когда-либо
> менялось в Remnawave. Вид `external_edit` присутствует в ограничении `CHECK` таблицы и в перечислении API,
> но его ничто не записывает: изменённый извне пользователь фиксируется как расхождение по конкретному полю.

Разрешённые строки расхождений — единственное, что здесь удаляет очистка по сроку хранения, после
`APP_RETENTION_DRIFT_DAYS` (по умолчанию 90). Права на услуги и история исполнения не удаляются никогда.

## Деньги и доступ отказывают независимо

Состояние платежа, состояние права на услугу и состояние Remnawave — три разные вещи, которые отказывают
по отдельности, и схема намеренно держит их раздельно. Успешный платёж переживает недоступность Remnawave:
деньги записаны, право на услугу записано, а выдача доступа повторяется. Ничто не переписывает финансовую
историю под сбой выдачи доступа.

Режим обслуживания проведён по той же линии. Он блокирует новые покупки и удерживает исходящую выдачу доступа
и никогда не трогает уже взятые деньги — расчёты, возвраты и история заказов продолжают работать всё это время.
Удержанная задача исполнения не проваливается, а откладывается: операция остаётся в `retrying` с
`last_error_code = maintenance_active`, счётчик её попыток возвращается назад, чтобы удержание не стоило
бюджета повторов, а задача откладывается на одну минуту. См.
[Режим обслуживания](https://omniflow-vpn.mintlify.site/ru/operations/maintenance-mode.md).

## Внутри Omniflow: надёжное, эфемерное и только на добавление

PostgreSQL — единственный надёжный источник истины. Valkey хранит счётчики ограничения частоты запросов
(`omniflow:rate:<scope>:<subject>`), одноразовые токены защиты от повтора (`omniflow:once:<scope>:<token>`),
блокировки и кеш. Потеря Valkey стоит этих счётчиков и больше ничего — именно поэтому стек compose запускает
его с полностью отключённым сохранением на диск.

Финансовые записи, записи аудита, идентичности, вебхуков и outbox доступны только на добавление, если
задокументированный жизненный цикл явно не разрешает изменение. Хранение отражает это: ежечасный проход удаляет
истёкшие сессии бота, брошенные сессии оформления заказа, устаревшие строки приёма вебхуков, истёкшие вложения
поддержки, опубликованные события outbox, старые события телеметрии и разрешённые строки расхождений. Он
никогда не трогает финансовую историю, историю аудита, согласий, идентичности и прав на услуги, которая
хранится всё время жизни установки.

Промокод никогда не удаляется, а только деактивируется, потому что ссылающиеся на него погашения — финансовые
записи. Отозванный способ оплаты сохраняет свою строку, чтобы историческое списание по-прежнему разрешалось в
тот способ, которым оно было сделано. Удаление клиентского OIDC-провайдера сохраняет связанные идентичности,
потому что их удаление молча лишило бы способа входа тех клиентов, которые им пользуются.

## Где конфигурация авторитетна

Одну границу стоит назвать прямо, потому что она выглядит как владение, но им не является: панель оператора
может сохранять настройки установки, но работающие процессы их не читают.

Строки `installation_settings` создаются пустыми и читаются ровно двумя вызывающими — самим экраном настроек и
предпросмотром телеметрии. URL и токен Remnawave, токен бота, группа операторов, политика режима обслуживания,
включение телеметрии и расписание резервного копирования читаются из переменных окружения один раз при старте
процесса. Сохранение раздела меняет то, что показывает панель, а не то, что делает процесс, и ротация токена
Remnawave означает правку окружения и перезапуск. Проверки соединения с Remnawave тоже нет:
`POST /v1/panel/settings/providers/{provider}/test` проверяет только платёжные адаптеры и отвечает
`422 provider_not_loaded` на любое другое имя.

[Настройки установки](https://omniflow-vpn.mintlify.site/ru/operations/installation-settings.md) описывают, какие разделы существуют и на что
каждый из них влияет сегодня.

## Смежные материалы

> **[Интеграция с Remnawave](https://omniflow-vpn.mintlify.site/ru/integrations/remnawave.md)**
>
> Адаптер, каждый его вызов, поддерживаемая версия и состояние здоровья.

> **[Исполнение заказов](https://omniflow-vpn.mintlify.site/ru/commerce/fulfillment.md)**
>
> Операции выдачи доступа, повторы, сверка и устранение расхождений.

> **[Подписки](https://omniflow-vpn.mintlify.site/ru/commerce/subscriptions.md)**
>
> Права на услуги, параллельность, апгрейды, льготный период и истечение.

> **[Безопасность](https://omniflow-vpn.mintlify.site/ru/operations/security.md)**
>
> Учётные данные, сессии, секреты, транспорт и границы приватности.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.