Skip to content

Основной конфиг (config.<mode>.yml) ​

Конфигурация разделена на несколько областей:

  1. app — параметры самого приложения (сервер, HTTP-клиенты, флаги безопасности, сессии, очистка кэша, модули).
  2. acp — авторизация ACP через HELM service token.
  3. infrastructure — подключения к внешним сервисам: PostgreSQL, Redis, Cloudflare и платформы.
  4. domain — бизнес-настройки (стримы, ивенты, конкурсы, соперники, user-data, очистка, заказы, кланы и войны кланов).
  5. observability — метрики и интеграция Prometheus.
  6. tasks — системные cron-задачи.
  7. alerts — назначения и шаблоны оповещений.

Ниже описан каждый блок.

1. app ​

app.server ​

yaml
app:
server:
    id: 1            # произвольная строка/число, попадает в X-Server-Id
    host: 0.0.0.0    # слушаемый адрес
    port: 8080       # порт HTTP-сервера

app.http.timeouts ​

Таймауты для общего HTTP-клиента, используются стримами, traffic-flow и т.д. Формат — time.Duration.

yaml
app:
  http:
    timeouts:
      request: 30s
      dial: 20s
      keep_alive: 20s
      tls_handshake: 10s
      response_header: 10s

app.security ​

yaml
app:
  security:
    secret: "..."          # главный секрет (подписи платформ и т.д.)
    disable_auth: false     # глобально выключить проверку платформенных токенов
    enable_cors: false      # включить permissive CORS middleware (только для dev/тестов)
    max_body_size: 4194304  # лимит тела запроса в БАЙТАХ (integer); 4194304 = 4 MB
    rate_limit:
      enabled: false       # НЕ рекомендуется за Cloudflare/Nginx — используйте WAF Rate Limiting
      rps: 20              # запросов в секунду на IP (ненадёжно за reverse-proxy)
      burst: 40            # максимальный burst на IP
    jwt:
      secret: "..."        # отдельный ключ для подписи сессионных JWT (HMAC-SHA256)
      ttl: 24h             # время жизни токена

Rate limiter хранится in-memory; при превышении возвращается HTTP 429 Too Many Requests.

jwt.secret — отдельный ключ, используемый для подписи и верификации JWT-токенов V2 сессий. Может быть использован другими сервисами для проверки токена. jwt.ttl — время жизни токена (формат time.Duration).

app.security.traffic_flow_webhook_secret ​

Секрет для проверки входящих вебхуков traffic-flow. Значение сравнивается с заголовком X-Webhook-Secret при обработке /ext/traffic-flows/webhook (constant-time сравнение); при несовпадении запрос отклоняется с unauthorized.

yaml
app:
  security:
    traffic_flow_webhook_secret: "..."

Эндпоинт fail-closed. Пустой секрет не означает «проверка выключена»: вебхук начисляет награды, поэтому при незаданном traffic_flow_webhook_secret он отвечает unauthorized на любой запрос и пишет об этом в лог. Чтобы вебхук работал, секрет обязателен.

Ключ объявлен в infrastructure/config/schemas/config.schema.json (app.security.traffic_flow_webhook_secret), поэтому его можно задавать прямо в YAML. Значение — секрет, так что в репозитории его держат пустым и подставляют через переменную окружения MARV__APP__SECURITY__TRAFFIC_FLOW_WEBHOOK_SECRET.

app.session.single_mode (DEPRECATED) ​

Deprecated: single-session mode — это legacy V1 функциональность. V2 использует исключительно JWT-авторизацию (Authorization: Bearer). Эти параметры будут удалены в будущих версиях.

yaml
app:
  session:
    single_mode:
      enabled: false  # V1 only: если true — выдаём X-Set-Session-Token и проверяем X-Session-Token
      blocking: false # V1 only: если true — блокируем запрос без актуальной сессии

app.online_ttl ​

