Випуск і експлуатація
Неперервна інтеграція: як зміна доводить готовність до об’єднання
У розділах 48 і 49 ми з’ясували, що можуть довести окремі тести й як поєднувати рівні перевірок за ризиком і часом. Тут перейдемо до автоматичного…
У розділах 48 і 49 ми з’ясували, що можуть довести окремі тести й як поєднувати рівні перевірок за ризиком і часом. Тут перейдемо до автоматичного процесу регулярної перевірки невеликих змін до їхнього об’єднання. Це неперервна інтеграція (continuous integration, CI). Розділи 54 і 55 розглянули відповідно безпеку застосунків та агентів; тут зосередимося на захисті автоматичних робочих процесів. Наступний розділ пояснює, як перевірену збірку просувати між середовищами.
CI не дорівнює «у репозиторії є файл конфігурації». Це звичка часто передавати невеликі зміни у спільну основну гілку й швидко перевіряти, чи ще працює спільний код. Файл автоматизації лише описує частину цієї практики. Сигнал залежить також від того, яку ревізію перевірили, які дії справді виконали, як команда тлумачить їхні результати та які перевірки блокують об’єднання.
56.1. Для чого команда інтегрує зміни часто
Дослідницька програма DevOps Research and Assessment (DORA) описує CI як часте об’єднання невеликих змін зі швидким відгуком збірок і тестів. Якщо коротка перевірка ламається невдовзі після кількох рядків коду, зазвичай легше знайти причину, ніж у великому пакеті змін. Довга гілка відкладає інтеграцію: чим довше код розходиться зі спільною версією, тим більше взаємних припущень і конфліктів команда має звести докупи. У звіті Accelerate State of DevOps 2021 DORA описує збірку й автоматичні тести після кожного коміту із сигналом за кілька хвилин і досліджує зв’язок цієї практики з показниками команд. Це дослідницькі результати й рекомендації, а не гарантія, що сам інструмент CI однаково покращить будь-який продукт (рекомендації DORA, звіт DORA 2021).
Для навчального сервісу бронювання команда може об’єднувати невеликі зміни щодня: окремо додати перевірку доступності слота, окремо — правило скасування, окремо — журналювання. Після кожного PR запускаються перевірки, здатні швидко виявити ризик саме цих змін; повільний наскрізний сценарій оплати можна запускати окремо або паралельно, якщо це дає потрібний баланс швидкості та вартості.
Рекомендація DORA — скорочувати час від зміни до корисного сигналу, а не гнатися за максимальною кількістю зелених позначок. Перевірка на сорок хвилин може змусити довго чекати або підштовхнути команду шукати обхідний шлях; надто вузький набір, навпаки, перестає помічати важливі помилки. Які перевірки запускати на кожну зміну, визначають ризик, межа доказу й допустимий час зворотного зв’язку.
56.2. З чого складається робочий процес
Нижче наведені терміни стосуються прикладів із GitHub Actions. Інші платформи автоматизації використовують схожі поняття, але назви, обмеження й точну семантику треба перевіряти в документації відповідного продукту.
| Термін | Просте значення | Для чого він потрібний |
|---|---|---|
| Подія (event) | Дія чи розклад, що запускає автоматизацію | Наприклад, новий запит на злиття, оновлення його гілки або нічний графік |
| Робочий процес (workflow) | Файл конфігурації у форматі YAML (YAML Ain’t Markup Language — формат серіалізації даних), зазвичай у .github/workflows/ |
Описує тригери, дозволи та завдання автоматизації |
| Завдання (job) | Група кроків, призначена виконавцю (runner) | Дозволяє виконувати перевірки паралельно або визначати їх залежності |
| Крок (step) | Одна команда або багаторазово використовувана дія всередині завдання | Наприклад, отримати файли, встановити залежності, запустити тести |
| Виконавець (runner) | Машина, яка запускає завдання | Може бути короткоживучою хмарною віртуальною машиною (virtual machine, VM) або власним сервером команди |
| Середовище (environment) | У самому CI — набір умов виконання; у GitHub Actions це також окремий механізм доступу до цілі розгортання | Не плутайте тестові змінні та сервіси з етапом публікації чи розгортання |
| Дія (action) | Готовий повторно використовуваний крок або набір кроків | Допомагає, наприклад, отримати код або встановити середовище виконання |
| Артефакт запуску (workflow artifact) | Збережений файл після завдання або для передавання іншому завданню | Наприклад, звіт тестів, журнал чи збірка |
| Кеш (cache) | Збережені залежності чи проміжні дані для прискорення наступного запуску | Скорочує повторну роботу, але не повинен бути необхідним джерелом правильності |
У GitHub Actions файл workflow описує події, jobs і steps; версія workflow пов’язана з ревізією або посиланням, що спричинили подію. Runner виконує jobs. Для GitHub-hosted runner зазвичай виділяється нова машина на job і прибирається після його завершення, тоді як кроки одного job ділять файлову систему. Власний сервер runner може зберігати стан між запусками, тому чистота середовища стає окремою відповідальністю команди (GitHub: Workflows, Workflow syntax, GitHub-hosted runners).
У цій главі «артефакт запуску» означає файл, збережений після job; це може бути журнал або звіт тестів. У розділі 57 артефактом буде пакет, який команда перевіряє й просуває між середовищами.
56.3. Який саме код перевірено
Зелений результат має сенс лише разом із ревізією коду, яку він перевірив. SHA (Secure Hash Algorithm, алгоритм криптографічного хешування) у GitHub — це стислий ідентифікатор коміту. Для перевірки запиту на злиття (pull request, PR) важливі щонайменше його номер, точний SHA, подія, ref — гілка або інше посилання — та результат кожного потрібного завдання.
У стандартному сценарії pull_request GitHub надає merge-ревізію, що поєднує поточну базову гілку з гілкою PR. Тому перевірка зазвичай відповідає на запитання «чи працює ця пропозиція разом із поточною базою?», а не лише «чи працює окрема гілка автора?». Водночас перевірка має стосуватися останнього потрібного SHA: зелений статус старого коміту не перевіряє зміни, додані пізніше. Якщо репозиторій використовує merge queue, workflow треба окремо під’єднати до події merge_group, інакше потрібна перевірка може не з’явитися для комбінації, яку черга збирається об’єднати. GitHub описує SHA та ref для подій pull_request і merge_group.
Робочий процес отримує потрібний знімок коду, відновлює залежності й виконує перевірки, які команда обрала як сигнал готовності. Часто спочатку запускають швидкі статичні перевірки, потім тести, а тоді збирають продукт. Проте універсального обов’язкового порядку немає: тест може вимагати зібраний бінарний файл, а сканер — уже зібрану залежність. Послідовність визначається залежностями jobs і виміряною ціною зворотного зв’язку.
56.4. Що означає результат перевірки
Не зводьте всі стани до пари «зелений або червоний». У звітах можуть бути успішний, невдалий, скасований, перевищений за часом, пропущений результат або відсутність запуску взагалі. Вони відповідають на різні питання.
| Стан | Що вже можна сказати | Наступна дія |
|---|---|---|
| Успішний (success) | Ці кроки завершилися без сигналу помилки | Подивитися, що саме вони виконали, на якому SHA й з якими межами |
| Невдалий (failure) | Команда повернула невдалий код, тестове твердження не збіглося або інший крок завершився помилкою | Прочитати найпершу релевантну помилку та зіставити її з кодом, тестом і середовищем |
| Перевищений час (timed out) | Завдання не завершилося за визначений інтервал | Визначити, чи це повільний продукт, завислий процес, інфраструктура або завузький ліміт |
| Скасований (cancelled) | Запуск припинили до завершення | З’ясувати, чи його замінив новий запуск, чи скасування приховало відсутню перевірку |
| Пропущений (skipped) | Умова не дозволила виконати певний крок або завдання | Перевірити цю умову та вимоги merge rules; пропущений workflow і пропущений job — не те саме |
| Не запускалося (not run / no check) | Для потрібної події або ревізії перевірки немає | Переконатися, що тригер відповідає події та гілці; не називати це успіхом |
У GitHub Actions job, пропущений умовою if, може вважатися успішним для обов’язкової перевірки. Якщо ж workflow не запустився через фільтр гілки чи шляхів або повідомлення про пропуск у коміті, потрібний статус може лишитися в очікуванні. Це різні випадки: перевірте умову job і причину відсутності запуску (GitHub: status checks, пропущені workflow-запуски).
Назва job test не робить його обов’язковим: потрібну перевірку задають у правилах гілки або ruleset, де можна також вказати очікуваний GitHub App-видавець (правила required status checks).
Зелений запуск — це свідчення про конкретні перевірки, а не сертифікат безпомилковості. Збірка може завершитись, навіть якщо тестовий набір не перевірив важливу продуктову умову. Тест може використовувати підроблений сервіс замість реального постачальника. Сканер виявляє лише визначені ним класи проблем. Межі доказу треба описати поряд із результатом, як ми це робили у главі 48.
56.5. Повторюваність, кеші й артефакти
Щоб відрізнити зміну продукту від відмінності у середовищі, зафіксуйте відтворювані складові: версію мови або runtime, інструментарій збірки, lockfile, параметри запуску, фікстури, часову зону, джерело тестових даних і потрібні зовнішні сервіси. Змінні мережеві відповіді, поточний час, випадковий порядок тестів і спільні ресурси можуть змінити результат навіть на незмінному коміті. Фіксація не завжди можлива для повного реального оточення, але тоді цей вплив слід назвати й обмежити.
У Node.js-проєкті менеджер пакетів npm виконує npm ci, щоб встановити залежності відповідно до lockfile; команда завершується помилкою, якщо файл не узгоджується з описом пакета, і сама його не переписує. Це приклад поведінки саме npm, а не універсальне правило для всіх пакетних менеджерів. Офіційний посібник GitHub про збірку та тестування Node.js показує використання lockfile та npm ci у workflow.
Кеш має лише прискорити роботу: без нього команда повинна вміти заново отримати залежності або створити проміжні файли. Ключ кешу зазвичай враховує операційну систему та хеш lockfile. Якщо користувач змінив конфігурацію залежностей, інший ключ має привести до відповідного набору даних. Не кладіть у кеш секрети чи довірені дані з привілейованого середовища: дані, відновлені з кешу, слід перевіряти як вхід з попереднього запуску. GitHub пояснює призначення та межі кешування окремо від збереження workflow artifacts.
Артефакт запуску зберігає результат job після його завершення; кеш натомість прискорює наступний запуск. Сам збережений файл не підтверджує походження чи придатність збірки до виробничого середовища. Для цього потрібен окремо налаштований доказ, наприклад attestation, із власними дозволами й перевіркою (GitHub: Workflow artifacts).
56.6. Паралельність, скасування й повтори
Матриця запусків (matrix) перевіряє кілька версій середовища виконання, операційних систем чи конфігурацій — наприклад, поточну й мінімально підтримувану версії Node.js. Вона збільшує покриття, час і вартість. Якщо кожен варіант матриці є обов’язковим для злиття, його результат і критерій успіху мають бути зрозумілими в правилах захисту. Jobs можуть працювати паралельно, якщо не залежать одне від одного через needs.
Правило concurrency може скасувати попередній запуск, коли новий коміт зробив його застарілим. Це заощаджує ресурси, але команда все одно має отримати результат для останнього SHA. Обмеження часу корисне як захист від зависання; воно не лікує повільний тест і саме собою не пояснює причину тайм-ауту.
Успішний повтор після падіння першого запуску вказує на нестабільність, але не скасовує попередній результат. Зберігайте обидва журнали, SHA й відмінності середовища: повтори до випадкового успіху приховують дефекти. У розділі 48 такий нестабільний результат уже розглянуто окремо.
56.7. Межі безпеки в workflow
Будь-який workflow, що виконує код із PR, має ставитися до цього коду як до потенційно недовіреного. Задайте мінімальні права GITHUB_TOKEN; звичайному lint/test-завданню зазвичай достатньо читання репозиторію. Не передавайте ключі розгортання, публікації чи виробничої бази у звичайний PR-запуск. Для сторонніх Actions використовуйте повний SHA коміту після перевірки його походження: GitHub називає повне SHA єдиним незмінним посиланням на конкретну версію дії. Не довіряйте лише рухомому тегу на кшталт @v3, якщо важлива незмінність коду. GitHub: Secure use reference.
Не об’єднуйте отримання привілейованого токена з виконанням коду з fork PR. Подія pull_request_target використовує контекст базового репозиторію з вищими правами; checkout і запуск зміненого PR коду в такому workflow може надати атакувальнику спосіб скористатися цими правами. Звичайний PR workflow для тестів, як правило, не потребує цього тригера. Артефакти з workflow, що виконував код fork, та його текстові результати також слід обробляти як недовірені дані. GitHub: secure use of pull_request_target.
Повідомлення коміту або опис PR не стає безпечною інструкцією для агента лише тому, що потрапив до workflow. Якщо агент отримує такі тексти, обмежте запуск завдання, відокремте секрети від недовіреного входу та дайте мінімальні дозволи. Це продовжує модель загроз із розділу 55.
56.8. Як агент має розбирати невдалий запуск
Агент може пришвидшити початковий огляд, але його перше пояснення — лише гіпотеза. Почніть із фактів: PR і точного SHA, workflow, runner, першого невдалого кроку та його коду завершення, повного релевантного журналу, команди й версій інструментів, тестових даних і можливих повторів. Далі розділіть принаймні чотири пояснення: регресія продукту, помилка тесту чи його очікування, відмінність середовища або недоступна інфраструктура.
Змінюйте найменшу ділянку, що відповідає підтвердженій причині. Якщо сценарій залежав від даних сусіднього тесту, ізолюйте стан або зробіть початкові дані явними; не вимикайте тестову умову лише заради зеленого індикатора. Якщо збій стосується продукту, збережіть перший журнал і додайте перевірку, яка демонструє той самий дефект. Якщо агента просять перезапускати перевірку, доки вона випадково не пройде, поясніть, що повтор не спростував першу відмову; не звітуйте про виправлення без підтвердженої причини.
В офіційній документації OpenAI Codex GitHub Action (openai/codex-action@v1) запускає Codex як крок GitHub Actions: він може підсумувати PR і зберегти файл результату. Сам текст final-message не стає автоматично обов’язковим статусом; умову такого статусу мають явно налаштувати в workflow. Anthropic описує claude-code-action@v1 та право actions: read для читання CI-результатів PR. В обох випадках потрібний результат для злиття визначає конфігурація GitHub, а доступ до секретів та тригерів — межі workflow і репозиторію. OpenAI: Codex GitHub Action, Anthropic: Claude Code GitHub Actions.
56.9. Повний приклад: перевірка зміни сервісу бронювання
Ситуація. Команда додає перевірку, що двом клієнтам не можна одночасно підтвердити останній вільний слот. У репозиторії є .node-version з точною версією Node.js, узгоджені package.json і package-lock.json, а команди lint, test і build описані у package.json. Команда хоче перевіряти кожен PR до main, кожен push у main і ревізію, яку GitHub merge queue збирається об’єднати.
Рішення та дія. Workflow отримує лише читання вмісту, запускає один job на кожну потрібну ревізію, завершує за визначений ліміт часу й скасовує застарілий запуск того самого PR. Кроки й порядок у цьому навчальному прикладі такі; реальний репозиторій має вказати свої перевірені команди.
name: booking-ci
on:
pull_request:
branches: [main]
push:
branches: [main]
merge_group:
types: [checks_requested]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
verify:
name: booking-verify
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Отримати ревізію події
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Встановити версію Node.js з репозиторію
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
cache: npm
- name: Відновити точні залежності з lockfile
run: npm ci
- name: Перевірити стиль і помилки коду
run: npm run lint
- name: Виконати тести
run: npm test
- name: Зібрати застосунок
run: npm run build
Спостережуваний результат. На сторінці PR видно booking-verify і SHA, який він перевірив. Ruleset може вимагати саме цей job; сам YAML-файл не створює merge gate. Для merge queue окрема збірка запускається через merge_group. Успіх означає, що задані lint, тести й збірка пройшли на цій ревізії та runner.
Чому це корисно. Команда отримує ранній сигнал із мінімальними правами, а повні commit SHA фіксують код сторонніх дій. Кеш npm пришвидшує повторні запуски; npm ci відновлює залежності з lockfile.
Межа. Workflow не доводить, що npm test перевіряє конкуренцію за останній слот. Він також не перевіряє постачальника платежів, виробничі секрети чи розгортання. Переконайтеся, що запуск стосується останнього SHA й ruleset вимагає правильний job. У прикладі Actions зафіксовані за повними SHA; їхні релізи перевірено 26 вересня 2026 року — checkout v7.0.1 і setup-node v7.0.0. Перед новим проєктом перевірте актуальні версії й оновлюйте їх контрольовано.
56.10. Повний приклад: зелений статус належить старому SHA
Ситуація. PR із новою перевіркою бронювання мав зелений booking-verify на коміті a17c9e2. Автор потім додав b38ad41, який змінив транзакцію. У списку запусків останній завершений workflow усе ще стосується a17c9e2, а для b38ad41 перевірки немає. Сторінка PR певний час показує попередній успіх, і команда не знає, чи можна зливати.
Рішення та дія. Звіряють список комітів PR із SHA перевірки та подією запуску: старий результат свідчить лише про a17c9e2. Для нового коміту перевіряють тригери pull_request і synchronize, фільтри branches або paths, стан workflow у вкладці Actions і запис required check у правилах репозиторію. Якщо завершений job пропустив умову if, з’ясовують, чи це передбачений режим саме для нього. Якщо цілий workflow не стартував через фільтр шляхів або повідомлення про пропуск, обов’язковий статус може лишитися в очікуванні; це різні причини й потребують різних виправлень.
Результат. Команда виправляє тригер або фільтр і запускає workflow для b38ad41. Саме цей новий запуск, а не старий зелений статус, стає доказом для зміненої транзакції. До його завершення PR очікує перевірки. Якщо після виправлення статус не з’явився або залишився в очікуванні, перевіряють його джерело й правила required checks, а не вимикають захист гілки. Перевірка прив’язана до ревізії, а не просто до назви PR чи workflow.
Межа. Новий зелений запуск усе ще не підтверджує властивості, яких workflow не перевіряє. Також причина відсутнього check може бути в налаштуванні події, permission, ліміті runner або тимчасовому сервісі; одну лише відсутність результату не можна оголошувати дефектом продукту.
56.11. Повний приклад: іноді падає інтеграційний тест
Ситуація. Тест сервісу бронювання створює дві вільні години у спільній тестовій базі й очікує побачити обидві. На тому самому runner інший паралельний тест очищує записи таблиці перед власним сценарієм. Один запуск показує одну годину, наступний — дві. Зелений локальний запуск на тих самих вихідних файлах не доводить, що випадковий CI-збій був безпечним.
Рішення та дія. Команда зберігає перше падіння, SHA, журнали запитів до бази даних мовою структурованих запитів (Structured Query Language, SQL) і порядок паралельних jobs. Порівняння окремого запуску з тією самою групою тестів показує, що тест стабільно проходить наодинці, але паралельно іноді втрачає запис. Трасування підтверджує, що очищення спільної таблиці зачіпає дані сусіднього сценарію, а продуктова функція читає лише той набір, що лишився. Команда ізолює кожний тест окремою схемою, простором клієнта (tenant) або унікальним ідентифікатором і гарантує, що очищення змінює лише його стан. Твердження «доступні два слоти» не змінюється; паралельний запуск із тим самим порядком перевіряє, чи спрацювала ізоляція. Тайм-аут не збільшують навмання й тест не вимикають.
Результат. Якщо після ізоляції тест стабільно бачить обидва записи, а паралельний сценарій більше не стирає їх, спостережувану нестабільність спричиняв спільний тестовий стан. Якщо сервіс і далі повертає один слот із бази, де їх два, треба досліджувати продуктовий запит або транзакцію як можливу регресію. Однаковий зовнішній симптом може мати різні причини; саме повторний зелений запуск сам собою нічого не доводить. Тест і далі перевіряє правило бронювання, але тепер не залежить від порядку чужих сценаріїв.
Межа. Це навчальна ситуація, не звіт про реальний інцидент. Ізоляція тестових даних не усуває баг у живій системі, де одночасні клієнти справді мають конкурувати за слот; цей продуктовий ризик потребує окремої перевірки транзакції. Якщо CI використовує зовнішню нестабільну базу даних, для розділення мережевої помилки й помилки продукту знадобляться додаткові журнали та спостереження.
56.12. Реальний випадок: pandas та строгі очікувані відмови
Ситуація. У листопаді 2018 року проєкт pandas готувався зробити xfail_strict = True стандартом для тестів. У pytest позначка xfail означає, що від тесту очікують невдачі; якщо позначений тест раптом проходить, strict-режим перетворює такий unexpected pass (XPASS, неочікуваний успіх) на помилку. У коментарі до PR #23721 maintainer побачив, що на CI з Python 3.7 деякі тести проходять, хоча локальна перевірка на Python 3.6 цього не показувала. Різниця середовищ змінювала результат, тож перше падіння PR саме собою не доводило дефект нового продуктового коду.
Рішення та дія. Автор залишив strict-поведінку загалом, щоб неочікуване виправлення старого дефекту теж ставало помітним. Після перегляду сигналів різних CI-запусків команда позначила один відомо нестабільний тест як strict=False, а інший додала до списку очікуваних невдач. PR оновлював багато тестових позначок і налаштування, а не вимикав перевірки повністю. Обговорення PR #23721 показує окремі проходи тестів і lint та різницю між версіями Python; issue #23057 пояснює намір зробити xfail строгим і розібрати тести, які іноді проходили.
Результат. PR із вісьмома комітами злили 3 грудня 2018 року. CI виявив середовищну відмінність, якої не було видно з локального запуску на одній версії Python. Уточнення очікуваного стану конкретних тестів зберегло сигнал неочікуваного успіху, замість того щоб замовчати всі такі випадки.
Межа. Це історичний приклад із Travis CI, а не сучасний шаблон GitHub Actions. Він показує користь матриці й точного тлумачення статусів, але не доводить, що будь-яке падіння між версіями runtime можна виправити зміною позначки: підтримувані версії та правила тестування pandas могли змінитися.
Короткий підсумок
CI робить регулярну перевірку змін спостережуваною: подія запускає workflow, jobs працюють на runner, steps дають команди, а результати прив’язуються до точного SHA. Довіряйте статусу лише після того, як перевірили, який код, які умови та які саме перевірки він представляє. Кеш прискорює повторну роботу, артефакт зберігає результат, а required check виникає з конкретного правила гілки чи ruleset. Коли запуск падає, перший лог і середовище зберігають важливіші дані, ніж випадковий зелений повтор. Якщо причина ще не відома, фіксуйте невизначеність замість вимикання тесту або захисту.
Джерела та подальше читання
Доступ до джерел перевірено 26 вересня 2026 року. Сторінки документації без номера версії є рухомими; прив’язки Actions у прикладі відповідають зазначеним нижче релізам.
- DORA, Continuous integration — малі зміни, автоматизована перевірка та швидкий зворотний зв’язок як інженерна спроможність.
- DORA, Accelerate State of DevOps 2021, розділ про технічні спроможності — опис CI та дослідницький контекст для частих інтеграцій і малих змін.
- GitHub Docs, Workflows, Workflow syntax, Events that trigger workflows — workflow, job, step, runner, події, ref і SHA.
- GitHub Docs, Status checks, Skipping workflow runs, Available rules for rulesets — що означають статуси, пропущені запуски та обов’язкові перевірки.
- GitHub Docs, Dependency caching, Workflow artifacts, GitHub-hosted runners, Deployment environments — кеші, збереження файлів запуску, життєвий цикл виконавців і межа середовища розгортання.
- GitHub Docs, Secure use reference, Securely using
pull_request_target— мінімальні права, прив’язка Actions до SHA та ризик недовіреного коду. - GitHub Docs, Building and testing Node.js — використання lockfile і
npm ciу прикладі для Node.js. actions/checkout, release v7.0.1 — коміт3d3c42e5aac5ba805825da76410c181273ba90b1, зафіксована версія у навчальному прикладі.actions/setup-node, release v7.0.0 — коміт820762786026740c76f36085b0efc47a31fe5020, зафіксована версія у навчальному прикладі.- OpenAI, Codex GitHub Action — запуск Codex у workflow, вихід
final-messageі рекомендації щодо секретів та вводу з PR. - Anthropic, Claude Code GitHub Actions — інтеграція агента з workflow та дозвіл
actions: readдля читання CI-результатів. - pandas, issue #23057, PR #23721 та зміни PR — реальний історичний приклад, як strict-xfail і відмінність Python 3.6/3.7 проявились у CI.