feat(agent): add harbor queue receipt readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 31s
CD Pipeline / build-and-deploy (push) Failing after 2m36s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 0s
CD Pipeline / post-deploy-checks (push) Has been skipped
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 31s
CD Pipeline / build-and-deploy (push) Failing after 2m36s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 0s
CD Pipeline / post-deploy-checks (push) Has been skipped
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Has been cancelled
This commit is contained in:
@@ -29,18 +29,23 @@ def validate_harbor_registry_controlled_recovery_receipt(
|
||||
watchdog_check = _parse_watchdog_output(watchdog_check_output)
|
||||
watchdog_repair = _parse_watchdog_output(watchdog_repair_output)
|
||||
verifier = _post_apply_verifier(receipt_payload)
|
||||
gitea_queue = _gitea_queue_readback(
|
||||
receipt_payload.get("gitea_actions_queue_readback")
|
||||
)
|
||||
|
||||
active_blockers = _active_blockers(
|
||||
ssh_local=ssh_local,
|
||||
watchdog_check=watchdog_check,
|
||||
watchdog_repair=watchdog_repair,
|
||||
verifier=verifier,
|
||||
gitea_queue=gitea_queue,
|
||||
)
|
||||
status = _status(
|
||||
ssh_local=ssh_local,
|
||||
watchdog_check=watchdog_check,
|
||||
watchdog_repair=watchdog_repair,
|
||||
verifier=verifier,
|
||||
gitea_queue=gitea_queue,
|
||||
active_blockers=active_blockers,
|
||||
)
|
||||
local_console_phase_readback = _local_console_phase_readback(
|
||||
@@ -64,12 +69,19 @@ def validate_harbor_registry_controlled_recovery_receipt(
|
||||
"ssh_local_repair_output": _text_stats(ssh_local_output),
|
||||
"watchdog_check_output": _text_stats(watchdog_check_output),
|
||||
"watchdog_repair_output": _text_stats(watchdog_repair_output),
|
||||
"gitea_actions_queue_readback": {
|
||||
"provided": gitea_queue["receipt_seen"],
|
||||
"metadata_only": True,
|
||||
"raw_output_returned": False,
|
||||
"top_visible_runs_returned": False,
|
||||
},
|
||||
},
|
||||
"readback": {
|
||||
"ssh_local_repair": ssh_local,
|
||||
"watchdog_check": watchdog_check,
|
||||
"watchdog_repair": watchdog_repair,
|
||||
"post_apply_verifier": verifier,
|
||||
"gitea_actions_queue": gitea_queue,
|
||||
},
|
||||
"local_console_phase_readback": local_console_phase_readback,
|
||||
"controlled_apply_policy": {
|
||||
@@ -109,6 +121,20 @@ def validate_harbor_registry_controlled_recovery_receipt(
|
||||
"watchdog_repair_receipt_seen": watchdog_repair["receipt_seen"],
|
||||
"watchdog_repair_harbor_ready": watchdog_repair["harbor_ready"],
|
||||
"post_apply_verifier_ready": verifier["registry_v2_ready"],
|
||||
"gitea_queue_readback_seen": gitea_queue["receipt_seen"],
|
||||
"gitea_queue_blocker_count": gitea_queue["blocker_count"],
|
||||
"gitea_queue_harbor_110_no_matching_runner": gitea_queue[
|
||||
"harbor_110_repair_no_matching_runner"
|
||||
],
|
||||
"gitea_queue_harbor_110_jobs_stale_or_mismatched": gitea_queue[
|
||||
"harbor_110_repair_jobs_stale_or_mismatched"
|
||||
],
|
||||
"gitea_queue_latest_cd_run_status": gitea_queue[
|
||||
"latest_visible_cd_run_status"
|
||||
],
|
||||
"gitea_queue_harbor_110_repair_run_status": gitea_queue[
|
||||
"latest_visible_harbor_110_repair_run_status"
|
||||
],
|
||||
"metadata_writeback_contract_ready": True,
|
||||
"local_console_phase_count": local_console_phase_readback["phase_count"],
|
||||
"local_console_completed_phase_count": local_console_phase_readback[
|
||||
@@ -317,12 +343,136 @@ def _post_apply_verifier(receipt_payload: dict[str, Any]) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _gitea_queue_readback(value: Any) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
return {
|
||||
"receipt_seen": False,
|
||||
"schema_version": "",
|
||||
"status": "not_provided",
|
||||
"latest_visible_cd_run_id": "",
|
||||
"latest_visible_cd_run_status": "",
|
||||
"latest_visible_cd_run_commit_sha": "",
|
||||
"latest_visible_harbor_110_repair_run_id": "",
|
||||
"latest_visible_harbor_110_repair_run_status": "",
|
||||
"harbor_110_repair_no_matching_runner_label": "",
|
||||
"harbor_110_repair_no_matching_runner": False,
|
||||
"harbor_110_repair_waiting": False,
|
||||
"harbor_110_repair_blocked": False,
|
||||
"harbor_110_repair_jobs_stale_or_mismatched": False,
|
||||
"harbor_110_repair_jobs_unexpected_names": [],
|
||||
"harbor_110_repair_jobs_labels": [],
|
||||
"harbor_110_repair_jobs_runner_names": [],
|
||||
"operation_boundary_violation": False,
|
||||
"blockers": [],
|
||||
"blocker_count": 0,
|
||||
"metadata_only": True,
|
||||
"raw_output_returned": False,
|
||||
"top_visible_runs_returned": False,
|
||||
}
|
||||
|
||||
readback = _dict(value.get("readback"))
|
||||
rollups = _dict(value.get("rollups"))
|
||||
operation_boundaries = _dict(value.get("operation_boundaries"))
|
||||
no_matching_label = str(
|
||||
rollups.get("harbor_110_repair_no_matching_runner_label")
|
||||
or readback.get("latest_visible_harbor_110_repair_no_matching_runner_label")
|
||||
or ""
|
||||
)
|
||||
jobs_stale = bool(
|
||||
rollups.get("harbor_110_repair_jobs_stale_or_mismatched") is True
|
||||
or readback.get("harbor_110_repair_jobs_stale_or_mismatched") is True
|
||||
)
|
||||
waiting = bool(
|
||||
rollups.get("harbor_110_repair_waiting") is True
|
||||
or readback.get("latest_visible_harbor_110_repair_waiting") is True
|
||||
)
|
||||
blocked = bool(
|
||||
rollups.get("harbor_110_repair_blocked") is True
|
||||
or readback.get("latest_visible_harbor_110_repair_blocked") is True
|
||||
)
|
||||
boundary_violation = any(
|
||||
operation_boundaries.get(flag) is True
|
||||
for flag in (
|
||||
"github_api_used",
|
||||
"host_write_performed",
|
||||
"runner_registration_performed",
|
||||
"runner_service_start_performed",
|
||||
"secret_or_runner_token_read",
|
||||
"workflow_dispatch_performed",
|
||||
)
|
||||
)
|
||||
blockers = _gitea_queue_blockers(
|
||||
no_matching_runner=bool(no_matching_label),
|
||||
jobs_stale=jobs_stale,
|
||||
blocked=blocked,
|
||||
boundary_violation=boundary_violation,
|
||||
)
|
||||
return {
|
||||
"receipt_seen": True,
|
||||
"schema_version": str(value.get("schema_version") or ""),
|
||||
"status": str(value.get("status") or "unknown"),
|
||||
"latest_visible_cd_run_id": str(readback.get("latest_visible_cd_run_id") or ""),
|
||||
"latest_visible_cd_run_status": str(
|
||||
readback.get("latest_visible_cd_run_status") or ""
|
||||
),
|
||||
"latest_visible_cd_run_commit_sha": str(
|
||||
readback.get("latest_visible_cd_run_commit_sha") or ""
|
||||
),
|
||||
"latest_visible_harbor_110_repair_run_id": str(
|
||||
readback.get("latest_visible_harbor_110_repair_run_id") or ""
|
||||
),
|
||||
"latest_visible_harbor_110_repair_run_status": str(
|
||||
readback.get("latest_visible_harbor_110_repair_run_status") or ""
|
||||
),
|
||||
"harbor_110_repair_no_matching_runner_label": no_matching_label,
|
||||
"harbor_110_repair_no_matching_runner": bool(no_matching_label),
|
||||
"harbor_110_repair_waiting": waiting,
|
||||
"harbor_110_repair_blocked": blocked,
|
||||
"harbor_110_repair_jobs_stale_or_mismatched": jobs_stale,
|
||||
"harbor_110_repair_jobs_unexpected_names": _strings(
|
||||
readback.get("harbor_110_repair_jobs_unexpected_names")
|
||||
),
|
||||
"harbor_110_repair_jobs_labels": _strings(
|
||||
readback.get("harbor_110_repair_jobs_labels")
|
||||
),
|
||||
"harbor_110_repair_jobs_runner_names": _strings(
|
||||
readback.get("harbor_110_repair_jobs_runner_names")
|
||||
),
|
||||
"operation_boundary_violation": boundary_violation,
|
||||
"blockers": blockers,
|
||||
"blocker_count": len(blockers),
|
||||
"metadata_only": True,
|
||||
"raw_output_returned": False,
|
||||
"top_visible_runs_returned": False,
|
||||
}
|
||||
|
||||
|
||||
def _gitea_queue_blockers(
|
||||
*,
|
||||
no_matching_runner: bool,
|
||||
jobs_stale: bool,
|
||||
blocked: bool,
|
||||
boundary_violation: bool,
|
||||
) -> list[str]:
|
||||
blockers: list[str] = []
|
||||
if no_matching_runner:
|
||||
blockers.append("gitea_queue_harbor_110_repair_no_matching_runner")
|
||||
elif blocked:
|
||||
blockers.append("gitea_queue_harbor_110_repair_blocked")
|
||||
if jobs_stale:
|
||||
blockers.append("gitea_queue_harbor_110_repair_jobs_stale_or_mismatched")
|
||||
if boundary_violation:
|
||||
blockers.append("gitea_queue_readback_boundary_violation")
|
||||
return blockers
|
||||
|
||||
|
||||
def _active_blockers(
|
||||
*,
|
||||
ssh_local: dict[str, Any],
|
||||
watchdog_check: dict[str, Any],
|
||||
watchdog_repair: dict[str, Any],
|
||||
verifier: dict[str, Any],
|
||||
gitea_queue: dict[str, Any],
|
||||
) -> list[str]:
|
||||
blockers: list[str] = []
|
||||
if ssh_local["receipt_seen"] and not ssh_local["control_channel_metadata_ready"]:
|
||||
@@ -341,6 +491,7 @@ def _active_blockers(
|
||||
blockers.append("public_registry_v2_verifier_not_green")
|
||||
if not verifier["internal_registry_v2_ready"]:
|
||||
blockers.append("internal_registry_v2_verifier_not_green")
|
||||
blockers.extend(_strings(gitea_queue.get("blockers")))
|
||||
return _unique_strings(blockers)
|
||||
|
||||
|
||||
@@ -350,10 +501,17 @@ def _status(
|
||||
watchdog_check: dict[str, Any],
|
||||
watchdog_repair: dict[str, Any],
|
||||
verifier: dict[str, Any],
|
||||
gitea_queue: dict[str, Any],
|
||||
active_blockers: list[str],
|
||||
) -> str:
|
||||
if verifier["registry_v2_ready"] and not active_blockers:
|
||||
return "harbor_registry_recovery_receipt_verified"
|
||||
if (
|
||||
verifier["registry_v2_ready"]
|
||||
and gitea_queue["receipt_seen"]
|
||||
and gitea_queue["blocker_count"] > 0
|
||||
):
|
||||
return "harbor_registry_recovery_receipt_verified_waiting_gitea_queue_clearance"
|
||||
if watchdog_repair["receipt_seen"]:
|
||||
return "harbor_registry_repair_receipt_waiting_registry_v2_verifier"
|
||||
if watchdog_check["receipt_seen"] and watchdog_check["harbor_ready"]:
|
||||
@@ -368,6 +526,8 @@ def _status(
|
||||
def _safe_next_step(*, status: str) -> str:
|
||||
if status == "harbor_registry_recovery_receipt_verified":
|
||||
return "retry_gitea_cd_then_verify_deploy_marker_and_priority_readback"
|
||||
if status == "harbor_registry_recovery_receipt_verified_waiting_gitea_queue_clearance":
|
||||
return "clear_harbor_110_runner_queue_then_retry_cd_marker_readback"
|
||||
if status == "harbor_registry_repair_receipt_waiting_registry_v2_verifier":
|
||||
return "rerun_public_and_internal_registry_v2_verifier_before_cd_retry"
|
||||
if status == "harbor_local_registry_ready_waiting_public_registry_v2_verifier":
|
||||
@@ -382,6 +542,8 @@ def _safe_next_step(*, status: str) -> str:
|
||||
def _current_apply_blocker(*, status: str) -> str:
|
||||
if status == "harbor_registry_recovery_receipt_verified":
|
||||
return "receipt_verified_waiting_cd_marker_readback"
|
||||
if status == "harbor_registry_recovery_receipt_verified_waiting_gitea_queue_clearance":
|
||||
return "gitea_queue_clearance_required_after_registry_receipt"
|
||||
if status == "harbor_watchdog_check_unhealthy_waiting_repair_once_receipt":
|
||||
return "repair_once_receipt_required_after_unhealthy_check"
|
||||
if status == "ssh_local_repair_receipt_waiting_harbor_watchdog_check":
|
||||
@@ -454,6 +616,16 @@ def _int_or_none(value: Any) -> int | None:
|
||||
return None
|
||||
|
||||
|
||||
def _dict(value: Any) -> dict[str, Any]:
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
|
||||
def _strings(value: Any) -> list[str]:
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
return [str(item) for item in value if str(item)]
|
||||
|
||||
|
||||
def _unique_strings(values: list[str]) -> list[str]:
|
||||
seen: set[str] = set()
|
||||
unique: list[str] = []
|
||||
|
||||
@@ -93,6 +93,51 @@ def test_harbor_recovery_receipt_routes_unhealthy_check_to_repair_once() -> None
|
||||
assert payload["controlled_apply_policy"]["manual_end_state"] is False
|
||||
|
||||
|
||||
def test_harbor_recovery_receipt_surfaces_gitea_queue_blockers() -> None:
|
||||
payload = validate_harbor_registry_controlled_recovery_receipt(
|
||||
{
|
||||
"watchdog_check_output": _watchdog_check_output(
|
||||
ready=True,
|
||||
status=401,
|
||||
),
|
||||
"public_registry_v2_http_status": 401,
|
||||
"internal_registry_v2_http_status": 401,
|
||||
"gitea_actions_queue_readback": _gitea_queue_no_matching_runner(),
|
||||
}
|
||||
)
|
||||
|
||||
assert payload["status"] == (
|
||||
"harbor_registry_recovery_receipt_verified_waiting_gitea_queue_clearance"
|
||||
)
|
||||
assert payload["accepted"] is False
|
||||
assert payload["safe_next_step"] == (
|
||||
"clear_harbor_110_runner_queue_then_retry_cd_marker_readback"
|
||||
)
|
||||
assert payload["active_blockers"] == [
|
||||
"gitea_queue_harbor_110_repair_no_matching_runner",
|
||||
"gitea_queue_harbor_110_repair_jobs_stale_or_mismatched",
|
||||
]
|
||||
queue = payload["readback"]["gitea_actions_queue"]
|
||||
assert queue["status"] == "blocked_harbor_110_repair_no_matching_runner"
|
||||
assert queue["harbor_110_repair_no_matching_runner_label"] == "awoooi-host"
|
||||
assert queue["harbor_110_repair_jobs_stale_or_mismatched"] is True
|
||||
assert queue["harbor_110_repair_jobs_unexpected_names"] == ["ai-code-review"]
|
||||
assert queue["raw_output_returned"] is False
|
||||
assert queue["top_visible_runs_returned"] is False
|
||||
assert payload["rollups"]["gitea_queue_blocker_count"] == 2
|
||||
assert payload["rollups"]["gitea_queue_harbor_110_no_matching_runner"] is True
|
||||
assert (
|
||||
payload["rollups"]["gitea_queue_harbor_110_jobs_stale_or_mismatched"]
|
||||
is True
|
||||
)
|
||||
assert payload["input_redaction"]["gitea_actions_queue_readback"] == {
|
||||
"provided": True,
|
||||
"metadata_only": True,
|
||||
"raw_output_returned": False,
|
||||
"top_visible_runs_returned": False,
|
||||
}
|
||||
|
||||
|
||||
def test_harbor_recovery_receipt_endpoint_redacts_raw_output() -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
@@ -156,3 +201,43 @@ container_remove_performed=false
|
||||
service_restart_performed=false
|
||||
host_reboot_performed=false
|
||||
"""
|
||||
|
||||
|
||||
def _gitea_queue_no_matching_runner() -> dict:
|
||||
return {
|
||||
"schema_version": "awoooi_public_gitea_actions_queue_readback_v1",
|
||||
"status": "blocked_harbor_110_repair_no_matching_runner",
|
||||
"operation_boundaries": {
|
||||
"public_gitea_read_only": True,
|
||||
"github_api_used": False,
|
||||
"host_write_performed": False,
|
||||
"runner_registration_performed": False,
|
||||
"runner_service_start_performed": False,
|
||||
"secret_or_runner_token_read": False,
|
||||
"workflow_dispatch_performed": False,
|
||||
},
|
||||
"readback": {
|
||||
"latest_visible_cd_run_id": "4110",
|
||||
"latest_visible_cd_run_status": "Running",
|
||||
"latest_visible_cd_run_commit_sha": (
|
||||
"5d10a9d1105757149b03373e35fc85a5aa772a96"
|
||||
),
|
||||
"latest_visible_harbor_110_repair_run_id": "4108",
|
||||
"latest_visible_harbor_110_repair_run_status": "Waiting",
|
||||
"latest_visible_harbor_110_repair_no_matching_runner_label": (
|
||||
"awoooi-host"
|
||||
),
|
||||
"latest_visible_harbor_110_repair_waiting": True,
|
||||
"latest_visible_harbor_110_repair_blocked": True,
|
||||
"harbor_110_repair_jobs_stale_or_mismatched": True,
|
||||
"harbor_110_repair_jobs_unexpected_names": ["ai-code-review"],
|
||||
"harbor_110_repair_jobs_labels": ["ubuntu-latest"],
|
||||
"harbor_110_repair_jobs_runner_names": ["wooo-runner"],
|
||||
},
|
||||
"rollups": {
|
||||
"harbor_110_repair_blocked": True,
|
||||
"harbor_110_repair_waiting": True,
|
||||
"harbor_110_repair_no_matching_runner_label": "awoooi-host",
|
||||
"harbor_110_repair_jobs_stale_or_mismatched": True,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -50878,3 +50878,27 @@ production browser smoke:
|
||||
|
||||
**下一步**:
|
||||
- rebase 最新 Gitea main 後 commit / push;若 CD 仍卡 Harbor / 110,P0 runtime 下一步仍是依 phase 在 110 local console / root shell 執行 check-mode / bounded repair,並提交 receipt 驗證 phase readback。
|
||||
|
||||
## 2026-06-30 — 23:08 Harbor recovery receipt Gitea queue metadata readback
|
||||
|
||||
**完成內容**:
|
||||
- `harbor-registry-controlled-recovery-receipt` 新增 `gitea_actions_queue_readback` metadata-only ingestion,將 public Gitea queue readback 中的 CD run、Harbor local repair run、`awoooi-host` no-matching runner、stale / mismatched jobs 與 operation boundary 轉成 receipt-side readback。
|
||||
- 新增 queue blocker 分類:`gitea_queue_harbor_110_repair_no_matching_runner`、`gitea_queue_harbor_110_repair_jobs_stale_or_mismatched`、`gitea_queue_harbor_110_repair_blocked`、`gitea_queue_readback_boundary_violation`。
|
||||
- registry verifier 已綠但 Gitea queue 仍卡住時,receipt status 會轉成 `harbor_registry_recovery_receipt_verified_waiting_gitea_queue_clearance`,避免把 source / registry receipt 誤報成 CD 可完成。
|
||||
- queue readback 不回傳 `top_visible_runs`、raw log、HTML 或 secret;只保留 run id、狀態、runner label、job name / label / runner name 等脫敏 metadata,供 KM / RAG / MCP / PlayBook / AI Agent 回寫。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `DATABASE_URL=postgresql+asyncpg://test:test@localhost:5432/test PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_harbor_registry_controlled_recovery_receipt.py -q`:`4 passed`。
|
||||
- `ruff check`、`py_compile`:通過。
|
||||
|
||||
**live truth**:
|
||||
- 23:08 前一輪 live readback:CD `#4110` Running / `5d10a9d11 feat(agent): add harbor recovery phase receipts`;Harbor repair `#4108` Waiting,no matching label `awoooi-host`;jobs API 讀到 stale / mismatched `ai-code-review` / `ubuntu-latest`,不可視為 Harbor repair 成功。
|
||||
- `https://registry.wooo.work/v2/`、`https://harbor.wooo.work/api/v2.0/health`、`http://192.168.0.110:5000/v2/` 仍回 502。
|
||||
|
||||
**仍維持**:
|
||||
- 沒有讀 secret / token / `.env` / raw sessions / SQLite / auth;沒有讀 `.runner` 內容。
|
||||
- 沒有使用 GitHub / gh / GitHub API / GitHub Actions。
|
||||
- 沒有重啟主機,沒有 Docker / Nginx / K3s / DB restart,沒有 workflow_dispatch,沒有 runtime write。
|
||||
|
||||
**下一步**:
|
||||
- 跑 focused suite / guards 後 commit / push;runtime 仍卡 Harbor / 110 時,P0 下一步仍是恢復 `awoooi-host` controlled lane 或在 110 local console / root shell 依 phase 執行 check-mode / bounded repair,再提交 queue + registry + deploy marker receipt。
|
||||
|
||||
Reference in New Issue
Block a user