Время, в течение которого пользователь считается «онлайн» после последнего запроса. По нему OnlineTracker отсекает старые отметки в наборе присутствия marv:persist:online_users и выставляет набору срок жизни.

yaml
app:
  online_ttl: 5m   # формат time.Duration; по умолчанию 5m

app.scheduler ​

Настройки планировщика cron-задач.

yaml
app:
  scheduler:
    task_timeout: 1h   # формат time.Duration; таймаут на выполнение одной задачи

task_timeout — предельное время выполнения отдельной cron-задачи (infrastructure/scheduler/manager.go). По истечении контекст задачи отменяется.

app.cache_clear ​

yaml
app:
  cache_clear:
    enabled: true       # чистить volatile-кэши при старте контейнера
    patterns:
      - "marv:cache:users:*"
      - "marv:cache:streams:*"

app.invitation_awards ​

Строки с JSON-наградой для получателя/отправителя инвайта:

yaml
app:
  invitation_awards:
    receiver: '{"currency":"coins","amount":100}'
    sender: '{"currency":"coins","amount":50}'

Зарезервировано / не подключено. Ключ присутствует в схеме, но в рантайме сейчас не читается: единственный потребитель — UserService.HandleInviterBonus — не имеет вызывающих сторон, поэтому награды по инвайтам не начисляются. Заполнение этих строк на текущее поведение не влияет.

app.modules ​

Список модулей для загрузки. Позволяет управлять набором функционала через конфиг (без изменения кода):

yaml
app:
  modules:
    - experiment
    - event
    - clan
    - message
    - bots
    - product
    - remote_config
    - rival
    - stream
    - ads
    - alert
    - order
    - traffic_flow
    - transaction
    - world

Модули system, user, cron_task, merchant, platform создаются по умолчанию и не нуждаются в перечислении; остальные — только если указаны. Полный набор допустимых значений (см. схему config.schema.json): ads, alert, bots, clan, cron_task, event, experiment, merchant, message, order, platform, product, remote_config, rival, stream, system, traffic_flow, transaction, user, world.

app.routes ​

Включение/отключение групп HTTP-маршрутов:

yaml
app:
  routes:
    acp: true       # Admin Control Panel
    api: true       # группа маршрутов /api
    ext: true       # внешние вебхуки (мерчанты)
    v1: true        # V1 API (deprecated)
    v2: true        # V2 API (основной)

В config.example.yml включены все группы; отключите ненужные, выставив false.

app.min_version ​

Минимальные версии клиента по маршрутам. Ключ — regex для HTTP-пути, значение — минимальная версия (semver). Заголовок X-Client-Version проверяется middleware; если версия ниже — возвращается ошибка.

yaml
app:
  min_version:
    "^/v2": "1.2.0"       # все /v2/* требуют клиент >= 1.2.0
    "^/v2/streams": "1.5.0" # стримы требуют >= 1.5.0

Если секция пуста или отсутствует — проверка версий отключена.

Прочие поля ​

  • app.log_dir — каталог для файловых логов (infrastructure/logger).

Ключи, разрешённые схемой, но не читаемые кодом ​

Схема запрещает лишние ключи (additionalProperties: false), поэтому удалить их значило бы сломать валидацию у уже развёрнутых конфигов. Они остаются разрешёнными и ни на что не влияют:

КлючПочему остался
app.http.timeouts.handlerТаймаут-middleware удалён
app.cdn_urlНе читается кодом
app.invite_bonusЛегаси-ключ, заменён на app.invitation_awards
app.validation.strictКонфиг всегда проверяется этой схемой
app.invitation_awardsСм. выше: единственный потребитель не вызывается

2. Авторизация ACP ​

acp (HELM service token) ​

ACP-запросы аутентифицируются токеном, выпущенным HELM (RS256 JWT, iss: helm). MARV проверяет подпись по публичным ключам HELM и извлекает per-env доступ оператора:

