From 693a31343bb525b655faffadafbcbff8246b1ff9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 1 Jul 2026 08:13:39 +0800 Subject: [PATCH] feat(agent): expose harbor receipt output contract --- ..._controlled_writeback_executor_readback.py | 78 +++++++++++++++++++ .../awoooi_priority_work_order_readback.py | 35 ++++++++- ...trolled_writeback_executor_readback_api.py | 32 ++++++++ ...awoooi_priority_work_order_readback_api.py | 26 +++++++ docs/LOGBOOK.md | 20 +++++ 5 files changed, 189 insertions(+), 2 deletions(-) diff --git a/apps/api/src/services/ai_agent_log_controlled_writeback_executor_readback.py b/apps/api/src/services/ai_agent_log_controlled_writeback_executor_readback.py index e7264f3e..a715d56d 100644 --- a/apps/api/src/services/ai_agent_log_controlled_writeback_executor_readback.py +++ b/apps/api/src/services/ai_agent_log_controlled_writeback_executor_readback.py @@ -115,6 +115,10 @@ def load_latest_ai_agent_log_controlled_writeback_executor_readback() -> dict[st item["harbor_recovery_receipt_input_count"] for item in current_blocker_queue ), + "current_blocker_harbor_recovery_receipt_output_contract_count": sum( + item["harbor_recovery_receipt_output_contract_count"] + for item in current_blocker_queue + ), "runtime_dispatch_performed": False, }, "active_blockers": active_blockers, @@ -353,6 +357,12 @@ def _current_blocker_queue_item(recovery: dict[str, Any]) -> dict[str, Any]: "harbor_recovery_receipt_input_count": len( _harbor_recovery_receipt_inputs() ), + "harbor_recovery_receipt_output_contract": ( + _harbor_recovery_receipt_output_contract() + ), + "harbor_recovery_receipt_output_contract_count": len( + _harbor_recovery_receipt_output_contract() + ), "queue_readback_normalizer_contract": _queue_readback_normalizer_contract(), "queue_readback_normalizer_contract_count": len( _queue_readback_normalizer_contract() @@ -468,6 +478,74 @@ def _harbor_recovery_receipt_inputs() -> list[dict[str, Any]]: ] +def _harbor_recovery_receipt_output_contract() -> list[dict[str, Any]]: + metadata_boundary = { + "metadata_only": True, + "raw_output_allowed": False, + "secret_value_allowed": False, + "writeback_targets": ["km", "rag", "playbook", "mcp", "verifier", "ai_agent"], + } + return [ + { + "output_id": "control_path_readiness", + "source": "harbor-registry-controlled-recovery-receipt.readback.control_path_readiness", + "required_when": "after_receipt_validation_before_km_rag_mcp_playbook_writeback", + "purpose": ( + "normalize 110 SSH, node load, awoooi-host queue, registry, " + "and safe-next-action signals into AI learning evidence" + ), + **metadata_boundary, + }, + { + "output_id": "controlled_cd_lane_readiness", + "source": "harbor-registry-controlled-recovery-receipt.readback.controlled_cd_lane_readiness", + "required_when": "before_retrying_harbor_110_local_repair_queue", + "purpose": ( + "write back controlled lane registration, service, fail-closed, " + "blocker count, and safe-next-step readiness" + ), + **metadata_boundary, + }, + { + "output_id": "gitea_actions_queue", + "source": "harbor-registry-controlled-recovery-receipt.readback.gitea_actions_queue", + "required_when": "after_public_gitea_queue_readback", + "purpose": ( + "write back normalized CD job, Harbor repair job, and " + "awoooi-host no-matching runner classifiers" + ), + **metadata_boundary, + }, + { + "output_id": "local_console_phase_readback", + "source": "harbor-registry-controlled-recovery-receipt.local_console_phase_readback", + "required_when": "after_receipt_validation", + "purpose": ( + "write back ordered diagnose, preflight, repair, verifier phase " + "status without raw console output" + ), + **metadata_boundary, + }, + { + "output_id": "post_apply_verifier", + "source": "harbor-registry-controlled-recovery-receipt.readback.post_apply_verifier", + "required_when": "after_registry_v2_verifier", + "purpose": "write back public and internal registry /v2/ readiness", + **metadata_boundary, + }, + { + "output_id": "deploy_marker", + "source": "harbor-registry-controlled-recovery-receipt.readback.deploy_marker", + "required_when": "after_registry_v2_ready_and_cd_retry", + "purpose": ( + "write back deploy marker, production image match, and latest " + "CD status before closing the P0 blocker" + ), + **metadata_boundary, + }, + ] + + def _queue_readback_normalizer_contract() -> list[dict[str, Any]]: return [ { 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 68be2969..9b520ef9 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -542,6 +542,15 @@ def apply_ai_loop_current_blocker_execution_queue( for item in harbor_recovery_receipt_inputs if item.get("input_id") ] + harbor_recovery_receipt_output_contract = [ + _dict(item) + for item in _list(first_item.get("harbor_recovery_receipt_output_contract")) + ] + harbor_recovery_receipt_output_ids = [ + str(item.get("output_id") or "") + for item in harbor_recovery_receipt_output_contract + if item.get("output_id") + ] queue_readback_normalizer_contract = [ _dict(item) for item in _list(first_item.get("queue_readback_normalizer_contract")) @@ -589,6 +598,12 @@ def apply_ai_loop_current_blocker_execution_queue( state["ai_loop_current_blocker_harbor_recovery_receipt_input_ids"] = ( harbor_recovery_receipt_input_ids ) + state["ai_loop_current_blocker_harbor_recovery_receipt_output_contract_count"] = ( + len(harbor_recovery_receipt_output_contract) + ) + state["ai_loop_current_blocker_harbor_recovery_receipt_output_ids"] = ( + harbor_recovery_receipt_output_ids + ) state["ai_loop_current_blocker_queue_readback_normalizer_contract_count"] = len( queue_readback_normalizer_contract ) @@ -665,6 +680,15 @@ def apply_ai_loop_current_blocker_execution_queue( evidence["ai_loop_current_blocker_harbor_recovery_receipt_inputs"] = ( harbor_recovery_receipt_inputs ) + evidence[ + "ai_loop_current_blocker_harbor_recovery_receipt_output_contract_count" + ] = len(harbor_recovery_receipt_output_contract) + evidence["ai_loop_current_blocker_harbor_recovery_receipt_output_ids"] = ( + harbor_recovery_receipt_output_ids + ) + evidence["ai_loop_current_blocker_harbor_recovery_receipt_output_contract"] = ( + harbor_recovery_receipt_output_contract + ) evidence["ai_loop_current_blocker_queue_readback_normalizer_contract"] = ( queue_readback_normalizer_contract ) @@ -715,8 +739,9 @@ def apply_ai_loop_current_blocker_execution_queue( "P0-006-AI-LOOP-CURRENT-BLOCKER-EXECUTION-QUEUE: execute the " f"{blocker_id} queue item through 110 control-path readback, " f"{len(local_console_plan)} ordered local-console phases, post-recovery " - "readback commands, Harbor receipt inputs, and metadata-only " - "KM/RAG/MCP/PlayBook writeback from normalized queue classifiers." + "readback commands, Harbor receipt inputs and outputs, and " + "metadata-only KM/RAG/MCP/PlayBook writeback from normalized queue " + "classifiers and control-path readiness classifiers." ), ( "P0-006-HARBOR-REGISTRY-CONTROLLED-RECOVERY-PREFLIGHT: after the " @@ -759,6 +784,12 @@ def apply_ai_loop_current_blocker_execution_queue( summary["ai_loop_current_blocker_harbor_recovery_receipt_input_ids"] = ( harbor_recovery_receipt_input_ids ) + summary["ai_loop_current_blocker_harbor_recovery_receipt_output_contract_count"] = ( + len(harbor_recovery_receipt_output_contract) + ) + summary["ai_loop_current_blocker_harbor_recovery_receipt_output_ids"] = ( + harbor_recovery_receipt_output_ids + ) summary["ai_loop_current_blocker_queue_readback_normalizer_contract_count"] = len( queue_readback_normalizer_contract ) diff --git a/apps/api/tests/test_ai_agent_log_controlled_writeback_executor_readback_api.py b/apps/api/tests/test_ai_agent_log_controlled_writeback_executor_readback_api.py index 0ee93157..f8707845 100644 --- a/apps/api/tests/test_ai_agent_log_controlled_writeback_executor_readback_api.py +++ b/apps/api/tests/test_ai_agent_log_controlled_writeback_executor_readback_api.py @@ -74,6 +74,12 @@ def _assert_executor_readback(payload: dict, *, public_endpoint: bool = False): payload["rollups"]["current_blocker_harbor_recovery_receipt_input_count"] == 9 ) + assert ( + payload["rollups"][ + "current_blocker_harbor_recovery_receipt_output_contract_count" + ] + == 6 + ) assert payload["rollups"]["runtime_dispatch_performed"] is False batches = {batch["target"]: batch for batch in payload["execution_batches"]} @@ -151,6 +157,32 @@ def _assert_executor_readback(payload: dict, *, public_endpoint: bool = False): assert current_queue[0]["harbor_recovery_receipt_inputs"][-1][ "expected_schema" ] == "awoooi_production_deploy_readback_blocker_v1" + assert current_queue[0]["harbor_recovery_receipt_output_contract_count"] == 6 + assert [ + item["output_id"] + for item in current_queue[0]["harbor_recovery_receipt_output_contract"] + ] == [ + "control_path_readiness", + "controlled_cd_lane_readiness", + "gitea_actions_queue", + "local_console_phase_readback", + "post_apply_verifier", + "deploy_marker", + ] + assert all( + item["metadata_only"] is True + and item["raw_output_allowed"] is False + and item["secret_value_allowed"] is False + and item["writeback_targets"] == [ + "km", + "rag", + "playbook", + "mcp", + "verifier", + "ai_agent", + ] + for item in current_queue[0]["harbor_recovery_receipt_output_contract"] + ) assert current_queue[0]["queue_readback_normalizer_contract_count"] == 3 assert [ item["field_id"] 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 ea1d3cfe..2c4347ba 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 @@ -344,6 +344,23 @@ def test_awoooi_priority_work_order_readback_overlays_ai_loop_current_blocker_qu assert evidence["ai_loop_current_blocker_harbor_recovery_receipt_inputs"][-1][ "expected_schema" ] == "awoooi_production_deploy_readback_blocker_v1" + assert ( + evidence[ + "ai_loop_current_blocker_harbor_recovery_receipt_output_contract_count" + ] + == 6 + ) + assert evidence["ai_loop_current_blocker_harbor_recovery_receipt_output_ids"] == [ + "control_path_readiness", + "controlled_cd_lane_readiness", + "gitea_actions_queue", + "local_console_phase_readback", + "post_apply_verifier", + "deploy_marker", + ] + assert evidence["ai_loop_current_blocker_harbor_recovery_receipt_output_contract"][ + 0 + ]["writeback_targets"] == ["km", "rag", "playbook", "mcp", "verifier", "ai_agent"] assert evidence["ai_loop_current_blocker_queue_readback_normalizer_field_ids"] == [ "cd_run_jobs_payload_classifier", "harbor_110_repair_jobs_payload_classifier", @@ -393,6 +410,15 @@ def test_awoooi_priority_work_order_readback_overlays_ai_loop_current_blocker_qu assert payload["summary"][ "ai_loop_current_blocker_harbor_recovery_receipt_input_ids" ][-1] == "deploy_marker_readback" + assert ( + payload["summary"][ + "ai_loop_current_blocker_harbor_recovery_receipt_output_contract_count" + ] + == 6 + ) + assert payload["summary"][ + "ai_loop_current_blocker_harbor_recovery_receipt_output_ids" + ][0] == "control_path_readiness" assert ( payload["summary"][ "ai_loop_current_blocker_queue_readback_normalizer_contract_count" diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index e97597b2..431ccfa1 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -51054,3 +51054,23 @@ production browser smoke: **下一步**: - commit / push 後讀回 Gitea queue、registry `/v2/`、Harbor health 與 priority work-order API;若仍卡 `awoooi-host` no matching,下一步維持 110 local console / controlled lane restore-registration verifier,不恢復 generic runner。 + +## 2026-07-01 — 08:12 AI Loop Harbor receipt output writeback contract + +**完成內容**: +- `agent-log-controlled-writeback-executor-readback` 新增 `harbor_recovery_receipt_output_contract`,把 Harbor receipt 產出的 `control_path_readiness`、`controlled_cd_lane_readiness`、`gitea_actions_queue`、`local_console_phase_readback`、`post_apply_verifier`、`deploy_marker` 6 個 metadata-only output 明確列為 KM / RAG / PlayBook / MCP / Verifier / AI Agent writeback 目標。 +- `awoooi-priority-work-order-readback` 同步投影 output contract count / ids / contract,讓主工作順序頁不只知道要收哪些 receipt input,也知道 receipt output 要沉澱到哪些學習節點。 +- next execution order 保留 `normalized queue classifiers` 相容字串,並新增 `control-path readiness classifiers`,避免舊下游判讀斷裂。 + +**本地驗證結果**: +- Focused suite:`74 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 後讀回 Gitea queue;runtime 若仍卡 Harbor 502 與 `awoooi-host` no matching,下一個主線仍是 110 controlled lane verifier / registration receipt,不恢復 generic runner。