From 88607eae4d368416d04f5fe14fc47a53cd4f04a6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Jun 2026 21:19:26 +0800 Subject: [PATCH] feat(delivery): expose dispatch channel readback --- .../services/delivery_closure_workbench.py | 31 +++++++++++++ .../test_delivery_closure_workbench_api.py | 24 +++++++++- apps/web/messages/en.json | 21 +++++---- apps/web/messages/zh-TW.json | 21 +++++---- apps/web/src/app/[locale]/delivery/page.tsx | 6 +++ apps/web/src/lib/api-client.ts | 46 ++++++++++++++++++- docs/LOGBOOK.md | 24 ++++++---- ...tion-deploy-readback-blocker.snapshot.json | 6 +-- 8 files changed, 150 insertions(+), 29 deletions(-) diff --git a/apps/api/src/services/delivery_closure_workbench.py b/apps/api/src/services/delivery_closure_workbench.py index 596f000b8..184556fa2 100644 --- a/apps/api/src/services/delivery_closure_workbench.py +++ b/apps/api/src/services/delivery_closure_workbench.py @@ -140,6 +140,23 @@ def build_delivery_closure_workbench( "authorized_dispatch_channel_ready" ) is True, + "manual_run_button_visible": production_deploy_readback.get( + "manual_run_button_visible" + ) + is True, + "gitea_sign_in_required": production_deploy_readback.get( + "gitea_sign_in_required" + ) + is True, + "dispatch_without_token_http_status": _int( + production_deploy_readback.get( + "dispatch_without_token_http_status" + ) + ), + "dispatch_without_token_message": str( + production_deploy_readback.get("dispatch_without_token_message") + or "" + ), "latest_visible_cd_run_id": str( production_deploy_readback.get("latest_visible_cd_run_id") or "" ), @@ -382,6 +399,20 @@ def build_delivery_closure_workbench( production_deploy_rollups.get("authorized_dispatch_channel_ready") is True ), + "production_deploy_manual_run_button_visible": production_deploy_readback.get( + "manual_run_button_visible" + ) + is True, + "production_deploy_gitea_sign_in_required": production_deploy_readback.get( + "gitea_sign_in_required" + ) + is True, + "production_deploy_dispatch_without_token_http_status": _int( + production_deploy_readback.get("dispatch_without_token_http_status") + ), + "production_deploy_dispatch_without_token_message": str( + production_deploy_readback.get("dispatch_without_token_message") or "" + ), "production_deploy_hard_blocker_count": _int( production_deploy_rollups.get("hard_blocker_count") ), diff --git a/apps/api/tests/test_delivery_closure_workbench_api.py b/apps/api/tests/test_delivery_closure_workbench_api.py index ec99db4c3..1243067c9 100644 --- a/apps/api/tests/test_delivery_closure_workbench_api.py +++ b/apps/api/tests/test_delivery_closure_workbench_api.py @@ -34,6 +34,16 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary(): data["summary"]["production_deploy_authorized_dispatch_channel_ready"] is False ) + assert data["summary"]["production_deploy_manual_run_button_visible"] is False + assert data["summary"]["production_deploy_gitea_sign_in_required"] is True + assert ( + data["summary"]["production_deploy_dispatch_without_token_http_status"] + == 401 + ) + assert ( + data["summary"]["production_deploy_dispatch_without_token_message"] + == "token is required" + ) 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"] == ( @@ -127,7 +137,7 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary(): assert lanes["production_deploy"]["blocker_count"] == 3 assert lanes["production_deploy"]["metric"][ "observed_source_control_main_short_sha" - ] == "666454212ac9" + ] == "b754e8a1a46a" assert lanes["production_deploy"]["metric"][ "production_image_tag_short_sha" ] == "af45811e87" @@ -140,6 +150,18 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary(): lanes["production_deploy"]["metric"]["authorized_dispatch_channel_ready"] is False ) + assert lanes["production_deploy"]["metric"]["manual_run_button_visible"] is False + assert lanes["production_deploy"]["metric"]["gitea_sign_in_required"] is True + assert ( + lanes["production_deploy"]["metric"][ + "dispatch_without_token_http_status" + ] + == 401 + ) + assert ( + lanes["production_deploy"]["metric"]["dispatch_without_token_message"] + == "token is required" + ) assert lanes["production_deploy"]["metric"]["latest_visible_cd_run_id"] == "3853" assert ( lanes["production_deploy"]["metric"]["latest_visible_cd_run_status"] diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 3dc498235..b7250406b 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -127,14 +127,19 @@ "boundaryDetail": "這些仍需明確授權,但不得阻擋低風險 coding / UI / test。" }, "lanes": { - "release": { - "title": "乾淨 release 工作流", - "description": "把可交付的變更切出乾淨分支與可驗證提交,避免髒分支整包推送。", - "metric": "阻擋 gate {blocked}" - }, - "github": { - "title": "GitHub 私有備援", - "description": "所有備援 repo 必須私有,公開可讀或未驗證 private 都不能標綠。", + "release": { + "title": "乾淨 release 工作流", + "description": "把可交付的變更切出乾淨分支與可驗證提交,避免髒分支整包推送。", + "metric": "阻擋 gate {blocked}" + }, + "production_deploy": { + "title": "Production deploy", + "description": "讀回 Gitea CD、production image tag 與 runner readiness,不把 source ready 誤判成已部署。", + "metric": "CD #{run} {status}" + }, + "github": { + "title": "GitHub 私有備援", + "description": "所有備援 repo 必須私有,公開可讀或未驗證 private 都不能標綠。", "metric": "已驗證 {verified}/{total}" }, "gitea": { diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 3dc498235..b7250406b 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -127,14 +127,19 @@ "boundaryDetail": "這些仍需明確授權,但不得阻擋低風險 coding / UI / test。" }, "lanes": { - "release": { - "title": "乾淨 release 工作流", - "description": "把可交付的變更切出乾淨分支與可驗證提交,避免髒分支整包推送。", - "metric": "阻擋 gate {blocked}" - }, - "github": { - "title": "GitHub 私有備援", - "description": "所有備援 repo 必須私有,公開可讀或未驗證 private 都不能標綠。", + "release": { + "title": "乾淨 release 工作流", + "description": "把可交付的變更切出乾淨分支與可驗證提交,避免髒分支整包推送。", + "metric": "阻擋 gate {blocked}" + }, + "production_deploy": { + "title": "Production deploy", + "description": "讀回 Gitea CD、production image tag 與 runner readiness,不把 source ready 誤判成已部署。", + "metric": "CD #{run} {status}" + }, + "github": { + "title": "GitHub 私有備援", + "description": "所有備援 repo 必須私有,公開可讀或未驗證 private 都不能標綠。", "metric": "已驗證 {verified}/{total}" }, "gitea": { diff --git a/apps/web/src/app/[locale]/delivery/page.tsx b/apps/web/src/app/[locale]/delivery/page.tsx index 000f6b121..f20681379 100644 --- a/apps/web/src/app/[locale]/delivery/page.tsx +++ b/apps/web/src/app/[locale]/delivery/page.tsx @@ -288,6 +288,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } }) return workbench.lanes.map(lane => { const iconMap = { release: Rocket, + production_deploy: Rocket, github: GitBranch, gitea: PackageCheck, runtime: Server, @@ -298,6 +299,11 @@ export default function DeliveryPage({ params }: { params: { locale: string } }) ? t('lanes.release.metric', { blocked: lane.metric.blocked }) : lane.metric.kind === 'private_backup_verified' ? t('lanes.github.metric', { verified: lane.metric.verified, total: lane.metric.total }) + : lane.metric.kind === 'deploy_readback' + ? t('lanes.production_deploy.metric', { + run: lane.metric.latest_visible_cd_run_id, + status: lane.metric.latest_visible_cd_run_status, + }) : lane.metric.kind === 'workflow_count' ? t('lanes.gitea.metric', { count: lane.metric.count }) : lane.metric.kind === 'surface_count' diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index a2f8c0d16..61a8b0d9b 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -2543,6 +2543,27 @@ export interface DeliveryClosureWorkbenchSnapshot { visibility_change_authorized: boolean refs_sync_authorized: boolean workflow_trigger_authorized: boolean + production_deploy_status: string + production_deploy_source_control_main_ready: boolean + production_deploy_image_tag_matches_main: boolean + production_deploy_governance_fields_present: boolean + production_deploy_authorized_dispatch_channel_ready: boolean + production_deploy_manual_run_button_visible: boolean + production_deploy_gitea_sign_in_required: boolean + production_deploy_dispatch_without_token_http_status: number + production_deploy_dispatch_without_token_message: string + production_deploy_hard_blocker_count: number + production_deploy_latest_visible_cd_run_id: string + production_deploy_latest_visible_cd_run_status: string + production_deploy_latest_visible_cd_run_jobs_total_count: number + production_deploy_non110_runner_ready: boolean + production_deploy_non110_runner_prepare_only_source_ready: boolean + production_deploy_non110_runner_safe_registration_helper_ready: boolean + production_deploy_non110_runner_registration_condition_required: boolean + production_deploy_non110_runner_ready_config_count: number + production_deploy_non110_runner_ready_service_count: number + production_deploy_non110_runner_ready_registration_count: number + production_deploy_non110_runner_remaining_blocker_count: number github_write_channel_ready: boolean github_account_status: string github_account_suspended: boolean @@ -2561,13 +2582,36 @@ export interface DeliveryClosureWorkbenchSnapshot { missing_reason: string }> lanes: Array<{ - id: 'release' | 'github' | 'gitea' | 'runtime' | 'backup' + id: 'release' | 'production_deploy' | 'github' | 'gitea' | 'runtime' | 'backup' source_id: string completion_percent: number status: string blocker_count: number metric: | { kind: 'blocked_gate'; blocked: number; total: number } + | { + kind: 'deploy_readback' + observed_source_control_main_short_sha: string + production_image_tag_short_sha: string + production_image_tag_matches_main: boolean + current_main_cd_run_visible: boolean + authorized_dispatch_channel_ready: boolean + manual_run_button_visible: boolean + gitea_sign_in_required: boolean + dispatch_without_token_http_status: number + dispatch_without_token_message: string + latest_visible_cd_run_id: string + latest_visible_cd_run_status: string + latest_visible_cd_run_jobs_total_count: number + non110_runner_ready: boolean + non110_runner_prepare_only_source_ready: boolean + non110_runner_safe_registration_helper_ready: boolean + non110_runner_registration_condition_required: boolean + non110_runner_ready_config_count: number + non110_runner_ready_service_count: number + non110_runner_ready_registration_count: number + non110_runner_remaining_blocker_count: number + } | { kind: 'private_backup_verified' verified: number diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index da9a720b2..69ecc1101 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,11 @@ +## 2026-06-28 — 21:15 Delivery Workbench dispatch channel blocker 投影 + +**完成內容**: +- 將 production deploy readback 的 `manual_run_button_visible=false`、`gitea_sign_in_required=true`、unauthenticated dispatch `401` 與 `token is required` 投影到 Delivery Workbench summary / `production_deploy` lane metric。 +- 刷新 source readback 觀測 SHA 為 `b754e8a1a46a`;production 仍舊版,CD `#3853` 仍 `Waiting` 且 jobs API `total_count=0`。 + +**邊界**:只改 committed source / snapshot / API 欄位;未使用 Gitea write API / workflow_dispatch;未讀 token / cookie / session / secret;未操作 host / Docker / K8s / runner service;未使用 GitHub。 + ## 2026-06-28 — 21:09 non-110 runner registration ceremony hardening **完成內容**: @@ -14,14 +22,6 @@ **邊界**:沒有讀 `.runner` 內容、runner token、secret、raw session、SQLite、auth 或 `.env`;沒有啟動 runner service;沒有重開 110 runner;沒有重啟 Docker / Nginx / firewall / K3s / DB;沒有使用 GitHub。 -## 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 — 21:08 Gitea workflow labels 對齊 non-110 runner **完成內容**: @@ -35,6 +35,14 @@ **邊界**:未讀 runner token、`.runner`、secret、raw session、SQLite、auth 或 `.env`;未啟動 188 runner service;未重開 110 runner;未使用 GitHub;沒有 force push。 +## 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 322613c97..7cb5b7ee2 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:50:00+08:00", + "generated_at": "2026-06-28T21:15:43+08:00", "status": "blocked_waiting_authorized_gitea_workflow_dispatch_and_runner_queue", "priority": "P0", "scope": "awoooi_production_truth", "readback": { - "observed_source_control_main_sha": "666454212ac9c3e10c3db927ebcf534f18203864", - "observed_source_control_main_short_sha": "666454212ac9", + "observed_source_control_main_sha": "b754e8a1a46a03bbbfb20f34bdef5fe4def2db90", + "observed_source_control_main_short_sha": "b754e8a1a46a", "governance_closure_merge_sha": "27b96f0450d0e3ca6651d6b5f274a341dd727ef2", "governance_closure_commit_sha": "9e3e7fbb6ba3ffd324b45abf3ad1e7b6ec826b22", "production_image_tag_sha": "af45811e876fda322ee63c036fbc39c9f07ffd76",