yaml
acp:
  access_key: "your-project/environment"             # ключ HELM: <project-slug>/<base-name>
  jwks_url: "https://helm.example.com/v1/auth/jwks"  # откуда тянуть публичные ключи HELM
  jwks: "{}"                                         # кэш JWKS; заполняется задачей helm_certs

Email оператора берётся из claim sub; битовая маска доступа — из access["<access_key>"] (0, если ключа нет). jwks периодически обновляется системной задачей helm_certs.

Legacy Google OAuth-вход в ACP удалён (oauth.google / задача google_certs больше не существуют) — авторизация ACP идёт исключительно через HELM service token.

3. infrastructure ​

infrastructure.postgres ​

yaml
infrastructure:
  postgres:
    host: localhost
    port: 5432
    username: app
    password: secret
    database: marv
    pool:
      max_open: 20
      max_idle: 10
      timeout: 300s
    sslmode: disable
    dry_run: false
    # Опциональная read-реплика: SELECT'ы идут на неё, запись/транзакции — на primary.
    # Опустите блок для одного пула. Creds/db/sslmode наследуются от primary; нужен только host.
    # read:
    #   host: replica.internal
    #   pool: { max_open: 40, max_idle: 20 }

infrastructure.redis ​

yaml
infrastructure:
  redis:
    address: 127.0.0.1:6379
    password: ""
    db: 0
    pool: 10
    timeout: 300s     # используется как pool-timeout и default TTL
    store_ttl: 4h     # TTL для Set() без явного timeout

infrastructure.cloudflare ​

Учетные данные для Cloudflare API.

yaml
infrastructure:
  cloudflare:
    account_id: "..."
    api_token: "..."
    creator_prefix: "example"   # префикс имени для загружаемых в Cloudflare Stream креативов

infrastructure.platforms ​

Словарь платформ. Ключ — строковый api_type (числовой идентификатор платформы).

yaml
infrastructure:
  platforms:
    "2":
      type: vk
      credentials:
        id: "7365134"
        secret_key: "..."
    "30":
      type: ms_start
      credentials:
        id: "9NPZT17FTVZ4"
        package_name: "HGPOINTLTD.MergeHotel"
        secret_key: "..."
      merchant_profiles:
        default:
          type: xsolla
          merchant_id: "702055"
          project_id: "265997"
          api_key: "..."
          webhook_secret: "..."
        promo:
          type: xsolla
          merchant_id: "702055"
          project_id: "400001"
          api_key: "..."
          webhook_secret: "..."

type — тип платформы. Допустимые значения (enum схемы config.schema.json): default, unauthorized, ok, vk, fb, fb_login, mm, mobage, dr, fs, gm, app_store, play_market, amazon, ms_store, ru_store, bazaar, galaxy_store, yandex, exe, beeline, play_deck, ms_start, tg, crazy_games, discord, myket, orbit, jest, google_signin, apple_signin, game_center, play_games.

name (опционально) — человекочитаемое имя платформы.

credentials — учётные данные платформы. Набор полей зависит от типа платформы:

ПолеОписание
idИдентификатор приложения
app_idАльтернативный ID приложения
uidUID приложения
secret_keyСекретный ключ
application_keyКлюч приложения (OK)
auth_tokenТокен авторизации
packageИмя пакета
package_nameИмя пакета (MS Store, RuStore)
client_idClient ID (OAuth)
refresh_tokenRefresh Token (Bazaar)
redirect_urlRedirect URL (OAuth)
consumer_keyConsumer Key (Mobage)
certСертификат
public_keyПубличный ключ (RuStore)
private_key_idID приватного ключа (RuStore, App Store)
issuer_idIssuer ID команды в App Store Connect (App Store)
company_idID компании (RuStore)
bot_tokenТокен бота (Telegram)

App Store (type: app_store) проверяет покупку через App Store Server API: сервер запрашивает у Apple транзакцию по transaction_id, который прислал клиент, и проверяет подписанный ответ Apple. Чек приложения (receipt) не используется. Учётные данные:

