fix(pipeline-status): dedup forgejo ci runs by workflow on same SHA #48
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Контекст
Зачем: после re-run failed jobs того же SHA оракул
_forgejo_ci_rollupсобирает и старый failed run, и новый success run для одного workflow →_classify_rollupвидит FAILURE первым → возвращает NOT_DONE → pipeline блокируется. PR с фикс-коммитом, равным HEAD PR (или после re-run), не может пройти CI фазу. Зафиксировано на PR#46 (fix-коммит2e490cb431= HEAD PR, в API mix runs: старый failed + новый success).Контекст:
_forgejo_ci_rollupв.opencode/scripts/pipeline-status.py:161-195— единственный фильтрr.get("head_sha") != sha: continue(line 178). Дедупликации по workflow нет. API/actions/tasks?limit=50возвращает последние 50 runs (по id descending), включая устаревшие failed runs от re-runs с тем жеhead_sha._classify_rollup(pipeline-status.py:717-742) работает с плоским списком conclusions — первый FAILURE → NOT_DONE, игнорирует новый SUCCESS.ADR-054 (
docs/decisions/054-pr-122-pipeline-status-statuscheckrollup.md:44-47) ЯВНО отверг альтернативу (C) "SHA + all workflows" именно по причине "выбор правильного run из нескольких". После миграции на Forgejo (ADR-092/093)statusCheckRollupисчез, reimplementation в_forgejo_ci_rollupповторил ровно ту проблему, которую ADR-054 называл причиной отказа от (C).Тестов на re-run scenario НЕТ:
tests/test_pipeline_status_ci.pyмокают выход_forgejo_ci_rollup(готовый JSON rollup), а не саму функцию. Тестtest_ci_partial_failure(lines 445-466) — про 2 РАЗНЫХ check'а, не про 2 runs одного workflow одного SHA. Тестов_forgejo_ci_rollupвообще нет.Re-run semantics Forgejo/GitHub Actions: "Re-run failed jobs" / "Re-run all jobs" создаёт НОВЫЙ run с тем же
head_sha, старый run остаётся в финальном статусе FAILURE.cancel-in-progress: trueв.github/workflows/ci.yml:13работает только на новый push (новый commit), не на re-run.Задача
.opencode/scripts/pipeline-status.pyфункция_forgejo_ci_rollup(lines 161-195) — после фильтра поhead_sha(line 178) добавить дедупликацию "latest run per workflow":name(имя workflow; проверить что Forgejo/actions/tasksвозвращает это поле — если нет, использоватьworkflow_idили комбинацию). Скорее всего API возвращает runs отсортированными по id descending (стандарт GitHub Actions) — тогда первый match per workflow = latest.id(полеidточно есть, используется в_forgejo_run_viewline 398).test_forgejo_ci_rollup_rerun_same_shaвtests/test_pipeline_status_ci.py— мокает_forgejo_requestчтобы вернуть 2 runs одного workflow с одинаковымhead_sha: old (id=100, status=failure, conclusion=failure) + new (id=200, status=success, conclusion=success). Ожидаемый verdict: DONE (CI green, latest run success). Мокировать черезsubprocess.runpatch (как существующиеtest_forgejo_*тесты вtests/test_pipeline_status.py:1281-1462).docs/decisions/054-pr-122-pipeline-status-statuscheckrollup.md) — добавить секцию "Update YYYY-MM-DD: after Forgejo migration,_forgejo_ci_rollupdeduplicates runs by workflow name (latest by id) to handle re-run scenario" с ссылкой на этот PR. ИЛИ создать новый ADR (номер определи черезls docs/decisions/— следующий свободныйNNN-pr-<this_pr>-forgejo-ci-rollup-dedup.md).Контракты
_forgejo_ci_rollup(repo, sha)возвращаетtuple[int, str, str]— без изменений в signature.GET /api/v1/repos/{repo}/actions/tasks?limit=50— без изменений (server-side фильтрация НЕ добавляется, дедупликация client-side).Инварианты
head_branch(он одинаковый для re-runs) и НЕ поevent(он одинаковый для re-runs).id(числовое сравнение черезint(r.get("id", 0))).nameотсутствует в API ответе — fallback наworkflow_id, потом наid(каждый run сам по себе, без дедупликации — корректно для edge case).Граничные случаи
workflow_runs: []→ rollup пустой →_classify_rollupвозвращает AMBIGUOUS "no checks found in rollup" (без изменений).head_sha(нет matching) → rollup пустой → AMBIGUOUS (без изменений).id(невозможно в API, но defensive) → первый побеждает (dict insert order).nameесть, но пустая строка → fallback наworkflow_id, потом наid.Влияние на связанные компоненты
pipeline-status.ts(.opencode/tools/pipeline-status.ts) — без изменений (TS wrapper только spawnSync'ит Python).tests/test_pipeline_status_ci.py— добавитьtest_forgejo_ci_rollup_rerun_same_sha(мок_forgejo_requestчерез monkeypatchps._forgejo_request).tests/test_pipeline_status.py— существующие 9test_forgejo_*(lines 1281-1462) тестируют_forgejo_requestHTTP-слой, НЕ затрагиваются (дедупликация поверх, не в HTTP-слое).run-pipeline/SKILL.mdTemplate D (fix_ci) — без изменений (поведение оракула улучшается, протокол тот же).docs/decisions/054-pr-122-pipeline-status-statuscheckrollup.md) — добавить update note ИЛИ создать новый ADR (supersedes portions of ADR-054 re: Forgejo migration).Вне scope
head_shaпараметром API (если Forgejo поддерживает — отдельный PR; сейчас client-side достаточно).event(pull_request vs push vs schedule) — не нужен, фильтр по SHA + workflow покрывает.create-pr.ts— отдельный issue (предотвращает попадание в CI, этот issue — починка оракула).pr-N— отдельный issue.Критерии приемки
pipeline-statusвозвращает DONE для CI фазыtest_forgejo_ci_rollup_rerun_same_shaпроходитtests/test_pipeline_status_ci.py+tests/test_pipeline_status.pyне ломаютсяruff format --check . && ruff check . && mypy .opencode/scripts/pipeline-status.py— green