IEK connector

core/ingestion/infra/iek — второй reference-коннектор ADR-0024 (после ETM). IEK проверяет архитектурную гибкость на трёх осях:

  1. HTTP Basic Auth вместо login-эндпоинта → SessionManager без сетевых вызовов, AuthBucket не задействован.
  2. Product endpoint /api/products: scheduled refresh забирает полный catalog snapshot без art, а Connector.Fetch использует /api/products?art=<sku>&entity=all для diagnostic/per-SKU lookup.
  3. Optional stock endpoint /api/commerce/Remains: stock/remains не входит в публичную документацию IEK API и выключен по умолчанию. Если конкретный tenant/credential получил рабочий endpoint, оператор включает SUPPLIER_IEK_REMAINS_ENABLED=true; тогда commerce refresh забирает полный snapshot остатков без art, а Connector.Fetch может запросить остатки SKU через art=<sku>.

Transport + endpoints

PathPurpose
GET /api/products?format=jsonScheduled full catalog snapshot; каждая строка уже содержит price fields.
GET /api/products?art=<sku>&entity=all&format=jsonProduct lookup. Возвращает JSON-массив, где первый элемент содержит имя, цену, НДС, бренд, media и ETIM-характеристики. entity=all обязателен для полного payload.
GET /api/commerce/Remains?format=jsonOptional scheduled commerce snapshot остатков; используется только при SUPPLIER_IEK_REMAINS_ENABLED=true.
GET /api/commerce/Remains?art=<sku>&format=jsonOptional per-SKU lookup остатков для live/proposal paths.

Рабочий prod-host сейчас https://lk.iek.ru; https://www.iek.ru/api/products отвечает redirect’ом туда. Коннектор принимает и корневой host https://lk.iek.ru, и полный endpoint https://lk.iek.ru/api/products.

Запрос всегда несёт Authorization: Basic <base64(login:password)>. Scheduled refresh не работает SKU-by-SKU: BulkSnapshotSource вызывает /api/products?format=json без art. Если SUPPLIER_IEK_REMAINS_ENABLED=true, commerce job дополнительно вызывает /api/commerce/Remains?format=json и раскладывает stock rows по SKU как KindRemains payload. При дефолтном false IEK commerce пишет price/goods observation без stock axis и не помечает тик partial из-за недокументированного endpoint. *_CATALOG_MAX_ITEMS=0 означает не ограничивать export; положительный лимит допустим только для smoke/debug.

iter-1-b не потребляет /api/ddp, /api/services, /api/seriesspecifications — добавится в Phase 5, когда появятся полные справочники.

Capabilities (статичное значение)

Определено в backend/internal/core/ingestion/infra/iek/capabilities.go:

  • Transport: REST
  • PriceSet: Net + Gross (вычисляются из price+vat+vat_included), ListTarif (активируется когда saleprice перекрывает price), VatRate как percent. RetailRec отсутствует.
  • Stock: false by default, StockForecast: false — публичный IEK API не документирует stock/remains. При SUPPLIER_IEK_REMAINS_ENABLED=true connector advertises Stock=true, текущие остатки идут из /api/commerce/Remains; forecast/delivery отсутствуют.
  • AsyncJobs: false — нет async-репортов.
  • Media: images + documents; watermarked=false (IEK-активы открытые).
  • MultiCurrency: только RUB.

Session policy

  • Нет логина. Basic Auth несётся на каждом запросе. SessionManager.Acquire возвращает SessionHandle с кредами в Opaque без сетевого вызова и без TTL (ValidUntil = +100 лет).
  • SessionManager.Release — no-op.
  • AuthBucket не задействован (тикер никогда не ждёт на логине).

Live contract notes

  • /api/products?format=json вернул 500 rows за ~0.6s (2026-05-07).
  • /api/commerce/Remains?format=json отвечает Basic Auth challenge на unauthenticated probe, но с текущим DB credential authenticated live probe возвращает 404 Route GET:/api/commerce/Remains?... not found (2026-05-30). До появления tenant-specific рабочего endpoint держать SUPPLIER_IEK_REMAINS_ENABLED=false.
  • price приходит number или null; saleprice в sample был null.