ПолеЗначение
idBundle ID приложения, например com.happygames.mergecafe
private_key_idKey ID ключа In-App Purchase: App Store Connect → Users and Access → Integrations → Keys: In-App Purchase. Ключ App Store Connect API (Team/Individual Keys) не подходит
issuer_idIssuer ID команды, вверху той же страницы
secret_keyСам ключ (.p8) в PEM или в base64 от PEM. Хранится в Vault и приходит через MARV__INFRASTRUCTURE__PLATFORMS__<api_type>__CREDENTIALS__SECRET_KEY

У ключа In-App Purchase нет роли или уровня доступа: при создании задаётся только имя. Ключ действует на все приложения команды, а конкретное приложение выбирается bundle id в запросе. Подходит только для App Store Server API, Advanced Commerce API и External Purchase Server API. .p8 можно скачать один раз.

yaml
    "10":
      type: app_store
      credentials:
        id: "com.happygames.mergecafe"
        private_key_id: "ABC123DEFG"
        issuer_id: "57246542-96fe-1a63-e053-0824d011072a"
        secret_key: ""   # из Vault

Покупка принимается, только если она сделана в этом приложении, совпадает с SKU, который назвал клиент, не возвращена и (для подписки) не истекла. Сервер сначала ищет транзакцию в production, затем в Sandbox: так проходят покупки App Review и TestFlight. Sandbox-покупка засчитывается, но в журнале помечена sandbox = true и выручкой не считается. Цену и валюту записывает Apple. Одна транзакция App Store засчитывается одному аккаунту: если её прислал другой аккаунт, сервер отвечает ошибкой iap.order_taken.

merchant_profiles — словарь профилей мерчанта. Ключ — произвольное имя профиля (используется в маршрутах /ext/{api_type}/{profile}/{action}).

Поля профиля:

ПолеОбязательноеОписание
typeдаТип мерчанта (xsolla, rustore, ...)
merchant_idнетID мерчанта
project_idнетID проекта
api_keyнетAPI-ключ
webhook_secretнетСекрет для подписи вебхуков
company_idнетID компании (RuStore)
private_key_idнетID приватного ключа (RuStore)

Для Xsolla Basic Auth (merchant_id:api_key) вычисляется автоматически при каждом запросе.

Профиль default используется, если в маршруте не указан конкретный профиль.

4. domain ​

