Генерация артикула резолвила lot_category из одного прайслиста конфигурации (GetLocalLotCategoriesByServerPricelistID), поэтому world-only LOT (напр. GPU_NV_RTX_PRO_6000D_SERVER_84GB_PCIE, есть только в world) молча выпадал из артикула. Теперь категории берутся через GetLocalComponentCategoriesByLotNames (тот же world ∪ estimate, что и весь конфигуратор). BuildOptions.ServerPricelist убран; preview-article принимает pricelist_id, но игнорирует. Нераспознанные токены больше не пишутся как UNK: в артикул идёт lot_category как плейсхолдер (4xGPU, 2xCPU), сегмент помечается Recognized=false, добавляется warning с именем LOT. Конфигуратор подсвечивает такие сегменты (amber) и выводит список предупреждений; сохранение/обновление/откат логируют WARN. Без каталога вендоров в репо детектируется только структурный сбой формы имени. parseGPUModel: принимает суффикс-букву в номере модели (6000D → RTX6000D), раньше терял её и схлопывал до RTX_84GB. ADL bible-local/decisions/2026-09-01-article-category-from-component-universe.md, 2026-09-01-article-degraded-token-visibility.md; раздел «Article generation» в 02-architecture.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RAhfF4P1ySRZ67yyUUeVw2
3.5 KiB
Decision: unparsed article tokens degrade to a visible category placeholder, never "UNK"
Date: 2026-09-01 Status: active
Context
internal/article builds each segment by parsing a spec token out of the lot_name
(CPU model, memory size, GPU model, disk capacity, port speed, wattage). When a
lot_name did not match the expected shape the generator failed silently or
misleadingly:
parseCPUModel/parseGPUModelfell back tonormalizeModelTokenor a literal"UNK"with no signal — and a partial parse (RTX_PRO_6000D→RTX_84GB) produced a plausible but wrong token indistinguishable from a real result;NET/PSUemittedUNKNET/UNKPSU;MEM/DISKdropped the segment or the capacity and emitted only a tersemem_unknown/disk_unknownwarning;BuildResult.Warningsexisted but nothing consumed it: thepreview-articlefrontend ignored the field and the create/update paths discarded it.
So a new naming shape (a vendor renames a card family, moves memory ahead of the model, glues an architecture onto the token) would silently produce a wrong or truncated article and no one would notice.
A vendor/model catalog in the repo to validate tokens against was rejected — it
violates bible/rules/patterns/no-hardcoded-vendors and needs constant upkeep.
Detection is therefore limited to structural failure (the name doesn't fit
{GROUP}_{VENDOR}_{MODEL}[_{SPEC}…]); semantic drift where a wrong token still
parses cleanly cannot be caught automatically and is out of scope.
Decision
"UNK"/"UNKNET"/"UNKPSU"are gone. When a spec token can't be parsed the article carries the LOT'slot_categoryas the token (4xGPU,2xCPU,768G+MEM, …) viaplaceholderToken.- Each
build*Segmentreturns asegmentResult{value, degraded, warnings}. Every degraded segment produces a warning that names the offendinglot_nameand states which category was written instead. BuildResultgainsSegments []ResultSegment{Group, Text, Recognized}.Recognized == falsemarks a segment whose token is a placeholder.parseCPUModel/parseGPUModelreturn(token, ok);ok == falseon the last-ditch fallback path.- Consumers surface it:
POST /api/configs/preview-articlereturnssegmentsandwarnings; the configurator's article line renders each not-recognizedsegment highlighted (amber) and lists the warnings beneath it (renderArticleDisplayinweb/templates/index.html);- create / update / rollback log
WARN "article generation degraded"with theserver_model, article and warnings.
- No list of real
lot_names or model names is committed to the repo — not as a fixture, not as a golden table. Tests use synthetic names only.
Consequences
- An unrecognised LOT is always visible in the UI (highlighted token + warning) and
in server logs — never a silent drop or a bare
UNK. - The stored
articlestring still contains only the placeholder token; therecognizedflags are not persisted (regenerated on next save / preview). compressArticlenow returns[]namedSeg(the caller re-joins) so the degraded flags survive compression.- Structural detection only: a wrong-but-well-formed token (e.g. a future
GTXwhereRTXwas expected) still passes. Closing that needs a validation source, deferred pending a server-side structured-attribute contract. - Covered by
TestBuild_UnparseableModel_CategoryPlaceholderandTestParseGPUModel_VendorLetterSuffix.