# softsign-decl — разрыв мягкого знака в декл-пост-чеке банка (fix-pack §а, finding-1) Замер $0 к backend-бэклог-строке 91 (finding-1): чем закрывать разрыв, когда мягкознаковое имя («Фан Юань», «Винный червь») склоняется («Фан Юаня», «Винного червя»), а стеммер §3 «ь» не стрипает, и потому декл-форму НЕ принимает. Наивное «добавить «ь» в `decl_suffix`» отвергнуто в fix-pack (глобальная коллизия «Синь/Линь»→«синий/линия»). Вопрос: движковый anchor-gated матч ЛУЧШЕ ли сид-перечисления — и безопасен ли он на реальном корпусе, а не на бумаге. ## Метод (конвенция полигона) - Харнесс `main.go` — БОЕВОЙ стеммер `lang.NewTargetStemmer(lang.TargetChecksFor("ru"))`; `build.sh` материализует `go.mod`+`replace` ВНЕ репо, пин бэкенда на коммит (`git archive`) — прецедент pkg7/minerharness. Ноль сети, ноль моделей, $0. Пин прогона: `78b7b34` (HEAD на момент замера — «ь» уже реверсирован, D39.75). - Корпус: банк `~/books/gu-zhenren/coldrun-a/BANK-FULL.tsv` (147 уник. dst) × боевой 25-глав-выход `~/books/gu-zhenren/acceptance/guzhenren-25ch-ru.txt` (37 480 слов, editor=glm-5, приёмка Ф1). - Сырьё/выход — durable `~/books/gu-zhenren/softsign-decl/` (правило 9): `softsign_probe_out.txt` (отчёт харнесса), `adversarial-review.txt` (author≠reviewer прогон). - Правило 10 (вскрытие единиц + 2-й путь счёта): каждый агрегат ниже сверен независимым `grep -oE` по боевому выходу; A0-реимплементация харнесса сверена с реальным (unexported) `dstFormPresent` in-package пробой — 0/29 расхождений — до порта на экспортные символы. Четыре правила матчинга декл-окна (база-цельнословная не меняется — она уже ловит номинатив): - **A0** — боевое: `declinedFormPresent` (mempostcheck.go:169-191), «ь» НЕ стрипается. - **A1** — наивное: «ь» в реестр, на КАЖДЫЙ компонент, БЕЗ якоря (отвергнутое; для контроля вреда). - **A2** — anchor-gated: мягкий стрип разрешён, если ДРУГОЙ компонент совпал ТОЧНО. - **A3** — anchor-gated: мягкий стрип разрешён, если ДРУГОЙ компонент совпал по НОРМАЛЬНОМУ стему (⊇ A2). ## Цифры ### Разрыв реален, но КОНЦЕНТРИРОВАН и много-словный 29 из 147 dst несут мягкознаковое слово. Захват окон над A0 (occurrence-level): | dst | слов | soft→stem | A0 | A3 | A3−A0 | статус | |---|---|---|---|---|---|---| | Фан Юань | 2 | юань→юан | 324 | 508 | **+184** | TRUE (склонение хвоста «Юаня/Юаню/Юанем/Юане», якорь «фан» точный) | | Винный червь | 2 | червь→черв | 46 | 113 | **+67** | TRUE (оба слова склонны: «Винного червя»; A2 промахивает ВСЕ 67) | | ветвь Чи | 2 | ветвь→ветв | 0 | 3 | +3 | TRUE (soft в ГОЛОВЕ, якорь «чи» точный) | | ветвь Фан | 2 | ветвь→ветв | 1 | 3 | +2 | TRUE | | демонический путь | 2 | путь→пут | 1 | 2 | +1 | TRUE («демонического пути») | | горная крепость | 2 | крепость→крепост | 0 | 1 | +1 | TRUE («горной крепости») | | девять рангов | 2 | девять→девят | 1 | 2 | +1 | **МЯГКАЯ НЕТОЧНОСТЬ** (см. ниже) | Итого A3−A0 ≈ **259 окон**, 258 истинных + 1 мягкая. A2−A0 = 189 (A2 промахивает 70 истинных «Винного червя» и др. — нет ТОЧНОГО якоря → A2 хуже A3). ### Практический эффект — на ПОРЯДОК меньше 259, и число НЕ робастно Пост-чек срабатывает ПО-ЧАНКОВО (наличие любой принятой формы в чанке), не по вхождениям. Частые имена почти всегда имеют номинатив в том же чанке. «Склонён-ТОЛЬКО (номинатив отсутствует)» — СИЛЬНО зависит от размера блока (2-й путь счёта, самопроверка поймала завышение): | блок ~симв | Фан Юань | Винный червь | Мочэнь | Чилянь | ИТОГО/25 глав | |---|---|---|---|---|---| | 800 | 32 | 22 | 2 | 1 | 57 | | 1500 | 8 | 16 | 1 | 1 | 26 | | 3000 | 0 | 12 | 1 | 0 | 13 | | **6000** | **0** | **6** | **1** | **0** | **7** | | **12000** | **0** | **4** | **0** | **0** | **4** | Боевой чанк ≈ 1–2k ТОКЕНОВ (~6–12k симв) → честный per-chunk промах = **~4–7 на 25 глав, почти весь — «Винный червь»**; «Фан Юань» на реальном размере чанка → **0** (номинатив всегда рядом). Прежняя оценка «~26» была артефактом блока 1.5k симв (меньше боевого чанка) — снята. ⚠ Это ПРОКСИ (не боевой чанкер; faithful — через `tmctl export` границ чанков). Порядок величины (одноцифровой, один термин) робастен. ### A1 отвергнут корректно — вред ГЛОБАЛЬНЫЙ, книго-независимый M4: `softSameStem("Синь","синим")=true`, `("Линь","линий")=true`, `("Тень","теней")=true` — стем совпадает («син/лин/тен»). Голое «ь» в реестр поражает глобальный `SameStem`, а через него — весь путь `declinedFormPresent`, т.е. и `Postcheck`, и `SpoilerLeaks`, в любой книге. Единственный функциональный потребитель `SameStem` — `declinedFormPresent` (mempostcheck.go:181); `seeding.go:138` только конструирует стеммер; `voice.go` его НЕ подключает. ## Adversarial-ревью (author≠reviewer) — что оно сломало A3 **не закрывает** коллизию Синь/Линь — оно её **ПЕРЕНОСИТ** с одно-словного класса на форму `[общий-титул] + [ь-фамилия]` (самая частая много-словная форма имени в жанре): - **Перенос (структурно ВЕРНО, здесь НЕ реализовано):** гипотетический банк-термин «старейшина Синь» под A3 ложно примет «старейшина синего» (титул «старейшина» — нормальный якорь, «синего»→«син» мягко). В gu-zhenren этого НЕТ: 18 из 30 soft-строк — CJK-транслит (юан/шэн/чилян/мочэн), чьи 3-руновые стемы просто не русские слова. Чистый прогон — **транслит-везение корпуса, не свойство гейта.** Форма-риска в банке есть («старейшина школы», «вождь четвёртого поколения»); реализуется в книге с русско-коллизионной фамилией. (Проверка: «старейшины синеватый» ×1 в выходе есть, но «синеватый»→«синеват»≠«син» — не сработало бы; точный «старейшина синего» — конструкт, не корпус.) - **Числовой класс — системно и НЕ защищён (реально на корпусе):** каждый ь-финальный кардинал (девять/десять/пять/шесть…) мягко сливается с ординалом/косвенной формой. В выходе: «пятого ранга» ×13, «шестого ранга» ×10, «девятого ранга» ×1 — поверхность 24+ вхождения; якорь («ранга») — общее слово, гейт даёт **ноль защиты**. «девять рангов»~«девятого ранга» — та самая 1 реализованная мягкая неточность. Класс растёт со счётом рангов (плотный в культивации). - **Поправка по SpoilerLeaks:** мой тезис «A3 не трогает SpoilerLeaks» — неверно обоснован. `declinedFormPresent` достижим из `SpoilerLeaks` (mempostcheck.go:254) → A3 его МЕНЯЕТ. Спасает не «неприкосновенность», а то, что SpoilerLeaks — observability-only, не диспозиция (mempostcheck.go:241): мягкий ложняк там = шум, не флип гейта. A3 всё же СТРОГО лучше A1 для SpoilerLeaks (закрывает одно-словный глобальный яд), но не чист (много-словный титул+фамилия остаётся). - **Ни один вариант не доминирует:** min-стем-гейт бесполезен («юан» и «син» оба 3 руны — не разделить); tail-restricted (soft только на последнем компоненте) точнее здесь, но теряет «ветвь Чи/Фан» (голова склоняется) и всё равно ловит хвост-фамилию; стоплист коллизий — хорош как ДОБАВКА, плох соло (открытый список — форма, от которой `target-ru.txt:19` уже предостерегает); сид-перечисление — золото для подписного канона, но не масштабируется на авто-майненный хвост. ## Вывод и рекомендация (на ратификацию, НЕ строить без слова владельца) > Заострено после 2-го адверсариал-прогона (аудит кода+рекомендации). Код харнесса — TRUSTWORTHY > (6/6 NO-BUG, 147 dst сверено с файлом). Рекомендация исправлена: §3 ниже НЕ «ноль действия». 1. **Разрыв реален, но мал:** ~4–7 ложных промахов/25 глав на реальном размере чанка, почти весь — ОДИН термин «Винный червь» («Фан Юань» → 0 per-chunk). Числа окон код-верифицированы. 2. **Голое A3 НЕ рекомендуется:** переносит коллизию на титул+фамилию (риск в другой книге) и не защищает числовой класс; чистота на gu-zhenren — транслит-везение (пара-зависимое доказательство). 3. **Стопгэп для finding-1 — сид-перечисление декл-форм для 1–2 ВИДИМЫХ мягких канон-имён** («Винный червь»; «Фан Юань» если важен SpoilerLeaks): `declForms` матчатся `containsWholeWord` ТОЧНО — ноль стемминга, ноль коллизии, ноль правок Go; данные книжного канона в сиде — легитимная общность. ⚠ Честно: `decl.forms` — РУКО-ПИСАНЫЙ сид ([memseed.go:89](../../backend/internal/membank/memseed.go#L89)), майнер морфологию НЕ майнит (его «decl» = ОТВЕРГНУТЫЕ поверхности, miner_emit.go:60); генератора склонений в Go нет. Значит для ВСЕГО мягкого класса это = ручной труд «142/142-null», который §3 и убивал → сид-перечень оправдан ТОЛЬКО как ограниченный стопгэп на горстку видимых имён, НЕ системно. 4. **Движковый anchor-gated матч — не «отложить» отмашкой, а ЗАГЕЙТИТЬ на корпус-замере** частоты ложняков переноса ([титул]+[ь-фамилия]) и числового класса — они НЕ измерены (M4 = 3 руко-выбранные пары, одна книга = предварительно, CLAUDE.md §4), а не опровергнуты. Форма, если строить — СЛОЙНАЯ (tail-restricted + стоплист ь-кардиналов), данные в `target-ru.txt`, алгоритм generic; но ядро Go не трогать, пока кросс-парный замер (≥2 пары) не оправдает. 5. Backend-действие сейчас: NB-коммент + регресс-гвоздь (`stemmer_test.go`, `TestSoftSignNamesDoNotCollideWithCommonWords`) ОСТАЮТСЯ как страховка от наивного ре-адда «ь». Гвоздь «Линь/линию» инертен — заменить на «Линь/линий» (M4 подтверждает: «линий»→«лин»). 6. **Долгий общий дом (НЕ существует сегодня):** майнер-морфология (наблюдать склонённые поверхности, а не кодировать «ь») был бы пара-агностичен — но это ОТДЕЛЬНЫЙ механизм bank-autonomy, не бесплатный и не «минер уже майнит» (ранняя формулировка была неверна — такого пути нет). ## Воспроизвести ``` cd eval/softsign-decl && ./build.sh HEAD /softsign-decl/softsign-decl \ -bank ~/books/gu-zhenren/coldrun-a/BANK-FULL.tsv \ -output ~/books/gu-zhenren/acceptance/guzhenren-25ch-ru.txt \ -report ~/books/gu-zhenren/softsign-decl/softsign_probe_out.txt ``` Прогон ~135с (O(слова×термины×режимы)). go.mod/replace — вне репо; второго модуля в git не появляется.