ADR-0047: canonical_assignments policy + strategies + trust scale
Status: accepted Date: 2026-04-30 Deciders: agent-claude
Контекст
Закрытый цикл 1 (ADR-0046) реализовал foundation авто-fill характеристик с единственной стратегией auto:latest. Продуктовое наблюдение: характеристики канонических товаров стабильны во времени; данные от разных поставщиков чаще комплементарны, чем противоречивы. Слепое перезаписывание свежим значением, которое случайно может оказаться менее точным или содержать опечатку, ухудшает качество каталога.
Требования к циклу 2:
- Поведение замены значения зависит от характеристики (одни блокируются — например MPN/артикул, другие могут уточняться, третьи накапливают множество значений).
- Стратегия первичного выбора учитывает доверие к поставщику.
- Шкала доверия настраивается админом, поддерживает per-(supplier, dimension) overrides.
- Заменять существующее автоматическое значение можно только если новое объективно «чётче».
- Multi-valued характеристики (цвета, разъёмы) не выбирают одно «победившее» значение, а накапливают множество с дедупликацией.
- Админ может точечно дополнять/удалять элементы множества, не перезаписывая всё назначение.
Решение
-
Per-characteristic policy — новая колонка
characteristics.policy text NOT NULLс CHECK constraint:single_locked— заполнили один раз, дальше не трогаем (auto не перезаписывает).single_sharpenable— авто может заменить, но только если новое значение объективно «чётче» по формальной метрике.multi_union— массив значений с дедупликацией по нормализованному ключу. Дефолт при auto-provision выводится черезDerivePolicy(data_type, code): эвристика по data_type (numeric → sharpenable; enum → multi_union; остальное → locked) плюс ручной override-список для известных кодов (mpn,model_number,manufacturer,sku,series,gtin,ean,barcode→ принудительно locked).
-
Захардкоженная цепочка стратегий первичного выбора
trust_weighted → mode → latest. Применяется при первом заполнении и при tie-break sharper-replacement. Без per-characteristic merge_strategy конфигурации — поведение управляется политикой, не выбором стратегии. -
Двухуровневая trust scale поставщиков:
supplier_trust_defaults(supplier_code, score smallint CHECK 0..100, ...)— дефолтный score per-supplier (sparse — отсутствие записи = 50).supplier_dimension_trust(supplier_code, dimension, score, ...)— override per-(supplier, dimension). Resolver fallback: dimension-override → supplier default → global 50.
-
Источник
dimensionдля категории: новая колонкаunits.dimension text(nullable). Сидится миграцией для 12 канонических размерностей (voltage, current, power, energy, length, mass, time, frequency, temperature, pressure, angle, area). Характеристика → unit → dimension → category для trust override. -
Sharpness chain (для
single_sharpenable) — три regex-правила с приоритетом:- ① диапазон → точка внутри диапазона (
"10-15A"vs"12A"). - ② значение с единицей → значение без единицы (
"220"vs"220V"). - ③ больше значащих цифр после запятой при одинаковых единицах (
"12"vs"12.5"). Если ни одно не сработало — keep existing.
- ① диапазон → точка внутри диапазона (
-
Multi-union с дедупликацией: значения хранятся как JSONB-массив
[{display, key}], ключ =lowercase(trim(value)). Индекс по выражению((value->>'key')) WHERE value ? 'key'для быстрой проверки существования. Display сохраняет оригинальное написание от первого поставщика. -
Расширение
sourceCHECK constraint на 7 значений:auto:latest,auto:mode,auto:trust_weighted,auto:sharpened,auto:union,manual,manual:union. Расширяет foundation cycle 1 (auto:latest,manual). -
decision_meta jsonb— новая колонка для observability. Воркер пишет{deciding_supplier, trust_score, trust_source}; GET assignments отдаёт в response. -
Hard reset migration (миграция 0052):
DELETE FROM canonical_assignments WHERE source LIKE 'auto:%'. БД пустая на момент деплоя цикла 2 — безопасно. -
Архитектурная декомпозиция β: новые подпакеты
catalog/canonical/app/strategy(pure functions: ChooseInChain, AssessSharpness, MergePolicy, NormalizeUnionKey) иcatalog/canonical/app/trust(Resolver interface). Постгрес-реализацияTrustRepoвinfra/postgres. Зеркалит паттерн P3/P5a/Tier 3. -
Sticky-override manual — наследуется из cycle 1: SQL
WHERE source LIKE 'auto:%'guard + domain-policy. Расширяетсяmanual:unionsource для административных операций над элементами множеств. -
Admin HTTP-API (11 новых endpoints):
PATCH /admin/characteristics/{id}/policy,GET /admin/units+PATCH /admin/units/{id}/dimension, два уровня trust CRUD (/admin/trust/suppliers,/admin/trust/suppliers/{code}/dimensions), точечные операцииPOST/DELETE /admin/canonical/{id}/assignments/{cid}/values. Все за Bearer middleware, монтируются в loopback :9093 черезloopbackhttp.Server.Extrahook.
Принятые ограничения
- Сравнение значений только по форме: семантическая нормализация (
Вт ↔ кВт,,↔.) — отдельная программа, см. roadmap §7. Sharpness работает с raw JSON через regex. - LLM-разрешение спорных случаев отложено в цикл 3.
- Per-characteristic merge_strategy не вводится — управление через policy достаточно.
- ETIM/eCl@ss классификация остаётся в roadmap §7.
dimensionединицы — упрощённая категория для цикла 2. - Multi-dimensional trust matrix (ADR-0026 §5b) — упрощённая 2-уровневая модель в цикле 2.
Альтернативы
- Per-characteristic merge_strategy колонка: отвергнута — добавляет точку конфигурации без подтверждённой потребности; цепочка стратегий покрывает случаи.
- Trust-tier (gold/silver/bronze) вместо score: отвергнут — числовая шкала позволяет точную настройку и расширение до multi-dim позже.
- LLM подбирает policy при auto-provision: отвергнут — лишний токен-бюджет, эвристика + ручной override-список покрывают 80% случаев.
- Категория из ETIM-класса: отложен в roadmap §7 (ETIM-классификация ещё не реализована).
Последствия
- Существующее значение в
canonical_assignmentsне перетирается без явного основания. Поток данных от ингеста становится append-only по умолчанию. - Trust scale настраивается через API, изменения вступают в силу со следующего тика воркера.
- Multi-union массивы естественно накапливают значения; админ-API позволяет точечную модерацию.
decision_metaобеспечивает observability — модератор видит, кто и почему принял решение.- Schema migration: 7 миграций (0048-0054), каждая откатываемая. БД на момент деплоя пустая — нет рисков для существующих данных.
Связанные документы
- Spec:
docs/superpowers/specs/2026-04-30-canonical-assignments-strategies-design.md - Plan:
docs/superpowers/plans/2026-04-30-canonical-assignments-strategies.md - Roadmap:
docs/superpowers/specs/2026-04-29-canonical-assignments-roadmap.md(§4) - Cycle 1: ADR-0046, foundation execution memory
- Foundation pattern: P3 (handler registry), P5a (ranking), Tier 3 (matcher) — pure-functions подпакеты + I/O resolver