diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 53d6d259..eb2513ee 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,17 @@ +## 2026-06-30 — 20:45 Harbor 110 repair waiting readback classifier + +**照主線修正的問題**: +- `ops/runner/read-public-gitea-actions-queue.py` 新增 `harbor-110-local-repair.yaml` 專用 readback:run id、status、kind、title、commit、waiting / running / blocked 與 no-matching runner label。 +- Rollups 新增 `harbor_110_repair_run_visible`、`harbor_110_repair_run_status`、`harbor_110_repair_waiting`、`harbor_110_repair_running`、`harbor_110_repair_blocked`,避免 110 local repair workflow 只藏在 `top_visible_runs` 靠人眼判讀。 +- Live readback 目前精準收斂:CD `#4061` failure,classifier=`harbor_registry_public_route_unavailable`、`registry_v2_status=502`、CD non110 self-heal skip=`not_110_host`;scheduled Harbor repair `#4060` visible 且 `Waiting`。 + +**驗證**: +- `py_compile`、`ruff check`、`pytest ops/runner/test_read_public_gitea_actions_queue.py -q` 通過(`15 passed`)。 +- `read-public-gitea-actions-queue.py --json` 成功讀出 `latest_visible_harbor_110_repair_run_id=4060`、`latest_visible_harbor_110_repair_run_status=Waiting`。 +- `git diff --check` 通過。 + +**邊界**:只讀 public Gitea / 本機 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:29 Harbor self-heal skip reason classifier **照主線修正的問題**: diff --git a/ops/runner/read-public-gitea-actions-queue.py b/ops/runner/read-public-gitea-actions-queue.py index 4e5bc32c..4a7b8379 100644 --- a/ops/runner/read-public-gitea-actions-queue.py +++ b/ops/runner/read-public-gitea-actions-queue.py @@ -182,6 +182,14 @@ def build_readback( (run for run in visible_runs if run.get("workflow") == "cd.yaml"), {}, ) + latest_harbor_110_repair_run = next( + ( + run + for run in visible_runs + if run.get("workflow") == "harbor-110-local-repair.yaml" + ), + {}, + ) cd_jobs = cd_jobs_payload if isinstance(cd_jobs_payload, dict) else {} actions_list = actions_list_payload if isinstance(actions_list_payload, dict) else {} actions_list_message = str(actions_list.get("message") or "") @@ -228,6 +236,10 @@ def build_readback( build_log_classifier = classify_cd_build_log(latest_cd_build_log_text) tests_log_classifier = classify_cd_tests_log(latest_cd_tests_log_text) latest_cd_visible_blocked = latest_cd_run.get("status", "") == "Blocked" + harbor_110_repair_status = latest_harbor_110_repair_run.get("status", "") + 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" readback = { "actions_page_visible_run_count": len(visible_runs), @@ -281,6 +293,25 @@ def build_readback( "harbor_public_route_blocked" ], "latest_visible_cd_tests_log_http_status": latest_cd_tests_log_http_status, + "latest_visible_harbor_110_repair_run_id": ( + latest_harbor_110_repair_run.get("run_id", "") + ), + "latest_visible_harbor_110_repair_run_status": harbor_110_repair_status, + "latest_visible_harbor_110_repair_run_kind": ( + latest_harbor_110_repair_run.get("kind", "") + ), + "latest_visible_harbor_110_repair_run_title": ( + latest_harbor_110_repair_run.get("title", "") + ), + "latest_visible_harbor_110_repair_run_commit_sha": ( + latest_harbor_110_repair_run.get("commit_sha", "") + ), + "latest_visible_harbor_110_repair_no_matching_runner_label": ( + latest_harbor_110_repair_run.get("no_matching_runner_label", "") + ), + "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, "latest_visible_cd_host_pressure_classifier": tests_log_classifier[ "host_pressure_classifier" ], @@ -316,6 +347,12 @@ def build_readback( if build_log_classifier["harbor_public_route_blocked"] else "blocked_host_web_build_pressure" if tests_log_classifier["host_pressure_blocked_or_waiting"] + else "harbor_110_repair_waiting_for_runner_or_queue" + if harbor_110_repair_waiting + else "harbor_110_repair_running" + if harbor_110_repair_running + else "blocked_harbor_110_repair_run" + if harbor_110_repair_blocked else "cd_jobs_stale_or_mismatched" if cd_jobs_stale_or_mismatched else "no_matching_runner_not_visible" @@ -351,6 +388,14 @@ def build_readback( "host_pressure_refused" ], "no_matching_online_runner_visible": bool(no_matching), + "harbor_110_repair_run_visible": bool(latest_harbor_110_repair_run), + "harbor_110_repair_run_status": harbor_110_repair_status, + "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_no_matching_runner_label": ( + latest_harbor_110_repair_run.get("no_matching_runner_label", "") + ), }, "operation_boundaries": { "public_gitea_read_only": True, @@ -498,6 +543,18 @@ def _human_summary(payload: dict[str, Any]) -> str: "LATEST_VISIBLE_CD_HOST_PRESSURE_CLASSIFIER=" f"{readback['latest_visible_cd_host_pressure_classifier']}" ), + ( + "LATEST_VISIBLE_HARBOR_110_REPAIR_RUN_ID=" + f"{readback['latest_visible_harbor_110_repair_run_id']}" + ), + ( + "LATEST_VISIBLE_HARBOR_110_REPAIR_RUN_STATUS=" + f"{readback['latest_visible_harbor_110_repair_run_status']}" + ), + ( + "LATEST_VISIBLE_HARBOR_110_REPAIR_NO_MATCHING_RUNNER_LABEL=" + f"{readback['latest_visible_harbor_110_repair_no_matching_runner_label']}" + ), "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 ab109746..dc6d0bdd 100644 --- a/ops/runner/test_read_public_gitea_actions_queue.py +++ b/ops/runner/test_read_public_gitea_actions_queue.py @@ -91,6 +91,40 @@ def _actions_html_single_cd_run() -> str: """ +def _actions_html_cd_running_harbor_repair_waiting() -> str: + return """ +