feat(runner): expose ordered non110 closure progress [skip ci]

This commit is contained in:
Your Name
2026-06-29 11:07:59 +08:00
parent f3634db184
commit 1ca806fd5d
8 changed files with 419 additions and 65 deletions

View File

@@ -244,6 +244,48 @@ def build_delivery_closure_workbench(
"non110_runner_cd_closure_required"
)
is True,
"non110_runner_cd_closure_ordered_step_count": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_ordered_step_count"
)
),
"non110_runner_cd_closure_ordered_completed_prefix_count": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_ordered_completed_prefix_count"
)
),
"non110_runner_cd_closure_evidence_completed_step_count": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_evidence_completed_step_count"
)
),
"non110_runner_cd_closure_ordered_completion_percent": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_ordered_completion_percent"
)
),
"non110_runner_cd_closure_evidence_completion_percent": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_evidence_completion_percent"
)
),
"non110_runner_cd_closure_next_blocked_step_index": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_next_blocked_step_index"
)
),
"non110_runner_cd_closure_next_blocked_step_id": str(
production_deploy_readback.get(
"non110_runner_cd_closure_next_blocked_step_id"
)
or ""
),
"non110_runner_cd_closure_next_blocked_step_action": str(
production_deploy_readback.get(
"non110_runner_cd_closure_next_blocked_step_action"
)
or ""
),
"non110_runner_ready": production_deploy_readback.get(
"non110_runner_ready"
)
@@ -633,6 +675,48 @@ def build_delivery_closure_workbench(
production_deploy_readback.get("non110_runner_cd_closure_required")
is True
),
"production_deploy_non110_runner_cd_closure_ordered_step_count": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_ordered_step_count"
)
),
"production_deploy_non110_runner_cd_closure_ordered_completed_prefix_count": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_ordered_completed_prefix_count"
)
),
"production_deploy_non110_runner_cd_closure_evidence_completed_step_count": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_evidence_completed_step_count"
)
),
"production_deploy_non110_runner_cd_closure_ordered_completion_percent": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_ordered_completion_percent"
)
),
"production_deploy_non110_runner_cd_closure_evidence_completion_percent": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_evidence_completion_percent"
)
),
"production_deploy_non110_runner_cd_closure_next_blocked_step_index": _int(
production_deploy_readback.get(
"non110_runner_cd_closure_next_blocked_step_index"
)
),
"production_deploy_non110_runner_cd_closure_next_blocked_step_id": str(
production_deploy_readback.get(
"non110_runner_cd_closure_next_blocked_step_id"
)
or ""
),
"production_deploy_non110_runner_cd_closure_next_blocked_step_action": str(
production_deploy_readback.get(
"non110_runner_cd_closure_next_blocked_step_action"
)
or ""
),
"production_deploy_non110_runner_ready": production_deploy_readback.get(
"non110_runner_ready"
)

View File

