Методологія
Як перевіряється модуль
Кожен модуль встановлюється в чисту базу відповідної серії Odoo з офіційного образу
odoo:<серія>, без демо-даних, командою -i <module> --stop-after-init.
Результат — код виходу процесу і лог.
Ставимо без demo-даних, і це навмисно
Точний прапорець — --without-demo=all, і це не деталь нашого стенду, а те, як
виглядає production-база. Модуль, чий post_init_hook чи data-файл звертається до
запису, який його ж манифест оголошує лише під demo:, у розробника з
демо-даними встановиться, а тут упаде. Це падіння справжнє — те саме сталося б і на
production-установці, — тому вердикт лишається, і перезапускати такий модуль із demo, щоб
зробити його зеленим, ми не будемо. Замість цього називаємо причину словами на сторінці
модуля, над сирим логом, щоб ніхто не мусив читати трейсбек, аби дізнатись: відсутній запис —
демонстраційний.
Це не проблема середовища: той статус лишається за тим, чого бракує в нашому образі. Тут в образі не бракує нічого.
Статуси
| Статус | Значення |
|---|---|
| ✓Встановлюється | Встановлюється |
| !Із попередженнями | Із попередженнями |
| ▲Блокує залежність | Блокує залежність |
| ~Проблема середовища | Проблема середовища |
| ✗Помилка install | Помилка install |
| ⏱Таймаут | Таймаут |
Що ми свідомо НЕ вважаємо несумісністю
Падіння через відсутній зовнішній python-пакет або системну утиліту в образі позначається як проблема середовища і не зараховується модулю. Так само — якщо сам харнес не подав модуль до Odoo. Це навмисно: без такого розділення статистика була б неправдивою, а неправдивий індекс не вартий нічого.
Одне виправлення зроблено без повторного прогону. 30 серпня 2026 ми виправили правило класифікатора, яке з'їдало назву пакета: Odoo пише «external dependency is not met: Python library not installed: transitions», а правило захоплювало лише перше слово після двокрапки, і вердикт читався як «Python». На стабільних серіях 119 вердиктів перейменовано за збереженим логом, нічого не встановлюючи заново — сам лог і дата прогону не змінились, змінилась лише назва того, що в лозі й так було написано. Шістнадцять із них заодно переїхали з «немає python-пакета» в «немає системної утиліти»: це інша проблема середовища, а не інший вердикт про модуль. Такі рядки позначені в датасеті як неперепрогнані.
Від чого рахуються відсотки
Кожен опублікований відсоток називає свій знаменник, бо відсоток без знаменника пересувається простою зміною того, що рахувати. Модуль вважається прогонабельним, лише якщо ми можемо його дістати, він сам заявляє себе встановлюваним, і серія — з тих, які ми справді проганяємо. Не входять ні в чисельник, ні в знаменник:
- Не встановлювані за манифестом — модуль сам ставить
installable: False. Це метапакети, залишки неперенесеного коду й оболонки для депрекації. Зарахувати їх до «зламаних» означало б рухати головну цифру разом із кількістю метапакетів, а це не має жодного стосунку до сумісності з версією. - Серія, яку ми не можемо прогнати — така, у якої ще немає гілок або немає перевіреного образу, — позначається як не охоплена, а не як така, що чекає прогону. Сьогодні це 20.0, у якої гілок OCA немає взагалі; кожна серія, у якої гілки є, проганяється повністю.
- Неможливі до перевірки — див. нижче про Apps Store.
Охоплення серій на момент генерації сторінки, «перевірено / прогонабельних»: 16.0 — 3 134/3 134 · 17.0 — 1 979/1 979 · 18.0 — 3 020/3 020 · 19.0 — 1 421/1 421 · 20.0 — 4/4. Повністю пройдені: 16.0, 17.0, 18.0, 19.0, 20.0.
Тому «96,3% встановлюються чисто» — частка на головній на момент генерації цієї сторінки — завжди пишеться повністю: скільки зі скількох перевірених, і окремо скільки прогонабельних із загальної кількості. Число в цьому реченні рахується разом із таблицею, а не набирається поруч із нею.
Чому для лінії розробки ми піднімаємо версію в манифесті
Odoo відмовляється встановлювати модуль, чия version у манифесті не починається
з серії самої платформи. Це порівняння рядків, а не перевірка коду, і воно стається до
того, як платформа прочитає хоч рядок модуля:
def check_version(version, should_raise=True):
serie = release.major_version # '19.5' на лінії розробки
if version.startswith(serie + '.'):
return True
Є помічник, який дописує серію автоматично, але в нього умова на довжину:
def adapt_version(version):
...
if len(version_parts) <= 3 and not version.startswith(serie):
return f"{serie}.{version}" # префікс лише версіям із 2–3 частин
return version
У OCA канонічна версія має п’ять частин, тому префікс не дописується ніколи.
Виміряно 25.08.2026: з 1 226 модулів гілки 19.0 п’ятичастинну версію оголошують
усі 1 226. Шлюз не проходить жоден. Прогін без підняття версії дав би 1 226 однакових
рядків uninstallable — те саме твердження про платформу, повторене 1 226 разів,
і жодного твердження про хоч один модуль.
Тому ми піднімаємо один рядок. Підняття версії — буквально перший механічний крок будь-якого порту: мейнтейнер, що портує модуль, змінює цей рядок першим і змінює його завжди. Ми відтворюємо стан, через який проходить кожен порт, а не вигадуємо стан, якого не буває. Що змінюється і що ні:
- Копіюється рівно один файл —
__manifest__.py. Решта теки модуля — симлінки на git-чекаут, тобто побайтово те, що лежить на GitHub. - У цьому файлі змінюється рівно одне значення —
version. Підстановка механічна: провідна серія замінюється наrelease.major_versionплатформи, решта частин зберігається:19.0.1.2.0→19.5.1.2.0. Не нормалізація, не переписування. - Платформу ми не патчимо. Стокова Odoo — це і є суть: саме її ми міряємо. Знявши шлюз в образі, ми отримали б не той Odoo, який поставить партнер.
Підняття версії нічого не лікує й нічого не приховує — але без нього платформа відмовляє ще до того, як прочитає код.
Дві назви для двох різних тверджень. В опублікованих даних прогін, що впав через
НАШ стенд — бракує python-пакета в образі, немає бінарника, якого ми не веземо, — несе
env_problem, і модулю це ніколи не зараховується. Прогін, демотований тому, що
зламала платформа, а не модуль, несе attributed_platform. До 19 вересня 2026 обидва
звалися env, і те саме слово означало «наша діра» до атрибуції і «платформи» після
неї.
Два датовані прогони і чим вони різняться
Лінію розробки зміряно двічі, і обидва виміри лишаються опублікованими. Перший ішов
10 вересня 2026 на нічній збірці master, яка тоді мала версію
19.5a1. Другий — 18–19 вересня 2026 на справжній гілці
20.0, закріпленій на коміті 1cb24d94. Тоді офіційної нічної збірки й
образу odoo:20.0 для тієї гілки не існувало, тому образ платформи зібрано з самої
гілки; офіційні нічні збірки 20.0 з'явились 24 вересня 2026, уже після того прогону.
Три речі відрізняються від опису вище, і кожна міняє сенс чисел:
- Версія, до якої ми піднімаємо, — це версія самої платформи:
19.0.1.2.0→19.5.1.2.0у першому прогоні й19.0.1.2.0→20.0.1.2.0у другому. Той самий один рядок, інша ціль. - Другий прогін іде на замороженому пулі: дерева модулів витягнуті з git на тих самих ревізіях, що й у першому прогоні, і записані справжніми файлами, а не симлінками в рухомий чекаут. Кожен модуль звірено з першим прогоном двома хешами — манифеста й решти дерева, — тож «той самий код» доведений, а не заявлений. Без цього гілка зрушила б під нами: за вісім днів код змінився у 115 із 1 321 модуля.
masterбільше не є наближенням 20.0. Його нічні збірки несли19.5a1до 17 вересня і20.1a1з 18 вересня — наступного дня після відгалуження. Жодна нічна збірка master ніколи не мала мітки 20.0.
Один прохід 10 вересня позначений недійсним і не враховується. Він ішов на застарілій
шаблонній БД — шаблон був зібраний ранішим образом — і дав 3,9% установок там, де
чинний прохід, перезапущений того ж дня о 12:59 UTC, дав 31,9% на тих самих модулях. Його 1 294
рядки лишаються в даних із названою причиною в runs.invalid_pass: ми не прибираємо
докази тихо. Усе опубліковане їх відсікає, а перевірка збірки не пропускає запит, який читає
таблицю прогонів і про них не згадує.
Скільки модулів це взагалі зачіпає — рахунок без жодного прогону
На 19.0 права доступу й правила запису — це дві окремі моделі: ir.model.access (оголошена в ir_model.py) і ir.rule (у ir_rule.py). У наступних серіях ir_rule.py зникає, а поруч з'являється ir_access.py з _name = 'ir.access', який замінює обидві. Це не перейменування однієї моделі, а злиття двох моделей безпеки в одну — і воно є вже в saas-19.4, тобто у ВИПУЩЕНІЙ лінії, а не лише в лінії розробки.
Скільки модулів це зачіпає, можна порахувати не запускаючи нічого: ім'я файла або значення model= — це ім'я моделі, третього стану немає. Підрахунок по знімку пулу від 2026-09-10, 1 321 модулів гілки 19.0:
| Що несе модуль | Модулів |
|---|---|
тільки ir.model.access.csv | 277 |
тільки записи model="ir.rule" | 25 |
| і те, і те | 137 |
| ні того, ні того | 882 |
торкається нової ir.access | 439 з 1 321 (33,2%) |
Це єдине число тут, яке не залежить від жодного нашого прогону: воно повторюється на будь-якому чекауті OCA тим самим підрахунком. Падає й той модуль, що цих назв у python-коді не згадує жодного разу, — тому шукати треба в даних, а не в коді.
Як перевірити самому
Кожен результат лінії розробки несе sha256 оригінального __manifest__.py,
рядок версії до і після, і sha256 дерева модуля без манифеста — щоб твердження
«решта побайтово така сама» було перевіряним, а не обіцянкою. Сам шлюз — одна
команда:
docker run --rm --entrypoint python3 <образ> -c "
from odoo.modules.module import adapt_version, check_version
import odoo.release as r
print('release.major_version =', r.major_version)
for v in ('19.0.1.0.0','19.5.1.0.0','1.0'):
print(repr(v), '-> adapt', adapt_version(v),
'| check_version', check_version(v, should_raise=False))"
release.major_version = 19.5
'19.0.1.0.0' -> adapt 19.0.1.0.0 | check_version False
'19.5.1.0.0' -> adapt 19.5.1.0.0 | check_version True
'1.0' -> adapt 19.5.1.0 | check_version True
Модуль, який чесно написав 19.0.1.0.0, не проходить, а модуль, який написав
1.0, проходить. Оце і є шлюз, і він про рядок.
Що результат лінії розробки означає, а що ні
Означає: код модуля запускається на лінії розробки після механічного підняття версії — або падає, і з якою саме помилкою. Не означає, що модуль сумісний з Odoo 20.0, і не означає, що модуль встановлюється на Odoo 20.0: на стоковій платформі він не встановиться, поки мейнтейнер не підніме версію.
Тридцять один модуль, якого лінія розробки не покриває
Знаменник — 1 291, а не 1 321. Тридцять один модуль гілки 19.0 оголошує
'installable': False, і ті самі тридцять один досі оголошують версію
18.0.* — множини збігаються точно. Вони лежать у трьох репозиторіях
(storage 19, queue 6, rest-framework 6); усі тридцять один є і на гілці 18.0, і жоден не
збігається побайтово зі своєю копією на 18.0 — лінія 18.0 рухалась далі після відкриття гілки.
Ми їх не проганяємо, і причина та сама, що дозволяє проганяти решту. Підняття версії — це
один рядок. Щоб ці тридцять один поставилися, довелося б змінити другий —
installable, — а цей рядок є власною заявою мейнтейнера, що модуль не готовий.
Перебити її означало б не відтворити крок порту, а заперечити модулю. Як є, усі тридцять один
повернуть той самий uninstallable з уже відомої причини, тобто прогін не додасть
інформації ні так, ні так. Вони поза кожним відсотком на цьому сайті, і на стокових серіях теж.
Платформа рухається, тому кожен результат називає її: тег образу, дату прогону й версію
платформи. Образ зібрано з датованого нічного пакета
(odoo_19.5a1.20260821_all.deb з nightly.odoo.com/master/nightly/deb/) —
саме він ідентифікує збірку: коміта odoo/odoo нічний .deb не несе.
Обмеження
- Каталог Apps Store ми не обходимо й не викачуємо: Store не має способу перелічити
свої листинги, який дозволяв би його власний
robots.txt, а платний модуль без ліцензії не встановлюється — ліцензій у нас немає, і купувати їх ми не будемо. Тому платний модуль з’являється тут лише тоді, коли його подав автор, і перевірити його ми можемо тільки з коду, який дав автор. Тоді він стоїть на окремій сторінці, поза всіма числами OCA, з тими самими даними, що й будь-яке подання, — назва, версія, серія, ліцензія, залежності — і ніколи з ціною чи оцінкою якості. - Install-прогін не є тестом функціональності: модуль може встановитися і працювати неправильно.
- Батч-режим: при масовому проході модулі ставляться групами, при падінні групи кожен
перевіряється окремо. У даних це позначено полем
batched.
Публікуємо факт прогону з датою і логом, а не оцінку якості вендора.
Де ми помилялись
Індекс, який ніколи не помилявся, — це індекс, якого ніхто не перевіряв. Кожен дефект нижче був на бойовому, більшість видно було ззовні, і в кожному рядку сказано, що тепер робить це неможливим. За рівної ваги новіші вище; записи не редагуються заднім числом, лише дописуються.
2026-08-25 Наш звіт GoAccess про трафік лежав публічно за адресою
/stats.html— 784 КБ, у тілі 382 унікальні IP-адреси відвідувачів (персональні дані третіх осіб за GDPR) і карта нашої ж інфраструктури: 404-и, шляхи, User-Agent’и, обсяги. Безnoindex. Публічним він був з 21.08 12:08 до 25.08 09:48.Звіт писався просто в публічний корінь сайту, і на вміст того кореня ніхто не дивився. Знайшлося це зовнішнім вимірюванням, не в нас.
Чого ми НЕ можемо довести: Логи доступу починаються 22.08 о 01:16, тобто близько тринадцяти годин того вікна нічим не покриті, і ми не можемо сказати, що в них було. У наявних логах до сторінки шість запитів, усі шість — від людини, яка нас перевіряла; жодного краулера, у
site:її немає.Тепер неможливо: Звіт живе тільки в приватному каналі; IP анонімізуються на вході (
--anonymize-ip);/stats.htmlвіддає 404 окремим правилом; аcheck_site_root()завалює збірку на будь-якому файлі в корені сайту, якого туди не поклав генератор.2026-08-26 Усі 774 опубліковані уривки логів починались із падіння нашого ж образу (збій
.pthуtyping_extensionsна старті інтерпретатора), а не з того падіння, доказом якого вони були, — бо вікно уривка анкерувалось на першомуTracebackу лозі. Для 389 із них — усіх прогонів із вердиктом «моделі немає в цій версії» — рядкаKeyError, який цей вердикт доводить, в опублікованому уривку не було взагалі. Числа два, бо міряють різне: 774 уривки стояли не в тому місці, а 389 вердиктів достовірно опубліковані без доказу під ними.Самі вердикти були правильні. Саме тому це гірше за помилковий вердикт: ми продаємо доказ, а доказ під правильною відповіддю виявився чужим.
Чого ми НЕ можемо довести: У решті 214 прогонів, класифікованих як помилка в XML, 98 уривків корінь таки містили; для інших обрізане вікно його не показує, тому поштучно сказати, які саме зачеплені, ми не можемо.
Тепер неможливо: Класифікатор повертає рядки, з яких виведено причину; збирач уривка падає, якщо їх немає у вікні; експорт відмовляється публікувати вердикт, під яким їх немає. 27.08 цей самий інваріант спіймав другий, протилежний наш дефект — 50 прогонів, де вікно стояло вже ПІСЛЯ доказу, — і саме для цього він і потрібен.
2026-08-24
llms.txt— файл, який читають мовні моделі — публічно заявляв «Tested: 1» при 9 067 протестованих парах модуль × серія. Головна писала «Ported 19.0→master 0,1%» і «master вийшов 11 місяців тому».ORDER BY seriesсортує текстом, томуmasterставав після19.0і робився «найновішою» серією, — а від найновішої рахується майже кожна цифра на головній.masterпри цьому взагалі не гілка OCA, це ключ образу.Тепер неможливо: Серії фільтруються й упорядковуються числом, одним фільтром на сайт,
llms.txtіstatus.jsonразом, плюс асерт, що найновіша серія — 19.0, поки жоден репозиторій OCA не має гілки 20.0 (в самої Odoo гілка з'явилась 17.09.2026).2026-08-26 Класифікатор приписував зміну платформи до «помилки в XML самого модуля». Щонайменше 96 із 214 прогонів мали неправильну причину — 81 про
ir.rule, 15 проir.model.access.Правило
views_xmlперевірялось передmodel_gone, а Odoo загортаєKeyErrorз іменем відсутньої моделі вParseError— тобто обидва рядки завжди в лозі, і обгортка матчилась першою. «Щонайменше» — бо в решті 118 обрізаний уривок кореня не показує, а це дефект вище.Тепер неможливо: «Корінь перед обгорткою» більше не домовленість про порядок правил, а асерт: якщо в лозі є
KeyError: '<модель.з.точками>', причиною не може бути помилка в XML, ассетах чи правах, і класифікатор падає, замість публікувати таку.2026-08-25
www.allservices.oneвіддавав увесь сайт замість редиректу — разом із власнимrobots.txtіsitemap.xml, тобто кожна сторінка існувала одночасно за двома адресами.Блок редиректу просто не був написаний, і хост провалювався в основний.
Тепер неможливо:
wwwвідповідає 301 на апекс, і це перевіряється поіменно після кожної зміни конфігурації.2026-08-20 З дванадцяти вердиктів
warn, які ми мали того дня, одинадцять були нашими, а не модулів. Перевірку пережив рівно один.Дві незалежні причини. Батч має спільний лог, а класифікатор рахувався раз на весь батч — тому попередження, що належало одному модулю, було записане ще сімом із трьох інших репозиторіїв. А ще чотири — це
DeprecationWarningсамого CPython про сторонню SWIG-бібліотеку, яку модуль лише імпортує: про його код воно не каже нічого, а на сторінці читалось як докір авторові.Тепер неможливо: Батч, який дав
warn, ділиться так само, як батч, що впав, а попередження зараховується, лише якщо в тому ж рядку названо код усередині Odoo. З 25.08 увесь прохід лінії розробки йде по одному модулю на батч, тобто в кожного модуля власний лог.2026-08-30 24 години сайт не публікував нічого. Щогодинна збірка падала на перевірці приватності, яка знайшла справжнє — адресу стороннього мейнтейнера у власному звіті про відвідування, — і не могла нічого вдіяти: файли разом із цією адресою вже були записані й віддавалися весь цей час. У лозі збірки при цьому стояв рядок «публікацію зупинено».
Збірка писала просто в теку, яку віддає веб-сервер, тобто запис БУВ публікацією, а всі перевірки стояли після нього. Це робило декоративними геть усі інваріанти, а не лише цей. Сама адреса повернулась до нас через наш же витік: URL, які ми публікували до того, як прибрали їх, лишились у базах краулерів, ті ходять по них далі, і звертання лягають у лог, з якого будується звіт. Добу цього ніхто не бачив, бо жоден юніт не сигналив про падіння.
Тепер неможливо: Збірка йде в теку, якої ніхто не віддає, перевірки працюють проти неї, і лише потім символьне посилання перемикається атомарним
rename. Тепер провал перевірки лишає вчорашній сайт — і це передбачений стан. Окремо: мертва форма URL відповідає 301 замість 200, у сирих логів названо термін зберігання, юніт про падіння шле лист, а другий сторож питає свіжість опублікованої сторінки по HTTP, а не код виходу юніта.2026-08-27 Набір лінії розробки міг бути оголошений повним у той час, як дев’ятнадцять модулів не проганялись жодного разу.
«Повний» порівнював пройдене з гілкою, а прогін іде по пулу, зібраному раніше. Модулі, додані в гілку після збірки пулу, не потрапляли ні в чергу, ні в результати, тому лічильник зійшовся б у нуль сам і опублікував набір як повний.
Тепер неможливо: Публікація додатково вимагає, щоб кількість прогонабельних модулів у пулі дорівнювала кількості справді прогнаних, а обидва знаменники стоять поруч у
status.json, кожен зі своєю датою.2026-08-26 Пул лінії розробки не був знімком: сімнадцять модулів прогнались проти коду, який уже підмінили під ними, посеред проходу.
Пул — це симлінки в git-чекаут, а нічна синхронізація робить цьому чекауту
git reset --hard. Результат, приписаний коду, якого вже немає, неможливо довести ні в той бік, ні в інший.Тепер неможливо: Sha дерева кожного модуля записується в момент збірки пулу, воркер звіряє його перед кожним прогоном і відмовляється при розбіжності, а синхронізація не робить fetch, поки не доведено, що черга порожня.
2026-08-26 Коміт, записаний поруч із результатом, був тим, що харвест побачив у віддаленому репозиторії, а не тим, що справді стояло в контейнері.
Поле означало не те, що про нього написано на сторінці, — той самий клас, що вердикт із чужим логом, тільки тихіший.
Тепер неможливо: Sha береться з того чекауту, який був змонтований, у момент прогону.
2026-08-21 Прибирання одноразових баз прогонів не спрацювало ЖОДНОГО разу: десять осиротілих баз, 270 МБ. До правки прибиралась рівно одна з дев’яти.
Дві незалежні причини в одному скрипті. Вік бази міряли за mtime теки, а його оновлює чекпойнт чи автовакуум — тому нічого ніколи не виглядало достатньо старим. А цикл
DROPчитав той самий stdin, яким подавався перелік баз, і з’їдав власний вхід після першого рядка.Тепер неможливо: Вік береться з файлів самої бази, а цикл більше не ділить stdin. Тепер прибирає все осиротіле за один прохід.
2026-08-24
/feed/master.xmlпережив прибирання, яке вилучилоmasterз переліку серій: порожній Atom-фід, який усе одно заявляв серію і на який усе одно можна було підписатися.Прибирання ходило лише по файлах
index.html, а виклик, яким воно їх видаляло, на файлі (а не на теці) не робить нічого взагалі — і мовчки, бо помилки ігнорувались.Тепер неможливо: Прибирання ходить і по фідах, і по сторінках, і відмовляється видаляти будь-що, якщо під видалення підпадає більше 2% сторінок або більше 100.
2026-09-18 Раннер передавав кожному прогону жорстко вписаний шлях адонів платформи —
/usr/lib/python3/dist-packages/odoo/addons. Для образу, зібраного з.debOdoo, він правильний, і такими були всі наші образи до 18 вересня 2026 — поки ми не зібрали образ із гілки20.0, де платформа лежить у/opt/odoo, а її адони розкладені у дві теки: 16 уodoo/addonsі 642 вaddons/.Хибний шлях не впав би. Odoo стартувала б, не знайшла ядрових бізнес-модулів і завалила б кожен модуль OCA за нерозвʼязаною залежністю. Прогін виглядав би як вимір платформи 20.0, а насправді зміряв би один рядок нашого власного коду — і це було б опубліковано під назвами чужих репозиторіїв.
Чого ми НЕ можемо довести: Ми не можемо сказати, як саме такий прогін прочитали б: його спинили до старту — smoke-тест і підрахунок тек з адонами, а не падіння прогону.
Тепер неможливо: Шлях питається в самого образу (
odoo.addons.__path__). Сусідня тека додається лише тоді, коли в основній немаєaccount— бо в офіційному образі така тека теж є (661 модуль), і безумовне додавання розширило б платформу під усіма наявними серіями, зробивши новий прогін непорівнянним зі старим через нашу ж правку.2026-09-10 Повний прохід лінії розробки — 1 294 прогони — виконано на застарілій шаблонній БД: шаблон був зібраний іншим образом, ніж той, на якому йшли прогони. Прохід дав частку
ok3,9%; чинний прохід, перезапущений того ж дня о 12:59 UTC, дав 31,9% на тих самих модулях.У шаблоні лежить установлений
base, і кожен прогін його клонує. Коли образ зрушив, а шаблон ні, Odoo оновлюєbaseзі схеми чужої збірки, і шкода дістається модулям — вони виглядають зламаними, тоді як зламаний стенд. Сам прогін про це не каже нічого: код виходу, лог і класифікатор поводяться звичайно.Чого ми НЕ можемо довести: Перекласифікувати ті прогони в щось осмислене неможливо: вони міряли стенд, який ми відтоді викинули, а не модулі.
Тепер неможливо: Рядки того проходу лишаються в даних і несуть названу причину в
runs.invalid_pass— ми не прибираємо докази тихо. Обидва опубліковані вигляди їх відсікають, перевірка збірки валить публікацію на будь-якому запиті доrunsбез згадки цієї ознаки, а раннер відмовляється стартувати, якщо шаблон зібраний не тим образом, яким він збирається гнати (цю перевірку додано того ж дня, і саме тому третього проходу не було).2026-09-02 Головна і
/blocking/публікували два різні числа того самого твердження — скільком модулям 18.0 немає версії на 19.0. Головна казала 1 834,/blocking/— 1 821, і обидві сторінки стояли під одним підписом «Дані оновлено».Дві одиниці на одне питання. Головна рахувала пари (репозиторій, модуль), тому модуль, який OCA між 18.0 і 19.0 переклала в інший репозиторій, виглядав неперенесеним;
/blocking/рахував назви — і це правильна одиниця, бо питання в тому, чи є модуль на 19.0, а не чи лежить він там, де лежав. Різниця — рівно ті 13 модулів, що переїхали, одинадцять із них — родинаbase_tier_validation, витягнута зserver-uxв окремий репозиторій.Чого ми НЕ можемо довести: Скільки саме часу два числа розходились, ми сказати не можемо: жодна зі сторінок не веде власної історії, обидві перезбирались щогодини. Рахунок парами такий самий давній, як головна.
Тепер неможливо: Обидві сторінки беруть число з однієї функції, а перевірка збірки валить публікацію, якщо вони знову розійдуться. Правильне число — менше: 1 821, і частка перенесених через це зросла з 36,9% до 37,3% — тобто виправлення на нашу користь, і саме тому воно вимагало перерахунку, а не переписування.
2026-09-02 Кожна англійська сторінка модуля друкувала причину падіння українською — «Помилка SQL при установці» там, де мало бути «SQL error while installing». Це було системно, а не на одній сторінці: зачеплений кожен вердикт із причиною.
Класифікатор пише людський підпис причини в базу вже однією мовою, а сторінка друкувала збережений рядок як є.
Тепер неможливо: Сторінка рендерить причину мовою сторінки зі збереженої, а перевірка збірки валить публікацію, якщо в підпису класифікатора немає англійської пари — тобто нове правило не зможе тихо повернути українську на англійські сторінки.
2026-09-02 П'ять днів статичний розклад
ir.accessна цій сторінці був порахований по знімку пулу, якого вже не існувало. Пул перезібрано 30.08 (1 261 модуль), а файл, який читає сторінка, лишився від 27.08 (1 245). Тобто єдине число тут, яке не залежить від жодного нашого прогону, стояло поруч із числами набору, зміряного на іншому знімку.Скрипт, який пише цей файл, не викликав ніхто. Його запустили руками один раз, і обіцянка «та сама дата, що й решта набору» трималась лише доти, доки хтось пам'ятав повторити запуск.
Чого ми НЕ можемо довести: Дата знімка весь цей час стояла поруч із числом, тобто читач міг побачити, який саме знімок воно описує. Довести, що хтось це побачив, ми не можемо.
Тепер неможливо:
poolsnap.pyпереписує розклад тим самим запуском, який замінює знімок, — дві дати більше не можуть роз'їхатись; а збій на цьому кроці називає себе в лозі, замість тихо лишити старий файл на місці.2026-09-28 З 24 по 28 вересня головна писала «Ported 19.0→20.0 1.6%», а трекер 20.0 рахував 25 модулів на гілках 20.0. Жоден не був перенесений: усі 25 — заглушки, якими OCA відкриває нову гілку, — копії з попередньої серії, версії досі 18.0/19.0, позначені
installable: False.«Перенесено на X» означало «на гілці X є тека з манифестом». Прапорець із манифеста для 20.0 ніхто не читав: розбір манифестів ходив лише по серіях, які проганяє індекс, а 20.0 серед них ще не було, — тож прапорець лишався порожнім, а порожній зараховувався як «встановлюваний».
Чого ми НЕ можемо довести: Те саме визначення рахувало перенесеними й 31 модуль, що є на 19.0, але там вимкнений. Тому число 18.0→19.0 зрушило з 1 814 на 1 845 модулів без версії 19.0; скільки часу кожен із цих 31 так рахувався, ми сказати не можемо — жодна зі сторінок не зберігає власної історії.
Тепер неможливо: «Перенесено на X» — це модуль на гілці X, який не вимкнений, однаково на головній і на
/blocking/, і збірка зупиняється, якщо вони розійдуться. 20.0 тепер у переліку серій індексу, тож її манифести розбираються щоночі.
Результат виглядає неправильним?
Повідомте — ми покажемо повний лог або перепрогонимо модуль. Це не формальність:
з дванадцяти вердиктів warn, які ми мали 20.08.2026, одинадцять були
нашими, а не модулів: перевірку пережив рівно один. Три вердикти fail
того дня виявились справжніми. Знайшлося це лише тому, що хтось відкрив лог — див.
журнал дефектів.
Основний канал — issue на GitHub: історія публічна, і видно, що ми відповідаємо. На кожній сторінці модуля є посилання, яке вже підставляє модуль, серію, дату прогону й вирок. Пошта — hello@allservices.one.
Два файли, які не є вердиктами
pr-queue.csv на сторінці датасету — черга рев'ю з табла 20.0 по
репозиторіях: відкриті pull request-и, чернетки, змерджені, відкриті за останні 24 години, і час
знімка. stuck.csv — кожен модуль 18.0, що не
встановлюється на 19.0, з колонкою why, виведеною лише з даних: є на гілці
19.0, але вимкнений; його називає відкритий pull request; бракує залежності; репозиторій
стоїть; або unknown. PR — це намір, установка — факт: жоден із файлів не
входить у відсотки сайту, і жоден нічого не каже про те, хто що супроводжує.
Дізнаватись, не даючи себе відслідковувати
Каналів два, і різниця між ними навмисна. Atom-фід анонімний: ні адреси, ні реєстрації, усі події індексу. Список сповіщень — протилежний обмін: ви даєте адресу й чуєте лише про названу серію або названі модулі. Фід нікуди не дівається, а табло 20.0 несе виключно фід.
Список зберігає адресу, випадковий токен, ваш вибір і часові мітки. Ні IP, ні User-Agent, ні звідки ви прийшли. Посилання підтвердження й відписки несуть токен, а не адресу, і це не обережність про запас: адреса в URL опиняється в лозі веб-сервера, і в серпні чужа адреса, яку ми самі вписали в URL, повернулась до нас через власний лог і на добу зупинила збірку сайту. Обидва посилання відкривають сторінку з кнопкою, а не діють по кліку: поштові клієнти передзавантажують посилання, а підписка, підтверджена сканером, підпискою не є. Відписка видаляє рядок, а не ставить позначку.
Maintainer, Module Health Index