yaml
domain:
  stream:
    delete_after: 1080h
    entrance_timeout: 900s
    proxy_url: "https://..."
    subdomain: "example.cloudflarestream.com"
  events:
    lookback: 336h
    dead_letter_queue:
      enabled: true
      max_retries: 3
  contest:
    group_size: 50
  rivals:
    max_count: 100
    compatible_api_types:
      vk: [1,2,3]
  user_updates:
    batch_size: 250
    ttl: 168h                # TTL записей в очереди обновлений
  cleanup:
    data_retention: 4320h   # хранить данные user/world столько (180d); используется задачей data_clean
  orders:
    ttl_days: 7              # через сколько дней удалять pending-заказы (cron order_cleanup)
  clans:
    member_cap: 50                     # дефолтный размер ростера нового клана
    join_request_limit: 5              # макс. активных заявок на игрока
    snapshot_min_write_interval: 60s   # rate-limit записи живого снапшота (анти-bloat)
    leader_inactive_succession: 336h   # авто-передача лидерства при простое лидера (14d)
    inactive_autokick: 720h            # мягкий авто-кик неактивных участников (30d)
    badwords_file: ""                  # путь к текстовому блоклисту имени/тега (одно слово на строку, # — коммент); пусто = выключено
  clan_war:
    participation_mode: auto           # auto | opt_in
    min_members: 1
    power_band_pct: 30                 # подбор: противники в пределах этого % силы
    attacks_per_player: 2
    retention_wars: 2                  # сколько прошлых войн отдавать в истории
    retention: 720h                    # GC: удалять finished-войны, чей конец завершился раньше (30d)
    attack_reserve_timeout: 180s       # держать зарезервированную атаку до просрочки
    attack_reserve_on_expire: refund   # refund | consume — что делать с атакой при просрочке
    phases:                            # упорядоченная последовательность фаз; каждая длится свой duration
      - { type: points,   duration: 24h, winning_points: 1 }
      - { type: points,   duration: 24h, winning_points: 2 }
      - { type: points,   duration: 24h, winning_points: 2 }
      - { type: points,   duration: 24h, winning_points: 2 }
      - { type: points,   duration: 24h, winning_points: 2 }
      - { type: battle,   duration: 24h, winning_points: 4, points_per_win: 1 }
      - { type: results,  duration: 24h }
    tier:                              # tier_points клана (счётчик побед в войнах) → тир
      points_per_win: 1
      points_per_loss: 0
      thresholds: { E: 0, D: 5, C: 10, B: 15, A: 20, "S+": 25, "S++": 35, "S+++": 50 }
    rewards:                           # числа-плейсхолдеры (баланс — на стороне гейм-дизайна)
      delivery: mail                   # mail: сервер кладёт в почту | claim: клиент забирает через clans/war/rewards
      min_points_for_reward: 1         # меньше этого — никакой общей награды (ни win, ни lose)
      min_points_for_win: 100          # win-награда только участникам, набравшим больше
      common:                          # общая награда пер-тир: <tier>: { win: {...}, lose: {...} }
        E:    { win: { gold: 1000 }, lose: { gold: 500 } }
        "S+": { win: { gold: 5300, tickets: 7800 }, lose: { gold: 2650, tickets: 3900 } }
      individual:                      # кумулятивная лестница (до 25 стадий): набрал points → выдаётся reward
        - { points: 500,  reward: { gold: 100 } }
        - { points: 1000, reward: { gold: 200 } }
  clan_chat:
    enabled: true                      # включён ли модуль чата
    max_message_length: 500            # потолок длины сообщения (в рунах)
    history_retention: 200             # сколько сообщений хранить на клан (ring buffer; лишнее чистит крон)
    send_min_interval: 2s              # кулдаун между сообщениями на отправителя
    send_burst_per_min: 15             # скользящий потолок сообщений в минуту на отправителя
    mute_max_duration: 168h            # потолок, на сколько модератор может замьютить (7d)

