From 51d87b237dded204ab35f8abaa16768e5436a522 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 30 Jun 2026 22:30:33 +0800 Subject: [PATCH] feat(agent): expose current blocker execution queue --- .../ai_agent_autonomous_runtime_control.py | 18 +++ ..._controlled_writeback_executor_readback.py | 92 ++++++++++- ..._log_controlled_writeback_plan_readback.py | 148 ++++++++++++------ .../ai_agent_log_feedback_receipt_dry_run.py | 5 + ...est_ai_agent_autonomous_runtime_control.py | 33 ++++ ...trolled_writeback_executor_readback_api.py | 36 +++++ ..._controlled_writeback_plan_readback_api.py | 22 +++ ..._agent_log_feedback_receipt_dry_run_api.py | 10 ++ docs/LOGBOOK.md | 19 +++ 9 files changed, 332 insertions(+), 51 deletions(-) diff --git a/apps/api/src/services/ai_agent_autonomous_runtime_control.py b/apps/api/src/services/ai_agent_autonomous_runtime_control.py index 3eefe58fb..1ed04dbd3 100644 --- a/apps/api/src/services/ai_agent_autonomous_runtime_control.py +++ b/apps/api/src/services/ai_agent_autonomous_runtime_control.py @@ -3018,6 +3018,11 @@ def _attach_runtime_receipt_readback( log_executor_queue = log_executor_context.get("next_action_queue") if not isinstance(log_executor_queue, list): log_executor_queue = [] + log_executor_current_blocker_queue = log_executor_context.get( + "current_blocker_execution_queue" + ) + if not isinstance(log_executor_current_blocker_queue, list): + log_executor_current_blocker_queue = [] log_executor_blockers = log_executor.get("active_blockers") if not isinstance(log_executor_blockers, list): log_executor_blockers = [] @@ -3140,6 +3145,19 @@ def _attach_runtime_receipt_readback( "live_log_controlled_writeback_next_action_queue_count": len( log_executor_queue ), + "live_log_controlled_writeback_current_blocker_queue_count": len( + log_executor_current_blocker_queue + ), + "live_log_controlled_writeback_current_blocker_control_path_blocked_count": ( + _int_value( + log_executor_rollups.get("current_blocker_control_path_blocked_count") + ) + ), + "live_log_controlled_writeback_current_blocker_local_recovery_package_count": ( + _int_value( + log_executor_rollups.get("current_blocker_local_recovery_package_count") + ) + ), "live_log_controlled_writeback_dispatch_count": _int_value( log_dispatch_summary.get("total") ), 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 3e7cb98d1..a59435405 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 @@ -37,6 +37,8 @@ def load_latest_ai_agent_log_controlled_writeback_executor_readback() -> dict[st writeback_plans=writeback_plans, execution_batches=execution_batches, ) + agent_context = _agent_consumption_context(execution_batches) + current_blocker_queue = agent_context["current_blocker_execution_queue"] return { "schema_version": _SCHEMA_VERSION, @@ -66,7 +68,7 @@ def load_latest_ai_agent_log_controlled_writeback_executor_readback() -> dict[st "post_apply_verifier_required": True, }, "execution_batches": execution_batches, - "agent_consumption_context": _agent_consumption_context(execution_batches), + "agent_consumption_context": agent_context, "rollups": { "source_writeback_plan_count": len(writeback_plans), "execution_batch_count": len(execution_batches), @@ -95,6 +97,15 @@ def load_latest_ai_agent_log_controlled_writeback_executor_readback() -> dict[st ), "controlled_executor_dispatch_ready": not active_blockers, "controlled_apply_enabled_by_policy": True, + "current_blocker_execution_queue_count": len(current_blocker_queue), + "current_blocker_control_path_blocked_count": sum( + 1 + for item in current_blocker_queue + if item["external_control_path_blocker"] + ), + "current_blocker_local_recovery_package_count": sum( + 1 for item in current_blocker_queue if item["controlled_recovery_package"] + ), "runtime_dispatch_performed": False, }, "active_blockers": active_blockers, @@ -133,6 +144,7 @@ def _execution_batch(target: str, plans: list[dict[str, Any]]) -> dict[str, Any] risk_tier = "medium" if target in {"km", "rag", "playbook"} else "low" target_selectors = [plan.get("target_selector") or {} for plan in plans] diffs = [plan.get("source_of_truth_diff") or {} for plan in plans] + current_blocker_recoveries = _current_blocker_recoveries(plans) rollback_refs = [ str((plan.get("rollback") or {}).get("rollback_ref") or "") for plan in plans @@ -162,6 +174,8 @@ def _execution_batch(target: str, plans: list[dict[str, Any]]) -> dict[str, Any] "target_selectors": target_selectors, "source_of_truth_diff_count": len(diffs), "source_of_truth_diffs": diffs, + "current_blocker_recovery_count": len(current_blocker_recoveries), + "current_blocker_recoveries": current_blocker_recoveries, "check_mode": { "enabled": True, "required": True, @@ -241,9 +255,15 @@ def _agent_consumption_context(execution_batches: list[dict[str, Any]]) -> dict[ "check_mode_required": batch["check_mode"]["required"], "rollback_required": batch["rollback"]["required"], "post_apply_verifier_required": batch["post_apply_verifier"]["required"], + "current_blocker_recovery_count": batch[ + "current_blocker_recovery_count" + ], } for batch in ready_batches ], + "current_blocker_execution_queue": _current_blocker_execution_queue( + ready_batches + ), "learning_feedback_targets": [ "km", "rag", @@ -256,6 +276,76 @@ def _agent_consumption_context(execution_batches: list[dict[str, Any]]) -> dict[ } +def _current_blocker_recoveries(plans: list[dict[str, Any]]) -> list[dict[str, Any]]: + recoveries = [] + for plan in plans: + recovery = plan.get("current_blocker_recovery") + if not isinstance(recovery, dict): + continue + recoveries.append(dict(recovery)) + return recoveries + + +def _current_blocker_execution_queue( + ready_batches: list[dict[str, Any]], +) -> list[dict[str, Any]]: + queue_by_key: dict[tuple[str, str], dict[str, Any]] = {} + for batch in ready_batches: + target = str(batch.get("target") or "") + batch_id = str(batch.get("batch_id") or "") + for recovery in batch.get("current_blocker_recoveries") or []: + if not isinstance(recovery, dict): + continue + source_sample_id = str(recovery.get("source_sample_id") or "") + blocker_id = str(recovery.get("blocker_id") or "") + if not source_sample_id or not blocker_id: + continue + key = (source_sample_id, blocker_id) + queue_item = queue_by_key.setdefault( + key, + _current_blocker_queue_item(recovery), + ) + queue_item["target_batches"] = _unique( + queue_item["target_batches"] + [batch_id] + ) + queue_item["learning_writeback_targets"] = _unique( + queue_item["learning_writeback_targets"] + [target] + ) + return list(queue_by_key.values()) + + +def _current_blocker_queue_item(recovery: dict[str, Any]) -> dict[str, Any]: + blocker_id = str(recovery.get("blocker_id") or "") + source_sample_id = str(recovery.get("source_sample_id") or "") + external_blocker = str(recovery.get("external_control_path_blocker") or "") + return { + "queue_item_id": f"current-p0-blocker::{blocker_id}", + "source_sample_id": source_sample_id, + "incident_id": str(recovery.get("incident_id") or ""), + "blocker_id": blocker_id, + "status": "controlled_recovery_packaged_waiting_control_path_readback", + "risk_tier": str(recovery.get("risk_tier") or "unknown"), + "runner_label": str(recovery.get("runner_label") or ""), + "registry_v2_status": recovery.get("registry_v2_status"), + "controlled_recovery_package": str( + recovery.get("controlled_recovery_package") or "" + ), + "post_apply_verifier": str(recovery.get("post_apply_verifier") or ""), + "safe_next_step": str(recovery.get("safe_next_step") or ""), + "runtime_write_gate": "controlled_after_110_local_console_preflight", + "runtime_apply_required_on_110_local_console": bool( + recovery.get("runtime_apply_required_on_110_local_console") is True + ), + "external_control_path_blocker": external_blocker, + "control_channel_readback_required": bool(external_blocker), + "metadata_only_writeback_ready": True, + "runtime_dispatch_performed": False, + "raw_payload_required": False, + "target_batches": [], + "learning_writeback_targets": [], + } + + def _active_blockers( *, plan_ready: bool, diff --git a/apps/api/src/services/ai_agent_log_controlled_writeback_plan_readback.py b/apps/api/src/services/ai_agent_log_controlled_writeback_plan_readback.py index f75f666d1..769b09cfd 100644 --- a/apps/api/src/services/ai_agent_log_controlled_writeback_plan_readback.py +++ b/apps/api/src/services/ai_agent_log_controlled_writeback_plan_readback.py @@ -85,6 +85,18 @@ def load_latest_ai_agent_log_controlled_writeback_plan_readback() -> dict[str, A "ready_writeback_plan_count": sum( 1 for plan in writeback_plans if plan["status"] == "controlled_apply_ready" ), + "current_blocker_recovery_plan_count": sum( + 1 for plan in writeback_plans if plan.get("current_blocker_recovery") + ), + "current_blocker_recovery_unique_count": len( + _unique( + [ + str((plan.get("current_blocker_recovery") or {}).get("blocker_id")) + for plan in writeback_plans + if plan.get("current_blocker_recovery") + ] + ) + ), "check_mode_plan_count": sum( 1 for plan in writeback_plans if plan["check_mode"]["enabled"] is True ), @@ -129,59 +141,95 @@ def _writeback_plans(receipts: list[dict[str, Any]]) -> list[dict[str, Any]]: source_sample_id = str(receipt.get("source_sample_id") or "") receipt_id = str(receipt.get("receipt_id") or "") plan_id = f"log-feedback-writeback::{target}::{source_sample_id}" - plans.append( - { - "plan_id": plan_id, - "receipt_id": receipt_id, - "target": target, - "status": "controlled_apply_ready", - "risk_tier": "medium" if target in {"km", "rag", "playbook"} else "low", - "target_selector": { - "project_id": receipt.get("project_id"), - "product": receipt.get("product"), - "service": receipt.get("service"), - "package": receipt.get("package"), - "tool": receipt.get("tool"), - "source_system": receipt.get("source_system"), - "source_sample_id": source_sample_id, - "target_surface": _target_surface(target), - }, - "source_of_truth_diff": { - "current_state": "metadata_only_feedback_not_written", - "desired_state": "metadata_only_feedback_receipt_bound_to_target", - "delta_kind": _delta_kind(target), - "raw_payload_included": False, - "redaction_state": receipt.get("redaction_state"), - "observed_event_count": receipt.get("observed_event_count"), - "observed_field_count": receipt.get("observed_field_count"), - }, - "check_mode": { - "enabled": True, - "checks": [ - "target_selector_resolves_single_surface", - "metadata_only_redaction_state", - "raw_log_payload_absent", - "post_apply_verifier_ref_present", - ], - }, - "rollback": { - "required": True, - "rollback_ref": f"rollback://ai-agent-log-feedback/{target}/{source_sample_id}", - "strategy": "mark_receipt_superseded_and_remove_target_binding", - }, - "post_apply_verifier": { - "required": True, - "verifier_ref": f"post-write-verifier://ai-agent-log-feedback/{target}/{source_sample_id}", - "canonical_readback": ( - "/api/v1/agents/agent-log-controlled-writeback-plan-readback" - ), - }, - "write_enabled_by_plan": False, - } - ) + current_blocker_recovery = _current_blocker_recovery(receipt) + plan = { + "plan_id": plan_id, + "receipt_id": receipt_id, + "target": target, + "status": "controlled_apply_ready", + "risk_tier": "medium" if target in {"km", "rag", "playbook"} else "low", + "target_selector": { + "project_id": receipt.get("project_id"), + "product": receipt.get("product"), + "service": receipt.get("service"), + "package": receipt.get("package"), + "tool": receipt.get("tool"), + "source_system": receipt.get("source_system"), + "source_sample_id": source_sample_id, + "target_surface": _target_surface(target), + }, + "source_of_truth_diff": { + "current_state": "metadata_only_feedback_not_written", + "desired_state": "metadata_only_feedback_receipt_bound_to_target", + "delta_kind": _delta_kind(target), + "raw_payload_included": False, + "redaction_state": receipt.get("redaction_state"), + "observed_event_count": receipt.get("observed_event_count"), + "observed_field_count": receipt.get("observed_field_count"), + }, + "check_mode": { + "enabled": True, + "checks": [ + "target_selector_resolves_single_surface", + "metadata_only_redaction_state", + "raw_log_payload_absent", + "post_apply_verifier_ref_present", + ], + }, + "rollback": { + "required": True, + "rollback_ref": f"rollback://ai-agent-log-feedback/{target}/{source_sample_id}", + "strategy": "mark_receipt_superseded_and_remove_target_binding", + }, + "post_apply_verifier": { + "required": True, + "verifier_ref": f"post-write-verifier://ai-agent-log-feedback/{target}/{source_sample_id}", + "canonical_readback": ( + "/api/v1/agents/agent-log-controlled-writeback-plan-readback" + ), + }, + "write_enabled_by_plan": False, + } + if current_blocker_recovery: + plan["current_blocker_recovery"] = current_blocker_recovery + plans.append(plan) return plans +def _current_blocker_recovery(receipt: dict[str, Any]) -> dict[str, Any] | None: + classification = receipt.get("classification") + if not isinstance(classification, dict): + return None + blocker_id = str(classification.get("current_blocker") or "") + if not blocker_id: + return None + source_sample_id = str(receipt.get("source_sample_id") or "") + return { + "source_sample_id": source_sample_id, + "incident_id": str(classification.get("incident_id") or ""), + "blocker_id": blocker_id, + "risk_tier": str(classification.get("risk_tier") or "unknown"), + "runner_label": str(classification.get("runner_label") or ""), + "ssh_auth_classification": str( + classification.get("ssh_auth_classification") or "" + ), + "node_load_classifier": str(classification.get("node_load_classifier") or ""), + "registry_v2_status": classification.get("registry_v2_status"), + "controlled_recovery_package": str( + classification.get("controlled_recovery_package") or "" + ), + "post_apply_verifier": str(classification.get("post_apply_verifier") or ""), + "safe_next_step": str(classification.get("safe_next_step") or ""), + "runtime_apply_required_on_110_local_console": ( + blocker_id == "harbor_110_repair_no_matching_runner" + ), + "external_control_path_blocker": str( + classification.get("ssh_auth_classification") or "" + ), + "metadata_only": True, + } + + def _target_rollups(plans: list[dict[str, Any]]) -> list[dict[str, Any]]: return [ { diff --git a/apps/api/src/services/ai_agent_log_feedback_receipt_dry_run.py b/apps/api/src/services/ai_agent_log_feedback_receipt_dry_run.py index 3eb77002e..2653abf2e 100644 --- a/apps/api/src/services/ai_agent_log_feedback_receipt_dry_run.py +++ b/apps/api/src/services/ai_agent_log_feedback_receipt_dry_run.py @@ -167,6 +167,9 @@ def _sample_rows(runtime_sample: dict[str, Any]) -> list[dict[str, Any]]: for sample in samples: if not isinstance(sample, dict): continue + classification = sample.get("classification") + if not isinstance(classification, dict): + classification = {} rows.append( { "sample_id": str(sample.get("sample_id") or ""), @@ -179,6 +182,7 @@ def _sample_rows(runtime_sample: dict[str, Any]) -> list[dict[str, Any]]: "redaction_state": str(sample.get("redaction_state") or ""), "observed_events": [str(item) for item in sample.get("observed_events") or []], "observed_fields": [str(item) for item in sample.get("observed_fields") or []], + "classification": dict(classification), } ) return rows @@ -202,6 +206,7 @@ def _candidate_receipts(samples: list[dict[str, Any]]) -> list[dict[str, Any]]: "redaction_state": sample["redaction_state"], "observed_event_count": len(sample["observed_events"]), "observed_field_count": len(sample["observed_fields"]), + "classification": sample["classification"], "dry_run_status": "candidate_ready", "write_enabled": False, "raw_log_payload_persisted": False, diff --git a/apps/api/tests/test_ai_agent_autonomous_runtime_control.py b/apps/api/tests/test_ai_agent_autonomous_runtime_control.py index a17905854..e7b660dc5 100644 --- a/apps/api/tests/test_ai_agent_autonomous_runtime_control.py +++ b/apps/api/tests/test_ai_agent_autonomous_runtime_control.py @@ -33,12 +33,29 @@ def _assert_log_controlled_writeback_executor(payload: dict): assert payload["rollups"]["source_of_truth_diff_count"] == 18 assert payload["rollups"]["controlled_executor_dispatch_ready"] is True assert payload["rollups"]["controlled_apply_enabled_by_policy"] is True + assert payload["rollups"]["current_blocker_execution_queue_count"] == 1 + assert payload["rollups"]["current_blocker_control_path_blocked_count"] == 1 + assert payload["rollups"]["current_blocker_local_recovery_package_count"] == 1 assert payload["rollups"]["runtime_dispatch_performed"] is False context = payload["agent_consumption_context"] assert "ai_agent_autonomous_runtime_control" in context["consumable_by"] assert "km_rag_playbook_learning_loop" in context["consumable_by"] assert len(context["next_action_queue"]) == 6 + assert len(context["current_blocker_execution_queue"]) == 1 + current_queue = context["current_blocker_execution_queue"][0] + assert current_queue["queue_item_id"] == ( + "current-p0-blocker::harbor_110_repair_no_matching_runner" + ) + assert current_queue["controlled_recovery_package"] == ( + "recover-110-control-path-and-harbor-local.sh --check" + ) + assert current_queue["post_apply_verifier"] == ( + "check-awoooi-110-controlled-cd-lane-readiness.sh" + ) + assert current_queue["external_control_path_blocker"] == ( + "publickey_offer_timeout" + ) assert context["raw_payload_required"] is False boundaries = payload["operation_boundaries"] @@ -239,6 +256,22 @@ def test_ai_agent_autonomous_runtime_control_exposes_reports_and_executor_receip assert data["rollups"]["live_log_controlled_writeback_executor_ready_count"] == 1 assert data["rollups"]["live_log_controlled_writeback_executor_blocker_count"] == 0 assert data["rollups"]["live_log_controlled_writeback_next_action_queue_count"] == 6 + assert ( + data["rollups"]["live_log_controlled_writeback_current_blocker_queue_count"] + == 1 + ) + assert ( + data["rollups"][ + "live_log_controlled_writeback_current_blocker_control_path_blocked_count" + ] + == 1 + ) + assert ( + data["rollups"][ + "live_log_controlled_writeback_current_blocker_local_recovery_package_count" + ] + == 1 + ) assert data["rollups"]["live_log_controlled_writeback_dispatch_count"] == 0 assert data["rollups"]["live_log_controlled_writeback_recent_dispatch_count"] == 0 assert data["rollups"]["live_log_controlled_writeback_consumer_binding_count"] == 0 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 869d4128b..6a349fbdb 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 @@ -62,6 +62,9 @@ def _assert_executor_readback(payload: dict): assert payload["rollups"]["post_apply_verifier_ready_count"] == expected_target_count assert payload["rollups"]["controlled_executor_dispatch_ready"] is True assert payload["rollups"]["controlled_apply_enabled_by_policy"] is True + assert payload["rollups"]["current_blocker_execution_queue_count"] == 1 + assert payload["rollups"]["current_blocker_control_path_blocked_count"] == 1 + assert payload["rollups"]["current_blocker_local_recovery_package_count"] == 1 assert payload["rollups"]["runtime_dispatch_performed"] is False batches = {batch["target"]: batch for batch in payload["execution_batches"]} @@ -86,6 +89,11 @@ def _assert_executor_readback(payload: dict): len(batch["post_apply_verifier"]["verifier_refs"]) == expected_runtime_sample_count ) + assert batch["current_blocker_recovery_count"] == 1 + assert batch["current_blocker_recoveries"][0]["blocker_id"] == ( + "harbor_110_repair_no_matching_runner" + ) + assert batch["current_blocker_recoveries"][0]["runner_label"] == "awoooi-host" assert batch["runtime_dispatch_performed"] is False context = payload["agent_consumption_context"] @@ -95,6 +103,34 @@ def _assert_executor_readback(payload: dict): assert len(context["evidence_chain"]) == 4 assert len(context["next_action_queue"]) == 6 assert {item["target"] for item in context["next_action_queue"]} == set(batches) + current_queue = context["current_blocker_execution_queue"] + assert len(current_queue) == 1 + assert current_queue[0]["queue_item_id"] == ( + "current-p0-blocker::harbor_110_repair_no_matching_runner" + ) + assert current_queue[0]["status"] == ( + "controlled_recovery_packaged_waiting_control_path_readback" + ) + assert current_queue[0]["runner_label"] == "awoooi-host" + assert current_queue[0]["registry_v2_status"] == 502 + assert current_queue[0]["controlled_recovery_package"] == ( + "recover-110-control-path-and-harbor-local.sh --check" + ) + assert current_queue[0]["post_apply_verifier"] == ( + "check-awoooi-110-controlled-cd-lane-readiness.sh" + ) + assert current_queue[0]["runtime_write_gate"] == ( + "controlled_after_110_local_console_preflight" + ) + assert current_queue[0]["runtime_apply_required_on_110_local_console"] is True + assert current_queue[0]["external_control_path_blocker"] == ( + "publickey_offer_timeout" + ) + assert current_queue[0]["control_channel_readback_required"] is True + assert set(current_queue[0]["learning_writeback_targets"]) == set(batches) + assert len(current_queue[0]["target_batches"]) == 6 + assert current_queue[0]["runtime_dispatch_performed"] is False + assert current_queue[0]["raw_payload_required"] is False assert context["learning_feedback_targets"] == [ "km", "rag", diff --git a/apps/api/tests/test_ai_agent_log_controlled_writeback_plan_readback_api.py b/apps/api/tests/test_ai_agent_log_controlled_writeback_plan_readback_api.py index 10dd016dc..5d4336899 100644 --- a/apps/api/tests/test_ai_agent_log_controlled_writeback_plan_readback_api.py +++ b/apps/api/tests/test_ai_agent_log_controlled_writeback_plan_readback_api.py @@ -51,6 +51,8 @@ def _assert_controlled_writeback_plan(payload: dict): assert payload["rollups"]["candidate_receipt_count"] == 18 assert payload["rollups"]["writeback_plan_count"] == 18 assert payload["rollups"]["ready_writeback_plan_count"] == 18 + assert payload["rollups"]["current_blocker_recovery_plan_count"] == 6 + assert payload["rollups"]["current_blocker_recovery_unique_count"] == 1 assert payload["rollups"]["check_mode_plan_count"] == 18 assert payload["rollups"]["rollback_plan_count"] == 18 assert payload["rollups"]["post_apply_verifier_plan_count"] == 18 @@ -119,6 +121,26 @@ def _assert_controlled_writeback_plan(payload: dict): ] assert len(p0_plans) == 6 assert {plan["target"] for plan in p0_plans} == set(target_rollups) + assert { + plan["current_blocker_recovery"]["blocker_id"] for plan in p0_plans + } == {"harbor_110_repair_no_matching_runner"} + assert { + plan["current_blocker_recovery"]["controlled_recovery_package"] + for plan in p0_plans + } == {"recover-110-control-path-and-harbor-local.sh --check"} + assert { + plan["current_blocker_recovery"]["post_apply_verifier"] + for plan in p0_plans + } == {"check-awoooi-110-controlled-cd-lane-readiness.sh"} + assert all( + plan["current_blocker_recovery"]["runtime_apply_required_on_110_local_console"] + is True + for plan in p0_plans + ) + assert { + plan["current_blocker_recovery"]["external_control_path_blocker"] + for plan in p0_plans + } == {"publickey_offer_timeout"} boundaries = payload["operation_boundaries"] assert boundaries["plan_readback_only"] is True diff --git a/apps/api/tests/test_ai_agent_log_feedback_receipt_dry_run_api.py b/apps/api/tests/test_ai_agent_log_feedback_receipt_dry_run_api.py index cfafeedd0..0122e98e7 100644 --- a/apps/api/tests/test_ai_agent_log_feedback_receipt_dry_run_api.py +++ b/apps/api/tests/test_ai_agent_log_feedback_receipt_dry_run_api.py @@ -91,6 +91,16 @@ def _assert_feedback_dry_run_payload(payload: dict): assert {receipt["service"] for receipt in p0_receipts} == { "awoooi-110-control-path" } + assert { + receipt["classification"]["current_blocker"] for receipt in p0_receipts + } == {"harbor_110_repair_no_matching_runner"} + assert { + receipt["classification"]["controlled_recovery_package"] + for receipt in p0_receipts + } == {"recover-110-control-path-and-harbor-local.sh --check"} + assert { + receipt["classification"]["post_apply_verifier"] for receipt in p0_receipts + } == {"check-awoooi-110-controlled-cd-lane-readiness.sh"} boundaries = payload["operation_boundaries"] assert boundaries["dry_run_only"] is True diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 2eee4eee8..a11801309 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -50759,3 +50759,22 @@ production browser smoke: **下一步**: - commit/push 後讀回 Gitea CD;若仍卡 Harbor 502,主線下一步仍是恢復 110 `awoooi-host` controlled lane / local recovery package readback,通過後再讓 `harbor-110-local-repair` 承接 queue。 + +## 2026-06-30 — 22:28 AI Loop 當前 P0 blocker execution queue + +**完成內容**: +- `agent-log-feedback-receipt-dry-run` 保留 runtime sample 的 `classification`,避免 `p0_110_harbor_runner_control_path_sample` 在 receipt / plan 階段只剩一般 metadata。 +- `agent-log-controlled-writeback-plan-readback` 新增 `current_blocker_recovery` plan fragment,將 `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` 與 metadata-only writeback 綁成 6 個 target plan。 +- `agent-log-controlled-writeback-executor-readback` 新增去重後的 `current_blocker_execution_queue`,目前 queue count 為 1,直接暴露當前 P0 blocker、controlled recovery package、post-apply verifier、`controlled_after_110_local_console_preflight` runtime write gate、KM/RAG/MCP/PlayBook/AI Agent learning targets。 +- `agent-autonomous-runtime-control` rollups 新增 `live_log_controlled_writeback_current_blocker_queue_count=1`、`live_log_controlled_writeback_current_blocker_control_path_blocked_count=1`、`live_log_controlled_writeback_current_blocker_local_recovery_package_count=1`,讓正式 runtime readback 不再只看到一般 LOG writeback batch。 + +**live truth**: +- public Gitea queue 仍是 `blocked_harbor_110_repair_no_matching_runner`;Harbor repair `#4096` Waiting,no matching label `awoooi-host`。 +- `https://registry.wooo.work/v2/`、`https://harbor.wooo.work/api/v2.0/health`、`http://192.168.0.110:5000/v2/` 均仍回 502;本輪未執行 runtime write、workflow dispatch、host restart、Docker / K3s / Nginx reload、secret / runner token 讀取。 + +**本地驗證結果**: +- `pytest` LOG intelligence / feedback / verifier / plan / executor / dispatch / consumer / autonomous focused chain:`28 passed`。 +- `ruff check`、`py_compile`、`ops/runner/guard-gitea-runner-pressure.py --root .`、`scripts/ci/check-gitea-step-env-secrets.js`、`git diff --check`:通過。 + +**下一步**: +- commit / push 後等待 Gitea CD;若仍卡 Harbor/110,P0 主線下一步是 110 local console / root shell 執行 recovery package check-mode,讀回 `awoooi-host` controlled lane verifier、Harbor queue、registry `/v2/`、deploy marker,並把 post-apply verifier receipt 寫回 AI Loop queue / KM / RAG / MCP / PlayBook。