@@ -44,7 +44,7 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
data["summary"]["production_deploy_dispatch_without_token_message"]
== "token is required"
)
assert data["summary"]["production_deploy_hard_blocker_count"] == 4
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"
@@ -65,23 +65,23 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
)
assert (
data["summary"]["production_deploy_latest_visible_waiting_runner_run_id"]
== "3857"
== ""
)
assert (
data["summary"]["production_deploy_latest_visible_waiting_runner_workflow"]
== "ai-technology-watch.yaml"
== ""
)
assert (
data["summary"]["production_deploy_latest_visible_waiting_runner_kind"]
== "Scheduled"
== ""
)
assert (
data["summary"]["production_deploy_latest_visible_waiting_runner_status"]
== "No matching online runner with label: awoooi-non110-ubuntu"
== ""
)
assert (
data["summary"]["production_deploy_latest_visible_waiting_runner_label"]
== "awoooi-non110-ubuntu"
== ""
)
assert (
data["summary"][
@@ -111,6 +111,53 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
"ops/runner/verify-awoooi-non110-cd-closure.py"
in data["summary"]["production_deploy_non110_runner_cd_closure_verifier"]
)
assert (
data["summary"][
"production_deploy_non110_runner_cd_closure_ordered_step_count"
]
== 6
)
assert (
data["summary"][
"production_deploy_non110_runner_cd_closure_ordered_completed_prefix_count"
]
== 0
)
assert (
data["summary"][
"production_deploy_non110_runner_cd_closure_evidence_completed_step_count"
]
== 2
)
assert (
data["summary"][
"production_deploy_non110_runner_cd_closure_ordered_completion_percent"
]
== 0
)
assert (
data["summary"][
"production_deploy_non110_runner_cd_closure_evidence_completion_percent"
]
== 33
)
assert (
data["summary"][
"production_deploy_non110_runner_cd_closure_next_blocked_step_index"
]
== 1
)
assert (
data["summary"][
"production_deploy_non110_runner_cd_closure_next_blocked_step_id"
]
== "non110_runner_registration_metadata"
)
assert data["summary"][
"production_deploy_non110_runner_cd_closure_next_blocked_step_action"
] == (
"run_register_awoooi_non110_runner_script_without_printing_token_then_autostart_path_will_enable_service_and_rerun_this_verifier"
)
assert data["summary"]["production_deploy_non110_runner_ready"] is False
assert (
data["summary"][
@@ -243,10 +290,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"] == 4
assert lanes["production_deploy"]["blocker_count"] == 3
assert lanes["production_deploy"]["metric"][
"observed_source_control_main_short_sha"
] == "7191193c71e"
] == "f3634db18477"
assert lanes["production_deploy"]["metric"][
"production_image_tag_short_sha"
] == "af45811e87"
@@ -296,27 +343,27 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
)
assert (
lanes["production_deploy"]["metric"]["latest_visible_waiting_runner_run_id"]
== "3857"
== ""
)
assert (
lanes["production_deploy"]["metric"][
"latest_visible_waiting_runner_workflow"
]
== "ai-technology-watch.yaml"
== ""
)
assert (
lanes["production_deploy"]["metric"]["latest_visible_waiting_runner_kind"]
== "Scheduled"
== ""
)
assert (
lanes["production_deploy"]["metric"][
"latest_visible_waiting_runner_status"
]
== "No matching online runner with label: awoooi-non110-ubuntu"
== ""
)
assert (
lanes["production_deploy"]["metric"]["latest_visible_waiting_runner_label"]
== "awoooi-non110-ubuntu"
== ""
)
assert (
lanes["production_deploy"]["metric"][
@@ -346,6 +393,53 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
"ops/runner/verify-awoooi-non110-cd-closure.py"
in lanes["production_deploy"]["metric"]["non110_runner_cd_closure_verifier"]
)
assert (
lanes["production_deploy"]["metric"][
"non110_runner_cd_closure_ordered_step_count"
]
== 6
)
assert (
lanes["production_deploy"]["metric"][
"non110_runner_cd_closure_ordered_completed_prefix_count"
]
== 0
)
assert (
lanes["production_deploy"]["metric"][
"non110_runner_cd_closure_evidence_completed_step_count"
]
== 2
)
assert (
lanes["production_deploy"]["metric"][
"non110_runner_cd_closure_ordered_completion_percent"
]
== 0
)
assert (
lanes["production_deploy"]["metric"][
"non110_runner_cd_closure_evidence_completion_percent"
]
== 33
)
assert (
lanes["production_deploy"]["metric"][
"non110_runner_cd_closure_next_blocked_step_index"
]
== 1
)
assert (
lanes["production_deploy"]["metric"][
"non110_runner_cd_closure_next_blocked_step_id"
]
== "non110_runner_registration_metadata"
)
assert lanes["production_deploy"]["metric"][
"non110_runner_cd_closure_next_blocked_step_action"
] == (
"run_register_awoooi_non110_runner_script_without_printing_token_then_autostart_path_will_enable_service_and_rerun_this_verifier"
)
assert lanes["production_deploy"]["metric"]["non110_runner_ready"] is False
assert (
lanes["production_deploy"]["metric"][

View File

@@ -2569,6 +2569,14 @@ export interface DeliveryClosureWorkbenchSnapshot {
production_deploy_non110_runner_cd_closure_verifier: string
production_deploy_non110_runner_cd_closure_status: string
production_deploy_non110_runner_cd_closure_required: boolean
production_deploy_non110_runner_cd_closure_ordered_step_count: number
production_deploy_non110_runner_cd_closure_ordered_completed_prefix_count: number
production_deploy_non110_runner_cd_closure_evidence_completed_step_count: number
production_deploy_non110_runner_cd_closure_ordered_completion_percent: number
production_deploy_non110_runner_cd_closure_evidence_completion_percent: number
production_deploy_non110_runner_cd_closure_next_blocked_step_index: number
production_deploy_non110_runner_cd_closure_next_blocked_step_id: string
production_deploy_non110_runner_cd_closure_next_blocked_step_action: string
production_deploy_non110_runner_ready: boolean
production_deploy_non110_runner_prepare_only_source_ready: boolean
production_deploy_non110_runner_safe_registration_helper_ready: boolean
@@ -2649,6 +2657,14 @@ export interface DeliveryClosureWorkbenchSnapshot {
non110_runner_cd_closure_verifier: string
non110_runner_cd_closure_status: string
non110_runner_cd_closure_required: boolean
non110_runner_cd_closure_ordered_step_count: number
non110_runner_cd_closure_ordered_completed_prefix_count: number
non110_runner_cd_closure_evidence_completed_step_count: number
non110_runner_cd_closure_ordered_completion_percent: number
non110_runner_cd_closure_evidence_completion_percent: number
non110_runner_cd_closure_next_blocked_step_index: number
non110_runner_cd_closure_next_blocked_step_id: string
non110_runner_cd_closure_next_blocked_step_action: string
non110_runner_ready: boolean
non110_runner_prepare_only_source_ready: boolean
non110_runner_safe_registration_helper_ready: boolean

View File

@@ -1,3 +1,29 @@
## 2026-06-29 — 10:58 non-110 CD closure ordered progress readback
**完成內容**
- `ops/runner/verify-awoooi-non110-cd-closure.py` 新增 `ordered_steps``progress`,固定順序為 registration metadata、active service、public queue match、production Workbench readback、production image tag、governance fields。
- production deploy snapshot 與 Delivery Workbench summary / `production_deploy` lane metric 現在可讀回 `next_blocked_step_id=non110_runner_registration_metadata``ordered_completion_percent=0``evidence_completion_percent=33`
- 刷新 sanitized source readback SHA 為 `f3634db18477`public queue readback 目前 `no_matching_runner_not_visible`3858 / 3857 / 3855 轉為 `Failure`,但 registration metadata 仍是第一 blocked step。
**驗證結果**
- `python3.11 -m pytest ops/runner/test_verify_awoooi_non110_cd_closure.py -q``6 passed`
- live sanitized closure verifier `/tmp/awoooi-non110-cd-closure-ordered-20260629.json``status=blocked_non110_runner_not_ready``next_blocked_step_id=non110_runner_registration_metadata``ordered_step_count=6``ordered_completed_prefix_count=0``evidence_completed_step_count=2`
- `python3 -m py_compile ops/runner/verify-awoooi-non110-cd-closure.py``git diff --check`:通過。
**邊界**:未讀 token / `.runner` 內容 / cookie / session / secret / auth / `.env`;未 workflow_dispatch未操作 host / Docker / K8s / runner service未使用 GitHub。
## 2026-06-29 — 11:02 post-commit cold-start / credential escrow replay
**完成內容**
- 以已同步到 `gitea-ssh/main` 的 HEAD `0121d9534` 重新跑同一輪 read-only post-reboot evidence chain。
- summary artifact `/tmp/awoooi-post-reboot-readiness-20260629-105655/summary.txt``POST_START_RESULT=FULL_STACK_GREEN_DR_ESCROW_BLOCKED``POST_START_PASS=43``POST_START_WARN=5``POST_START_BLOCKED=0``POST_START_SERVICE_WARNINGS=0``SERVICE_GREEN=1``PRODUCT_DATA_GREEN=1``STOCK_FRESHNESS_STATUS=ok``STOCK_LATEST_TRADING_DATE=2026-06-26``BACKUP_CORE_GREEN=1``DR_ESCROW_BLOCKED=1``ESCROW_MISSING_COUNT=5``HOST_188_HYGIENE_BLOCKED=0``WAZUH_MANAGER_REGISTRY_ACCEPTED=6``RUNTIME_ACTION_AUTHORIZED=0``NEXT_REQUIRED_GATES=credential_escrow_evidence`
- declaration guard`POST_REBOOT_DECLARATION_GUARD_OK status=allowed_with_boundary_blockers allowed=5 forbidden=4 next_gates=1 rejected_proposed=0`
- owner packet artifact `/tmp/awoooi-post-reboot-owner-packets-20260629-105655.json` 通過 contract guard`POST_REBOOT_OWNER_PACKET_CONTRACT_GUARD_OK gates=1 request_sent=0 accepted=0 runtime_gate=0`
- owner response placeholder preflight`POST_REBOOT_OWNER_RESPONSE_PREFLIGHT_BLOCKED status=blocked_waiting_owner_response_content expected_gates=1 received=0 accepted=0 runtime_gate=0 blockers=27`
- credential escrow scorecard`STATUS=blocked_waiting_non_secret_credential_escrow_evidence``ACTIVE_GATE_PRESENT=1``EFFECTIVE_ESCROW_MISSING_COUNT=5``OWNER_RESPONSE_RECEIVED_COUNT=0``OWNER_RESPONSE_ACCEPTED_COUNT=0``RUNTIME_GATE_COUNT=0``SECRET_VALUE_COLLECTION_ALLOWED=0``CREDENTIAL_MARKER_WRITE_AUTHORIZED_COUNT=0``FORBIDDEN_TRUE_FIELD_COUNT=0`
**邊界**read-only replay only未讀 password / token / `.runner` / raw session / SQLite / auth / `.env`,未寫 credential marker未操作 host / Docker / K8s / runner service未使用 GitHub未重啟 Docker / Nginx / firewall / K3s / DB。
## 2026-06-29 — 10:56 credential escrow reviewer acceptance deadlock fix
**完成內容**

View File

@@ -1,12 +1,12 @@
{
"schema_version": "awoooi_production_deploy_readback_blocker_v1",
"generated_at": "2026-06-29T10:44:45+08:00",
"status": "blocked_waiting_authorized_gitea_workflow_dispatch_and_runner_queue",
"generated_at": "2026-06-29T11:08:12+08:00",
"status": "blocked_production_image_not_current",
"priority": "P0",
"scope": "awoooi_production_truth",
"readback": {
"observed_source_control_main_sha": "7191193c71e5c82d6ae703a9c530d676862aa178",
"observed_source_control_main_short_sha": "7191193c71e",
"observed_source_control_main_sha": "f3634db18477a06af94ec4eae227ee96378aae0a",
"observed_source_control_main_short_sha": "f3634db18477",
"governance_closure_merge_sha": "27b96f0450d0e3ca6651d6b5f274a341dd727ef2",
"governance_closure_commit_sha": "9e3e7fbb6ba3ffd324b45abf3ad1e7b6ec826b22",
"production_image_tag_sha": "af45811e876fda322ee63c036fbc39c9f07ffd76",
@@ -22,17 +22,25 @@
"latest_visible_cd_run_commit_short_sha": "1e68f9ff27",
"gitea_actions_list_without_token_http_status": 401,
"gitea_actions_list_without_token_message": "token is required",
"latest_visible_waiting_runner_run_id": "3857",
"latest_visible_waiting_runner_workflow": "ai-technology-watch.yaml",
"latest_visible_waiting_runner_kind": "Scheduled",
"latest_visible_waiting_runner_status": "No matching online runner with label: awoooi-non110-ubuntu",
"latest_visible_waiting_runner_label": "awoooi-non110-ubuntu",
"latest_visible_waiting_runner_run_id": "",
"latest_visible_waiting_runner_workflow": "",
"latest_visible_waiting_runner_kind": "",
"latest_visible_waiting_runner_status": "",
"latest_visible_waiting_runner_label": "",
"public_actions_queue_readback_schema_version": "awoooi_public_gitea_actions_queue_readback_v1",
"public_actions_queue_readback_verifier": "ops/runner/read-public-gitea-actions-queue.py --json",
"non110_runner_cd_closure_verifier_schema_version": "awoooi_non110_cd_closure_verifier_v1",
"non110_runner_cd_closure_verifier": "ops/runner/verify-awoooi-non110-cd-closure.py --production-deploy-snapshot-json-file docs/operations/awoooi-production-deploy-readback-blocker.snapshot.json --readiness-file <sanitized-non110-readiness.txt> --queue-json-file <public-actions-queue.json> --production-workbench-json-file <delivery-closure-workbench.json> --json",
"non110_runner_cd_closure_status": "blocked_non110_runner_not_ready",
"non110_runner_cd_closure_status": "blocked_production_image_not_current",
"non110_runner_cd_closure_required": true,
"non110_runner_cd_closure_ordered_step_count": 6,
"non110_runner_cd_closure_ordered_completed_prefix_count": 4,
"non110_runner_cd_closure_evidence_completed_step_count": 4,
"non110_runner_cd_closure_ordered_completion_percent": 67,
"non110_runner_cd_closure_evidence_completion_percent": 67,
"non110_runner_cd_closure_next_blocked_step_index": 5,
"non110_runner_cd_closure_next_blocked_step_id": "production_image_tag_current",
"non110_runner_cd_closure_next_blocked_step_action": "complete_authorized_cd_then_verify_image_tag_matches_main",
"current_main_cd_run_visible": false,
"manual_run_button_visible": false,
"gitea_sign_in_required": true,
@@ -44,22 +52,18 @@
"non110_runner_workflow_labels_aligned": true,
"non110_runner_host_label": "awoooi-non110-host",
"non110_runner_ubuntu_label": "awoooi-non110-ubuntu",
"non110_runner_online_label_match": false,
"non110_runner_online_label_match": true,
"non110_runner_autostart_path_armed": true,
"non110_runner_ready_autostart_path_count": 1,
"non110_runner_registration_condition_required": true,
"non110_runner_ready": false,
"non110_runner_ready": true,
"non110_runner_ready_config_count": 1,
"non110_runner_ready_binary_count": 1,
"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_register_awoooi_non110_runner_script_without_printing_token_then_autostart_path_will_enable_service_and_rerun_this_verifier"
"non110_runner_ready_registration_count": 1,
"non110_runner_ready_active_service_count": 1,
"non110_runner_remaining_blockers": [],
"non110_runner_safe_next_step": "complete_authorized_cd_then_verify_image_tag_matches_main"
},
"blockers": [
{
@@ -71,52 +75,36 @@
"safe_boundary": "不得讀 token/cookie/session不得改 workflow 為 push trigger不得手改 K8s tag不得重開 110 runner 或 host/K8s runtime。"
},
{
"id": "gitea_cd_runner_queue_not_accepting_visible_run",
"kind": "runner_queue_runtime_readback",
"id": "production_image_tag_not_current_after_non110_ready",
"kind": "production_readback",
"severity": "P0",
"description": "Public Gitea readback 顯示 CD run #3853 仍為 Waitingjobs API 回 total_count=0尚未有 runner 接走可見 run。",
"blocked_action": "complete_cd_run_and_update_production_image_tag",
"safe_boundary": "只允許讀 public Gitea/status 與 source verifier不得登入、讀 token、操作 hostDockerK8s 或 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。"
},
{
"id": "non110_runner_label_has_no_matching_online_runner",
"kind": "runner_queue_runtime_readback",
"severity": "P0",
"description": "Public Gitea Actions HTML 顯示 ai-technology-watch.yaml #3857 等待 awoooi-non110-ubuntu但沒有 matching online runnernon-110 labels 已進 sourceruntime runner channel 尚未 online。",
"blocked_action": "accept_non110_labeled_gitea_actions_jobs",
"safe_boundary": "只允許讀 public Gitea HTML/API 與 committed source不得登入、讀 token、註冊 runner、啟動 service 或操作 Docker/host。"
"description": "Non-110 runner readiness 已讀回 readypublic queue 也不再顯示 no-matching-runnerproduction Delivery Workbench 仍是舊版 source_count=5image tag 尚未跟目前 Gitea main 對齊。",
"blocked_action": "verify_current_main_deployed_to_production",
"safe_boundary": "只允許讀 production API / public Gitea queue / sanitized verifier不得讀 token、手改 K8s tag、force push、使用 GitHub 或操作 host/Docker/K8s。"
}
],
"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 內容。",
"註冊與 autostart 後,使用 ops/runner/verify-awoooi-non110-cd-closure.py 彙整 non-110 readiness、public queue 與 production workbenchclosure_verified 前不得宣稱 CD lane 已恢復。",
"讀回 Gitea Actions HTML 的 no-matching-runner status在 runner channel online 前不要把 workflow labels aligned 誤判為可部署。",
"Non-110 runner readiness 已成立;下一步完成 Gitea CD並確認 production image tag 跟目前 main 對齊。",
"CD 完成後讀回 production image tag確認不再是 af45811e87。",
"重新執行 ops/runner/verify-awoooi-non110-cd-closure.py目標是 status=closure_verified。",
"重新讀回 /api/v1/agents/github-target-controlled-execution-preflight 與 /api/v1/agents/delivery-closure-workbench確認 internal_governance_writeback 與 KM / PlayBook counters 出現。"
],
"rollups": {
"hard_blocker_count": 4,
"next_action_count": 7,
"hard_blocker_count": 2,
"next_action_count": 4,
"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_online_label_match": false,
"non110_runner_ready": true,
"non110_runner_online_label_match": true,
"non110_runner_autostart_path_armed": true,
"non110_runner_cd_closure_required": true,
"non110_runner_cd_closure_status": "blocked_non110_runner_not_ready",
"non110_runner_remaining_blocker_count": 3,
"non110_runner_cd_closure_status": "blocked_production_image_not_current",
"non110_runner_cd_closure_ordered_completion_percent": 67,
"non110_runner_cd_closure_evidence_completion_percent": 67,
"non110_runner_cd_closure_next_blocked_step_id": "production_image_tag_current",
"non110_runner_remaining_blocker_count": 0,
"runtime_write_performed": false,
"secret_values_collected": false
},

View File

@@ -546,6 +546,11 @@ source-level fail-closed evidence不是 live host proof。
只有 `AWOOOI_NON110_RUNNER_READY=1`、public queue 不再顯示
`No matching online runner`、production image tag 已跟 main 對齊且 governance fields
已出現時,才會輸出 `closure_verified`
verifier 也會輸出 `ordered_steps``progress`:順序固定為 registration metadata、
active service、public queue match、production Workbench readback、production image tag、
governance fields。若 live readback 仍缺 `.runner` metadata`next_blocked_step_id`
必須維持 `non110_runner_registration_metadata`,不得跳到 dispatch、image tag 或
governance fields。
`--enable` 只允許在 `AWOOOI_NON110_ENABLE=1``act_runner` executable、
`config.yaml` present、`.runner` present 且 service 已由 verifier 證明 target / limits

View File

@@ -118,6 +118,15 @@ def test_closure_verifier_blocks_runner_not_ready_without_secret_leak() -> None:
text = json.dumps(payload, sort_keys=True)
assert payload["schema_version"] == module.SCHEMA_VERSION
assert payload["status"] == "blocked_non110_runner_not_ready"
assert payload["progress"]["ordered_step_count"] == 6
assert payload["progress"]["ordered_completed_prefix_count"] == 0
assert payload["progress"]["next_blocked_step_index"] == 1
assert payload["progress"]["next_blocked_step_id"] == (
"non110_runner_registration_metadata"
)
assert payload["ordered_steps"][0]["status"] == "blocked"
assert payload["ordered_steps"][0]["evidence_ready"] is False
assert payload["ordered_steps"][1]["status"] == "pending"
assert "runner_registration_missing" in payload["runner_readiness_blockers"]
assert payload["readback"]["non110_runner_ready_registration_count"] == 0
assert payload["operation_boundaries"]["secret_or_runner_token_read"] is False
@@ -133,6 +142,12 @@ def test_closure_verifier_blocks_queue_after_runner_ready() -> None:
production_workbench=_workbench(image_current=False, governance_ready=False),
)
assert payload["status"] == "blocked_no_matching_online_runner"
assert payload["progress"]["ordered_completed_prefix_count"] == 2
assert payload["progress"]["next_blocked_step_index"] == 3
assert payload["progress"]["next_blocked_step_id"] == "public_queue_runner_match"
assert payload["ordered_steps"][0]["status"] == "complete"
assert payload["ordered_steps"][1]["status"] == "complete"
assert payload["ordered_steps"][2]["status"] == "blocked"
assert "public_queue_still_has_no_matching_online_runner" in payload["blockers"]
@@ -170,6 +185,10 @@ def test_closure_verifier_accepts_full_closure_evidence() -> None:
production_workbench=_workbench(image_current=True, governance_ready=True),
)
assert payload["status"] == "closure_verified"
assert payload["progress"]["ordered_completed_prefix_count"] == 6
assert payload["progress"]["ordered_completion_percent"] == 100
assert payload["progress"]["next_blocked_step_id"] == ""
assert all(step["status"] == "complete" for step in payload["ordered_steps"])
assert payload["blockers"] == []
assert payload["readback"]["production_deploy_image_tag_matches_main"] is True

View File

@@ -19,6 +19,7 @@ DEFAULT_PRODUCTION_DEPLOY_SNAPSHOT = (
DEFAULT_PRODUCTION_WORKBENCH_URL = (
"https://awoooi.wooo.work/api/v1/agents/delivery-closure-workbench"
)
ORDERED_STEP_COUNT = 6
def _read_text(path: str | None) -> str:
@@ -157,6 +158,116 @@ def _production_summary(workbench: dict[str, Any]) -> dict[str, Any]:
return summary if isinstance(summary, dict) else {}
def _build_ordered_steps(
*,
readiness: dict[str, Any],
no_matching_runner_visible: bool,
production_workbench_present: bool,
production_image_tag_matches_main: bool,
production_governance_fields_present: bool,
) -> list[dict[str, Any]]:
registration_ready = (
readiness["provided"]
and readiness["ready_registration_count"] > 0
and not readiness["raw_runner_registration_read"]
)
active_service_ready = (
readiness["provided"]
and readiness["ready_active_service_count"] > 0
and not readiness["raw_runner_registration_read"]
)
definitions = [
{
"id": "non110_runner_registration_metadata",
"title": "non-110 runner registration metadata present",
"evidence_ready": registration_ready,
"next_action": readiness["safe_next_step"]
or "rerun_non110_runner_readiness_verifier",
},
{
"id": "non110_runner_active_service",
"title": "non-110 runner service active",
"evidence_ready": active_service_ready,
"next_action": "wait_for_autostart_path_or_rerun_non110_runner_readiness_verifier",
},
{
"id": "public_queue_runner_match",
"title": "public Gitea queue no longer shows no-matching-runner",
"evidence_ready": not no_matching_runner_visible,
"next_action": "rerun_public_queue_readback_until_no_matching_runner_is_absent",
},
{
"id": "production_workbench_readback",
"title": "production Delivery Workbench readback present",
"evidence_ready": production_workbench_present,
"next_action": "read_production_delivery_workbench_after_deploy",
},
{
"id": "production_image_tag_current",
"title": "production image tag matches Gitea main",
"evidence_ready": production_image_tag_matches_main,
"next_action": "complete_authorized_cd_then_verify_image_tag_matches_main",
},
{
"id": "production_governance_fields_present",
"title": "production governance fields present after deploy",
"evidence_ready": production_governance_fields_present,
"next_action": "verify_internal_governance_writeback_fields_after_deploy",
},
]
first_blocked_index = next(
(
index
for index, step in enumerate(definitions)
if step["evidence_ready"] is not True
),
None,
)
steps: list[dict[str, Any]] = []
for index, step in enumerate(definitions):
if step["evidence_ready"] is True:
status = "complete"
elif index == first_blocked_index:
status = "blocked"
else:
status = "pending"
steps.append(
{
"index": index + 1,
"id": step["id"],
"title": step["title"],
"status": status,
"evidence_ready": step["evidence_ready"] is True,
"next_action": step["next_action"] if status == "blocked" else "",
}
)
return steps
def _progress_from_steps(steps: list[dict[str, Any]]) -> dict[str, Any]:
evidence_completed = sum(1 for step in steps if step["evidence_ready"] is True)
prefix_completed = 0
for step in steps:
if step["status"] != "complete":
break
prefix_completed += 1
next_blocked = next((step for step in steps if step["status"] == "blocked"), {})
return {
"ordered_step_count": len(steps),
"ordered_completed_prefix_count": prefix_completed,
"evidence_completed_step_count": evidence_completed,
"ordered_completion_percent": round(
(prefix_completed / max(len(steps), 1)) * 100
),
"evidence_completion_percent": round(
(evidence_completed / max(len(steps), 1)) * 100
),
"next_blocked_step_index": _int(next_blocked.get("index")),
"next_blocked_step_id": str(next_blocked.get("id") or ""),
"next_blocked_step_action": str(next_blocked.get("next_action") or ""),
}
def build_closure_verifier(
*,
readiness_text: str,
@@ -221,9 +332,19 @@ def build_closure_verifier(
else:
status = "closure_verified"
ordered_steps = _build_ordered_steps(
readiness=readiness,
no_matching_runner_visible=no_matching_runner_visible,
production_workbench_present=production_workbench_present,
production_image_tag_matches_main=production_image_tag_matches_main,
production_governance_fields_present=production_governance_fields_present,
)
progress = _progress_from_steps(ordered_steps)
return {
"schema_version": SCHEMA_VERSION,
"status": status,
"progress": progress,
"readback": {
"non110_runner_ready": readiness["ready"],
"non110_runner_readiness_source": readiness["source"],
@@ -259,6 +380,7 @@ def build_closure_verifier(
),
},
"blockers": blockers,
"ordered_steps": ordered_steps,
"runner_readiness_blockers": readiness["blockers"],
"runner_readiness_warnings": readiness["warnings"],
"next_actions": [