domain.clans — параметры уровня клана; domain.clan_war — цикл войны (сиблинг domain.rivals/contest/events/stream). Скалярные длительности — Go duration-строки (Xh/Xm/Xs; дни — часами, напр. 336h = 14d, 720h = 30d), как везде в конфиге. badwords_file — путь к текстовому блоклисту имени/тега клана (одно слово/фраза на строку, # — комментарий, пустые строки игнорируются; матч case/leet-insensitive как подстрока); пустой путь или отсутствующий файл = фильтр выключен (см. config/badwords.example.txt). Запрета на вступление после выхода нет: вступить в клан можно сразу, а уход из клана во время войны закрывает участие в клановых войнах до конца текущего цикла (ограничение clans.war, см. Ограничения аккаунта). Ключ join_cooldown устарел и игнорируется; схема его принимает, чтобы старые конфиги загружались.

domain.clan_chat — базовый переиспользуемый модуль клан-чата (сиблинг clan_war). Модуль владеет своими таблицами и обращается к клановой системе только через порт membership, поэтому переносится в другие игры на этой кодовой базе без правок. Непрочитанное отдаётся в clans/check (секция chat); сообщения и модерация — под clans/chat/*. Ретеншн (history_retention) — кольцевой буфер на клан, лишнее подрезает крон clan_chat_retention. Системные события клана (вступил/вышел/исключён/смена роли/передача лидерства) публикуются в ленту как system-сообщения через общий порт эмиттера — что именно попадает в ленту, решает хост. Через тот же порт война (Forge-специфичная) шлёт в чат старт (код 100), смену фазы (код 102, meta {phase_index, type}) и завершение (код 101, meta.outcome = win/lose/tie) — по обоим кланам. Сам чат-модуль эмитит служебное «сообщение удалено» (код 900) для клиентов, уже прочитавших дальше.

Модерация чата двухуровневая. Внутри клана удалять сообщения и мьютить может co-leader+ (clans/chat/delete, clans/chat/mute). Игрок может пожаловаться на сообщение (clans/chat/report) — жалоба уходит руководству клана (clans/chat/reports), которое либо отклоняет её, либо эскалирует модератору (clans/chat/reports/escalate). Оператор в ACP видит очередь эскалированных жалоб, может посмотреть чат любого клана и удалить сообщение (acp/clans/chat/*); каждое операторское действие пишет clan_moderation_actions. Жалоба снапшотит текст и автора сообщения, поэтому переживает подрезку ретеншном.

Война — это упорядоченный список фаз (phases), каждая со своим duration. Планировщик закрывает активную фазу по истечении окна (сравнивает очки сторон → победитель фазы → начисляет winning_points, ничья — обоим → фиксирует MVP) и активирует следующую, либо финиширует войну (победитель — по сумме winning points; ничья → без победителя). Типы фаз: points (игроки репортят очки), battle (1×1 хендшейк, points_per_win за побеждённого врага, каждого — раз), results (пауза), squad5x5 (бой 5×5, squad_size бойцов). Порядок и состав меняются в phases — движок один. tier маппит счётчик tier_points клана в тир по порогам (тир замораживается на войне и задаёт размер наград). rewards.delivery выбирает доставку: mail (сервер кладёт в почту, хранится ~месяц) или claim (клиент забирает через clans/war/rewards + clans/war/rewards/claim); начисление идемпотентно (reconcile в планировщике переживает сбои). Общую награду получает только участник, набравший за войну не меньше rewards.min_points_for_reward очков (0 или не задано = 1); win-награду из них — участник победившего клана, набравший больше rewards.min_points_for_win. Состав войны на подборе не фиксируется: воюют текущие участники клана, а min_members и сила клана на подборе считаются только по тем, кто будет воевать (см. Войны кланов).

5. observability ​

yaml
observability:
  metrics:
    http_buckets_ms: [50, 100, 200, 500, 1000, 2000, 5000]
    prometheus_enabled: false

6. tasks ​

tasks.system ​

Системные cron-задачи, которые поднимаются из конфигурации (без записи в БД).

yaml
tasks:
  system:
    - name: "Reload HELM JWKS"
      type: helm_certs
      schedule: "0 */6 * * *"
      enabled: true
      start_immediately: true
      scope: all          # JWKS нужен на каждом узле; leader (по умолчанию) или all
    - name: "Persist user data updates"
      type: user_data_batch_update
      schedule: "*/5 * * * *"
      enabled: true
      scope: leader
    - name: "Dispatch alerts"
      type: alert_dispatcher
      schedule: "*/1 * * * *"
      enabled: true
      scope: leader
    - name: "Clean old data"
      type: data_clean
      schedule: "0 2 * * *"
      enabled: true
      scope: leader
    - name: "Stats snapshot"
      type: stats_snapshot
      schedule: "0 * * * *"
      enabled: true
      start_immediately: true
      scope: leader
    - name: "Order cleanup"
      type: order_cleanup
      schedule: "0 3 * * *"
      enabled: true
      scope: leader
    - name: "User backup"
      type: user_backup
      schedule: "0 4 * * *"    # ежедневно 04:00 (последние 24ч)
      enabled: true
      scope: leader
    - name: "World backup"
      type: world_backup
      schedule: "30 4 * * *"   # ежедневно 04:30 (последние 24ч)
      enabled: true
      scope: leader
    - name: "Clan war matchmaker"
      type: clan_war_matchmaker
      schedule: "0 17 * * 1"    # Пн 17:00 UTC — открыть войны нового цикла
      enabled: true
      start_immediately: false
      scope: leader
    - name: "Clan war scheduler"
      type: clan_war_scheduler
      schedule: "*/10 * * * *"  # продвинуть фазы войны + просрочить зависшие атаки
      enabled: true
      start_immediately: false
      scope: leader
    - name: "Clan maintenance"
      type: clan_maintenance
      schedule: "0 3 * * *"     # ежедневно: наследование лидера + автокик неактивных + GC старых войн
      enabled: true
      start_immediately: false
      scope: leader
    - name: "Clan chat retention"
      type: clan_chat_retention
      schedule: "0 4 * * *"     # ежедневно: подрезка чата каждого клана до окна ретеншна
      enabled: true
      start_immediately: false
      scope: leader

stats_snapshot — периодически рассчитывает агрегированную статистику пользователей (total, today, week, month, DAU, MAU) и сохраняет в in-memory StatsStore. Результат отдаётся через POST /acp/system/overview. Рекомендуется запускать раз в час на лидере; при start_immediately: true первый расчёт происходит сразу после старта.

scope управляет тем, где выполняется задача:

  • leader — только на активном лидере (рекомендовано для операций с единственным воркером: перенос очередей из Redis в БД и т.п.)
  • all — на каждом сервере (подходит для задач вроде обновления сертификатов или прогрева кэшей).

Клановые cron-задачи (все scope: leader):

  • clan_war_matchmaker — открывает войны нового цикла, подбирая подходящие кланы и материализуя фазы из расписания (те же, что и ручной POST /acp/clans/war/run-matchmaking).
  • clan_war_scheduler — продвигает фазы войн (закрывает истёкшую → активирует следующую → финиширует войну), начисляет награды завершённых войн (reconcile, идемпотентно) и просрочивает зависшие атаки. Ручной POST /acp/clans/war/advance двигает только фазы, но НЕ начисляет награды и НЕ просрочивает атаки — это делает лишь этот крон.
  • clan_maintenance — суточная уборка: наследование лидерства при простое (leader_inactive_succession), мягкий автокик неактивных участников (inactive_autokick) и retention-GC старых войн (clan_war.retention).
  • clan_chat_retention — подрезает чат каждого клана до окна clan_chat.history_retention (кольцевой буфер).

7. alerts ​

Шлюз критических уведомлений.

yaml
alerts:
  destinations:
    - name: payments_slack
      provider: slack                # slack / telegram / helm
      endpoint: "https://hooks.slack.com/services/..."
      severities: [critical]
      tags: ["payments"]
      template: payment_critical
    - name: oncall_tg
      provider: telegram
      token: "<bot-token>"
      chat_id: -1001234567
      severities: [critical, warning]
      template: default_telegram
    - name: helm_alerts
      provider: helm                 # пересылка событий в HELM
      endpoint: "https://helm.example.com/ext/alerts/ingest"
      token: ""                      # HELM ext.api_key — override через .env
      severities: [info, warning, critical]
  templates:
    - id: payment_critical
      provider: slack
      text: |
        :rotating_light: *{{ .Title }}*
        {{ .Message }}
        {{ if .Payload.transaction_id }}ID: `{{ .Payload.transaction_id }}`{{ end }}
    - id: default_telegram
      provider: telegram
      text: |
        {{ .Title }}
        {{ .Message }}
        severity={{ .Severity }}
  • destinations — куда отправлять события (Slack webhook, Telegram-чат и т.д.), плюс фильтры по severity/tag, выбор шаблона.
  • templates — текстовые шаблоны (Go text/template). Доступные поля: Title, Message, Severity, Tags, Payload.

Полная JSON Schema расположена в infrastructure/config/schemas/config.schema.json.