Skip to content

fix(cacbg): преходът от режима преди #309 е декларирано оттегляне, не находка - #333

Merged
todorkolev merged 1 commit into
mainfrom
fix/cacbg-preevidence-null-rules
Aug 24, 2026
Merged

fix(cacbg): преходът от режима преди #309 е декларирано оттегляне, не находка#333
todorkolev merged 1 commit into
mainfrom
fix/cacbg-preevidence-null-rules

Conversation

@todorkolev

Copy link
Copy Markdown
Collaborator

Какво

Snapshot заявката в load.mjs вече оставя null за връзка без ред в interest_link_evidence, вместо
да я маскира с текущата RULES_VERSION. Гейтът за монотонност в audit.mjs вече чете това като
декларирано оттегляне и отпечатва точно основанието "the pre-evidence regime — predates §8 and #309".

Защо

interest_link_evidence е добавена от #309 (acfcdcef). Връзки, публикувани преди това, нямат ред в
нея - на staging това са всички 103. Snapshot заявката прави LEFT JOIN и мапваше липсата с
r.rules_version ?? RULES_VERSION. Схемата държи rules_version TEXT NOT NULL, значи fallback-ът се
задейства САМО при LEFT JOIN пропуск, тоест точно на прехода.

Ефектът: първото пълно пускане към staging след бумването (32736025202) стигна до одита с 731/731
присъди и 277 публикувани, но гейтът отчете:

## monotonicity — 103 published last run, 277 now; 37 regression(s), 0 declared removal(s)

Режимът, който беше публикувал тези 103, вече не съществува. tr-rules-2 изисква регистърно
доказателство, а те нямат такова, защото таблицата не е съществувала. Но fallback-ът вкарваше
tr-rules-2 за всичките - гейтът виждаше "изчезнала при непроменена версия" вместо "изчезнала при
смяна на режима".

Поправката

Три части, всяка обяснена в коментарите:

  1. load.mjs: премахнат ?? RULES_VERSION. rules_version е NOT NULL в схемата, така че липсата
    винаги означава "няма ред", което е точно преходният случай. Пази null и предава семантиката.
  2. audit.mjs: declaredRemoval вече чете null !== RULES_VERSION → true. Изричен клон в
    съобщението назовава основанието с думи ("the pre-evidence regime"), вместо да отпечатва "rules
    change (null → tr-rules-2)".
  3. Тестове (два): в audit.test.mjs - snapshot с rules_version: null → декларирано оттегляне и
    думите се появяват. В load.test.mjs - симулира преходния случай (изтрива реда за евидиране
    между две пускания) и чака null в експортирания snapshot. Убива връщането на fallback-а.

Всичко: 46 минават. Работният път (връзка с ред за евидиране) не се променя - fallback-ът е бил
неактивен при него от NOT NULL нататък.

Какво следва

При пускането след сливането: гейтът вижда 103-те стари като декларирани оттегляния, минава, а
пътят продължава към Apply schema → Ship → Reindex. sigma-stage.midt.bg/conflicts се напълва.

Извън обхвата

Дали 37-те стари връзки трябва пак да се публикуват под tr-rules-2 е отделен въпрос за
методологията, не за гейта. Ако да - от следващото пускане ще имат ред за евидиране и ще се държат
нормално.

… находка

Snapshot заявката прави LEFT JOIN на interest_links към
interest_link_evidence и мапваше липсващия ред към текущата RULES_VERSION
(rules_version ?? RULES_VERSION). Схемата има rules_version NOT NULL,
значи fallback-ът се задейства САМО когато няма ред за връзката - точно
случая на 103-те връзки, публикувани преди #309 да въведе таблицата.

Ефектът беше отпечатан: първото пълно пускане към staging след
бумването на правилата (32736025202) стигна одита с 731/731 присъди и
277 нови публикувани, но гейтът за монотонност отчете 37 регресии и 0
декларирани оттегляния - режимът, който ги беше публикувал, вече не
съществува, а fallback-ът маскираше това.

Решението: премахни fallback-а. Липсата на ред остава null, а
declaredRemoval в audit.mjs чете null !== RULES_VERSION → декларирано
оттегляне. audit.mjs добавя изричен клон, който отпечатва точно това
основание, вместо "rules change (null → tr-rules-2)".

