diff --git a/apps/api/src/services/harbor_registry_controlled_recovery_receipt.py b/apps/api/src/services/harbor_registry_controlled_recovery_receipt.py index 193addaa7..035d9a631 100644 --- a/apps/api/src/services/harbor_registry_controlled_recovery_receipt.py +++ b/apps/api/src/services/harbor_registry_controlled_recovery_receipt.py @@ -220,6 +220,15 @@ def validate_harbor_registry_controlled_recovery_receipt( "gitea_queue_latest_cd_run_status": gitea_queue[ "latest_visible_cd_run_status" ], + "gitea_queue_current_cd_waiting_for_runner_or_queue": gitea_queue[ + "current_cd_waiting_for_runner_or_queue" + ], + "gitea_queue_current_cd_no_matching_runner_label": gitea_queue[ + "current_cd_no_matching_runner_label" + ], + "gitea_queue_controlled_profile_no_matching_runner_label_count": ( + gitea_queue["controlled_profile_no_matching_runner_label_count"] + ), "gitea_queue_harbor_110_repair_run_status": gitea_queue[ "latest_visible_harbor_110_repair_run_status" ], @@ -840,10 +849,15 @@ def _gitea_queue_readback(value: Any) -> dict[str, Any]: "status": "not_provided", "latest_visible_cd_run_id": "", "latest_visible_cd_run_status": "", + "latest_visible_cd_run_waiting": False, "latest_visible_cd_run_commit_sha": "", + "current_cd_no_matching_runner_label": "", + "current_cd_waiting_for_runner_or_queue": False, "current_cd_inflight_classifier": "", "current_cd_harbor_latest_registry_v2_status": "", "current_cd_harbor_retrying_unavailable": False, + "controlled_profile_no_matching_runner_labels": {}, + "controlled_profile_no_matching_runner_label_count": 0, "cd_run_jobs_stale_or_mismatched": False, "cd_run_jobs_payload_classifier": "", "cd_run_jobs_head_sha_mismatch": False, @@ -905,6 +919,23 @@ def _gitea_queue_readback(value: Any) -> dict[str, Any]: or readback.get("latest_visible_cd_inflight_classifier") or "" ) + current_cd_waiting = bool( + rollups.get("current_main_cd_run_waiting") is True + or readback.get("latest_visible_cd_run_waiting") is True + ) + current_cd_no_matching_label = str( + rollups.get("current_main_cd_no_matching_runner_label") + or readback.get("latest_visible_cd_no_matching_runner_label") + or "" + ) + controlled_profile_no_matching_labels = { + str(key): str(label) + for key, label in _dict( + readback.get("controlled_profile_no_matching_runner_labels") + or rollups.get("controlled_profile_no_matching_runner_labels") + ).items() + if label + } current_cd_latest_registry_status = str( rollups.get("current_main_cd_harbor_latest_registry_v2_status") or readback.get("latest_visible_cd_harbor_latest_registry_v2_status") @@ -956,6 +987,8 @@ def _gitea_queue_readback(value: Any) -> dict[str, Any]: jobs_stale=jobs_stale, jobs_cross_workflow=jobs_cross_workflow, current_cd_harbor_retrying=current_cd_harbor_retrying, + current_cd_waiting=current_cd_waiting, + current_cd_no_matching_runner=bool(current_cd_no_matching_label), cd_jobs_stale=cd_jobs_stale, cd_jobs_head_sha_mismatch=cd_jobs_head_sha_mismatch, cd_jobs_run_id_mismatch=cd_jobs_run_id_mismatch, @@ -966,6 +999,8 @@ def _gitea_queue_readback(value: Any) -> dict[str, Any]: cd_jobs_payload_classifier=cd_jobs_payload_classifier, harbor_jobs_payload_classifier=harbor_jobs_payload_classifier, no_matching_label=no_matching_label, + current_cd_no_matching_label=current_cd_no_matching_label, + current_cd_waiting=current_cd_waiting, blockers=blockers, ) return { @@ -976,14 +1011,23 @@ def _gitea_queue_readback(value: Any) -> dict[str, Any]: "latest_visible_cd_run_status": str( readback.get("latest_visible_cd_run_status") or "" ), + "latest_visible_cd_run_waiting": current_cd_waiting, "latest_visible_cd_run_commit_sha": str( readback.get("latest_visible_cd_run_commit_sha") or "" ), + "current_cd_no_matching_runner_label": current_cd_no_matching_label, + "current_cd_waiting_for_runner_or_queue": current_cd_waiting, "current_cd_inflight_classifier": current_cd_inflight_classifier, "current_cd_harbor_latest_registry_v2_status": ( current_cd_latest_registry_status ), "current_cd_harbor_retrying_unavailable": current_cd_harbor_retrying, + "controlled_profile_no_matching_runner_labels": ( + controlled_profile_no_matching_labels + ), + "controlled_profile_no_matching_runner_label_count": len( + controlled_profile_no_matching_labels + ), "cd_run_jobs_stale_or_mismatched": cd_jobs_stale, "cd_run_jobs_payload_classifier": cd_jobs_payload_classifier, "cd_run_jobs_head_sha_mismatch": cd_jobs_head_sha_mismatch, @@ -1040,9 +1084,11 @@ def _gitea_queue_normalized_classifier_fields( cd_jobs_payload_classifier: str, harbor_jobs_payload_classifier: str, no_matching_label: str, + current_cd_no_matching_label: str, + current_cd_waiting: bool, blockers: list[str], ) -> list[dict[str, Any]]: - return [ + fields = [ { "field_id": "cd_run_jobs_payload_classifier", "value": cd_jobs_payload_classifier, @@ -1075,6 +1121,22 @@ def _gitea_queue_normalized_classifier_fields( "raw_output_returned": False, }, ] + if current_cd_no_matching_label or current_cd_waiting: + fields.append( + { + "field_id": "current_cd_workflow_runner_readiness", + "value": current_cd_no_matching_label, + "waiting": current_cd_waiting, + "blockers": [ + item + for item in blockers + if item.startswith("gitea_queue_current_cd_") + ], + "metadata_only": True, + "raw_output_returned": False, + } + ) + return fields def _gitea_queue_blockers( @@ -1083,6 +1145,8 @@ def _gitea_queue_blockers( jobs_stale: bool, jobs_cross_workflow: bool, current_cd_harbor_retrying: bool, + current_cd_waiting: bool, + current_cd_no_matching_runner: bool, cd_jobs_stale: bool, cd_jobs_head_sha_mismatch: bool, cd_jobs_run_id_mismatch: bool, @@ -1092,6 +1156,10 @@ def _gitea_queue_blockers( blockers: list[str] = [] if current_cd_harbor_retrying: blockers.append("gitea_queue_current_cd_harbor_retrying_unavailable") + if current_cd_no_matching_runner: + blockers.append("gitea_queue_current_cd_no_matching_runner") + if current_cd_waiting: + blockers.append("gitea_queue_current_cd_waiting_for_runner_or_queue") if cd_jobs_head_sha_mismatch: blockers.append("gitea_queue_cd_jobs_head_sha_mismatch") if cd_jobs_run_id_mismatch: diff --git a/apps/api/tests/test_harbor_registry_controlled_recovery_receipt.py b/apps/api/tests/test_harbor_registry_controlled_recovery_receipt.py index 744e0d9eb..7a93968d9 100644 --- a/apps/api/tests/test_harbor_registry_controlled_recovery_receipt.py +++ b/apps/api/tests/test_harbor_registry_controlled_recovery_receipt.py @@ -530,6 +530,50 @@ def test_harbor_recovery_receipt_surfaces_cd_jobs_payload_classifier() -> None: ] +def test_harbor_recovery_receipt_surfaces_current_cd_runner_readiness() -> None: + payload = validate_harbor_registry_controlled_recovery_receipt( + { + "gitea_actions_queue_readback": _gitea_queue_current_cd_waiting_non110(), + } + ) + + assert "gitea_queue_current_cd_no_matching_runner" in payload["active_blockers"] + assert ( + "gitea_queue_current_cd_waiting_for_runner_or_queue" + in payload["active_blockers"] + ) + queue = payload["readback"]["gitea_actions_queue"] + assert queue["current_cd_waiting_for_runner_or_queue"] is True + assert queue["current_cd_no_matching_runner_label"] == "awoooi-non110-ubuntu" + assert queue["controlled_profile_no_matching_runner_labels"] == { + "cd.yaml": "awoooi-non110-ubuntu", + "harbor-110-local-repair.yaml": "awoooi-non110-host", + } + normalized = { + item["field_id"]: item for item in queue["normalized_classifier_fields"] + } + assert normalized["current_cd_workflow_runner_readiness"]["value"] == ( + "awoooi-non110-ubuntu" + ) + assert normalized["current_cd_workflow_runner_readiness"]["waiting"] is True + assert normalized["current_cd_workflow_runner_readiness"]["blockers"] == [ + "gitea_queue_current_cd_no_matching_runner", + "gitea_queue_current_cd_waiting_for_runner_or_queue", + ] + assert payload["rollups"][ + "gitea_queue_current_cd_waiting_for_runner_or_queue" + ] is True + assert payload["rollups"]["gitea_queue_current_cd_no_matching_runner_label"] == ( + "awoooi-non110-ubuntu" + ) + assert ( + payload["rollups"][ + "gitea_queue_controlled_profile_no_matching_runner_label_count" + ] + == 2 + ) + + def test_harbor_recovery_receipt_waits_for_deploy_marker_readback() -> None: payload = validate_harbor_registry_controlled_recovery_receipt( { @@ -864,6 +908,40 @@ def _gitea_queue_cd_jobs_head_sha_mismatch() -> dict: return payload +def _gitea_queue_current_cd_waiting_non110() -> dict: + payload = _gitea_queue_cd_jobs_head_sha_mismatch() + payload["status"] = "blocked_current_cd_workflow_waiting_for_runner_or_queue" + payload["readback"].update( + { + "latest_visible_cd_run_status": "Waiting", + "latest_visible_cd_run_waiting": True, + "latest_visible_cd_no_matching_runner_label": "awoooi-non110-ubuntu", + "controlled_profile_no_matching_runner_labels": { + "cd.yaml": "awoooi-non110-ubuntu", + "harbor-110-local-repair.yaml": "awoooi-non110-host", + }, + "controlled_profile_no_matching_runner_label_count": 2, + "latest_visible_harbor_110_repair_no_matching_runner_label": ( + "awoooi-non110-host" + ), + } + ) + payload["rollups"].update( + { + "current_main_cd_run_status": "Waiting", + "current_main_cd_run_waiting": True, + "current_main_cd_no_matching_runner_label": "awoooi-non110-ubuntu", + "controlled_profile_no_matching_runner_label_count": 2, + "controlled_profile_no_matching_runner_labels": { + "cd.yaml": "awoooi-non110-ubuntu", + "harbor-110-local-repair.yaml": "awoooi-non110-host", + }, + "harbor_110_repair_no_matching_runner_label": "awoooi-non110-host", + } + ) + return payload + + def _deploy_marker_verified() -> dict: return { "schema_version": "awoooi_production_deploy_readback_blocker_v1", diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 739ee42f7..7133b9b22 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -51094,3 +51094,24 @@ production browser smoke: **下一步**: - commit / push 後讀回 Gitea queue;runtime 若仍卡 Harbor 502 與 `awoooi-host` no matching,下一個主線仍是 110 controlled lane verifier / registration receipt,不恢復 generic runner。 + +## 2026-07-01 — 08:20 Gitea queue current CD / non110 runner readiness classifier + +**完成內容**: +- live queue 在 `fix(cd): keep recovery receipts on controlled profile` 後轉為 CD `#4191 Waiting`、Harbor repair `#4190 Waiting`,labels 變成 `awoooi-non110-ubuntu` / `awoooi-non110-host`;本輪把這個新狀態從舊 `harbor_110` 泛稱中拆出。 +- `read-public-gitea-actions-queue.py` 新增 `latest_visible_cd_run_waiting`、`latest_visible_cd_no_matching_runner_label`、`controlled_profile_no_matching_runner_labels`、`controlled_profile_no_matching_runner_label_count` 與 status `blocked_current_cd_workflow_waiting_for_runner_or_queue`。 +- `harbor-registry-controlled-recovery-receipt` ingestion 新增 current-CD waiting / no-matching blockers:`gitea_queue_current_cd_no_matching_runner`、`gitea_queue_current_cd_waiting_for_runner_or_queue`,並在 normalized classifier fields 中動態加入 `current_cd_workflow_runner_readiness`。 +- 舊 `harbor_110_*` 欄位保留相容;新欄位只在 current CD waiting / no-matching 出現時追加,避免破壞既有下游。 + +**本地驗證結果**: +- Focused suite:`109 passed`。 +- `ruff check`、`py_compile`、`ops/runner/guard-gitea-runner-pressure.py --root .`、`scripts/ci/check-gitea-step-env-secrets.js`、`git diff --check`:通過。 +- runner pressure guard 仍讀回 `auto_branch_events_on_110=0`、`generic_runner_labels=0`。 + +**仍維持**: +- 沒有讀 secret / token / `.env` / raw sessions / SQLite / auth;沒有讀 `.runner` 內容。 +- 沒有使用 GitHub / gh / GitHub API / GitHub Actions。 +- 沒有重啟主機,沒有 Docker / Nginx / K3s / DB restart,沒有 workflow_dispatch,沒有 runtime write。 + +**下一步**: +- commit / push 後讀回最新 queue;若 CD / Harbor repair 仍 Waiting,下一個主線是 non110 controlled runner readiness receipt / registration evidence,而不是恢復 110 generic runner。 diff --git a/ops/runner/read-public-gitea-actions-queue.py b/ops/runner/read-public-gitea-actions-queue.py index 697729e2f..77b45568a 100644 --- a/ops/runner/read-public-gitea-actions-queue.py +++ b/ops/runner/read-public-gitea-actions-queue.py @@ -364,6 +364,16 @@ 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" + latest_cd_waiting = latest_cd_run.get("status", "") == "Waiting" + latest_cd_no_matching_runner_label = ( + latest_cd_run.get("no_matching_runner_label", "") + or workflow_no_matching_labels.get("cd.yaml", "") + ) + controlled_profile_no_matching_runner_labels = { + workflow: label + for workflow, label in workflow_no_matching_labels.items() + if label.startswith("awoooi-non110") + } harbor_110_repair_status = latest_harbor_110_repair_run.get("status", "") harbor_110_repair_run_id = latest_harbor_110_repair_run.get("run_id", "") harbor_110_repair_no_matching_runner_label = ( @@ -453,10 +463,20 @@ def build_readback( "workflow_no_matching_runner_labels": workflow_no_matching_labels, "latest_visible_cd_run_id": latest_cd_run.get("run_id", ""), "latest_visible_cd_run_status": latest_cd_run.get("status", ""), + "latest_visible_cd_run_waiting": latest_cd_waiting, "latest_visible_cd_run_blocked": latest_cd_visible_blocked, "latest_visible_cd_run_kind": latest_cd_run.get("kind", ""), "latest_visible_cd_run_title": latest_cd_run.get("title", ""), "latest_visible_cd_run_commit_sha": latest_cd_run.get("commit_sha", ""), + "latest_visible_cd_no_matching_runner_label": ( + latest_cd_no_matching_runner_label + ), + "controlled_profile_no_matching_runner_labels": ( + controlled_profile_no_matching_runner_labels + ), + "controlled_profile_no_matching_runner_label_count": len( + controlled_profile_no_matching_runner_labels + ), "latest_visible_cd_build_log_http_status": latest_cd_build_log_http_status, "latest_visible_cd_failure_classifier": build_log_classifier[ "failure_classifier" @@ -581,6 +601,8 @@ def build_readback( if no_matching else "blocked_latest_visible_cd_run" if latest_cd_visible_blocked + else "blocked_current_cd_workflow_waiting_for_runner_or_queue" + if latest_cd_waiting else ( "blocked_harbor_public_route_unavailable_after_harbor_110_repair_success" ) @@ -635,7 +657,17 @@ def build_readback( "cd_run_jobs_payload_classifier": cd_jobs_payload_classifier, "current_main_cd_run_visible": bool(latest_cd_run), "current_main_cd_run_status": latest_cd_run.get("status", ""), + "current_main_cd_run_waiting": latest_cd_waiting, "current_main_cd_run_blocked": latest_cd_visible_blocked, + "current_main_cd_no_matching_runner_label": ( + latest_cd_no_matching_runner_label + ), + "controlled_profile_no_matching_runner_label_count": len( + controlled_profile_no_matching_runner_labels + ), + "controlled_profile_no_matching_runner_labels": ( + controlled_profile_no_matching_runner_labels + ), "current_main_cd_failure_classifier": build_log_classifier[ "failure_classifier" ], diff --git a/ops/runner/test_read_public_gitea_actions_queue.py b/ops/runner/test_read_public_gitea_actions_queue.py index f29f967a5..81ea78308 100644 --- a/ops/runner/test_read_public_gitea_actions_queue.py +++ b/ops/runner/test_read_public_gitea_actions_queue.py @@ -144,6 +144,48 @@ def _actions_html_harbor_repair_waiting_with_workflow_no_matching() -> str: """ + _actions_html_cd_running_harbor_repair_waiting() +def _actions_html_cd_waiting_with_non110_no_matching() -> str: + return """ +
+