From cb442cdbaa3c6539ea06d1e65d772bcafb6150d4 Mon Sep 17 00:00:00 2001 From: ogt Date: Thu, 2 Jul 2026 14:31:36 +0800 Subject: [PATCH] feat(reboot): expose service data backup readback --- .../awoooi_priority_work_order_readback.py | 100 ++++++++++++- .../reboot_auto_recovery_slo_scorecard.py | 141 ++++++++++++++++++ ...awoooi_priority_work_order_readback_api.py | 92 ++++++++++++ ..._reboot_auto_recovery_slo_scorecard_api.py | 54 +++++++ docs/LOGBOOK.md | 20 +++ 5 files changed, 399 insertions(+), 8 deletions(-) diff --git a/apps/api/src/services/awoooi_priority_work_order_readback.py b/apps/api/src/services/awoooi_priority_work_order_readback.py index 4a9d1519..e96708c8 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -2399,9 +2399,18 @@ def _enrich_from_current_readbacks(payload: dict[str, Any]) -> None: reboot_slo = load_latest_reboot_auto_recovery_slo_scorecard() reboot_slo_rollups = _dict(reboot_slo.get("rollups")) reboot_stockplatform = _dict(reboot_slo.get("stockplatform_data_freshness")) + reboot_service_data_backup = _dict( + reboot_slo.get("controlled_service_data_backup_readback") + ) reboot_preflight_rollups = _dict(reboot_preflight.get("rollups")) reboot_preflight_target_selector = _dict(reboot_preflight.get("target_selector")) reboot_active_blockers = _strings(reboot_slo.get("active_blockers")) + service_data_backup_readback_present = ( + reboot_service_data_backup.get("readback_present") is True + ) + service_data_backup_blocking_fields = _strings( + reboot_service_data_backup.get("blocking_fields") + ) p0_006_event_gated = bool( reboot_slo_rollups.get("blocked_by_fresh_reboot_window_only") is True ) @@ -2443,6 +2452,25 @@ def _enrich_from_current_readbacks(payload: dict[str, Any]) -> None: state["active_p0_can_claim_10_minute_recovery"] = bool( reboot_slo.get("can_claim_all_services_recovered_within_target") is True ) + state["controlled_service_data_backup_readback_present"] = ( + service_data_backup_readback_present + ) + state["controlled_service_data_backup_readback_status"] = str( + reboot_service_data_backup.get("status") or "unknown" + ) + state["controlled_service_data_backup_blocker_count"] = len( + service_data_backup_blocking_fields + ) + state["controlled_service_data_backup_blocking_fields"] = ( + service_data_backup_blocking_fields + ) + state["controlled_service_data_backup_can_clear_blockers"] = bool( + reboot_service_data_backup.get("can_clear_service_data_backup_blockers") + is True + ) + state["controlled_service_data_backup_next_safe_action"] = str( + reboot_service_data_backup.get("next_safe_action") or "" + ) state["stale_snapshot_or_old_cd_runs_must_not_reopen_closed_work"] = True state["p0_004_template_copy_apply_gate_production_http_status"] = 200 state["p0_004_template_copy_apply_gate_runtime_readback_state"] = ( @@ -2481,7 +2509,12 @@ def _enrich_from_current_readbacks(payload: dict[str, Any]) -> None: "or_separate_break_glass_reboot_drill_authorization" ) if p0_006_event_gated - else "blocked_reboot_slo_scorecard_requires_controlled_service_data_backup_readback" + else ( + "blocked_reboot_slo_scorecard_service_data_backup_readback_present_" + "blockers_open" + if service_data_backup_readback_present + else "blocked_reboot_slo_scorecard_requires_controlled_service_data_backup_readback" + ) ) runtime_sha = str( @@ -2574,6 +2607,27 @@ def _enrich_from_current_readbacks(payload: dict[str, Any]) -> None: reboot_slo_rollups.get("stockplatform_controlled_recovery_gate_required") is True ) + evidence["controlled_service_data_backup_readback_present"] = ( + service_data_backup_readback_present + ) + evidence["controlled_service_data_backup_readback_status"] = str( + reboot_service_data_backup.get("status") or "unknown" + ) + evidence["controlled_service_data_backup_blocker_count"] = len( + service_data_backup_blocking_fields + ) + evidence["controlled_service_data_backup_blocking_fields"] = ( + service_data_backup_blocking_fields + ) + evidence["controlled_service_data_backup_can_clear_blockers"] = bool( + reboot_service_data_backup.get( + "can_clear_service_data_backup_blockers" + ) + is True + ) + evidence["controlled_service_data_backup_next_safe_action"] = str( + reboot_service_data_backup.get("next_safe_action") or "" + ) evidence["drill_preflight_status"] = str(reboot_preflight.get("status") or "") evidence["drill_preflight_ready"] = ( reboot_preflight_rollups.get("preflight_ready") is True @@ -2633,18 +2687,21 @@ def _enrich_from_current_readbacks(payload: dict[str, Any]) -> None: professional_fix["action"] = ( "Continue P0-006 from the reboot SLO scorecard truth: close the " "service, product-data, backup, 188 service, host reachability, " - "disk, and Wazuh blockers in controlled lanes; for StockPlatform " - "freshness/ingestion postgres_not_ready, use the production " - "migration/control-channel path with target selector, dry-run, " - "rollback, public API verifier, and KM/RAG/MCP/PlayBook writeback. " + "disk, and Wazuh blockers in controlled lanes; keep the " + "controlled service/data/backup readback visible as the source " + "selector for these blockers. For StockPlatform freshness/ingestion " + "postgres_not_ready, use the production migration/control-channel " + "path with target selector, dry-run, rollback, public API verifier, " + "and KM/RAG/MCP/PlayBook writeback. " "Do not reboot, restart services, write DB rows, fake freshness, " "trigger workflows, or read secrets from this lane." ) workplan["reason"] = ( "The current reboot SLO scorecard is blocked, not merely waiting " - "for a fresh reboot window. Service/product-data/backup and " - "StockPlatform freshness/ingestion evidence must stay visible in " - "the priority readback until controlled recovery evidence closes." + "for a fresh reboot window. Controlled service/product-data/backup " + "readback is present, but its blocker fields remain open and must " + "stay visible in the priority readback until controlled recovery " + "evidence closes." ) workplan["safe_next_step"] = str(reboot_slo.get("safe_next_step") or "") workplan["status"] = "blocked_reboot_auto_recovery_slo_not_ready" @@ -2808,6 +2865,15 @@ def _set_rollups_and_summary( "active_p0_immediate_apply_gap_count": active_gap_count, "active_p0_event_gated_by_fresh_reboot_window_only": p0_006_event_gated, "active_p0_live_active_blocker_count": len(active_blockers), + "controlled_service_data_backup_readback_present": ( + state.get("controlled_service_data_backup_readback_present") is True + ), + "controlled_service_data_backup_blocker_count": _int( + state.get("controlled_service_data_backup_blocker_count") + ), + "controlled_service_data_backup_can_clear_blockers": ( + state.get("controlled_service_data_backup_can_clear_blockers") is True + ), "p0_004_runtime_readback_ready": p0_004_ready, "reboot_drill_preflight_runtime_readback_ready": ( state.get("reboot_drill_preflight_runtime_readback_state") == "ready" @@ -2825,6 +2891,24 @@ def _set_rollups_and_summary( state.get("active_p0_readiness_percent") ), "active_p0_live_active_blockers": active_blockers, + "controlled_service_data_backup_readback_present": ( + state.get("controlled_service_data_backup_readback_present") is True + ), + "controlled_service_data_backup_readback_status": str( + state.get("controlled_service_data_backup_readback_status") or "" + ), + "controlled_service_data_backup_blocker_count": _int( + state.get("controlled_service_data_backup_blocker_count") + ), + "controlled_service_data_backup_blocking_fields": _strings( + state.get("controlled_service_data_backup_blocking_fields") + ), + "controlled_service_data_backup_can_clear_blockers": ( + state.get("controlled_service_data_backup_can_clear_blockers") is True + ), + "controlled_service_data_backup_next_safe_action": str( + state.get("controlled_service_data_backup_next_safe_action") or "" + ), "latest_successful_deployed_source_sha": latest_source_sha, "latest_successful_deployed_source_short_sha": latest_source_sha[:10], "latest_successful_deploy_marker": str( diff --git a/apps/api/src/services/reboot_auto_recovery_slo_scorecard.py b/apps/api/src/services/reboot_auto_recovery_slo_scorecard.py index 46133655..d49cfa97 100644 --- a/apps/api/src/services/reboot_auto_recovery_slo_scorecard.py +++ b/apps/api/src/services/reboot_auto_recovery_slo_scorecard.py @@ -79,6 +79,13 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]: product_data_green = post_reboot_readiness.get("product_data_green") is True backup_core_green = post_reboot_readiness.get("backup_core_green") is True host_188_service_green = post_reboot_readiness.get("host_188_service_green") is True + controlled_service_data_backup_readback = ( + _build_controlled_service_data_backup_readback( + post_reboot_readiness=post_reboot_readiness, + stockplatform=stockplatform, + active_blockers=active_blockers, + ) + ) blocked_by_fresh_reboot_window_only = active_blockers == [ "host_boot_observation_older_than_target_window" ] @@ -136,6 +143,24 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]: "required" ) is True, + "controlled_service_data_backup_readback_present": ( + controlled_service_data_backup_readback["readback_present"] is True + ), + "controlled_service_data_backup_readback_status": str( + controlled_service_data_backup_readback["status"] + ), + "controlled_service_data_backup_blocker_count": len( + _strings(controlled_service_data_backup_readback.get("blocking_fields")) + ), + "controlled_service_data_backup_can_clear_blockers": ( + controlled_service_data_backup_readback[ + "can_clear_service_data_backup_blockers" + ] + is True + ), + "controlled_service_data_backup_next_safe_action": str( + controlled_service_data_backup_readback["next_safe_action"] + ), } return { "schema_version": _API_SCHEMA_VERSION, @@ -187,8 +212,20 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]: "readiness_percent": readiness_percent, "blocked_by_fresh_reboot_window_only": blocked_by_fresh_reboot_window_only, "latest_verify_only_metric_present": latest_verify_only_metric_present, + "controlled_service_data_backup_readback_status": rollups[ + "controlled_service_data_backup_readback_status" + ], + "controlled_service_data_backup_blocker_count": rollups[ + "controlled_service_data_backup_blocker_count" + ], + "controlled_service_data_backup_next_safe_action": rollups[ + "controlled_service_data_backup_next_safe_action" + ], }, "reboot_sop_progress": sop_progress, + "controlled_service_data_backup_readback": ( + controlled_service_data_backup_readback + ), "host_boot_detection": host_boot_detection, "post_reboot_readiness": post_reboot_readiness, "stockplatform_data_freshness": stockplatform, @@ -211,6 +248,110 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]: } +def _build_controlled_service_data_backup_readback( + *, + post_reboot_readiness: dict[str, Any], + stockplatform: dict[str, Any], + active_blockers: list[str], +) -> dict[str, Any]: + service_green = post_reboot_readiness.get("service_green") is True + product_data_green = post_reboot_readiness.get("product_data_green") is True + backup_core_green = post_reboot_readiness.get("backup_core_green") is True + host_188_service_green = post_reboot_readiness.get("host_188_service_green") is True + post_start_blocked = _int(post_reboot_readiness.get("post_start_blocked")) + wazuh_dashboard_degraded = ( + post_reboot_readiness.get("wazuh_dashboard_degraded") is True + ) + stockplatform_freshness_status = str( + stockplatform.get("freshness_status") or "unknown" + ) + stockplatform_ingestion_status = str( + stockplatform.get("ingestion_status") or "unknown" + ) + blocking_fields: list[str] = [] + if not service_green: + blocking_fields.append("service_green") + if post_start_blocked != 0: + blocking_fields.append("post_start_blocked") + if not product_data_green: + blocking_fields.append("product_data_green") + if not backup_core_green: + blocking_fields.append("backup_core_green") + if not host_188_service_green: + blocking_fields.append("host_188_service_green") + if wazuh_dashboard_degraded: + blocking_fields.append("wazuh_dashboard_degraded") + if stockplatform_freshness_status != "ok": + blocking_fields.append("stockplatform_freshness_status") + if stockplatform_ingestion_status not in {"ok", "unknown"}: + blocking_fields.append("stockplatform_ingestion_status") + + related_blockers = [ + blocker + for blocker in active_blockers + if blocker + in { + "backup_core_green_not_1", + "host_188_service_green_not_1", + "post_start_blocked_not_zero", + "product_data_green_not_1", + "service_green_not_1", + "stockplatform_freshness_blocked", + "stockplatform_ingestion_blocked", + "wazuh_dashboard_degraded", + } + ] + readback_present = ( + post_reboot_readiness.get("summary_present") is True + or stockplatform.get("freshness_endpoint_readback_present") is True + or stockplatform.get("ingestion_endpoint_readback_present") is True + ) + can_clear_blockers = readback_present and not blocking_fields + return { + "schema_version": "controlled_service_data_backup_readback_v1", + "status": ( + "ready_service_data_backup_green" + if can_clear_blockers + else "blocked_service_data_backup_readback_not_green" + ), + "readback_present": readback_present, + "service_green": service_green, + "product_data_green": product_data_green, + "backup_core_green": backup_core_green, + "host_188_service_green": host_188_service_green, + "post_start_blocked": post_start_blocked, + "wazuh_dashboard_degraded": wazuh_dashboard_degraded, + "stockplatform_freshness_status": stockplatform_freshness_status, + "stockplatform_ingestion_status": stockplatform_ingestion_status, + "stockplatform_freshness_blockers": _strings( + stockplatform.get("freshness_blockers") + ), + "stockplatform_ingestion_blockers": _strings( + stockplatform.get("ingestion_blockers") + ), + "blocking_fields": blocking_fields, + "related_active_blockers": related_blockers, + "can_clear_service_data_backup_blockers": can_clear_blockers, + "next_safe_action": ( + "keep_timer_live_wait_for_fresh_all_host_reboot_event_or_drill_preflight" + if can_clear_blockers + else "collect_public_service_data_backup_readback_and_refresh_reboot_slo_scorecard_no_reboot_no_db_write" + ), + "controlled_lane": "read_only_verifier_input", + "forbidden_actions": [ + "host_reboot", + "service_restart", + "manual_db_update", + "truncate_or_restore", + "fake_freshness_marker", + "secret_value_read", + "workflow_dispatch", + "github_api", + ], + "runtime_write_allowed": False, + } + + def _build_reboot_sop_progress( *, scorecard: dict[str, Any], 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 3e2db4da..2da6b4fa 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 @@ -51,8 +51,50 @@ def test_awoooi_priority_work_order_readback_loader_returns_mainline_order(): assert payload["mainline_execution_state"][ "next_executable_mainline_workplan_id" ] == "P0-006-REBOOT-AUTO-RECOVERY-SLO-SCORECARD" + assert payload["mainline_execution_state"][ + "next_executable_mainline_state" + ] == ( + "blocked_reboot_slo_scorecard_service_data_backup_readback_present_" + "blockers_open" + ) assert payload["mainline_execution_state"]["active_p0_immediate_apply_gap_count"] == 0 assert payload["mainline_execution_state"]["active_p0_readiness_percent"] == 18 + assert ( + payload["mainline_execution_state"][ + "controlled_service_data_backup_readback_present" + ] + is True + ) + assert ( + payload["mainline_execution_state"][ + "controlled_service_data_backup_readback_status" + ] + == "blocked_service_data_backup_readback_not_green" + ) + assert ( + payload["mainline_execution_state"][ + "controlled_service_data_backup_blocker_count" + ] + == 8 + ) + assert payload["mainline_execution_state"][ + "controlled_service_data_backup_blocking_fields" + ] == [ + "service_green", + "post_start_blocked", + "product_data_green", + "backup_core_green", + "host_188_service_green", + "wazuh_dashboard_degraded", + "stockplatform_freshness_status", + "stockplatform_ingestion_status", + ] + assert ( + payload["mainline_execution_state"][ + "controlled_service_data_backup_can_clear_blockers" + ] + is False + ) assert payload["mainline_execution_state"][ "stale_snapshot_or_old_cd_runs_must_not_reopen_closed_work" ] is True @@ -70,6 +112,36 @@ def test_awoooi_priority_work_order_readback_loader_returns_mainline_order(): assert in_progress["evidence"]["stock_ingestion_blockers"] == [ "postgres_not_ready" ] + assert ( + in_progress["evidence"][ + "controlled_service_data_backup_readback_present" + ] + is True + ) + assert in_progress["evidence"][ + "controlled_service_data_backup_readback_status" + ] == "blocked_service_data_backup_readback_not_green" + assert in_progress["evidence"][ + "controlled_service_data_backup_blocker_count" + ] == 8 + assert in_progress["evidence"][ + "controlled_service_data_backup_blocking_fields" + ] == [ + "service_green", + "post_start_blocked", + "product_data_green", + "backup_core_green", + "host_188_service_green", + "wazuh_dashboard_degraded", + "stockplatform_freshness_status", + "stockplatform_ingestion_status", + ] + assert ( + in_progress["evidence"][ + "controlled_service_data_backup_can_clear_blockers" + ] + is False + ) assert in_progress["evidence"]["drill_preflight_ready"] is False assert in_progress["evidence"]["drill_preflight_blocker_count"] == 9 assert ( @@ -81,13 +153,33 @@ def test_awoooi_priority_work_order_readback_loader_returns_mainline_order(): assert "reboot SLO scorecard truth" in in_progress[ "professional_fix" ]["action"] + assert "controlled service/data/backup readback visible" in in_progress[ + "professional_fix" + ]["action"] assert "production migration/control-channel" in in_progress[ "professional_fix" ]["action"] assert "StockPlatform freshness/ingestion are ok" not in in_progress[ "professional_fix" ]["action"] + assert "readback is present" in in_progress["reason"] assert "not merely waiting for a fresh reboot window" in in_progress["reason"] + assert ( + payload["rollups"]["controlled_service_data_backup_readback_present"] + is True + ) + assert payload["rollups"]["controlled_service_data_backup_blocker_count"] == 8 + assert ( + payload["summary"]["controlled_service_data_backup_readback_present"] + is True + ) + assert payload["summary"]["controlled_service_data_backup_blocker_count"] == 8 + assert payload["summary"][ + "controlled_service_data_backup_next_safe_action" + ] == ( + "collect_public_service_data_backup_readback_and_refresh_reboot_slo_" + "scorecard_no_reboot_no_db_write" + ) assert payload["operation_boundaries"]["github_api_used"] is False assert payload["operation_boundaries"]["github_cli_used"] is False assert payload["operation_boundaries"]["secret_or_runner_token_read"] is False 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 a66cc0b2..a7888707 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 @@ -147,6 +147,18 @@ def _assert_reboot_slo_payload(payload: dict): assert payload["rollups"]["stockplatform_ingestion_status"] == "not_configured" assert payload["rollups"]["stockplatform_freshness_blocker_count"] == 1 assert payload["rollups"]["stockplatform_ingestion_blocker_count"] == 1 + assert ( + payload["rollups"]["controlled_service_data_backup_readback_present"] + is True + ) + assert payload["rollups"]["controlled_service_data_backup_blocker_count"] == 8 + assert ( + payload["rollups"]["controlled_service_data_backup_can_clear_blockers"] + is False + ) + assert payload["rollups"]["controlled_service_data_backup_readback_status"] == ( + "blocked_service_data_backup_readback_not_green" + ) assert payload["rollups"]["stockplatform_final_retry_window_passed"] is False assert ( payload["rollups"]["stockplatform_controlled_recovery_gate_required"] @@ -164,6 +176,48 @@ def _assert_reboot_slo_payload(payload: dict): assert progress["readiness_percent"] == 18 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"] + assert service_data_backup["schema_version"] == ( + "controlled_service_data_backup_readback_v1" + ) + assert service_data_backup["status"] == ( + "blocked_service_data_backup_readback_not_green" + ) + assert service_data_backup["readback_present"] is True + assert service_data_backup["service_green"] is False + assert service_data_backup["product_data_green"] is False + assert service_data_backup["backup_core_green"] is False + assert service_data_backup["host_188_service_green"] is False + assert service_data_backup["post_start_blocked"] == 8 + assert service_data_backup["wazuh_dashboard_degraded"] is True + assert service_data_backup["stockplatform_freshness_status"] == "not_configured" + assert service_data_backup["stockplatform_ingestion_status"] == "not_configured" + assert service_data_backup["blocking_fields"] == [ + "service_green", + "post_start_blocked", + "product_data_green", + "backup_core_green", + "host_188_service_green", + "wazuh_dashboard_degraded", + "stockplatform_freshness_status", + "stockplatform_ingestion_status", + ] + assert service_data_backup["related_active_blockers"] == [ + "backup_core_green_not_1", + "host_188_service_green_not_1", + "post_start_blocked_not_zero", + "product_data_green_not_1", + "service_green_not_1", + "wazuh_dashboard_degraded", + ] + assert service_data_backup["can_clear_service_data_backup_blockers"] is False + assert service_data_backup["next_safe_action"] == ( + "collect_public_service_data_backup_readback_and_refresh_reboot_slo_" + "scorecard_no_reboot_no_db_write" + ) + assert service_data_backup["runtime_write_allowed"] is False + assert "host_reboot" in service_data_backup["forbidden_actions"] + assert "manual_db_update" in service_data_backup["forbidden_actions"] stockplatform = payload["stockplatform_data_freshness"] assert stockplatform["freshness_endpoint_readback_present"] is True assert stockplatform["ingestion_endpoint_readback_present"] is True diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 4377dc35..66ff6304 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,23 @@ +## 2026-07-02 — 14:28 P0-006 service / data / backup controlled readback 產品化 + +**完成內容**: +- `/api/v1/agents/reboot-auto-recovery-slo-scorecard` 新增 `controlled_service_data_backup_readback`,把 `service_green`、`post_start_blocked`、`product_data_green`、`backup_core_green`、`host_188_service_green`、`wazuh_dashboard_degraded`、StockPlatform freshness / ingestion 狀態拆成可機器判讀的 blocking fields。 +- AwoooP priority readback 不再把目前 P0-006 說成「缺 service/data/backup readback」;現在明確顯示 `controlled_service_data_backup_readback_present=true`、`blocker_count=8`,並把下一步固定為 `collect_public_service_data_backup_readback_and_refresh_reboot_slo_scorecard_no_reboot_no_db_write`。 +- 這是 read-only verifier / source selector 層,不宣稱 10 分鐘 reboot SLO 已通過,也不清除 host boot、disk、Wazuh 或 backup blockers。 + +**驗證**: +- `python3.11 -m py_compile apps/api/src/services/reboot_auto_recovery_slo_scorecard.py apps/api/src/services/awoooi_priority_work_order_readback.py`:通過。 +- `DATABASE_URL=sqlite:////Users/ogt/.codex/tmp/awoooi-reboot-slo.db python3.11 -m pytest apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py -q`:`4 passed`。 +- `DATABASE_URL=sqlite:////Users/ogt/.codex/tmp/awoooi-priority.db python3.11 -m pytest apps/api/tests/test_awoooi_priority_work_order_readback_api.py -q`:`14 passed`。 +- rebase 最新 Gitea `main`(`3679c1cf fix(awooop): show inserted work items on work-items page`)後,focused API / Delivery tests:`24 passed`。 +- `ops/runner/test_cd_controlled_runtime_profile.py`:`44 passed`。 +- `pnpm --dir apps/web exec tsc --noEmit --incremental false`:通過。 +- `apps/web/messages/zh-TW.json` / `apps/web/messages/en.json` JSON parse:通過。 +- `git diff --check`:通過。 + +**仍維持**: +- 未使用 GitHub / `gh` / GitHub API;未讀 secret / token / `.env` / raw sessions / SQLite / auth;未觸發 workflow;未重啟主機 / Docker / Nginx / K3s / DB / firewall;未寫 production DB。 + ## 2026-07-02 — 14:12 API rollout CrashLoop root cause 與 bootstrap DDL timeout 修復 **完成內容**: