Методологія

Як перевіряється модуль

Кожен модуль встановлюється в чисту базу відповідної серії 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-пакета» в «немає системної утиліти»: це інша проблема середовища, а не інший вердикт про модуль. Такі рядки позначені в датасеті як неперепрогнані.

Від чого рахуються відсотки

Кожен опублікований відсоток називає свій знаменник, бо відсоток без знаменника пересувається простою зміною того, що рахувати. Модуль вважається прогонабельним, лише якщо ми можемо його дістати, він сам заявляє себе встановлюваним, і серія — з тих, які ми справді проганяємо. Не входять ні в чисельник, ні в знаменник:

Охоплення серій на момент генерації сторінки, «перевірено / прогонабельних»: 16.0 — 3 100/3 100 · 17.0 — 1 932/1 932 · 18.0 — 2 934/2 934 · 19.0 — 1 290/1 290. Повністю пройдені: 16.0, 17.0, 18.0, 19.0.

Тому «96,4% встановлюються чисто» — частка на головній на момент генерації цієї сторінки — завжди пишеться повністю: скільки зі скількох перевірених, і окремо скільки прогонабельних із загальної кількості. Число в цьому реченні рахується разом із таблицею, а не набирається поруч із нею.

Чому для лінії розробки ми піднімаємо версію в манифесті

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 разів, і жодного твердження про хоч один модуль.

Тому ми піднімаємо один рядок. Підняття версії — буквально перший механічний крок будь-якого порту: мейнтейнер, що портує модуль, змінює цей рядок першим і змінює його завжди. Ми відтворюємо стан, через який проходить кожен порт, а не вигадуємо стан, якого не буває. Що змінюється і що ні:

Підняття версії нічого не лікує й нічого не приховує — але без нього платформа відмовляє ще до того, як прочитає код.

Скільки модулів це взагалі зачіпає — рахунок без жодного прогону

На 19.0 права доступу й правила запису — це дві окремі моделі: ir.model.access (оголошена в ir_model.py) і ir.ruleir_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.csv277
тільки записи model="ir.rule"25
і те, і те137
ні того, ні того882
торкається нової ir.access439 з 1 321 (33,2%)

Це єдине число тут, яке не залежить від жодного нашого прогону: воно повторюється на будь-якому чекауті OCA тим самим підрахунком. Падає й той модуль, що цих назв у python-коді не згадує жодного разу, — тому шукати треба в даних, а не в коді.

Що з цього вийшло: 1 290 модулів

Знаменник — датований знімок пулу. 10.09.2026 06:16 UTC: 1 290 модулів гілки 19.0, придатних до прогону, і саме на них прогін виконувався. Гілка тим часом росте: на 10.09.2026 03:28 UTC на ній 1 290 таких модулів. Розриву між знімком і гілкою зараз немає.

86 (6,7%) запускаються на лінії розробки без жодної зміни коду, крім рядка версії. 1 189 не встають через зміни в самій платформі: той самий код і той самий sha дерева дають ok на 19.0, тому різницю не можна віднести до модуля. 15 падають з власною помилкою, яка є й на серіях, що вже вийшли.

Найчастіша причина названа й порахована. Із 1 204 непройдених model_gone дає 131, а access_model — 10: разом 141 із 1 204 (11,7%). Обидві причини — це одне й те саме злиття ir.model.access і ir.rule у нову ir.access, названа з двох різних боків: перша ловить звернення до зниклої моделі, друга — файл прав, який більше нікуди покласти.

Окремо — чий файл упав, за шляхом у лозі. Пул адонів плоский, тож перший сегмент шляху після /mnt/pool це ім'я модуля, і власник файла читається з самого логу. З 1 204 падінь 42 не мають файла-винуватця за природою: залежності немає в addons-path або відсутній оголошений python-пакет, тобто падіння сталося на перевірці манифеста, до читання будь-якого файла. Питання «чий файл» до них не стоїть, тому вони не в знаменнику.

Лишається 1 162 падінь, у яких файл є. З них 51 — у файлі самого модуля, 123 — у файлі іншого модуля, тобто вердикт стосується залежності, а не того, чию сторінку ви читаєте. Ще 988 (85,0%) лишаються без імені файла, і рівень логування тут ні до чого: набір прогнано на --log-level=info.

На INFO Odoo друкує рядок odoo.modules.loading: loading <файл>, і взяти його прямо було б спокусливо — він підняв би атрибуцію відразу на кілька десятків падінь. Ми його так не беремо: цей рядок називає останній оголошений файл, а не той, що впав. Коли падіння сталося вже після завантаження даних, він вкаже на цілком сторонній файл — тобто ми отримали б свіжий екземпляр рівно тієї помилки, від якої тут захищаємось, тільки з виглядом виміру. Тому рядок зараховується лише разом із кадром трейсбека, який доводить, що падіння сталося всередині завантаження файла (load_data, convert_file, convert_csv_import, convert_xml_import). Другий запобіжник: рядок мусить бути в тому уривку логу, що надрукований на сторінці модуля — атрибуція з тексту, якого перед читачем немає, не перевіряється очима.

Ці 988 — майже цілком дві причини: sql_error — 896 із 897, not_installed_despite_rc0 — 76 із 76 (разом 972 із 988). Обидві спрацьовують після завантаження даних, тому ім'я файла в лозі є, а чий це файл — з нього не виводиться. Вони не розкидані між двома попередніми категоріями, а стоять окремо, бо цього ми не виміряли.

Помодульно, разом зі шляхом файла й сирими raw_status/raw_cause до атрибуції — devline.csv.

Як перевірити самому

Кожен результат лінії розробки несе 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 290, а не 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 не несе.

Обмеження

Публікуємо факт прогону з датою і логом, а не оцінку якості вендора.

Де ми помилялись

Індекс, який ніколи не помилявся, — це індекс, якого ніхто не перевіряв. Кожен дефект нижче був на бойовому, більшість видно було ззовні, і в кожному рядку сказано, що тепер робить це неможливим. За рівної ваги новіші вище; записи не редагуються заднім числом, лише дописуються.

  1. 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() завалює збірку на будь-якому файлі в корені сайту, якого туди не поклав генератор.

  2. 2026-08-26 Усі 774 опубліковані уривки логів починались із падіння нашого ж образу (збій .pth у typing_extensions на старті інтерпретатора), а не з того падіння, доказом якого вони були, — бо вікно уривка анкерувалось на першому Traceback у лозі. Для 389 із них — усіх прогонів із вердиктом «моделі немає в цій версії» — рядка KeyError, який цей вердикт доводить, в опублікованому уривку не було взагалі. Числа два, бо міряють різне: 774 уривки стояли не в тому місці, а 389 вердиктів достовірно опубліковані без доказу під ними.

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

    Чого ми НЕ можемо довести: У решті 214 прогонів, класифікованих як помилка в XML, 98 уривків корінь таки містили; для інших обрізане вікно його не показує, тому поштучно сказати, які саме зачеплені, ми не можемо.

    Тепер неможливо: Класифікатор повертає рядки, з яких виведено причину; збирач уривка падає, якщо їх немає у вікні; експорт відмовляється публікувати вердикт, під яким їх немає. 27.08 цей самий інваріант спіймав другий, протилежний наш дефект — 50 прогонів, де вікно стояло вже ПІСЛЯ доказу, — і саме для цього він і потрібен.

  3. 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, поки гілки 20.0 не існує.

  4. 2026-08-26 Класифікатор приписував зміну платформи до «помилки в XML самого модуля». Щонайменше 96 із 214 прогонів мали неправильну причину — 81 про ir.rule, 15 про ir.model.access.

    Правило views_xml перевірялось перед model_gone, а Odoo загортає KeyError з іменем відсутньої моделі в ParseError — тобто обидва рядки завжди в лозі, і обгортка матчилась першою. «Щонайменше» — бо в решті 118 обрізаний уривок кореня не показує, а це дефект вище.

    Тепер неможливо: «Корінь перед обгорткою» більше не домовленість про порядок правил, а асерт: якщо в лозі є KeyError: '<модель.з.точками>', причиною не може бути помилка в XML, ассетах чи правах, і класифікатор падає, замість публікувати таку.

  5. 2026-08-25 www.allservices.one віддавав увесь сайт замість редиректу — разом із власним robots.txt і sitemap.xml, тобто кожна сторінка існувала одночасно за двома адресами.

    Блок редиректу просто не був написаний, і хост провалювався в основний.

    Тепер неможливо: www відповідає 301 на апекс, і це перевіряється поіменно після кожної зміни конфігурації.

  6. 2026-08-20 З дванадцяти вердиктів warn, які ми мали того дня, одинадцять були нашими, а не модулів. Перевірку пережив рівно один.

    Дві незалежні причини. Батч має спільний лог, а класифікатор рахувався раз на весь батч — тому попередження, що належало одному модулю, було записане ще сімом із трьох інших репозиторіїв. А ще чотири — це DeprecationWarning самого CPython про сторонню SWIG-бібліотеку, яку модуль лише імпортує: про його код воно не каже нічого, а на сторінці читалось як докір авторові.

    Тепер неможливо: Батч, який дав warn, ділиться так само, як батч, що впав, а попередження зараховується, лише якщо в тому ж рядку названо код усередині Odoo. З 25.08 увесь прохід лінії розробки йде по одному модулю на батч, тобто в кожного модуля власний лог.

  7. 2026-08-30 24 години сайт не публікував нічого. Щогодинна збірка падала на перевірці приватності, яка знайшла справжнє — адресу стороннього мейнтейнера у власному звіті про відвідування, — і не могла нічого вдіяти: файли разом із цією адресою вже були записані й віддавалися весь цей час. У лозі збірки при цьому стояв рядок «публікацію зупинено».

    Збірка писала просто в теку, яку віддає веб-сервер, тобто запис БУВ публікацією, а всі перевірки стояли після нього. Це робило декоративними геть усі інваріанти, а не лише цей. Сама адреса повернулась до нас через наш же витік: URL, які ми публікували до того, як прибрали їх, лишились у базах краулерів, ті ходять по них далі, і звертання лягають у лог, з якого будується звіт. Добу цього ніхто не бачив, бо жоден юніт не сигналив про падіння.

    Тепер неможливо: Збірка йде в теку, якої ніхто не віддає, перевірки працюють проти неї, і лише потім символьне посилання перемикається атомарним rename. Тепер провал перевірки лишає вчорашній сайт — і це передбачений стан. Окремо: мертва форма URL відповідає 301 замість 200, у сирих логів названо термін зберігання, юніт про падіння шле лист, а другий сторож питає свіжість опублікованої сторінки по HTTP, а не код виходу юніта.

  8. 2026-08-27 Набір лінії розробки міг бути оголошений повним у той час, як дев’ятнадцять модулів не проганялись жодного разу.

    «Повний» порівнював пройдене з гілкою, а прогін іде по пулу, зібраному раніше. Модулі, додані в гілку після збірки пулу, не потрапляли ні в чергу, ні в результати, тому лічильник зійшовся б у нуль сам і опублікував набір як повний.

    Тепер неможливо: Публікація додатково вимагає, щоб кількість прогонабельних модулів у пулі дорівнювала кількості справді прогнаних, а обидва знаменники стоять поруч у status.json, кожен зі своєю датою.

  9. 2026-08-26 Пул лінії розробки не був знімком: сімнадцять модулів прогнались проти коду, який уже підмінили під ними, посеред проходу.

    Пул — це симлінки в git-чекаут, а нічна синхронізація робить цьому чекауту git reset --hard. Результат, приписаний коду, якого вже немає, неможливо довести ні в той бік, ні в інший.

    Тепер неможливо: Sha дерева кожного модуля записується в момент збірки пулу, воркер звіряє його перед кожним прогоном і відмовляється при розбіжності, а синхронізація не робить fetch, поки не доведено, що черга порожня.

  10. 2026-08-26 Коміт, записаний поруч із результатом, був тим, що харвест побачив у віддаленому репозиторії, а не тим, що справді стояло в контейнері.

    Поле означало не те, що про нього написано на сторінці, — той самий клас, що вердикт із чужим логом, тільки тихіший.

    Тепер неможливо: Sha береться з того чекауту, який був змонтований, у момент прогону.

  11. 2026-08-21 Прибирання одноразових баз прогонів не спрацювало ЖОДНОГО разу: десять осиротілих баз, 270 МБ. До правки прибиралась рівно одна з дев’яти.

    Дві незалежні причини в одному скрипті. Вік бази міряли за mtime теки, а його оновлює чекпойнт чи автовакуум — тому нічого ніколи не виглядало достатньо старим. А цикл DROP читав той самий stdin, яким подавався перелік баз, і з’їдав власний вхід після першого рядка.

    Тепер неможливо: Вік береться з файлів самої бази, а цикл більше не ділить stdin. Тепер прибирає все осиротіле за один прохід.

  12. 2026-08-24 /feed/master.xml пережив прибирання, яке вилучило master з переліку серій: порожній Atom-фід, який усе одно заявляв серію і на який усе одно можна було підписатися.

    Прибирання ходило лише по файлах index.html, а виклик, яким воно їх видаляло, на файлі (а не на теці) не робить нічого взагалі — і мовчки, бо помилки ігнорувались.

    Тепер неможливо: Прибирання ходить і по фідах, і по сторінках, і відмовляється видаляти будь-що, якщо під видалення підпадає більше 2% сторінок або більше 100.

  13. 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% — тобто виправлення на нашу користь, і саме тому воно вимагало перерахунку, а не переписування.

  14. 2026-09-02 Кожна англійська сторінка модуля друкувала причину падіння українською«Помилка SQL при установці» там, де мало бути «SQL error while installing». Це було системно, а не на одній сторінці: зачеплений кожен вердикт із причиною.

    Класифікатор пише людський підпис причини в базу вже однією мовою, а сторінка друкувала збережений рядок як є.

    Тепер неможливо: Сторінка рендерить причину мовою сторінки зі збереженої, а перевірка збірки валить публікацію, якщо в підпису класифікатора немає англійської пари — тобто нове правило не зможе тихо повернути українську на англійські сторінки.

  15. 2026-09-02 П'ять днів статичний розклад ir.access на цій сторінці був порахований по знімку пулу, якого вже не існувало. Пул перезібрано 30.08 (1 261 модуль), а файл, який читає сторінка, лишився від 27.08 (1 245). Тобто єдине число тут, яке не залежить від жодного нашого прогону, стояло поруч із числами набору, зміряного на іншому знімку.

    Скрипт, який пише цей файл, не викликав ніхто. Його запустили руками один раз, і обіцянка «та сама дата, що й решта набору» трималась лише доти, доки хтось пам'ятав повторити запуск.

    Чого ми НЕ можемо довести: Дата знімка весь цей час стояла поруч із числом, тобто читач міг побачити, який саме знімок воно описує. Довести, що хтось це побачив, ми не можемо.

    Тепер неможливо: poolsnap.py переписує розклад тим самим запуском, який замінює знімок, — дві дати більше не можуть роз'їхатись; а збій на цьому кроці називає себе в лозі, замість тихо лишити старий файл на місці.

