Adding a new supplier
Спецификация приемки нового поставщика end-to-end. Ее цель - не просто добавить HTTP-клиент, а гарантировать, что ассортимент, цены, остатки по всем складам, характеристики, описания и downstream matching регулярно попадают в нашу базу без SKU-by-SKU узких мест.
Непереговорные правила
- Credentials поставщиков живут в Credentials BC / базе данных. В
.envразрешены только инфраструктурные настройки: base URL, лимиты, таймауты, режим enumerator, DSN, Redis/S3/LLM endpoints. Логины, пароли, токены, master key и клиентские доступы в.envне искать и не добавлять. - Для поставщика с batch, bulk, cursor, file-feed или incremental API запрещено строить регулярную загрузку через отдельный запрос на каждый SKU. Per-SKU запросы допустимы только как явно задокументированный fallback для поставщика без массового API или для точечной диагностики.
- Остатки нужно собирать по всем складам, которые отдает поставщик. Если
authoritative stock snapshot успешно загружен и в нем нет SKU, это означает
загруженный пустой остаток, а не ошибку
missing_remains. - Каждый поставщик обязан иметь mock/imitation service до merge. Mock должен воспроизводить не только happy path, но и лимиты, ошибки, paging, частичные данные и реальные странности payload.
- Новый supplier считается готовым только после локальных contract/mock тестов,
bounded live tick на реальных DB-backed credentials и downstream прогонов:
charnorm-worker,matcher-worker,canonical-assignment-worker.
1. Разбор спецификации API
- Описать transport: REST, SOAP, file-feed, SFTP, headless или гибрид.
- Выделить оси данных:
- catalog/goods: название, описание, бренд, категория, изображения, документы;
- characteristics: сырые атрибуты, единицы измерения, группы;
- prices: типы цен, валюта, НДС, минимальные партии, инкременты;
- stock/remains: остатки по всем складам, коды складов, названия, резервы;
- delivery/availability: сроки, кратность, ограничения поставки.
- Зафиксировать ведущую ось snapshot: обычно catalog/goods. Price-only или stock-only SKU нельзя молча превращать в полноценный товар без описания.
- Найти массовые стратегии:
- full catalog snapshot;
- batch prices by page/cursor/file;
- stock all warehouses;
- incremental changes by timestamp/cursor;
- batch details by list of SKU, если полного snapshot нет.
- Записать лимиты: page size, rate limit, timeout, daily quota, retry-after, ограничения по IP whitelist.
- Сопоставить ошибки поставщика с
ConnectorErrorCode:AuthRejected,SessionExpired,RateLimited,NotFound,OnRequest,Transient,Permanent,PartialSuccess.
2. Capabilities и стратегия загрузки
- Добавить
Capabilitiesдля поставщика и явно описать:- поддерживается ли
BulkSnapshotSource; - есть ли incremental/cursor режим;
- есть ли batch lookup по списку SKU;
- есть ли per-warehouse stock snapshot;
- какие оси могут быть частичными.
- поддерживается ли
- Регулярный refresh должен использовать самую широкую доступную стратегию:
- incremental/cursor, если поставщик гарантирует полноту изменений;
- full/bulk snapshot по страницам или файлам;
- batch by SKU list;
- per-SKU только как последний fallback с отдельным лимитом и алертом.
- Если
Capabilities.ScheduledRefreshсодержит batch/paged/incremental стратегию, supplier обязан иметь зарегистрированный bulk snapshot source. Router не должен молча падать в legacy per-SKU pipeline. - Для batch endpoint нельзя связывать данные по номеру страницы между разными endpoint. Join делается только через supplier SKU / commercialRef / артикул поставщика.
- Late page error не должен обнулять уже загруженные страницы. Поток должен отдавать завершенные items и отдельно фиксировать ошибку поздней страницы.
- Если ось stock загружена полностью, отсутствие SKU в stock snapshot
трактуется как пустые остатки. Если stock endpoint упал, это
partialсmissing_remains.
3. Интеграция в backend
-
core/ingestion/infra/<supplier>/session.go- SessionManager, login, session cache, auth bucket. -
core/ingestion/infra/<supplier>/bulk_snapshot_source.goили equivalent - массовая загрузка данных. Для batch-capable поставщика это основной путь. -
core/ingestion/infra/<supplier>/connector.go- точечный fetch только если нужен fallback или diagnostic path. -
core/ingestion/infra/<supplier>/enumerator_*.go- enumerator не должен заставлять основной refresh ходить по SKU по одному, если есть bulk API. -
core/normalization/infra/<supplier>/- mappers для goods, price, stock, characteristics и raw refs. - Category mapper (
offer_mapper.go::categoriesFrom*) обязательно заполняетnormdom.CategoryRef.ExternalRefraw supplier-native ID до санитизации slug’а (per-level). Resolver использует это как natural key.Nameможет быть пустым (fallback на Slug);ExternalRefпустым быть не может — иначе resolver вернётCodeCategoryResolverInvalidRef. -
core/ingestion/infra/<supplier>/capabilities.goиdi.go- supplier module. -
cmd/supplier-sync/main.go- зарегистрировать module и wiring. - ЛК клиента - добавить поставщика в клиентские сценарии подключения, отображение статуса credentials, health, errors и доступные scopes.
- Kafka topics - если появились новые producer topics, добавить их в startup initialization для local и e2e compose.
4. Mock/imitation service
Mock обязателен и должен жить в deploy/docker/resources/mock-<supplier>/.
- Сервис поднимается в docker compose profile
test. - Фикстуры лежат рядом с mock или в
backend/test/fixtures/<supplier>/. - Happy path включает несколько страниц catalog, price, stock и минимум два склада.
- Проверяются реальные edge cases:
- auth success/fail;
- expired session;
- rate limit и retry-after;
- transient 5xx на поздней странице;
- paging order mismatch между catalog и price;
- numeric и string supplier SKU;
- null/empty arrays;
- SKU без stock row при полном stock snapshot;
- SKU без price row;
- partial axis response;
- неизвестный склад;
- конфликтующие характеристики;
- provider-specific strange payload, найденный на live данных.
- Если live проверка нашла новый формат payload, добавить sanitized fixture и regression test.
5. Тесты
- Provider contract suite:
backend/test/provider-contract/<supplier>_connector_contract_test.go. - Unit tests для session, request builder, error classifier и rate limit.
- Mapper tests для goods, price, stock, characteristics:
- numeric/string SKU;
- пустые значения;
- валюта, НДС, упаковка;
- все склады;
- raw refs сохраняются.
- Bulk snapshot tests:
- regular refresh не делает per-SKU запросы для batch-capable supplier;
- batch-capable supplier без bulk source завершает tick явной ошибкой, а не legacy per-SKU fallback;
- join между осями идет по SKU, а не по странице;
- late page error сохраняет уже отданные items;
- stock absence после полного snapshot дает empty stock payload;
- provider pagination не теряет последнюю страницу.
- Mock E2E:
supplier-sync --tick-onceпротив mock пишетsupplier_offersиoffer_observations. - Downstream tests или integration smoke:
charnorm-worker,matcher-worker,canonical-assignment-worker.
6. Live verification
Основной runbook: ../40-operations/runbooks/live-supplier-contour.md.
- Не искать provider credentials в
.env. - Проверить активные system/customer credentials через БД или app repositories без вывода секретов.
- Запустить live supplier tick на реальных данных. Если API отдает
full-export,
max_itemsдолжен быть0и обработка идет по всему export; положительныйmax_itemsдопустим только для отдельного smoke/debug-прогона и не считается production acceptance. - После ingestion запустить:
charnorm-worker --batch-once;matcher-worker --batch-once;canonical-assignment-worker --batch-once.
- DB assertions должны показать:
- количество уникальных SKU;
- goods payload count;
- price payload count;
- stock payload count;
- non-empty prices;
- non-empty stock by warehouses;
- partial/error counts and missing kinds;
characteristic_rawrows;- charnorm mappings/backfills;
- matcher decisions;
- canonical assignments или assignment disputes.
- После ручного one-shot вернуть постоянно работающий
supplier-sync, если он был остановлен для проверки. - Для supplier overlap проверить не только количество canonical products, но и реальные группы схлопывания. Одинаковый физический товар у разных поставщиков должен иметь один canonical; товары разных производителей или брендов не схлопывать только из-за совпадения характеристик.
Минимальный формат отчета:
supplier:
credential scope: system/customer/both
command:
unique_sku:
goods_payload:
price_payload:
stock_payload:
nonempty_price:
nonempty_stock:
warehouses_seen:
partial:
errors:
raw_characteristics:
charnorm_mappings:
matcher_decisions:
canonical_assignments:
assignment_disputes:
canonical_overlap_groups:
gaps:7. LLM, характеристики и конфликты
- Сырые характеристики должны попасть в
characteristic_raw. - Charnorm должен создать/обновить mappings и backfill normalized attrs.
- Matcher должен создать decisions для новых offers.
- Canonical assignment должен создать assignments либо disputes.
- LLM resolver должен валидировать output:
- confidence может приходить как
0.87или87; - selected values не должны сохраняться с лишними кавычками;
- hallucinated/unknown values уходят в manual review или retryable state;
in_progressdisputes не должны зависать навсегда.
- confidence может приходить как
- Resolver нельзя проверять серией быстрых рестартов: один LLM batch должен
либо завершиться summary, либо штатно вернуться через stale reclaim. Иначе
тест сам создаёт временные
in_progress.
8. Kafka topics
Все producer topics из production Go-кода должны создаваться при старте local и e2e compose. Перед merge выполнить сравнение:
prod_topics=$(mktemp); local_topics=$(mktemp); e2e_topics=$(mktemp)
rg -o '"[a-z0-9_.-]+\.v[0-9]+"' backend/internal --glob '*.go' --glob '!*_test.go' | sed 's/.*"\(.*\)"/\1/' | sort -u > "$prod_topics"
sed -n '/for topic in \\/,/; do/p' deploy/docker/docker-compose.yml | rg -o '[a-z0-9_.-]+\.v[0-9]+' | sort -u > "$local_topics"
sed -n '/for topic in \\/,/; do/p' deploy/docker/docker-compose.e2e.yml | rg -o '[a-z0-9_.-]+\.v[0-9]+' | sort -u > "$e2e_topics"
comm -23 "$prod_topics" "$local_topics"
comm -23 "$prod_topics" "$e2e_topics"
rm -f "$prod_topics" "$local_topics" "$e2e_topics"Обе команды comm не должны вывести ни одной строки.
9. Documentation and runbook
-
docs/docs/30-services/ingestion/connectors/<supplier>.md- transport, capabilities, session, rate limits, error taxonomy, egress, mock service, live verification notes. -
docs/docs/30-services/ingestion/supplier-sync-runbook.md- custom ops steps if supplier needs them. - ADR only if supplier requires behavioral deviation from ADR-0024 / ADR-0033.
- Сохранить реализованные API contracts и sanitized payload fixtures, даже если полноценная интеграция поставщика переносится на будущее.
Definition of done
- Batch/bulk/incremental стратегия выбрана и покрыта тестами.
- Per-SKU регулярной загрузки нет для supplier с массовыми API.
- Остатки собираются по всем складам.
- Price и stock данные не теряются из-за разного порядка страниц.
- Mock service покрывает happy path и edge cases.
- Local mock E2E проходит.
- Bounded live contour проверен на DB-backed credentials.
- Charnorm, matcher, canonical assignment прошли после загрузки.
- Canonical merge проверен на targeted overlap-наборе, включая multi-supplier совпадение и negative case разных брендов/производителей.
- ЛК клиента умеет подключить поставщика и показать health/errors.
- Kafka topics создаются при старте local/e2e compose.
- Все regression tests и
git diff --checkпроходят.
References
- ADR-0024 supplier connector contract
- ADR-0033 supplier imitation services mandated
docs/plans/2026-04-20-11-50-phase1a-ingestion-etm.md(ETM template)docs/superpowers/specs/2026-04-20-ingestion-phase1-a-design.md