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 623ad6c6a..e2f1942d5 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 @@ -69,7 +69,7 @@ def test_awoooi_priority_work_order_readback_loader_returns_mainline_order(): "blockers_open" ) assert payload["mainline_execution_state"]["active_p0_immediate_apply_gap_count"] == 0 - assert payload["mainline_execution_state"]["active_p0_readiness_percent"] == 40 + assert payload["mainline_execution_state"]["active_p0_readiness_percent"] == 47 assert ( payload["mainline_execution_state"][ "controlled_service_data_backup_readback_present" diff --git a/apps/api/tests/test_delivery_closure_workbench_api.py b/apps/api/tests/test_delivery_closure_workbench_api.py index 4b15e7826..3766d2620 100644 --- a/apps/api/tests/test_delivery_closure_workbench_api.py +++ b/apps/api/tests/test_delivery_closure_workbench_api.py @@ -22,7 +22,6 @@ REBOOT_ACTIVE_BLOCKERS = [ "wazuh_dashboard_degraded", "windows99_remote_execution_channel_unavailable", "windows99_vmware_autostart_readback_missing", - "public_maintenance_fallback_runtime_readback_missing", ] DRILL_ACTIVE_BLOCKERS = [ "service_green_not_ready", @@ -254,8 +253,8 @@ def test_delivery_closure_workbench_exposes_p0_006_reboot_slo_lane(): assert lane["source_id"] == "reboot_auto_recovery_slo_scorecard" assert lane["status"] == "blocked_reboot_auto_recovery_slo_not_ready" - assert lane["blocker_count"] == 12 - assert lane["completion_percent"] == 40 + assert lane["blocker_count"] == 11 + assert lane["completion_percent"] == 47 assert lane["metric"]["kind"] == "reboot_auto_recovery_slo" assert lane["metric"]["workplan_id"] == "P0-006" assert lane["metric"]["target_minutes"] == 10 @@ -338,7 +337,7 @@ def _assert_delivery_workbench_shape(data: dict): ) assert data["readback"]["current_p0_safe_next_step"] == data["safe_next_step"] assert data["readback"]["current_p0_active_blockers"] == data["active_blockers"] - assert data["readback"]["current_p0_readiness_percent"] == 40 + assert data["readback"]["current_p0_readiness_percent"] == 47 assert data["readback"]["current_p0_drill_preflight_status"] == ( "blocked_reboot_drill_preflight_not_ready" ) @@ -363,8 +362,8 @@ def _assert_delivery_workbench_shape(data: dict): assert data["rollups"]["high_risk_blocker_count"] == data["summary"][ "high_risk_blocker_count" ] - assert data["rollups"]["current_p0_blocker_count"] == 12 - assert data["rollups"]["current_p0_readiness_percent"] == 40 + assert data["rollups"]["current_p0_blocker_count"] == 11 + assert data["rollups"]["current_p0_readiness_percent"] == 47 assert data["rollups"]["current_p0_blocked_by_fresh_reboot_window_only"] is False assert data["rollups"]["current_p0_service_green"] is False assert data["rollups"]["current_p0_product_data_green"] is True @@ -447,8 +446,8 @@ def _assert_delivery_workbench_shape(data: dict): "blocked_reboot_auto_recovery_slo_not_ready" ) assert data["summary"]["reboot_auto_recovery_workplan_id"] == "P0-006" - assert data["summary"]["reboot_auto_recovery_readiness_percent"] == 40 - assert data["summary"]["reboot_auto_recovery_active_blocker_count"] == 12 + assert data["summary"]["reboot_auto_recovery_readiness_percent"] == 47 + assert data["summary"]["reboot_auto_recovery_active_blocker_count"] == 11 assert data["summary"]["reboot_auto_recovery_can_claim_slo"] is False assert data["summary"]["reboot_auto_recovery_service_green"] is False assert data["summary"]["reboot_auto_recovery_product_data_green"] is True diff --git a/apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py b/apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py index f8cb45c74..11da92234 100644 --- a/apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py +++ b/apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py @@ -24,7 +24,6 @@ EXPECTED_REBOOT_SLO_BLOCKERS = [ "wazuh_dashboard_degraded", "windows99_remote_execution_channel_unavailable", "windows99_vmware_autostart_readback_missing", - "public_maintenance_fallback_runtime_readback_missing", ] EXPECTED_DRILL_PREFLIGHT_BLOCKERS = [ @@ -96,8 +95,8 @@ def test_reboot_auto_recovery_slo_scorecard_endpoint_overlays_live_stockplatform assert payload["required_checks"]["stockplatform_freshness_ok"] is False assert payload["required_checks"]["stockplatform_ingestion_ok"] is False assert payload["required_checks"]["product_data_green"] is False - assert payload["readiness_percent"] == 20 - assert payload["active_blocker_count"] == 16 + assert payload["readiness_percent"] == 27 + assert payload["active_blocker_count"] == 15 assert "product_data_green_not_1" in payload["active_blockers"] assert "stockplatform_freshness_core_margin_short_daily_missing" in payload[ "active_blockers" @@ -150,8 +149,8 @@ def _assert_reboot_slo_payload(payload: dict): assert payload["safe_next_step"] == EXPECTED_WINDOWS99_NEXT_STEP assert payload["next_safe_action"] == payload["safe_next_step"] assert payload["can_claim_all_services_recovered_within_target"] is False - assert payload["active_blocker_count"] == 12 - assert payload["readiness_percent"] == 40 + assert payload["active_blocker_count"] == 11 + assert payload["readiness_percent"] == 47 assert payload["service_green"] is False assert payload["product_data_green"] is True assert payload["backup_core_green"] is False @@ -170,12 +169,17 @@ def _assert_reboot_slo_payload(payload: dict): assert payload["stockplatform_ingestion_status"] == "ok" assert payload["required_checks"][ "public_maintenance_fallback_runtime_ready" - ] is False - assert payload["public_maintenance_fallback"]["runtime_readback_present"] is False - assert payload["public_maintenance_fallback"]["ready"] is False - assert payload["public_maintenance_fallback"]["blockers"] == [ - "public_maintenance_fallback_runtime_readback_missing" - ] + ] is True + assert payload["public_maintenance_fallback"]["runtime_readback_present"] is True + assert payload["public_maintenance_fallback"]["ready"] is True + assert payload["public_maintenance_fallback"]["blockers"] == [] + assert payload["public_maintenance_fallback"]["route_count"] == 6 + assert payload["public_maintenance_fallback"][ + "raw_5xx_without_fallback_count" + ] == 0 + assert payload["public_maintenance_fallback"][ + "route_unreachable_without_external_fallback_count" + ] == 0 assert payload["windows99_vmware_verify_ready"] is False assert payload["windows99_update_no_auto_reboot_ready"] is False assert payload["windows99_remote_execution_channel_ready"] is False @@ -205,12 +209,12 @@ def _assert_reboot_slo_payload(payload: dict): ) assert payload["readback"]["safe_next_step"] == EXPECTED_WINDOWS99_NEXT_STEP assert payload["readback"]["next_safe_action"] == payload["safe_next_step"] - assert payload["readback"]["active_blocker_count"] == 12 - assert payload["readback"]["readiness_percent"] == 40 + assert payload["readback"]["active_blocker_count"] == 11 + assert payload["readback"]["readiness_percent"] == 47 assert payload["readback"]["blocked_by_fresh_reboot_window_only"] is False assert payload["readback"]["latest_verify_only_metric_present"] is False - assert payload["readback"]["public_maintenance_runtime_readback_present"] is False - assert payload["readback"]["public_maintenance_fallback_ready"] is False + assert payload["readback"]["public_maintenance_runtime_readback_present"] is True + assert payload["readback"]["public_maintenance_fallback_ready"] is True assert payload["readback"]["public_route_raw_5xx_without_fallback_count"] == 0 assert payload["readback"]["windows99_vmware_verify_ready"] is False assert payload["readback"]["windows99_update_no_auto_reboot_ready"] is False @@ -223,8 +227,8 @@ def _assert_reboot_slo_payload(payload: dict): ) assert payload["readback"]["windows99_remote_execution_channel_ready"] is False assert payload["readback"]["windows99_ssh_batch_status"] == "permission_denied" - assert payload["rollups"]["active_blocker_count"] == 12 - assert payload["rollups"]["readiness_percent"] == 40 + assert payload["rollups"]["active_blocker_count"] == 11 + assert payload["rollups"]["readiness_percent"] == 47 assert payload["rollups"]["observed_host_count"] == 7 assert payload["rollups"]["missing_host_count"] == 0 assert payload["rollups"]["unreachable_host_count"] == 1 @@ -241,8 +245,8 @@ def _assert_reboot_slo_payload(payload: dict): assert payload["rollups"]["stockplatform_ingestion_status"] == "ok" assert payload["rollups"]["stockplatform_freshness_blocker_count"] == 0 assert payload["rollups"]["stockplatform_ingestion_blocker_count"] == 0 - assert payload["rollups"]["public_maintenance_runtime_readback_present"] is False - assert payload["rollups"]["public_maintenance_fallback_ready"] is False + assert payload["rollups"]["public_maintenance_runtime_readback_present"] is True + assert payload["rollups"]["public_maintenance_fallback_ready"] is True assert payload["rollups"]["public_route_raw_5xx_without_fallback_count"] == 0 assert ( payload["rollups"]["controlled_service_data_backup_readback_present"] @@ -297,8 +301,8 @@ def _assert_reboot_slo_payload(payload: dict): "event_and_probe" ) assert progress["primary_blocker"] == "reboot_event_required_host_unreachable" - assert progress["active_blocker_count"] == 12 - assert progress["readiness_percent"] == 40 + assert progress["active_blocker_count"] == 11 + assert progress["readiness_percent"] == 47 assert progress["next_safe_action"] == payload["safe_next_step"] assert progress["fixed_triage_order"][0] == "99_vmware_autostart_and_vm_power" service_data_backup = payload["controlled_service_data_backup_readback"] @@ -466,8 +470,8 @@ def _assert_drill_preflight_payload(payload: dict): } current = payload["current_readback"] assert current["scorecard_status"] == "blocked_reboot_auto_recovery_slo_not_ready" - assert current["readiness_percent"] == 40 - assert current["active_blocker_count"] == 12 + assert current["readiness_percent"] == 47 + assert current["active_blocker_count"] == 11 assert current["active_blockers"] == EXPECTED_REBOOT_SLO_BLOCKERS assert current["latest_verify_only_metric_ready"] == 0 assert current["latest_verify_only_metric_blocker_count"] == 0 @@ -492,7 +496,7 @@ def _assert_drill_preflight_payload(payload: dict): assert readback["workplan_id"] == "P0-006" assert readback["preflight_ready"] is False assert readback["target_selector"]["required_host_count"] == 7 - assert readback["current_readback"]["active_blocker_count"] == 12 + assert readback["current_readback"]["active_blocker_count"] == 11 assert readback["safe_next_step"] == payload["safe_next_step"] rollups = payload["rollups"] assert rollups["preflight_ready"] is False diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 101a83565..ef60f1d53 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,19 @@ +## 2026-07-02 — 22:02 P0-006 public maintenance runtime readback closure + +**完成內容**: +- 接續 Gitea CD `#4456` Success / deploy marker `31471ce9` / production source `758e7e9616` readback;tests job `609 passed`,post-deploy Playwright smoke `5 passed`,AlertChain / SourceLink / Monitoring / Smoke 全部 `✅`。 +- 以 public-only `scripts/reboot-recovery/public-maintenance-fallback-probe.py` 讀回 6 條 public routes:`awoooi` health、`awoooi` web、StockPlatform freshness、MOMO health、Bitan、Tsenyang 全部 HTTP `200`,raw 5xx without fallback=`0`,unreachable without L1 fallback=`0`。 +- `docs/operations/awoooi-reboot-auto-recovery-slo-scorecard.snapshot.json` 已寫入 `public_maintenance_fallback` runtime readback;API / Workbench / Priority contract 同步:P0-006 blockers 由 `12` 降為 `11`,readiness 由 `40` 提升為 `47`。 +- 此修正只關閉 `public_maintenance_fallback_runtime_readback_missing`;host / Windows99 / Wazuh / service/backup blockers 仍保留,不宣稱 10 分鐘全主機恢復 SLO 完成。 + +**驗證**: +- `python3.11 scripts/reboot-recovery/public-maintenance-fallback-probe.py --timeout 8 --output /tmp/public-maintenance-fallback-readback.json --prometheus-output /tmp/public-maintenance-fallback-readback.prom ...`:`ready=true`、`route_count=6`、`raw_5xx_without_fallback_count=0`、`route_unreachable_without_external_fallback_count=0`。 +- `DATABASE_URL=sqlite+aiosqlite:////tmp/awoooi-codex-api-test.db PYTHONPATH=apps/api python3.11 -m pytest -q apps/api/tests/test_delivery_closure_workbench_api.py apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py apps/api/tests/test_awoooi_priority_work_order_readback_api.py -p no:cacheprovider`:`28 passed`。 +- `DATABASE_URL=sqlite+aiosqlite:////tmp/awoooi-codex-api-test.db PYTHONPATH=apps/api python3.11 -m pytest -q apps/api/tests/test_delivery_closure_workbench_api.py apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py apps/api/tests/test_awoooi_priority_work_order_readback_api.py apps/api/tests/test_stockplatform_public_api_runtime_readback.py apps/api/tests/test_stockplatform_public_api_controlled_recovery_preflight.py scripts/reboot-recovery/tests/test_reboot_auto_recovery_slo_scorecard.py scripts/reboot-recovery/tests/test_reboot_p0_operational_contract.py apps/api/tests/test_platform_read_model_fallbacks.py -p no:cacheprovider`:`65 passed`。 + +**仍維持**: +- 未讀 secret / token / `.env` / raw sessions / SQLite / auth;未使用 GitHub / gh;未 SSH、未 Docker / K8s / firewall / Wazuh 寫入、未重啟 host / VM / service、未 workflow_dispatch、未 DROP / TRUNCATE / restore / prune。 + ## 2026-07-02 — 21:56 P0-006 L0 fallback CD profile blocker fix **完成內容**: diff --git a/docs/operations/awoooi-reboot-auto-recovery-slo-scorecard.snapshot.json b/docs/operations/awoooi-reboot-auto-recovery-slo-scorecard.snapshot.json index a83b67b59..4cb265331 100644 --- a/docs/operations/awoooi-reboot-auto-recovery-slo-scorecard.snapshot.json +++ b/docs/operations/awoooi-reboot-auto-recovery-slo-scorecard.snapshot.json @@ -174,6 +174,96 @@ "wazuh_dashboard_degraded": true }, "primary_blocker": "reboot_event_required_host_unreachable", + "public_maintenance_fallback": { + "schema_version": "awoooi_public_maintenance_fallback_probe_v1", + "generated_at": "2026-07-02T21:58:46+08:00", + "runtime_readback_present": true, + "ready": true, + "target_url_count": 6, + "route_count": 6, + "raw_5xx_without_fallback_count": 0, + "route_unreachable_without_external_fallback_count": 0, + "maintenance_fallback_route_count": 0, + "raw_5xx_without_fallback_urls": [], + "route_unreachable_without_external_fallback_urls": [], + "maintenance_fallback_urls": [], + "routes": [ + { + "error": "", + "fallback_header": "", + "http_status": 200, + "maintenance_body_detected": false, + "maintenance_fallback_serving": false, + "raw_5xx_without_fallback": false, + "route_unreachable_without_external_fallback": false, + "status": "normal_origin_serving", + "url": "https://awoooi.wooo.work/api/v1/health" + }, + { + "error": "", + "fallback_header": "", + "http_status": 200, + "maintenance_body_detected": false, + "maintenance_fallback_serving": false, + "raw_5xx_without_fallback": false, + "route_unreachable_without_external_fallback": false, + "status": "normal_origin_serving", + "url": "https://awoooi.wooo.work/" + }, + { + "error": "", + "fallback_header": "", + "http_status": 200, + "maintenance_body_detected": false, + "maintenance_fallback_serving": false, + "raw_5xx_without_fallback": false, + "route_unreachable_without_external_fallback": false, + "status": "normal_origin_serving", + "url": "https://stock.wooo.work/api/v1/system/freshness" + }, + { + "error": "", + "fallback_header": "", + "http_status": 200, + "maintenance_body_detected": false, + "maintenance_fallback_serving": false, + "raw_5xx_without_fallback": false, + "route_unreachable_without_external_fallback": false, + "status": "normal_origin_serving", + "url": "https://mo.wooo.work/health" + }, + { + "error": "", + "fallback_header": "", + "http_status": 200, + "maintenance_body_detected": false, + "maintenance_fallback_serving": false, + "raw_5xx_without_fallback": false, + "route_unreachable_without_external_fallback": false, + "status": "normal_origin_serving", + "url": "https://bitan.wooo.work/" + }, + { + "error": "", + "fallback_header": "", + "http_status": 200, + "maintenance_body_detected": false, + "maintenance_fallback_serving": false, + "raw_5xx_without_fallback": false, + "route_unreachable_without_external_fallback": false, + "status": "normal_origin_serving", + "url": "https://www.tsenyang.com/" + } + ], + "blockers": [], + "forbidden_actions": [ + "nginx_reload_from_probe", + "dns_cutover_from_probe", + "service_restart_from_probe", + "host_reboot_from_probe", + "secret_value_read" + ] + }, "readback": { "active_blocker_count": 11, "active_blockers": [