diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 78b54c53e..397b57bac 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -336,6 +336,7 @@ from src.services.awoooi_gitea_onboarding_warning_step_template_copy_receipt imp load_latest_awoooi_gitea_onboarding_warning_step_template_copy_receipt, ) from src.services.awoooi_priority_work_order_readback import ( + apply_ai_loop_current_blocker_execution_queue, apply_harbor_registry_controlled_recovery_preflight, apply_stockplatform_public_api_controlled_recovery_preflight, apply_stockplatform_public_api_runtime_readback, @@ -1114,6 +1115,10 @@ async def get_awoooi_priority_work_order_readback() -> dict[str, Any]: payload, harbor_recovery, ) + log_executor = await asyncio.to_thread( + load_latest_ai_agent_log_controlled_writeback_executor_readback + ) + apply_ai_loop_current_blocker_execution_queue(payload, log_executor) return redact_public_lan_topology(payload) except FileNotFoundError as exc: raise HTTPException( 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 610e5e9f4..f89288907 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -498,6 +498,146 @@ def apply_harbor_registry_controlled_recovery_preflight( ) +def apply_ai_loop_current_blocker_execution_queue( + payload: dict[str, Any], + executor_readback: dict[str, Any], +) -> None: + """Overlay the AI Loop current blocker queue onto the ordered work board.""" + if payload.get("status") != ( + "p0_006_blocked_harbor_registry_controlled_recovery_preflight" + ): + return + + context = _dict(executor_readback.get("agent_consumption_context")) + queue = list(_list(context.get("current_blocker_execution_queue"))) + if not queue: + return + + first_item = _dict(queue[0]) + blocker_id = str(first_item.get("blocker_id") or "") + controlled_recovery_package = str( + first_item.get("controlled_recovery_package") or "" + ) + post_apply_verifier = str(first_item.get("post_apply_verifier") or "") + learning_targets = _strings(first_item.get("learning_writeback_targets")) + target_batches = _strings(first_item.get("target_batches")) + external_blocker = str(first_item.get("external_control_path_blocker") or "") + runtime_write_gate = str(first_item.get("runtime_write_gate") or "") + + state = _dict(payload.setdefault("mainline_execution_state", {})) + state["active_p0_state"] = "blocked_ai_loop_current_blocker_execution_queue" + state["next_executable_mainline_workplan_id"] = ( + "P0-006-AI-LOOP-CURRENT-BLOCKER-EXECUTION-QUEUE" + ) + state["next_executable_mainline_state"] = ( + "ai_loop_current_blocker_queue_waiting_110_control_path_readback" + ) + state["ai_loop_current_blocker_execution_queue_count"] = len(queue) + state["ai_loop_current_blocker_id"] = blocker_id + state["ai_loop_current_blocker_control_path_blocker"] = external_blocker + state["ai_loop_current_blocker_runtime_write_gate"] = runtime_write_gate + state["ai_loop_current_blocker_learning_target_count"] = len(learning_targets) + state["active_p0_live_active_blockers"] = _unique_strings( + _strings(state.get("active_p0_live_active_blockers")) + + ["ai_loop_current_blocker_execution_queue"] + + ([blocker_id] if blocker_id else []) + + ([external_blocker] if external_blocker else []) + ) + + for item in _list(payload.get("in_progress_or_blocked_in_priority_order")): + workplan = _dict(item) + if workplan.get("workplan_id") != "P0-006": + continue + + evidence = _dict(workplan.setdefault("evidence", {})) + evidence["ai_loop_current_blocker_execution_queue_count"] = len(queue) + evidence["ai_loop_current_blocker_id"] = blocker_id + evidence["ai_loop_current_blocker_queue_item_id"] = str( + first_item.get("queue_item_id") or "" + ) + evidence["ai_loop_current_blocker_runner_label"] = str( + first_item.get("runner_label") or "" + ) + evidence["ai_loop_current_blocker_registry_v2_status"] = first_item.get( + "registry_v2_status" + ) + evidence["ai_loop_current_blocker_controlled_recovery_package"] = ( + controlled_recovery_package + ) + evidence["ai_loop_current_blocker_post_apply_verifier"] = ( + post_apply_verifier + ) + evidence["ai_loop_current_blocker_runtime_write_gate"] = runtime_write_gate + evidence["ai_loop_current_blocker_control_path_blocker"] = external_blocker + evidence["ai_loop_current_blocker_learning_writeback_targets"] = ( + learning_targets + ) + evidence["ai_loop_current_blocker_target_batches"] = target_batches + evidence["ai_loop_current_blocker_metadata_only_writeback_ready"] = bool( + first_item.get("metadata_only_writeback_ready") is True + ) + evidence["ai_loop_current_blocker_runtime_dispatch_performed"] = bool( + first_item.get("runtime_dispatch_performed") is True + ) + evidence["ai_loop_current_blocker_raw_payload_required"] = bool( + first_item.get("raw_payload_required") is True + ) + + workplan["status"] = "blocked_ai_loop_current_blocker_execution_queue" + workplan["safe_next_step"] = str(first_item.get("safe_next_step") or "") + workplan["reason"] = ( + "The Harbor / 110 blocker is now visible as an AI Loop execution " + "queue item with metadata-only KM/RAG/MCP/PlayBook/AI Agent " + "writeback targets. The next action is not generic manual triage; " + "it is 110 control-path readback plus the bounded local recovery " + "package and post-apply verifier." + ) + professional_fix = _dict(workplan.setdefault("professional_fix", {})) + professional_fix["action"] = ( + "Run the AI Loop current blocker package in order: verify 110 " + "control path, execute " + f"{controlled_recovery_package}, run {post_apply_verifier}, then " + "rerun Harbor queue / registry /v2/ / deploy-marker readbacks and " + "write metadata-only KM/RAG/MCP/PlayBook receipts. Do not restart " + "Docker daemon, reboot hosts, drain nodes, switch registry " + "provider, trigger workflows, or read runner tokens from this lane." + ) + professional_fix["owner"] = ( + "AI Loop current blocker queue plus P0-006 Harbor/110 control path" + ) + + payload["status"] = "p0_006_blocked_ai_loop_current_blocker_execution_queue" + payload["next_execution_order"] = [ + ( + "P0-006-AI-LOOP-CURRENT-BLOCKER-EXECUTION-QUEUE: execute the " + f"{blocker_id} queue item through 110 control-path readback, " + "bounded recovery package check-mode, post-apply verifier, and " + "metadata-only KM/RAG/MCP/PlayBook writeback." + ), + ( + "P0-006-HARBOR-REGISTRY-CONTROLLED-RECOVERY-PREFLIGHT: after the " + "AI Loop queue item verifies 110 control path, rerun Harbor " + "watchdog check-mode / repair-once and registry /v2/ readback." + ), + ( + "P0-006-CD-DEPLOY-MARKER-READBACK: after registry /v2/ is 200/401, " + "let the next Gitea CD run build/push/deploy and then verify " + "delivery-closure-workbench, priority work order, and production " + "marker advance." + ), + ] + _refresh_rollups_after_stockplatform_overlay(payload, state) + summary = _dict(payload.setdefault("summary", {})) + summary["ai_loop_current_blocker_execution_queue_count"] = len(queue) + summary["ai_loop_current_blocker_id"] = blocker_id + summary["ai_loop_current_blocker_control_path_blocker"] = external_blocker + summary["ai_loop_current_blocker_controlled_recovery_package"] = ( + controlled_recovery_package + ) + summary["ai_loop_current_blocker_post_apply_verifier"] = post_apply_verifier + summary["ai_loop_current_blocker_learning_target_count"] = len(learning_targets) + + def _harbor_registry_next_state(*, status: str, candidate_packaged: bool) -> str: if candidate_packaged and "upstream_502" in status: 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 9f8726964..bfa32e611 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 @@ -9,7 +9,11 @@ from fastapi.testclient import TestClient from src.api.v1 import agents from src.api.v1.agents import router +from src.services.ai_agent_log_controlled_writeback_executor_readback import ( + load_latest_ai_agent_log_controlled_writeback_executor_readback, +) from src.services.awoooi_priority_work_order_readback import ( + apply_ai_loop_current_blocker_execution_queue, apply_harbor_registry_controlled_recovery_preflight, apply_stockplatform_public_api_controlled_recovery_preflight, apply_stockplatform_public_api_runtime_readback, @@ -177,6 +181,80 @@ def test_awoooi_priority_work_order_readback_overlays_harbor_deploy_blocker( ) +def test_awoooi_priority_work_order_readback_overlays_ai_loop_current_blocker_queue( + tmp_path: Path, +): + payload = load_latest_awoooi_priority_work_order_readback() + recovery = load_latest_harbor_registry_controlled_recovery_preflight( + route_source_path=_harbor_route_source(tmp_path), + watchdog_source_path=_harbor_watchdog_source(tmp_path), + probe=_harbor_upstream_502_probe, + ) + executor = load_latest_ai_agent_log_controlled_writeback_executor_readback() + + apply_harbor_registry_controlled_recovery_preflight(payload, recovery) + apply_ai_loop_current_blocker_execution_queue(payload, executor) + + state = payload["mainline_execution_state"] + in_progress = payload["in_progress_or_blocked_in_priority_order"][0] + evidence = in_progress["evidence"] + assert payload["status"] == ( + "p0_006_blocked_ai_loop_current_blocker_execution_queue" + ) + assert state["active_p0_state"] == ( + "blocked_ai_loop_current_blocker_execution_queue" + ) + assert state["next_executable_mainline_workplan_id"] == ( + "P0-006-AI-LOOP-CURRENT-BLOCKER-EXECUTION-QUEUE" + ) + assert state["ai_loop_current_blocker_execution_queue_count"] == 1 + assert state["ai_loop_current_blocker_id"] == ( + "harbor_110_repair_no_matching_runner" + ) + assert state["ai_loop_current_blocker_control_path_blocker"] == ( + "publickey_offer_timeout" + ) + assert evidence["ai_loop_current_blocker_execution_queue_count"] == 1 + assert evidence["ai_loop_current_blocker_id"] == ( + "harbor_110_repair_no_matching_runner" + ) + assert evidence["ai_loop_current_blocker_runner_label"] == "awoooi-host" + assert evidence["ai_loop_current_blocker_registry_v2_status"] == 502 + assert evidence["ai_loop_current_blocker_controlled_recovery_package"] == ( + "recover-110-control-path-and-harbor-local.sh --check" + ) + assert evidence["ai_loop_current_blocker_post_apply_verifier"] == ( + "check-awoooi-110-controlled-cd-lane-readiness.sh" + ) + assert evidence["ai_loop_current_blocker_runtime_write_gate"] == ( + "controlled_after_110_local_console_preflight" + ) + assert evidence["ai_loop_current_blocker_control_path_blocker"] == ( + "publickey_offer_timeout" + ) + assert set(evidence["ai_loop_current_blocker_learning_writeback_targets"]) == { + "km", + "rag", + "playbook", + "mcp", + "verifier", + "ai_agent", + } + assert len(evidence["ai_loop_current_blocker_target_batches"]) == 6 + assert evidence["ai_loop_current_blocker_metadata_only_writeback_ready"] is True + assert evidence["ai_loop_current_blocker_runtime_dispatch_performed"] is False + assert evidence["ai_loop_current_blocker_raw_payload_required"] is False + assert "not generic manual triage" in in_progress["reason"] + assert "Do not restart Docker daemon" in in_progress["professional_fix"]["action"] + assert payload["summary"]["ai_loop_current_blocker_execution_queue_count"] == 1 + assert payload["summary"]["ai_loop_current_blocker_id"] == ( + "harbor_110_repair_no_matching_runner" + ) + assert payload["next_execution_order"][0].startswith( + "P0-006-AI-LOOP-CURRENT-BLOCKER-EXECUTION-QUEUE" + ) + + def test_awoooi_priority_work_order_readback_overlays_live_stockplatform_drift(): payload = load_latest_awoooi_priority_work_order_readback() diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 473fcc1d8..522b4dc75 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -50810,3 +50810,23 @@ production browser smoke: **下一步**: - commit / push 後等待 Gitea CD;若 runtime 仍卡 Harbor / 110,P0 主線仍是恢復 110 `awoooi-host` controlled lane 或 110 local recovery package,再重讀 registry `/v2/`、full-stack cold-start、Stock freshness、backup core 與 SLO scorecard。 + +## 2026-06-30 — 22:46 AI Loop current blocker queue 投影到主線工作順序 + +**完成內容**: +- `awoooi-priority-work-order-readback` 新增 AI Loop current blocker overlay:當 Harbor / 110 preflight 已成為 P0 blocker,且 `agent-log-controlled-writeback-executor-readback` 有 `current_blocker_execution_queue` 時,主線工作順序會改投影為 `P0-006-AI-LOOP-CURRENT-BLOCKER-EXECUTION-QUEUE`。 +- P0 evidence 現在直接帶出 `harbor_110_repair_no_matching_runner`、`awoooi-host`、`publickey_offer_timeout`、`recover-110-control-path-and-harbor-local.sh --check`、`check-awoooi-110-controlled-cd-lane-readiness.sh`、6 個 KM/RAG/MCP/PlayBook/verifier/AI Agent writeback targets、metadata-only / no raw payload boundary。 +- `/api/v1/agents/awoooi-priority-work-order-readback` 接上 `load_latest_ai_agent_log_controlled_writeback_executor_readback()`,讓工作順序頁與 AI Loop executor 不再分裂:AI Agent 看得到的 current blocker queue,P0 work-order 也看得到。 + +**本地驗證結果**: +- `DATABASE_URL=postgresql+asyncpg://test:test@localhost:5432/test PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_awoooi_priority_work_order_readback_api.py apps/api/tests/test_harbor_registry_controlled_recovery_preflight.py apps/api/tests/test_ai_agent_log_controlled_writeback_executor_readback_api.py apps/api/tests/test_ai_agent_autonomous_runtime_control.py apps/api/tests/test_ai_agent_autonomous_runtime_control_api.py ops/runner/test_read_public_gitea_actions_queue.py -q`:`48 passed`。 +- `ruff check`、`py_compile`、`ops/runner/guard-gitea-runner-pressure.py --root .`、`scripts/ci/check-gitea-step-env-secrets.js`、`git diff --check`:通過。 +- 本地 payload 摘要:`status=p0_006_blocked_ai_loop_current_blocker_execution_queue`、`next_workplan=P0-006-AI-LOOP-CURRENT-BLOCKER-EXECUTION-QUEUE`、`queue_count=1`。 + +**live truth**: +- Gitea latest CD `#4102` 正在跑 `b474f80c6`;`harbor-110-local-repair` `#4099` 仍 Waiting,no matching label `awoooi-host`。 +- `https://registry.wooo.work/v2/` 與 `https://harbor.wooo.work/api/v2.0/health` 仍回 502。 +- 本輪未執行 runtime write、workflow dispatch、host restart、Docker / K3s / Nginx reload、secret / runner token 讀取。 + +**下一步**: +- commit / push 後等待 CD;若仍卡 Harbor / 110,主線下一步不變:恢復 110 `awoooi-host` controlled lane 或在 110 local console / root shell 執行 recovery package check-mode,再重讀 Harbor queue、registry `/v2/`、deploy marker,並把 post-apply verifier receipt 回寫到 AI Loop。