supplier-sync runbook
Worker-процесс ingestion: стучится в поставщика через 12h-тикер,
нормализует ответ, апсертит offer’ы, пишет raw-payload в S3. Public
HTTP отсутствует — все пробы loopback-only на 127.0.0.1:9091.
Start
Локальная разработка (с mock-серверами трёх поставщиков):
# Поднять инфраструктуру + mock-ETM + mock-IEK + mock-Systeme
docker compose --profile test up -d
# Одноразовый тик — быстрая проверка без запуска тикера
docker compose --profile test run --rm supplier-sync /usr/local/bin/supplier-sync --tick-once
# Убедиться, что данные записаны
docker compose exec postgres psql -U tracium -d tracium \
-c "SELECT supplier, count(*) FROM supplier_offers GROUP BY supplier;"Ожидаемый результат после первого тика с дефолтными seed-файлами:
supplier | count
----------+-------
etm | 30
iek | 29
Это результат по умолчанию, потому что SUPPLIER_SYNC_ENABLED остаётся
etm,iek. systeme подключён в compose и коде, но включается только
явным opt-in.
Чтобы прогнать systeme локально:
SUPPLIER_SYNC_ENABLED=etm,iek,systeme \
docker compose --profile test run --rm supplier-sync /usr/local/bin/supplier-sync --tick-onceНа дефолтном mock-корпусе systeme даёт один happy-path offer
(C9F34106); остальные seed-SKU намеренно проверяют 200 data:null, 429,
403 и generic unknown-SKU поведение.
Без --profile test mock-серверы не стартуют → supplier-sync стучится по
несуществующим адресам и тик падает с ошибкой подключения. Это нормально:
процесс всё равно поднимается для /healthz smoke.
Manual live first-layer smoke
Полный live contour runbook с downstream проверками:
../../40-operations/runbooks/live-supplier-contour.md.
Для ручной проверки реальных поставщиков используется тот же entrypoint:
TRACIUM_E2E_LIVE_SUPPLIERS=1 \
TRACIUM_E2E_LIVE_ENABLED=etm,iek,systeme \
SUPPLIER_ETM_BASE_URL=https://ipro.etm.ru/api/v1 \
SUPPLIER_IEK_BASE_URL=https://lk.iek.ru \
SUPPLIER_SYSTEME_BASE_URL=https://api.systeme.ru \
sh ./scripts/backend_e2e_compose.shLive mode:
- включается только явным
TRACIUM_E2E_LIVE_SUPPLIERS=1; - не поднимает
mock-etm,mock-iek,mock-systeme; - использует active
catalog_synccredentials из БД, а не локальныеSUPPLIER_*_LOGIN/PASSWORD/API_TOKEN; - для последующих
charnorm-worker,matcher-workerTier 3 иcanonical-assignment-workerresolver использует внешний CLI ProxyLLM_BASE_URL=https://lgm.tracium.ru/v1;LLM_API_KEYхранится в ignoreddeploy/.env; - валидирует только first layer:
supplier_offers, latestoffer_observations,raw_refsи наличие raw objects в MinIO; - не запускает matcher/proposal pipeline и не проверяет customer-scoped сценарии.
Обязательные env в live mode:
SUPPLIER_ETM_BASE_URLSUPPLIER_IEK_BASE_URLSUPPLIER_SYSTEME_BASE_URL
Скрипт fail-fast завершится до docker compose up, если обязательный
base URL отсутствует. Секреты поставщиков должны быть заранее заведены
в supplier_credentials / supplier_credential_secrets.
Multi-supplier scheduling (Phase 1-b)
Тикер обходит поставщиков из env SUPPLIER_SYNC_ENABLED (CSV,
порядок сохраняется) последовательно — по одному RunOnce на
supplier за тик. Падение одного поставщика не блокирует остальных:
тикер логирует warning и продолжает с следующего имени; последняя
ошибка возвращается наружу для учёта в метриках.
| Env | Default | Purpose |
|---|---|---|
SUPPLIER_SYNC_ENABLED | etm,iek | CSV-список активных supplier-имён |
Интервал (SUPPLIER_SYNC_TICK_INTERVAL) и прогон-при-старте
(SUPPLIER_SYNC_RUN_ON_START) описаны ниже в разделе Env — они
применяются к тикеру целиком, не per-supplier.
Параметры запуска --tick-once работают по той же схеме: проходят по
всем Enabled последовательно и завершают процесс.
Поддерживаемые supplier-имена
etm— см.connectors/etm.md.iek— см.connectors/iek.md(iter-1-b).systeme— см.connectors/systeme/README.md(iter-1-c).russvet— см.connectors/russvet.md.
Расширение списка — через fx-группу ingestion.bundles: каждый
infra-модуль публикует ingdom.ConnectorBundle с своим Name;
ingestion-aggregator собирает map, тикер использует ключи как
идентификаторы в SUPPLIER_SYNC_ENABLED.
Troubleshooting
no bundle registered→ supplier из CSV не имеет infra-модуля или у него пустойBaseURL. Проверитьdeploy/.envнаSUPPLIER_<NAME>_BASE_URL.no active credentials→StaticCredentialProviderвingestion/di.goне содержит записи для этого supplier. Добавить запись в map и редеплоить.- Упал ровно один supplier, остальные ОК → нормальное поведение
iter-1-b: тикер продолжает, smoke-check через
curl http://127.0.0.1:9091/readyzпокажетlast_tick_fresh.
Новые env для IEK (Phase 1-b)
| Env | Default | Purpose |
|---|---|---|
SUPPLIER_IEK_BASE_URL | — | IEK API endpoint (mock-iek в dev, https://lk.iek.ru или полный https://lk.iek.ru/api/products в prod) |
SUPPLIER_IEK_LOGIN | — | Обязателен при непустом BaseURL |
SUPPLIER_IEK_PASSWORD | — | Обязателен при непустом BaseURL |
SUPPLIER_IEK_SEED_FILE | backend/config/iek-seed.yaml | Путь к seed-yaml |
Новые env для Systeme (Phase 1-c)
| Env | Default | Purpose |
|---|---|---|
SUPPLIER_SYSTEME_BASE_URL | — | Systeme API endpoint (http://mock-systeme:9200 в compose test profile) |
SUPPLIER_SYSTEME_API_TOKEN | — | Обязателен при непустом BaseURL |
SUPPLIER_SYSTEME_SEED_FILE | backend/config/systeme-seed.yaml | Путь к seed-yaml |
SUPPLIER_SYSTEME_PRICE_METHOD | getprice | Override price endpoint на getekaterinburgprice |
Health endpoints
Loopback-only, доступ через docker compose exec:
docker compose exec supplier-sync wget -qO- http://127.0.0.1:9091/healthz
docker compose exec supplier-sync wget -qO- http://127.0.0.1:9091/readyz
docker compose exec supplier-sync wget -qO- http://127.0.0.1:9091/metrics
/readyz семантика:
- Агрегирует все
health.Checker(PG/Redis/Kafka/MinIO) — первый fail даёт 503. - Плюс
last_tickfreshness: если последний успешный тик был больше 24h назад, возвращается 503 с сообщениемlast_tick: stale: .... На свежем старте, когда тика ещё не было, возвращается 200 сlast_tick: none(решение iter-1: no-tick-yet — это не деградация).
Env
| Переменная | Дефолт | Назначение |
|---|---|---|
SUPPLIER_ETM_BASE_URL | http://mock-etm:9000 | URL поставщика. Для ручной отладки можно переключить на https://itest2.etm.ru/api/v1 или https://ipro.etm.ru/api/v1. Пустое значение отключает тикер, процесс всё равно поднимается для healthcheck-smoke. |
SUPPLIER_ETM_LOGIN / SUPPLIER_ETM_PASSWORD | mock-login / mock-password | ETM credentials. Реальные — для ручного прогона против itest2 или prod. |
SUPPLIER_SYNC_TICK_INTERVAL | 12h | Период тика. Dev может сократить до 5m для быстрой итерации. |
SUPPLIER_SYNC_RUN_ON_START | false | true — один тик при старте (dev feedback). В prod оставлять false чтобы рестарты не хаммерили поставщика. |
POSTGRES_DSN | из compose | Подключение к основной БД. |
KAFKA_BROKERS | kafka:9092 | Брокеры для outbox dispatcher. |
S3_* | из compose | raw-payload bucket. |
Graceful shutdown
docker compose stop supplier-sync триггерит fx OnStop:
- Loopback-HTTP закрывается первым (5s grace).
- Тикер получает cancel на run-ctx; текущий RunOnce должен респектнуть ctx и освободить advisory-lock (lease rollback).
- Если тик не завершается за
ShutdownTimeout(30s),Stop()возвращает timeout error — fx логирует и форсит exit.
Lease release автоматический через tx rollback на ctx cancel, так что даже принудительный kill не блокирует следующий запуск.
Прицельное наблюдение
- Ticker log pattern:
scheduler: tick failed(уровеньwarn) на временные ошибки,scheduler: tick panicked(уровеньerror) на panic внутри RunOnce. - Orchestrator TickReport: одна структурированная лог-строка на
RunOnce (см.
internal/core/ingestion/app/orchestrator.go).
Известные ловушки при добавлении нового supplier
При подключении нового infra-модуля в supplier-sync обязательно проверить:
1. ratebuckets.Module() нужен в platformModules()
ingetm.Module() (и любой supplier-модуль с rate-limiting) требует
ratebuckets.RatePolicy из platform/ratebuckets. Модуль не транзитивно
включается через ingestion.Module() — его надо явно добавлять в
platformModules() в cmd/supplier-sync/main.go.
Симптом отсутствия: fx wiring fail при старте:
missing type: ratebuckets.RatePolicy
2. Миграции должны быть в Docker-образе
providePostgresPool запускает миграции из cfg.Postgres.MigrationsDir
(env POSTGRES_MIGRATIONS_DIR, default /app/migrations). В Dockerfile
обязателен:
COPY backend/migrations /app/migrations
ENV POSTGRES_MIGRATIONS_DIR=/app/migrationsСимптом: migrations directory does not exist при старте.
3. CredentialRef должен быть UUID-совместимой строкой
offer_observations.credential_ref — тип uuid в Postgres.
staticCredentialProvider в internal/core/ingestion/di.go задаёт
CredentialRef как строку. В iter-1 используются nil-namespace UUID:
- ETM:
00000000-0000-0000-0000-000000000001 - IEK:
00000000-0000-0000-0000-000000000002
Каждый новый supplier должен получить уникальный nil-namespace UUID (инкрементировать последний октет). Phase 2 заменит это на реальный Credentials BC.
Симптом: invalid input syntax for type uuid: "etm-system" в логах upsert.
4. Провайдеры DI должны возвращать конкретные типы, не интерфейсы
При наличии нескольких supplier-модулей в одном fx-графе каждый должен
возвращать конкретный тип (*etm.Connector, *iek.Connector), а не
ingdom.Connector. Иначе fx не может различить провайдеров по типу.
Симптом: missing types: domain.Connector (did you mean *etm.Connector?).
5. systeme по умолчанию не включён в SUPPLIER_SYNC_ENABLED
Даже если mock-systeme поднят и env SUPPLIER_SYSTEME_* заданы,
worker не пойдёт в него без явного systeme в CSV.
Симптом: mock работает, но в логах/БД нет ни одного systeme-тика.
6. 403 у Systeme — это не session expiry
Для systeme 403 маппится в AuthRejected, а не в refresh session.
Если видите серию 403, проверяйте token и статус его блокировки у
партнёра, а не retry/login логику.
ADR-0033: imitation service mandated
Каждый поставщик имеет mock в deploy/docker/resources/mock-<name>/
который отдаёт канонический fixture-набор под compose profile
test. Не направляйте local dev на реальные supplier sandbox без
явного TRACIUM_<SUPPLIER>_REAL=1 opt-in. См.
ADR-0033.