Результат виглядає неправильним?

Повідомте — ми покажемо повний лог або перепрогонимо модуль. Це не формальність: з дванадцяти вердиктів warn, які ми мали 20.08.2026, одинадцять були нашими, а не модулів: перевірку пережив рівно один. Три вердикти fail того дня виявились справжніми. Знайшлося це лише тому, що хтось відкрив лог — див. журнал дефектів.

Основний канал — issue на GitHub: історія публічна, і видно, що ми відповідаємо. На кожній сторінці модуля є посилання, яке вже підставляє модуль, серію, дату прогону й вирок. Пошта — hello@allservices.one.

Дізнаватись, не даючи себе відслідковувати

Каналів два, і різниця між ними навмисна. Atom-фід анонімний: ні адреси, ні реєстрації, усі події індексу. Список сповіщень — протилежний обмін: ви даєте адресу й чуєте лише про названу серію або названі модулі. Фід нікуди не дівається, а табло 20.0 несе виключно фід.

Список зберігає адресу, випадковий токен, ваш вибір і часові мітки. Ні IP, ні User-Agent, ні звідки ви прийшли. Посилання підтвердження й відписки несуть токен, а не адресу, і це не обережність про запас: адреса в URL опиняється в лозі веб-сервера, і в серпні чужа адреса, яку ми самі вписали в URL, повернулась до нас через власний лог і на добу зупинила збірку сайту. Обидва посилання відкривають сторінку з кнопкою, а не діють по кліку: поштові клієнти передзавантажують посилання, а підписка, підтверджена сканером, підпискою не є. Відписка видаляє рядок, а не ставить позначку.

Maintainer, Module Health Index

Дані оновлено 2026-09-10 14:00 UTC · публікуємо факт прогону з логом і датою, не оцінку вендора · CSV і JSON відкриті

Питання або хибний результат: issue на GitHub чи hello@allservices.one

Незалежний проєкт. Не пов'язаний з Odoo S.A. чи Odoo Community Association.