ADR-0065: Per-supplier поведение через инъекцию стратегий + канонический словарь SupplierRef

Status: accepted Date: 2026-07-07 Deciders: Maxim Belkanov

Контекст

Логика конкретного поставщика протекла в общие слои (domain/app/platform), а выбор поведения делается switch/if по строке поставщика вместо инъекции стратегии. Строковый ключ ("etm", "russvet", …) не типизирован и встречается ~1160 раз без единого источника правды. Каждый infra/normalization-модуль держит собственный const SupplierName = "...", синхронизируемый вручную; sentinel-тест каталога проверяет только len(supportedSuppliers), не паритет с зарегистрированными модулями — добавление поставщика в каталог без правки всех модулей компилируется, но падает в runtime. Это нарушает ADR-0031 (domain чистый, взаимодействие через порты), ADR-0030 (композиция в DI-корне) и ADR-0064 §4 (generic-код домен-агностичен).

Решение

  1. Специфика поставщика — за интерфейсом в правильном BC (ingestion/normalization infra), выбирается в DI-корне через существующий fx-group реестр (bundles/normalizers), а не ветвлением по строке. Новый рукописный локатор не вводим (ADR-0030 rule 16); fx-group→map легитимен, т.к. собирается самим fx.
  2. Канонический типизированный SupplierRef — единственный источник правды. Набор констант (SupplierETM, SupplierIEK, SupplierSysteme, SupplierDKC, SupplierRussvet, SupplierETMWeb) + KnownSuppliers() (5 customer-facing, без etm_web) + ParseSupplierRef() живут в supplier-network/domain. Per-module const SupplierName = ссылка на канонический ref, ручная синхронизация устранена.
  3. switch/if по поставщику и голые supplier-литералы в domain/app/platform запрещены. Per-supplier поведение выражается стратегией/конфигом, инъектируемым по SupplierRef.
  4. Энфорсмент. Sentinel-тест паритета: supplier_catalog.All()KnownSuppliers() ↔ множество Normalizer.Name() зарегистрированных customer-facing нормализаторов совпадают. Расширение cmd/layerlint: supplier-литерал / switch-on-supplier в domain/app/platformактивен как error (фаза 0 — warn; Phase 4 полного sweep’а — эскалировано до error, 2026-07-07). Долг до-ADR фиксируется в allowlist линтера (сокращать, не расширять). Исключение: catalog/taxonomy_builder/app/ — text-matching по меткам таксономии авторской разметки (смесь канонических ссылок и синонимов брендов); архитектурно запрещено импортировать supplier-network/domain.

Рефакторинг ведётся фазами (фундамент → packing → характеристики → периферия → sweep → refs.go → триаж TODO), каждая фаза = отдельный PR-scope с gitnexus_impact перед правкой, gitnexus_detect_changes перед коммитом, make backend-layerlint + backend-archlint + go test.

Последствия

Плюсы

  • Единый типизированный словарь поставщиков; рассинхрон каталог↔модули ловится compile-time/sentinel-тестом вместо runtime-падения.
  • Новый поставщик подключается регистрацией стратегии в DI, без правок generic-слоёв.
  • domain/app/platform очищаются от supplier-специфики (behaviour-preserving).

Минусы

  • Фаза полного sweep’а (~1160 литералов) массова и рискует задеть map-ключи/фикстуры; митигируется пофазностью по слоям и build+test per слой.
  • refs.go/Warehouse.Supplier затрагивает 10+ BC importers (proposal-pipeline); изолированная поздняя фаза.

Нейтральные последствия

  • etm_web остаётся вне KnownSuppliers()/каталога (internal-only), появляется лишь как SupplierETMWeb и в AllForAdmin().

Рассмотренные альтернативы

Отдельный kernel/domain для словаря

Отклонено: отдельного kernel/domain в проекте нет (есть только catalog/kernel/infra — утилиты). SupplierRef уже живёт в supplier-network/domain и импортируется 10+ BC; новый общий kernel не оправдан.

Новый рукописный supplier-локатор

Отклонено ADR-0030 rule 16: расширяем существующий fx-group реестр, а не заводим параллельный ручной registry.

Ссылки

  • ADR-0011 (no-proxy), 0024 (supplier connector contract), 0030 (fx DI + rule 16), 0031 (microkernel sub-modules), 0039 (handler registry), 0047 (strategy pure-functions), 0064 (clean layers + generic-код домен-агностичен §4).
  • Design spec: docs/superpowers/specs/2026-07-07-supplier-strategy-architecture-design.md.
  • План фазы 0: docs/superpowers/plans/2026-07-07-supplier-strategy-phase0-foundation.md.