ETM connector
core/ingestion/infra/etm — iter-1 reference implementation of ADR-0024
supplier-connector contract.
Transport + endpoints
Base URL is environment-selected: local mock http://mock-etm:9000, ETM test https://itest2.etm.ru/api/v1, ETM prod https://ipro.etm.ru/api/v1.
| Path | Kind | Purpose |
|---|---|---|
POST /user/login | auth | exchange login/password for session token |
| async catalog report | catalog | live enumerator получает catalog snapshot через ETM report |
GET /goods/{sku} | goods | diagnostic/single SKU product descriptor + characteristics |
GET /goods/{sku1,sku2,...}/price | price | scheduled refresh batch по 25 SKU |
GET /goods/remains | remains | scheduled refresh warehouse stock export for all goods |
GET /goods/{sku}/remains | remains | diagnostic/single SKU fallback |
Login uses form-urlencoded log/pwd. All data-plane requests carry type=etm&session-id=<id> in the query string after first login.
Capabilities (static)
Defined in backend/internal/core/ingestion/infra/etm/capabilities.go:
- Transport: REST
- PriceSet: net + gross + list_tarif + retail_rec (no VAT rate)
- Stock + StockForecast: true; warehouse kinds regional_center, logistics_center, pickup_office, manufacturer_warehouse
- AsyncJobs: declared true (seed enumerator does not trigger iter-1)
- Media: images + videos + certificates; watermarked=true; unwatermarked gate = requires_agreement
- MultiCurrency: RUB only
Session policy
- Login gated by AuthBucket (1 req / 120s).
- SessionHandle cached under
tracium:etm:session:<credential_ref>in Redis (TTL 2h). In-memory fallback when Redis nil. - 403 on a Fetch → re-login single retry (session invalidated server- side).
- 3 consecutive AuthRejected after fresh login → mark supplier failing (iter-1: log + stop, manual resume).
Rate limits
| Bucket | Limit | Scope |
|---|---|---|
| AuthBucket | 1 req / 120s | login only, blocking wait |
| DataBucket[goods] | 1 req / 1s | per request |
| DataBucket[price] | 1 req / 1s | per request |
| DataBucket[remains] | 1 req / 1s | per request |
| DataBucket[async_poll] | 1 req / 1s | declared; Phase 5 usage |
Data-bucket exhaustion returns ConnectorError{Code:RateLimited} and
orchestrator skips the SKU for the current tick.
Scheduled refresh не работает SKU-by-SKU: catalog приходит report snapshot,
prices идут batch по 25 SKU, stock берется bulk endpoint /goods/remains и
фильтруется по текущему catalog snapshot. При *_CATALOG_MAX_ITEMS=0 это
полный snapshot, а не window. В live local-prod прогоне 2026-05-06:
1000 catalog items, 1000 price payloads за 318691 ms, stock export за 2672 ms,
24 SKU с ненулевым stock payload. raw_remains=1000 означает обработанную
stock axis; пустой stock для SKU не является ошибкой.
Live contract 2026-05-07: /goods/{sku1,...,sku50}/price вернул 50 rows за
~18.6s; price fields (price, pricewnds, price_tarif, price_retail) в
этом ответе number. /goods/remains вернул 11588 rows за ~3s; GdsCode,
StoreCode, RemInfo приходят string и преобразуются bulk source’ом в
InfoStores для stock mapper.
Error taxonomy mapping (ADR-0024 §4)
| HTTP | ConnectorErrorCode | Orchestrator behaviour |
|---|---|---|
| 200 | (none) | store payload |
| 401 | AuthRejected | surface to SessionManager → retry once then fail tick |
| 403 | SessionExpired | invalidate cache; re-login single retry |
| 404 | NotFound | write empty-map observation (§3.1.3); SKU stays in seed |
| 429 | RateLimited | skip SKU this tick; retry next tick |
| 4xx (other) | Permanent | write observation with source_outcome=partial |
| 5xx | Transient | retry with exp-backoff (1→2→4s) |
EgressPolicy
FixedIPRequired=true for real ETM. dev-stack (mock-ETM) ignores;
prod deploy runbook (docs/docs/30-services/ingestion/supplier-sync-runbook.md)
gates on whitelist verification.
Imitation service
deploy/docker/resources/mock-etm/ — Go stdlib HTTP server under
compose profile test. Covers happy path + NOTFOUND/RATELIMITED
special SKUs. Mandated by ADR-0033. Live validation against ETM test
or prod is gated behind TRACIUM_ETM_REAL=1 in the provider-contract
suite.
Seed enumerator
SeedEnumerator reads backend/config/etm-seed.yaml — 30 synthetic
SKUs iter-1. Full-catalog enumerator arrives Phase 5.
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 - runbook
docs/docs/30-services/ingestion/supplier-sync-runbook.md - detailed transport reference:
./etm/spec.md,./etm/mapping.md,./etm/rate-budget.md