diff --git a/apps/api/src/services/delivery_closure_workbench.py b/apps/api/src/services/delivery_closure_workbench.py index 764afcf74..8777fb473 100644 --- a/apps/api/src/services/delivery_closure_workbench.py +++ b/apps/api/src/services/delivery_closure_workbench.py @@ -152,6 +152,44 @@ def build_delivery_closure_workbench( "latest_visible_cd_run_jobs_total_count" ) ), + "non110_runner_ready": production_deploy_readback.get( + "non110_runner_ready" + ) + is True, + "non110_runner_prepare_only_source_ready": production_deploy_readback.get( + "non110_runner_prepare_only_source_ready" + ) + is True, + "non110_runner_safe_registration_helper_ready": production_deploy_readback.get( + "non110_runner_safe_registration_helper_ready" + ) + is True, + "non110_runner_registration_condition_required": production_deploy_readback.get( + "non110_runner_registration_condition_required" + ) + is True, + "non110_runner_ready_config_count": _int( + production_deploy_readback.get( + "non110_runner_ready_config_count" + ) + ), + "non110_runner_ready_service_count": _int( + production_deploy_readback.get( + "non110_runner_ready_service_count" + ) + ), + "non110_runner_ready_registration_count": _int( + production_deploy_readback.get( + "non110_runner_ready_registration_count" + ) + ), + "non110_runner_remaining_blocker_count": len( + _strings( + production_deploy_readback.get( + "non110_runner_remaining_blockers" + ) + ) + ), }, "href": "/deployments", "next_action": _first_string(production_deploy.get("next_actions")), @@ -348,6 +386,44 @@ def build_delivery_closure_workbench( "latest_visible_cd_run_jobs_total_count" ) ), + "production_deploy_non110_runner_ready": production_deploy_readback.get( + "non110_runner_ready" + ) + is True, + "production_deploy_non110_runner_prepare_only_source_ready": ( + production_deploy_readback.get("non110_runner_prepare_only_source_ready") + is True + ), + "production_deploy_non110_runner_safe_registration_helper_ready": ( + production_deploy_readback.get( + "non110_runner_safe_registration_helper_ready" + ) + is True + ), + "production_deploy_non110_runner_registration_condition_required": ( + production_deploy_readback.get( + "non110_runner_registration_condition_required" + ) + is True + ), + "production_deploy_non110_runner_ready_config_count": _int( + production_deploy_readback.get("non110_runner_ready_config_count") + ), + "production_deploy_non110_runner_ready_service_count": _int( + production_deploy_readback.get("non110_runner_ready_service_count") + ), + "production_deploy_non110_runner_ready_registration_count": _int( + production_deploy_readback.get( + "non110_runner_ready_registration_count" + ) + ), + "production_deploy_non110_runner_remaining_blocker_count": len( + _strings( + production_deploy_readback.get( + "non110_runner_remaining_blockers" + ) + ) + ), "github_write_channel_ready": github_preflight.get( "github_write_channel_ready" ) diff --git a/apps/api/tests/test_delivery_closure_workbench_api.py b/apps/api/tests/test_delivery_closure_workbench_api.py index d367bbb3d..5cc293803 100644 --- a/apps/api/tests/test_delivery_closure_workbench_api.py +++ b/apps/api/tests/test_delivery_closure_workbench_api.py @@ -34,7 +34,7 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary(): data["summary"]["production_deploy_authorized_dispatch_channel_ready"] is False ) - assert data["summary"]["production_deploy_hard_blocker_count"] == 2 + assert data["summary"]["production_deploy_hard_blocker_count"] == 3 assert data["summary"]["production_deploy_latest_visible_cd_run_id"] == "3853" assert data["summary"]["production_deploy_latest_visible_cd_run_status"] == ( "Waiting" @@ -43,6 +43,35 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary(): data["summary"]["production_deploy_latest_visible_cd_run_jobs_total_count"] == 0 ) + assert data["summary"]["production_deploy_non110_runner_ready"] is False + assert ( + data["summary"][ + "production_deploy_non110_runner_prepare_only_source_ready" + ] + is True + ) + assert ( + data["summary"][ + "production_deploy_non110_runner_safe_registration_helper_ready" + ] + is True + ) + assert ( + data["summary"][ + "production_deploy_non110_runner_registration_condition_required" + ] + is True + ) + assert data["summary"]["production_deploy_non110_runner_ready_config_count"] == 2 + assert data["summary"]["production_deploy_non110_runner_ready_service_count"] == 1 + assert ( + data["summary"]["production_deploy_non110_runner_ready_registration_count"] + == 0 + ) + assert ( + data["summary"]["production_deploy_non110_runner_remaining_blocker_count"] + == 3 + ) assert data["summary"]["github_write_channel_ready"] is False assert data["summary"]["github_account_status"] == "suspended" assert data["summary"]["github_account_suspended"] is True @@ -83,10 +112,10 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary(): assert lanes["production_deploy"]["status"] == ( "blocked_waiting_authorized_gitea_workflow_dispatch_and_runner_queue" ) - assert lanes["production_deploy"]["blocker_count"] == 2 + assert lanes["production_deploy"]["blocker_count"] == 3 assert lanes["production_deploy"]["metric"][ "observed_source_control_main_short_sha" - ] == "5f20d654d4dc" + ] == "c18200c23ca2" assert lanes["production_deploy"]["metric"][ "production_image_tag_short_sha" ] == "af45811e87" @@ -110,6 +139,39 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary(): ] == 0 ) + assert lanes["production_deploy"]["metric"]["non110_runner_ready"] is False + assert ( + lanes["production_deploy"]["metric"][ + "non110_runner_prepare_only_source_ready" + ] + is True + ) + assert ( + lanes["production_deploy"]["metric"][ + "non110_runner_safe_registration_helper_ready" + ] + is True + ) + assert ( + lanes["production_deploy"]["metric"][ + "non110_runner_registration_condition_required" + ] + is True + ) + assert lanes["production_deploy"]["metric"]["non110_runner_ready_config_count"] == 2 + assert lanes["production_deploy"]["metric"]["non110_runner_ready_service_count"] == 1 + assert ( + lanes["production_deploy"]["metric"][ + "non110_runner_ready_registration_count" + ] + == 0 + ) + assert ( + lanes["production_deploy"]["metric"][ + "non110_runner_remaining_blocker_count" + ] + == 3 + ) assert lanes["github"]["metric"]["kind"] == "private_backup_verified" assert lanes["gitea"]["metric"]["kind"] == "workflow_count" assert lanes["runtime"]["metric"]["kind"] == "surface_count" diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 1598bd37e..68ed31230 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,11 @@ +## 2026-06-28 — 20:45 Delivery Workbench non-110 runner readiness 投影 + +**完成內容**: +- 將 production deploy blocker 補上 non-110 runner readiness metadata:prepare-only source ready、safe registration helper ready、registration condition required、config `2`、binary `2`、service `1`、registration `0`、active service `0`、remaining blocker `3`。 +- Delivery Workbench summary 與 `production_deploy` lane metric 現在可直接讀出 `production_deploy_non110_runner_ready=false` 與 registration / service / blocker counts,對齊 CD `#3853` 仍 `Waiting`、jobs API `total_count=0` 的 runner channel blocker。 + +**邊界**:只改 committed source / snapshot / API 欄位;未讀 runner token 或 `.runner` 內容;未操作 host / Docker / K8s / runner service;未使用 GitHub。 + ## 2026-06-28 — 20:44 non-110 runner service registration condition verifier **完成內容**: diff --git a/docs/operations/awoooi-production-deploy-readback-blocker.snapshot.json b/docs/operations/awoooi-production-deploy-readback-blocker.snapshot.json index 1a8ffda53..11e60cd8e 100644 --- a/docs/operations/awoooi-production-deploy-readback-blocker.snapshot.json +++ b/docs/operations/awoooi-production-deploy-readback-blocker.snapshot.json @@ -1,12 +1,12 @@ { "schema_version": "awoooi_production_deploy_readback_blocker_v1", - "generated_at": "2026-06-28T20:39:15+08:00", + "generated_at": "2026-06-28T20:50:00+08:00", "status": "blocked_waiting_authorized_gitea_workflow_dispatch_and_runner_queue", "priority": "P0", "scope": "awoooi_production_truth", "readback": { - "observed_source_control_main_sha": "5f20d654d4dc4e3267954aea4bd9aabdd9313bed", - "observed_source_control_main_short_sha": "5f20d654d4dc", + "observed_source_control_main_sha": "c18200c23ca285e1342193601bc0418db7364fda", + "observed_source_control_main_short_sha": "c18200c23ca2", "governance_closure_merge_sha": "27b96f0450d0e3ca6651d6b5f274a341dd727ef2", "governance_closure_commit_sha": "9e3e7fbb6ba3ffd324b45abf3ad1e7b6ec826b22", "production_image_tag_sha": "af45811e876fda322ee63c036fbc39c9f07ffd76", @@ -25,7 +25,22 @@ "gitea_sign_in_required": true, "dispatch_without_token_http_status": 401, "dispatch_without_token_message": "token is required", - "authorized_dispatch_channel_ready": false + "authorized_dispatch_channel_ready": false, + "non110_runner_prepare_only_source_ready": true, + "non110_runner_safe_registration_helper_ready": true, + "non110_runner_registration_condition_required": true, + "non110_runner_ready": false, + "non110_runner_ready_config_count": 2, + "non110_runner_ready_binary_count": 2, + "non110_runner_ready_service_count": 1, + "non110_runner_ready_registration_count": 0, + "non110_runner_ready_active_service_count": 0, + "non110_runner_remaining_blockers": [ + "runner_registration_missing", + "runner_service_not_active", + "no_active_runner_service" + ], + "non110_runner_safe_next_step": "run_safe_registration_helper_from_interactive_tty_without_capturing_token_then_enable_service_and_rerun_verifier" }, "blockers": [ { @@ -43,21 +58,32 @@ "description": "Public Gitea readback 顯示 CD run #3853 仍為 Waiting,jobs API 回 total_count=0;尚未有 runner 接走可見 run。", "blocked_action": "complete_cd_run_and_update_production_image_tag", "safe_boundary": "只允許讀 public Gitea/status 與 source verifier;不得登入、讀 token、操作 host、Docker、K8s 或 runner service。" + }, + { + "id": "non110_runner_registration_and_active_service_missing", + "kind": "runner_readiness_metadata", + "severity": "P0", + "description": "Non-110 runner prepare-only source 與 safe registration helper 已存在,但 readback 顯示 registration metadata 缺席、runner service 未 active,因此尚未形成可接走 CD run 的 runner channel。", + "blocked_action": "make_authorized_runner_channel_available_for_cd", + "safe_boundary": "不得讀 runner token 或 .runner 內容;不得由此 API 直接註冊、啟動、重啟 runner 或操作 Docker/host。" } ], "next_actions": [ "等 non-110 或硬限制 runner readiness channel 成立後,使用已授權的 Gitea workflow_dispatch channel 觸發 cd.yaml ref=main。", "若 #3853 仍 Waiting 且 jobs_total_count=0,先不要重推或手改 K8s tag;改以 runner readiness verifier 的非 secret readback 建立可用 runner channel。", + "使用 safe registration helper 完成 registration metadata 與 active service readback,但不得輸出 token 或 .runner 內容。", "CD 完成後讀回 production image tag,確認不再是 af45811e87。", "重新讀回 /api/v1/agents/github-target-controlled-execution-preflight 與 /api/v1/agents/delivery-closure-workbench,確認 internal_governance_writeback 與 KM / PlayBook counters 出現。" ], "rollups": { - "hard_blocker_count": 2, - "next_action_count": 4, + "hard_blocker_count": 3, + "next_action_count": 5, "source_control_main_ready": true, "production_image_tag_matches_main": false, "production_governance_fields_present": false, "authorized_dispatch_channel_ready": false, + "non110_runner_ready": false, + "non110_runner_remaining_blocker_count": 3, "runtime_write_performed": false, "secret_values_collected": false },