diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index eb2513ee0..8baa4fc17 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,17 @@ +## 2026-06-30 — 20:52 Harbor 110 repair waiting status precedence + +**照主線修正的問題**: +- Public Gitea live readback 已同時看到 CD `#4062` failure(`harbor_registry_public_route_unavailable`、`registry_v2_status=502`)與 `harbor-110-local-repair.yaml #4063` `Waiting`。 +- `ops/runner/read-public-gitea-actions-queue.py` 新增 `harbor_110_repair_waiting_after_cd_harbor_blocker`,當 CD 已證明 Harbor 502 且 110 local repair workflow 等 runner/queue 時,總狀態提升為 `blocked_harbor_110_repair_workflow_waiting`。 +- 這讓下一步優先順序直接指到 110-local repair lane 未接 job,而不是重複停在泛化 Harbor 502。 + +**驗證**: +- `pytest ops/runner/test_read_public_gitea_actions_queue.py -q` 通過(`16 passed`)。 +- `py_compile`、`guard-gitea-runner-pressure.py`、`check-gitea-step-env-secrets.js`、`git diff --check` 通過。 +- Live reader 回 `status=blocked_harbor_110_repair_workflow_waiting`、`latest_visible_harbor_110_repair_run_id=4063`、`latest_visible_harbor_110_repair_run_status=Waiting`。 + +**邊界**:只讀 public Gitea / production HTTP 與本機 source;未讀 secret / token / `.env` / raw sessions / SQLite / auth;未使用 GitHub / `gh` / GitHub API;未 workflow_dispatch;未 SSH / Docker / Nginx / K3s / DB / firewall runtime 寫入。 + ## 2026-06-30 — 20:45 Harbor 110 repair waiting readback classifier **照主線修正的問題**: diff --git a/ops/runner/read-public-gitea-actions-queue.py b/ops/runner/read-public-gitea-actions-queue.py index 4a7b8379f..83b86762e 100644 --- a/ops/runner/read-public-gitea-actions-queue.py +++ b/ops/runner/read-public-gitea-actions-queue.py @@ -240,6 +240,10 @@ def build_readback( harbor_110_repair_waiting = harbor_110_repair_status == "Waiting" harbor_110_repair_running = harbor_110_repair_status == "Running" harbor_110_repair_blocked = harbor_110_repair_status == "Blocked" + harbor_110_repair_waiting_after_cd_harbor_blocker = ( + build_log_classifier["harbor_public_route_blocked"] + and harbor_110_repair_waiting + ) readback = { "actions_page_visible_run_count": len(visible_runs), @@ -312,6 +316,9 @@ def build_readback( "latest_visible_harbor_110_repair_waiting": harbor_110_repair_waiting, "latest_visible_harbor_110_repair_running": harbor_110_repair_running, "latest_visible_harbor_110_repair_blocked": harbor_110_repair_blocked, + "harbor_110_repair_waiting_after_cd_harbor_blocker": ( + harbor_110_repair_waiting_after_cd_harbor_blocker + ), "latest_visible_cd_host_pressure_classifier": tests_log_classifier[ "host_pressure_classifier" ], @@ -343,6 +350,8 @@ def build_readback( if no_matching else "blocked_latest_visible_cd_run" if latest_cd_visible_blocked + else "blocked_harbor_110_repair_workflow_waiting" + if harbor_110_repair_waiting_after_cd_harbor_blocker else "blocked_harbor_public_route_unavailable" if build_log_classifier["harbor_public_route_blocked"] else "blocked_host_web_build_pressure" @@ -393,6 +402,9 @@ def build_readback( "harbor_110_repair_waiting": harbor_110_repair_waiting, "harbor_110_repair_running": harbor_110_repair_running, "harbor_110_repair_blocked": harbor_110_repair_blocked, + "harbor_110_repair_waiting_after_cd_harbor_blocker": ( + harbor_110_repair_waiting_after_cd_harbor_blocker + ), "harbor_110_repair_no_matching_runner_label": ( latest_harbor_110_repair_run.get("no_matching_runner_label", "") ), @@ -555,6 +567,10 @@ def _human_summary(payload: dict[str, Any]) -> str: "LATEST_VISIBLE_HARBOR_110_REPAIR_NO_MATCHING_RUNNER_LABEL=" f"{readback['latest_visible_harbor_110_repair_no_matching_runner_label']}" ), + ( + "HARBOR_110_REPAIR_WAITING_AFTER_CD_HARBOR_BLOCKER=" + f"{int(readback['harbor_110_repair_waiting_after_cd_harbor_blocker'])}" + ), "WRITE_PERFORMED=false", "TOKEN_COLLECTED=false", ] diff --git a/ops/runner/test_read_public_gitea_actions_queue.py b/ops/runner/test_read_public_gitea_actions_queue.py index dc6d0bdda..14d3600a5 100644 --- a/ops/runner/test_read_public_gitea_actions_queue.py +++ b/ops/runner/test_read_public_gitea_actions_queue.py @@ -434,6 +434,32 @@ def test_harbor_blocker_takes_status_precedence_over_stale_jobs_payload() -> Non ) +def test_harbor_repair_waiting_takes_actionable_precedence_after_harbor_blocker() -> None: + module = _load_module() + payload = module.build_readback( + actions_html=_actions_html_cd_running_harbor_repair_waiting(), + actions_list_http_status=401, + actions_list_payload={"message": "token is required"}, + cd_jobs_http_status=200, + cd_jobs_payload={"jobs": [], "total_count": 0}, + latest_cd_build_log_http_status=200, + latest_cd_build_log_text=_harbor_blocked_log(), + ) + + assert payload["status"] == "blocked_harbor_110_repair_workflow_waiting" + assert payload["readback"]["latest_visible_harbor_110_repair_waiting"] is True + assert ( + payload["readback"][ + "harbor_110_repair_waiting_after_cd_harbor_blocker" + ] + is True + ) + assert ( + payload["rollups"]["harbor_110_repair_waiting_after_cd_harbor_blocker"] + is True + ) + + def test_derive_jobs_api_url_tracks_latest_visible_run_id() -> None: module = _load_module()