Rate limits

BucketЛимитScope
DataBucket[goods/remains]1 req / 1s/api/products; /api/commerce/Remains только при включенном Remains

IEK публично не документирует свои лимиты — взят консервативный 1 rps дефолт. При появлении данных от IEK — обновить backend/internal/core/ingestion/infra/iek/di.go (параметр ratebuckets.NewMemoryBucket).

AuthBucket / AsyncPoll / другие классы — не применимы.

Error taxonomy mapping (ADR-0024 §4)

HTTPConnectorErrorCodeПоведение оркестратора
200(none)сохраняем payload под KindGoods
200 + []NotFoundпишем пустое observation (§3.1.3); SKU остаётся в seed
401AuthRejectedвозвращаем наверх без повторов (iter-1-b: ручной resume)
429RateLimitedskip SKU этого тика; повтор в следующем
4xx (прочее)Permanentobservation с source_outcome=partial
5xxTransientповторить по RetryPolicy (1→2→4s)

Отдельных ETM-nuances вроде «403 → SessionExpired» здесь не требуется — IEK сессий нет.

EgressPolicy

FixedIPRequired = false — IEK API публичный, whitelist IP не требует. Контраст с ETM (FixedIPRequired = true). Запуск в prod не требует предварительной регистрации IP.

Imitation service

deploy/docker/resources/mock-iek/ — Go stdlib HTTP server под compose profile test. Эмулирует /api/products и /api/commerce/Remains, проверяет Basic Auth (любой непустой Basic валиден), специальные SKU: IEK-NOTFOUND200 [], IEK-RATELIMITED → 429.

  • docker compose --profile test up -d mock-iek — поднять.
  • Порт 9100 внутри сети compose (публикация в host не настроена).
  • Fixtures — в deploy/docker/resources/mock-iek/fixtures/; три happy-path SKU (IEK-ABC123, IEK-DEF456, IEK-GHI789).
  • ADR-0033: mock — merge blocker. Реальный IEK sandbox запускаем только через TRACIUM_IEK_REAL=1 вручную.

Seed enumerator

SeedEnumerator читает backend/config/iek-seed.yaml — 30 синтетических SKU. Первые 5 — документированные маркеры (happy + два error-пути), остальные 25 — placeholder’ы IEK-XYZ001..IEK-XYZ025. Phase 5 заменит seed-enumerator на dictionary-driven (полный каталог через /api/ddp + /api/products).

Env: SUPPLIER_IEK_SEED_FILE (default backend/config/iek-seed.yaml).

Конфигурация (env)

EnvDefaultНазначение
SUPPLIER_IEK_BASE_URLТочка входа IEK API (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_FILEbackend/config/iek-seed.yamlПуть к seed-yaml
SUPPLIER_IEK_REMAINS_ENABLEDfalseВключает optional stock axis /api/commerce/Remains; держать false, пока live credential не подтвердил endpoint.
SUPPLIER_IEK_REMAINS_PATH/api/commerce/RemainsPath optional stock endpoint при включенном SUPPLIER_IEK_REMAINS_ENABLED.
SUPPLIER_SYNC_ENABLEDetmCSV-список поставщиков в тике. Для активации IEK — etm,iek

Пустой BaseURL → IEK infra-module отдаёт zero-value ConnectorBundle, ingestion-aggregator его фильтрует, тикер молча пропускает supplier (сам supplier-sync стартует нормально).

References

  • ADR-0024 supplier connector contract
  • ADR-0033 supplier imitation services mandated
  • spec docs/superpowers/specs/2026-04-20-ingestion-phase1-a-design.md §3.1
  • IEK public API: https://www.iek.ru/products/api/intro/
  • runbook docs/docs/30-services/ingestion/supplier-sync-runbook.md
  • provider-contract suite: backend/test/provider-contract/iek_connector_contract_test.go