From 106acf68300c3d847e064c9ee542534ff9793907 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 30 Jun 2026 22:05:46 +0800 Subject: [PATCH] fix(api): surface 110 harbor control channel blocker --- .../awoooi_priority_work_order_readback.py | 18 ++++ ..._registry_controlled_recovery_preflight.py | 82 +++++++++++++++++-- ...awoooi_priority_work_order_readback_api.py | 12 +++ ..._registry_controlled_recovery_preflight.py | 34 +++++++- 4 files changed, 140 insertions(+), 6 deletions(-) diff --git a/apps/api/src/services/awoooi_priority_work_order_readback.py b/apps/api/src/services/awoooi_priority_work_order_readback.py index 072437f9d..610e5e9f4 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -413,6 +413,15 @@ def apply_harbor_registry_controlled_recovery_preflight( evidence["harbor_registry_watchdog_executor_ready"] = bool( rollups.get("watchdog_executor_ready") is True ) + evidence["harbor_registry_control_channel_readback_required"] = bool( + rollups.get("control_channel_readback_required") is True + ) + evidence["harbor_registry_awoooi_host_runner_required"] = bool( + rollups.get("awoooi_host_runner_required") is True + ) + evidence["harbor_registry_ssh_publickey_diagnosis_required"] = bool( + rollups.get("ssh_publickey_diagnosis_required") is True + ) evidence["harbor_registry_controlled_work_item_count"] = _int( rollups.get("controlled_work_item_count") ) @@ -478,6 +487,15 @@ def apply_harbor_registry_controlled_recovery_preflight( summary["harbor_registry_watchdog_executor_ready"] = bool( rollups.get("watchdog_executor_ready") is True ) + summary["harbor_registry_control_channel_readback_required"] = bool( + rollups.get("control_channel_readback_required") is True + ) + summary["harbor_registry_awoooi_host_runner_required"] = bool( + rollups.get("awoooi_host_runner_required") is True + ) + summary["harbor_registry_ssh_publickey_diagnosis_required"] = bool( + rollups.get("ssh_publickey_diagnosis_required") is True + ) def _harbor_registry_next_state(*, status: str, candidate_packaged: bool) -> str: diff --git a/apps/api/src/services/harbor_registry_controlled_recovery_preflight.py b/apps/api/src/services/harbor_registry_controlled_recovery_preflight.py index 502e5769c..cde0d8f6f 100644 --- a/apps/api/src/services/harbor_registry_controlled_recovery_preflight.py +++ b/apps/api/src/services/harbor_registry_controlled_recovery_preflight.py @@ -167,6 +167,11 @@ def _build_payload( "/api/v1/agents/harbor-registry-controlled-recovery-receipt" ), }, + "control_channel_contract": _control_channel_contract( + registry_ready=registry_ready, + candidate_packaged=candidate_packaged, + upstream_502_shape=upstream_502_shape, + ), "source_of_truth_diff": { "status": _source_diff_status( registry_ready=registry_ready, @@ -238,6 +243,15 @@ def _build_payload( "watchdog_executor_ready": watchdog_executor_ready, "harbor_registry_upstream_502_shape": upstream_502_shape, "public_gateway_route_drift_shape": gateway_drift_shape, + "control_channel_readback_required": ( + not registry_ready and upstream_502_shape + ), + "awoooi_host_runner_required": ( + not registry_ready and upstream_502_shape + ), + "ssh_publickey_diagnosis_required": ( + not registry_ready and upstream_502_shape + ), "controlled_recovery_candidate_packaged": candidate_packaged, "controlled_work_item_count": len( _controlled_work_items( @@ -362,8 +376,8 @@ def _safe_next_step( return "rerun_gitea_cd_and_verify_deploy_marker" if candidate_packaged and upstream_502_shape: return ( - "run_harbor_watchdog_check_then_repair_once_with_public_" - "registry_v2_verifier" + "restore_110_ssh_or_awoooi_host_runner_control_channel_then_run_" + "harbor_watchdog_check_and_repair_once_with_public_registry_v2_verifier" ) if candidate_packaged and gateway_drift_shape: return ( @@ -395,6 +409,8 @@ def _active_blockers( blockers.append("harbor_watchdog_single_run_executor_contract_missing") if upstream_502_shape: blockers.append("harbor_registry_upstream_502") + blockers.append("awoooi_host_runner_or_ssh_control_channel_unavailable") + blockers.append("ssh_publickey_diagnosis_required_before_repair") if gateway_drift_shape: blockers.append("registry_public_gateway_route_drift") blockers.append("harbor_registry_control_channel_readback_required") @@ -429,7 +445,10 @@ def _current_apply_blocker( if registry_ready: return "" if candidate_packaged and upstream_502_shape: - return "110_harbor_watchdog_control_channel_readback_required_before_repair_once" + return ( + "110_ssh_or_awoooi_host_runner_control_channel_readback_required_" + "before_harbor_watchdog_repair_once" + ) if candidate_packaged and gateway_drift_shape: return "188_nginx_rendered_diff_and_nginx_t_required_before_route_apply" return "registry_v2_shape_and_executor_contract_required_before_apply" @@ -447,6 +466,20 @@ def _controlled_work_items( else "blocked_waiting_registry_v2_evidence" ) return [ + { + "id": "110-control-channel-readback", + "title": "110 SSH / awoooi-host runner control-channel readback", + "owner_agent": "DevOps Agent", + "status": ( + "blocked_waiting_ssh_publickey_or_awoooi_host_runner" + if upstream_502_shape + else "not_required_for_current_shape" + ), + "required_output": ( + "diagnose-110-ssh-publickey-auth plus public Gitea " + "awoooi-host runner queue readback without token collection" + ), + }, { "id": "harbor-registry-v2-readback", "title": "Registry /v2 public and internal readback", @@ -511,7 +544,7 @@ def _controlled_action_candidates( "id": "harbor_watchdog_check", "execution_route": "host_110_harbor_watchdog_executor", "current_status": ( - "queued_waiting_control_channel_readback" + "blocked_waiting_110_ssh_or_awoooi_host_runner_control_channel" if candidate_packaged else "blocked_waiting_registry_v2_evidence" ), @@ -531,7 +564,9 @@ def _controlled_action_candidates( { "id": "harbor_watchdog_repair_once", "execution_route": "host_110_harbor_watchdog_executor", - "current_status": "waiting_check_unhealthy_receipt", + "current_status": ( + "blocked_until_110_control_channel_and_check_unhealthy_receipt" + ), "check_mode": "watchdog_check_then_single_repair_attempt", "bounded_apply": ( "run existing harbor-watchdog --repair-once only; no " @@ -555,6 +590,43 @@ def _controlled_action_candidates( return candidates +def _control_channel_contract( + *, + registry_ready: bool, + candidate_packaged: bool, + upstream_502_shape: bool, +) -> dict[str, Any]: + required = (not registry_ready) and candidate_packaged and upstream_502_shape + return { + "required_before_runtime_apply": required, + "accepted_control_channels": [ + "110_local_console_or_root_shell", + "working_ssh_to_wooo_192_168_0_110", + "online_gitea_runner_label_awoooi-host", + ], + "current_required_readbacks": ( + [ + "scripts/reboot-recovery/diagnose-110-ssh-publickey-auth.sh", + "ops/runner/check-awoooi-110-controlled-cd-lane-readiness.sh", + "ops/runner/read-public-gitea-actions-queue.py --json", + "scripts/reboot-recovery/recover-110-control-path-and-harbor-local.sh --check", + ] + if required + else [] + ), + "blocked_runtime_apply_reason": ( + "harbor_watchdog_repair_once_requires_110_local_or_awoooi_host_runner_control_channel" + if required + else "" + ), + "token_collection_allowed": False, + "runner_registration_token_read_allowed": False, + "raw_authorized_keys_read_allowed": False, + "workflow_dispatch_allowed": False, + "host_write_allowed_by_this_contract": False, + } + + def _post_apply_verifiers() -> list[dict[str, Any]]: return [ { diff --git a/apps/api/tests/test_awoooi_priority_work_order_readback_api.py b/apps/api/tests/test_awoooi_priority_work_order_readback_api.py index 7fe039fb7..9f8726964 100644 --- a/apps/api/tests/test_awoooi_priority_work_order_readback_api.py +++ b/apps/api/tests/test_awoooi_priority_work_order_readback_api.py @@ -150,6 +150,9 @@ def test_awoooi_priority_work_order_readback_overlays_harbor_deploy_blocker( assert evidence["harbor_registry_controlled_recovery_candidate_packaged"] is True assert evidence["harbor_registry_upstream_502_shape"] is True assert evidence["harbor_registry_watchdog_executor_ready"] is True + assert evidence["harbor_registry_control_channel_readback_required"] is True + assert evidence["harbor_registry_awoooi_host_runner_required"] is True + assert evidence["harbor_registry_ssh_publickey_diagnosis_required"] is True assert evidence["harbor_registry_runtime_write_gate"] == "controlled_after_preflight" assert evidence["harbor_registry_current_apply_allowed"] is False assert "not a manual end state" in in_progress["reason"] @@ -160,6 +163,15 @@ def test_awoooi_priority_work_order_readback_overlays_harbor_deploy_blocker( assert payload["summary"][ "harbor_registry_controlled_recovery_candidate_packaged" ] is True + assert ( + payload["summary"]["harbor_registry_control_channel_readback_required"] + is True + ) + assert payload["summary"]["harbor_registry_awoooi_host_runner_required"] is True + assert ( + payload["summary"]["harbor_registry_ssh_publickey_diagnosis_required"] + is True + ) assert payload["next_execution_order"][0].startswith( "P0-006-HARBOR-REGISTRY-CONTROLLED-RECOVERY-PREFLIGHT" ) diff --git a/apps/api/tests/test_harbor_registry_controlled_recovery_preflight.py b/apps/api/tests/test_harbor_registry_controlled_recovery_preflight.py index 559e2ae9b..25ba071a3 100644 --- a/apps/api/tests/test_harbor_registry_controlled_recovery_preflight.py +++ b/apps/api/tests/test_harbor_registry_controlled_recovery_preflight.py @@ -29,15 +29,46 @@ def test_harbor_registry_preflight_packages_110_upstream_502(tmp_path: Path) -> ) assert payload["rollups"]["harbor_registry_upstream_502_shape"] is True assert payload["rollups"]["controlled_recovery_candidate_packaged"] is True + assert payload["rollups"]["control_channel_readback_required"] is True + assert payload["rollups"]["awoooi_host_runner_required"] is True + assert payload["rollups"]["ssh_publickey_diagnosis_required"] is True assert payload["rollups"]["watchdog_executor_ready"] is True + assert payload["control_channel_contract"]["required_before_runtime_apply"] is True + assert ( + payload["control_channel_contract"]["blocked_runtime_apply_reason"] + == "harbor_watchdog_repair_once_requires_110_local_or_awoooi_host_runner_control_channel" + ) + assert payload["control_channel_contract"]["token_collection_allowed"] is False + assert ( + payload["control_channel_contract"]["runner_registration_token_read_allowed"] + is False + ) + assert payload["control_channel_contract"]["raw_authorized_keys_read_allowed"] is False assert payload["controlled_apply_policy"]["manual_end_state"] is False assert payload["controlled_apply_policy"]["break_glass_required"] is False assert payload["controlled_apply_policy"]["current_apply_allowed"] is False assert payload["controlled_action_candidates"][1]["id"] == ( "harbor_watchdog_repair_once" ) - assert payload["controlled_work_items"][2]["id"] == "harbor-watchdog-repair-once" + assert payload["controlled_action_candidates"][0]["current_status"] == ( + "blocked_waiting_110_ssh_or_awoooi_host_runner_control_channel" + ) + assert payload["controlled_work_items"][0]["id"] == "110-control-channel-readback" + assert payload["controlled_work_items"][0]["status"] == ( + "blocked_waiting_ssh_publickey_or_awoooi_host_runner" + ) + assert payload["controlled_work_items"][3]["id"] == "harbor-watchdog-repair-once" assert "harbor_registry_upstream_502" in payload["active_blockers"] + assert ( + "awoooi_host_runner_or_ssh_control_channel_unavailable" + in payload["active_blockers"] + ) + assert "ssh_publickey_diagnosis_required_before_repair" in payload[ + "active_blockers" + ] + assert payload["safe_next_step"].startswith( + "restore_110_ssh_or_awoooi_host_runner_control_channel" + ) assert "docker_daemon_restart" in payload["controlled_apply_policy"][ "forbidden_scope" ] @@ -58,6 +89,7 @@ def test_harbor_registry_preflight_ready_when_registry_v2_ready(tmp_path: Path) assert payload["status"] == "harbor_registry_ready" assert payload["active_blockers"] == [] assert payload["rollups"]["registry_ready"] is True + assert payload["control_channel_contract"]["required_before_runtime_apply"] is False assert payload["safe_next_step"] == "rerun_gitea_cd_and_verify_deploy_marker"