Тестове: 46 минават. Тестът в load.test.mjs симулира точно преходния
случай - изтрива реда за евидиране между две пускания и чака null в
експортирания snapshot. Убива връщането на fallback-а обратно.
@github-actions

Copy link
Copy Markdown

Test coverage

Workspace Lines Δ Branches Δ Functions Statements
apps/etl 75.43% +1.43pp 63.52% +5.32pp 70.00% 74.11%
apps/web 91.07% +0.07pp 82.44% +0.04pp 91.30% 89.75%
packages/config 92.85% +0.05pp 72.22% +0.02pp 92.85% 89.18%
packages/db 94.55% +0.05pp 79.34% +0.04pp 87.29% 91.58%
packages/ingest 89.71% +3.41pp 85.52% +5.12pp 81.74% 88.14%
packages/shared 95.50% +0.00pp 80.83% +0.03pp 92.30% 89.56%
Total (informational) 91.43% 81.30% 87.21% 89.32%

✅ No workspace dropped below its baseline (tolerance 0.5pp).

📈 Coverage rose by more than 1pp — run node scripts/check-coverage.mjs --update locally and commit coverage-baseline.json to ratchet the threshold up.

@todorkolev
todorkolev merged commit e72aab1 into main Aug 24, 2026
5 checks passed
@lyubomir-bozhinov

Copy link
Copy Markdown
Collaborator

Гейт-фиксът е коректен — проверих го при e72aab1b. null rules_version от LEFT JOIN пропуск наистина може да е само прех-#309 връзка (interest_link_evidence.rules_version е NOT NULL), а насочването ѝ към „декларирано оттегляне" (null !== RULES_VERSION, с null-клона пръв за точното основание) спира фалшивото падане на монотонността точно за този еднопосочен преход. Двата теста го заковават.

Едно нещо за отделна проверка — не блокер на този PR, и въпрос към #309 rebuild-а, не към гейта: 37-те връзки, които изпаднаха от публикувания сет при прехода (103 → 277 публикувани, но 37 от старите ги няма). Гейтът вече минава по тях като „оттеглени по регимен преход" — което е вярно САМО ако всичките 37 наистина не покриват evidence бара на #309 (нямат document/confirmed ред след rebuild-а). Ако някоя е истинска връзка, която rebuild-ът просто не е ре-evidence-нал, тя тихо изчезва от публичната повърхност — оттеглянето е безопасната посока за libel, но е загуба на истинска прозрачност (същия клас като 23-те върнати в #226).

Струва си еднократен spot-check на staging: за 37-те link_key — има ли след rebuild ред в interest_link_evidence с evidence_kind IN ('document','confirmed')? Ако има и пак са изпаднали → publish/гейтът ги дропва по друга причина, която трябва да се хване; ако нямат → оттеглянето е коректно и въпросът е затворен.

todorkolev added a commit that referenced this pull request Aug 26, 2026
Първата версия на печата имаше преходен капан от същия вид, който #332 и
#333 вече ни удари: всички съществуващи кешове са отпреди печата, тоест
ход без обхождане (понеделнишкият cron) щеше да откаже на extract - не
защото корпусът е отрязан, а защото е СТАР. И поправката щеше да зависи
от поредността "слей, после пусни full_crawl=true преди понеделник".

Вместо това ходът се лекува сам. Нова стъпка след възстановяването
проверява печата; краулът тръгва при full_crawl ИЛИ при липсващ печат.
Върху пълен кеш това е ~2 минути (файловете на диска се прескачат) и
сверява срещу живия регистър - авторитетния източник, а не евристика
върху това какво има на диска. Запазването и проверката на кеша следват
същото условие, за да не се изгуби довършеното.

Стабилно състояние с печатан кеш остава без мрежа, по замисъла на
графика: лекуването се задейства само за кеш, който никога не е бил
потвърден цял - точно случаят, в който довярването му е бъгът.

Гейтът в extract.mjs остава непроменен и вече означава друго: всички
пътища дотам гарантират печат, значи задействането му е непредвидено
състояние, не позната преходност. Fail-closed е верният отговор за това.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants